diff --git a/src/cryptography/hazmat/primitives/serialization/ssh.py b/src/cryptography/hazmat/primitives/serialization/ssh.py index f33edd55e0ea..fc9fbf42584f 100644 --- a/src/cryptography/hazmat/primitives/serialization/ssh.py +++ b/src/cryptography/hazmat/primitives/serialization/ssh.py @@ -64,6 +64,10 @@ def _bcrypt_kdf( _ECDSA_NISTP521 = b"ecdsa-sha2-nistp521" _CERT_SUFFIX = b"-cert-v01@openssh.com" +# U2F application string suffixed pubkey +_SK_SSH_ED25519 = b"sk-ssh-ed25519@openssh.com" +_SK_SSH_ECDSA_NISTP256 = b"sk-ecdsa-sha2-nistp256@openssh.com" + # These are not key types, only algorithms, so they cannot appear # as a public key type _SSH_RSA_SHA256 = b"rsa-sha2-256" @@ -572,6 +576,56 @@ def encode_private( f_priv.put_sshstr(f_keypair) +def load_application(data) -> tuple[memoryview, memoryview]: + """ + U2F application strings + """ + application, data = _get_sshstr(data) + if not application.tobytes().startswith(b"ssh:"): + raise ValueError( + "U2F application string does not start with b'ssh:' " + f"({application})" + ) + return application, data + + +class _SSHFormatSKEd25519: + """ + The format of a sk-ssh-ed25519@openssh.com public key is: + + string "sk-ssh-ed25519@openssh.com" + string public key + string application (user-specified, but typically "ssh:") + """ + + def load_public( + self, data: memoryview + ) -> tuple[ed25519.Ed25519PublicKey, memoryview]: + """Make Ed25519 public key from data.""" + public_key, data = _lookup_kformat(_SSH_ED25519).load_public(data) + application, data = load_application(data) + return public_key, data + + +class _SSHFormatSKECDSA: + """ + The format of a sk-ecdsa-sha2-nistp256@openssh.com public key is: + + string "sk-ecdsa-sha2-nistp256@openssh.com" + string curve name + ec_point Q + string application (user-specified, but typically "ssh:") + """ + + def load_public( + self, data: memoryview + ) -> tuple[ec.EllipticCurvePublicKey, memoryview]: + """Make Ed25519 public key from data.""" + public_key, data = _lookup_kformat(_ECDSA_NISTP256).load_public(data) + application, data = load_application(data) + return public_key, data + + _KEY_FORMATS = { _SSH_RSA: _SSHFormatRSA(), _SSH_DSA: _SSHFormatDSA(), @@ -579,6 +633,8 @@ def encode_private( _ECDSA_NISTP256: _SSHFormatECDSA(b"nistp256", ec.SECP256R1()), _ECDSA_NISTP384: _SSHFormatECDSA(b"nistp384", ec.SECP384R1()), _ECDSA_NISTP521: _SSHFormatECDSA(b"nistp521", ec.SECP521R1()), + _SK_SSH_ED25519: _SSHFormatSKEd25519(), + _SK_SSH_ECDSA_NISTP256: _SSHFormatSKECDSA(), } diff --git a/tests/hazmat/primitives/test_ssh.py b/tests/hazmat/primitives/test_ssh.py index cda2aad77b59..82f398305e21 100644 --- a/tests/hazmat/primitives/test_ssh.py +++ b/tests/hazmat/primitives/test_ssh.py @@ -55,6 +55,10 @@ class TestOpenSSHSerialization: ("ecdsa-nopsw.key.pub", "ecdsa-nopsw.key-cert.pub"), ("ed25519-psw.key.pub", None), ("ed25519-nopsw.key.pub", "ed25519-nopsw.key-cert.pub"), + ("sk-ecdsa-psw.key.pub", None), + ("sk-ecdsa-nopsw.key.pub", None), + ("sk-ed25519-psw.key.pub", None), + ("sk-ed25519-nopsw.key.pub", None), ], ) def test_load_ssh_public_key(self, key_file, cert_file, backend): @@ -80,10 +84,14 @@ def test_load_ssh_public_key(self, key_file, cert_file, backend): ) else: public_key = load_ssh_public_key(pub_data, backend) - assert ( - public_key.public_bytes(Encoding.OpenSSH, PublicFormat.OpenSSH) - == nocomment_data - ) + if not key_file.startswith("sk-"): + # SK keys do not round-trip + assert ( + public_key.public_bytes( + Encoding.OpenSSH, PublicFormat.OpenSSH + ) + == nocomment_data + ) self.run_partial_pubkey(pub_data, backend) @@ -1800,3 +1808,20 @@ def test_sign_and_byte_compare_ed25519(self, monkeypatch, backend): b"t8yRa8IRbxvOyA9TZYDGG1dRE3DiR0fuudU20v6vqfTd1gx0S5QyEdECXLl9ZI3" b"AwZgc=" ) + + +class TestSSHSK: + @staticmethod + def ssh_str(application): + data = ( + len(application).to_bytes(length=4, byteorder="big") + + application.encode() + ) + return memoryview(data) + + def test_load_application(self): + ssh.load_application(self.ssh_str("ssh:test")) + + def test_load_application_valueerror(self): + with pytest.raises(ValueError): + ssh.load_application(self.ssh_str("hss:test")) diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/gen.sh b/vectors/cryptography_vectors/asymmetric/OpenSSH/gen.sh index b18c338b3803..4a494bda1153 100755 --- a/vectors/cryptography_vectors/asymmetric/OpenSSH/gen.sh +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/gen.sh @@ -19,10 +19,13 @@ getecbits() { genkey() { fn="$1" args="-f $fn -C $fn" + sk="-O application=ssh:the-application-string" case "$fn" in + sk-ecdsa-*) args="$args -t ecdsa-sk -b $(getecbits) $sk" ;; ecdsa-*) args="$args -t ecdsa -b $(getecbits)" ;; rsa-*) args="$args -t rsa" ;; dsa-*) args="$args -t dsa" ;; + sk-ed25519-*) args="$args -t ed25519-sk $sk" ;; ed25519-*) args="$args -t ed25519" ;; esac password='' @@ -33,12 +36,13 @@ genkey() { } # generate private key files -for ktype in rsa dsa ecdsa ed25519; do +for ktype in rsa dsa ecdsa sk-ecdsa ed25519 sk-ed25519; do for psw in nopsw psw; do genkey "${ktype}-${psw}.key" done done + # generate public key files for fn in *.key; do ssh-keygen -q -y -f "$fn" > /dev/null diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-nopsw.key b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-nopsw.key new file mode 100644 index 000000000000..23fd193a92fa --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-nopsw.key @@ -0,0 +1,11 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlQAAACJzay1lY2 +RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQQ7XunI8QRf +myT0PKWJXtaE0lA6+Hy5HTfIDfHexsZV68AGAj0nYyf2+mAK/vPp6IyVBALJqdzdJYiyeX +p/3neLAAAAGnNzaDp0aGUtYXBwbGljYXRpb24tc3RyaW5nAAABAOGdI7jhnSO4AAAAInNr +LWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBDte6c +jxBF+bJPQ8pYle1oTSUDr4fLkdN8gN8d7GxlXrwAYCPSdjJ/b6YAr+8+nojJUEAsmp3N0l +iLJ5en/ed4sAAAAac3NoOnRoZS1hcHBsaWNhdGlvbi1zdHJpbmcBAAAAQDkL+WvhalaEJi +Lf/MaFsFeYzwvC06GZVqUXgCnzyutZzMB9a1deF9uFke1ib56tgZR9iVsskIJeWuwiAIg0 +es4AAAAAAAAAEnNrLWVjZHNhLW5vcHN3LmtleQECAwQ= +-----END OPENSSH PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-nopsw.key.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-nopsw.key.pub new file mode 100644 index 000000000000..7c4193df3826 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-nopsw.key.pub @@ -0,0 +1 @@ +sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBDte6cjxBF+bJPQ8pYle1oTSUDr4fLkdN8gN8d7GxlXrwAYCPSdjJ/b6YAr+8+nojJUEAsmp3N0liLJ5en/ed4sAAAAac3NoOnRoZS1hcHBsaWNhdGlvbi1zdHJpbmc= sk-ecdsa-nopsw.key diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-psw.key b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-psw.key new file mode 100644 index 000000000000..b406fa06800d --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-psw.key @@ -0,0 +1,12 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDIj2qUG3 +LdljUMp0/4zuFuAAAAEAAAAAEAAACVAAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3Bl +bnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBACdJuKxgDLk+a1NeeCtRqCropd0hXume/cTdO +vV/B4lmupr9viNQsUT09wbKRflnOc9jxPAiQOzZbXTkmnV8kkAAAAac3NoOnRoZS1hcHBs +aWNhdGlvbi1zdHJpbmcAAAEAO6Vsfb59XIe524NKbXMjA0xleAi3lcZ5EF0dF48yRO2LfA +12B948LzsKOrgo+Cdq7BMLkCCA1z2811yvKtvy/7cR3D/p31cW7VEun4OAn+QoPCHmv25r +WVfUAv5PC5Ofdm7dtExTcMmyNUMcziovirTyhnlpc/wHD+wgp2oQGpcm+rjQlqX96cLJ7H +PM3wls38biP3wh2QWkoKWPyq7tMR4PiJOw9h6YNeZY3M1JnC9b2b0iHD6Ra/5LBBqV/Uyu +irkHWLB7ASchamexxRqu4fLFK4tjijhLV8hc/XLsQGeDNBHf4QSvZJP0usSSP37F1Ai+XM +stjM1iCsk1UEV9aA== +-----END OPENSSH PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-psw.key.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-psw.key.pub new file mode 100644 index 000000000000..b9a6fa34156c --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ecdsa-psw.key.pub @@ -0,0 +1 @@ +sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBACdJuKxgDLk+a1NeeCtRqCropd0hXume/cTdOvV/B4lmupr9viNQsUT09wbKRflnOc9jxPAiQOzZbXTkmnV8kkAAAAac3NoOnRoZS1hcHBsaWNhdGlvbi1zdHJpbmc= sk-ecdsa-psw.key diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-nopsw.key b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-nopsw.key new file mode 100644 index 000000000000..db48fcd3e9a5 --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-nopsw.key @@ -0,0 +1,10 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAYAAAABpzay1zc2 +gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACB6auRr7BwVOqTawgDOxUpaUFcN8SZ7SWzoR2Vs +ubbk3wAAABpzc2g6dGhlLWFwcGxpY2F0aW9uLXN0cmluZwAAARCWIPLyliDy8gAAABpzay +1zc2gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACB6auRr7BwVOqTawgDOxUpaUFcN8SZ7SWzo +R2Vsubbk3wAAABpzc2g6dGhlLWFwcGxpY2F0aW9uLXN0cmluZwEAAACAQPv/aY2F3YN1kD +1FHPa1HpEHOGAbsYj/2b6h8Rn+N4pU6hdTD5v19Efdz5jlt8Y84c61+8HKDPCI/g5Cbcvd +3uuGHuFUdgiarOZqKyuwBj3Kll9Whb/yV4wGo/NVXtCHa2SnWr2wjYtRTGPNNCgGPsLU05 +/KTNCStsNhEcsNDjEAAAAAAAAAFHNrLWVkMjU1MTktbm9wc3cua2V5AQIDBAUGBw== +-----END OPENSSH PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-nopsw.key.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-nopsw.key.pub new file mode 100644 index 000000000000..dc900ed9dd6f --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-nopsw.key.pub @@ -0,0 +1 @@ +sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIHpq5GvsHBU6pNrCAM7FSlpQVw3xJntJbOhHZWy5tuTfAAAAGnNzaDp0aGUtYXBwbGljYXRpb24tc3RyaW5n sk-ed25519-nopsw.key diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-psw.key b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-psw.key new file mode 100644 index 000000000000..92328aa1ecdd --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-psw.key @@ -0,0 +1,11 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBZQIE5S+ +fq0J5esB3Jo4smAAAAEAAAAAEAAABgAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t +AAAAIHf0iiNQTiR7NNAbeAwY+READVx9G0mP6idSAZ7bPTrMAAAAGnNzaDp0aGUtYXBwbG +ljYXRpb24tc3RyaW5nAAABEEeyENyjnVry24AKkT0cC6nRakzHeBY7nSmDiy3MX7sQNRze +illy4uWLZyv022QlMR4GqnXwnQ9bPqcPD0S/SAhuYnFRWI6PPUXkNqiqiS/ZsMkaSKDvBS +UKv5EXjBBk3Sh9IjNXXK8tt0+WIIR973hVEtolcgxvFZpc1IJuRl9gkpKlQFNzwcANTuwB +kr6t0qad/fp0bZldBL/zRtqfgMHTSFzNoITTaxA8ZQZ1Zm585u0NIX4ZDrTaoZVaO8t7Z5 +3r1784oCk6h/lomf9Qsg2eBf6CHMGlTHVFPop5VtGDKFVlgIxQCdwt0V1e6dWK6j5zOzBh +mNA7qT0q3quRLBqUADN698q5fLRFR1PzQ5bx +-----END OPENSSH PRIVATE KEY----- diff --git a/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-psw.key.pub b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-psw.key.pub new file mode 100644 index 000000000000..65fc4c31591b --- /dev/null +++ b/vectors/cryptography_vectors/asymmetric/OpenSSH/sk-ed25519-psw.key.pub @@ -0,0 +1 @@ +sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIHf0iiNQTiR7NNAbeAwY+READVx9G0mP6idSAZ7bPTrMAAAAGnNzaDp0aGUtYXBwbGljYXRpb24tc3RyaW5n sk-ed25519-psw.key