diff --git a/bless/ssh/certificates/ed25519_certificate_builder.py b/bless/ssh/certificates/ed25519_certificate_builder.py new file mode 100644 index 0000000..8293980 --- /dev/null +++ b/bless/ssh/certificates/ed25519_certificate_builder.py @@ -0,0 +1,39 @@ +""" +.. module: bless.ssh.certificates.ed25519_certificate_builder + :copyright: (c) 2016 by Netflix Inc., see AUTHORS for more + :license: Apache, see LICENSE for more details. +""" +from bless.ssh.certificates.ssh_certificate_builder import \ + SSHCertificateBuilder, SSHCertifiedKeyType +from bless.ssh.protocol.ssh_protocol import pack_ssh_string + + +class ED25519CertificateBuilder(SSHCertificateBuilder): + def __init__(self, ca, cert_type, ssh_public_key_ed25519): + """ + Produces an SSH certificate for ED25519 public keys. + :param ca: The SSHCertificateAuthority that will sign the certificate. The + SSHCertificateAuthority type does not need to be the same type as the + SSHCertificateBuilder. + :param cert_type: The SSHCertificateType. Is this a User or Host certificate? Some of + the SSH Certificate fields do not apply or have a slightly different meaning depending on + the certificate type. + See http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys + :param ssh_public_key_ed25519: The ED25519PublicKey to issue a certificate for. + """ + super(ED25519CertificateBuilder, self).__init__(ca, cert_type) + self.cert_key_type = SSHCertifiedKeyType.ED25519 + self.ssh_public_key = ssh_public_key_ed25519 + self.public_key_comment = ssh_public_key_ed25519.key_comment + self.a = ssh_public_key_ed25519.a + + def _serialize_ssh_public_key(self): + """ + Serialize the Public Key into a string. This is not specified in + http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys + but https://tools.ietf.org/id/draft-ietf-curdle-ssh-ed25519-02.html + :return: The bytes that belong in the SSH Certificate between the nonce and the + certificate serial number. + """ + public_key = pack_ssh_string(self.a) + return public_key diff --git a/bless/ssh/certificates/ssh_certificate_builder_factory.py b/bless/ssh/certificates/ssh_certificate_builder_factory.py index e26d7b7..802c210 100644 --- a/bless/ssh/certificates/ssh_certificate_builder_factory.py +++ b/bless/ssh/certificates/ssh_certificate_builder_factory.py @@ -5,6 +5,8 @@ """ from bless.ssh.certificates.rsa_certificate_builder \ import RSACertificateBuilder +from bless.ssh.certificates.ed25519_certificate_builder \ + import ED25519CertificateBuilder from bless.ssh.public_keys.ssh_public_key import SSHPublicKeyType from bless.ssh.public_keys.ssh_public_key_factory import get_ssh_public_key @@ -23,5 +25,7 @@ def get_ssh_certificate_builder(ca, cert_type, public_key_to_sign): if ssh_public_key.type is SSHPublicKeyType.RSA: return RSACertificateBuilder(ca, cert_type, ssh_public_key) + elif ssh_public_key.type is SSHPublicKeyType.ED25519: + return ED25519CertificateBuilder(ca, cert_type, ssh_public_key) else: raise TypeError("Unsupported Public Key Type") diff --git a/tests/ssh/test_ssh_certificate_builder_factory.py b/tests/ssh/test_ssh_certificate_builder_factory.py index a9ce14b..e35c9f8 100644 --- a/tests/ssh/test_ssh_certificate_builder_factory.py +++ b/tests/ssh/test_ssh_certificate_builder_factory.py @@ -4,6 +4,7 @@ get_ssh_certificate_authority from bless.ssh.certificates.rsa_certificate_builder import RSACertificateBuilder, \ SSHCertifiedKeyType +from bless.ssh.certificates.ed25519_certificate_builder import ED25519CertificateBuilder from bless.ssh.certificates.ssh_certificate_builder import SSHCertificateType from bless.ssh.certificates.ssh_certificate_builder_factory import get_ssh_certificate_builder from tests.ssh.vectors import RSA_CA_PRIVATE_KEY, RSA_CA_PRIVATE_KEY_PASSWORD, \ @@ -18,10 +19,12 @@ def test_valid_rsa_request(): assert cert.startswith(SSHCertifiedKeyType.RSA) -def test_invalid_ed25519_request(): - with pytest.raises(TypeError): - ca = get_ssh_certificate_authority(RSA_CA_PRIVATE_KEY, RSA_CA_PRIVATE_KEY_PASSWORD) - get_ssh_certificate_builder(ca, SSHCertificateType.USER, EXAMPLE_ED25519_PUBLIC_KEY) +def test_valid_ed25519_request(): + ca = get_ssh_certificate_authority(RSA_CA_PRIVATE_KEY, RSA_CA_PRIVATE_KEY_PASSWORD) + cert_builder = get_ssh_certificate_builder(ca, SSHCertificateType.USER, EXAMPLE_ED25519_PUBLIC_KEY) + cert = cert_builder.get_cert_file() + assert isinstance(cert_builder, ED25519CertificateBuilder) + assert cert.startswith(SSHCertifiedKeyType.ED25519) def test_invalid_key_request(): diff --git a/tests/ssh/test_ssh_certificate_rsa.py b/tests/ssh/test_ssh_certificate_rsa.py index 6c2d439..eae09d1 100644 --- a/tests/ssh/test_ssh_certificate_rsa.py +++ b/tests/ssh/test_ssh_certificate_rsa.py @@ -5,8 +5,10 @@ from bless.ssh.certificate_authorities.rsa_certificate_authority import RSACertificateAuthority from bless.ssh.certificates.rsa_certificate_builder import RSACertificateBuilder +from bless.ssh.certificates.ed25519_certificate_builder import ED25519CertificateBuilder from bless.ssh.certificates.ssh_certificate_builder import SSHCertificateType from bless.ssh.public_keys.rsa_public_key import RSAPublicKey +from bless.ssh.public_keys.ed25519_public_key import ED25519PublicKey from tests.ssh.vectors import RSA_CA_PRIVATE_KEY, RSA_CA_PRIVATE_KEY_PASSWORD, \ EXAMPLE_RSA_PUBLIC_KEY, EXAMPLE_RSA_PUBLIC_KEY_NO_DESCRIPTION, RSA_USER_CERT_MINIMAL, \ RSA_USER_CERT_DEFAULTS, RSA_USER_CERT_DEFAULTS_NO_PUBLIC_KEY_COMMENT, \ @@ -14,7 +16,8 @@ RSA_USER_CERT_FORCE_COMMAND_AND_SOURCE_ADDRESS, \ RSA_USER_CERT_FORCE_COMMAND_AND_SOURCE_ADDRESS_KEY_ID, RSA_HOST_CERT_MANY_PRINCIPALS_KEY_ID, \ RSA_USER_CERT_MANY_PRINCIPALS_KEY_ID, RSA_USER_CERT_DEFAULTS_NO_PUBLIC_KEY_COMMENT_KEY_ID, \ - RSA_USER_CERT_DEFAULTS_KEY_ID, SSH_CERT_DEFAULT_EXTENSIONS, SSH_CERT_CUSTOM_EXTENSIONS + RSA_USER_CERT_DEFAULTS_KEY_ID, SSH_CERT_DEFAULT_EXTENSIONS, SSH_CERT_CUSTOM_EXTENSIONS, \ + EXAMPLE_ED25519_PUBLIC_KEY, ED25519_USER_CERT_DEFAULTS, ED25519_USER_CERT_DEFAULTS_KEY_ID USER1 = 'user1' @@ -219,3 +222,15 @@ def test_nonce(): cert_builder2.set_nonce() assert cert_builder.nonce != cert_builder2.nonce + + +def test_ed25519_user_cert_defaults(): + ca = get_basic_rsa_ca() + pub_key = ED25519PublicKey(EXAMPLE_ED25519_PUBLIC_KEY) + cert_builder = ED25519CertificateBuilder(ca, SSHCertificateType.USER, pub_key) + cert_builder.set_nonce( + nonce=extract_nonce_from_cert(ED25519_USER_CERT_DEFAULTS)) + cert_builder.set_key_id(ED25519_USER_CERT_DEFAULTS_KEY_ID) + + cert = cert_builder.get_cert_file() + assert ED25519_USER_CERT_DEFAULTS == cert diff --git a/tests/ssh/vectors.py b/tests/ssh/vectors.py index 35c3e93..b87a16c 100644 --- a/tests/ssh/vectors.py +++ b/tests/ssh/vectors.py @@ -51,3 +51,7 @@ SSH_CERT_CUSTOM_EXTENSIONS = base64.b64decode( 'AAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAA==') + +# ssh-keygen -s test-rsa-ca -I "ssh-keygen -s test-rsa-ca -I '' test-ed25519-user-all-defaults.pub" test-ed25519-user-all-defaults.pub +ED25519_USER_CERT_DEFAULTS = 'ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAINa9ZmyR9YBRNfC464IJE2AlDa0xU02tVbY37AlRr79/AAAAIG7+cAbT4EFSPs87oS4kDYStQ0KL0xwHWqVSZ2bYHIApAAAAAAAAAAAAAAABAAAAQnNzaC1rZXlnZW4gLXMgdGVzdC1yc2EtY2EgLUkgJycgdGVzdC1lZDI1NTE5LXVzZXItYWxsLWRlZmF1bHRzLnB1YgAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAhcAAAAHc3NoLXJzYQAAAAMBAAEAAAIBAOI0JnxeJSdtUXtHu7x3MVVLdDN2iFCr8B8xCSGsdmJ+VdI2sBF0xc7l5/HZVyATgCBTt9dT7eG6zFCnQhPJUUB+uhUu9EeKwu3Pqw9mWnTXy/sa/R++yswFjU1Syxws+iBbiG9IG1BIqXY+eOuXaf+8kEFi877bjU4Nrl0U/KL2qpCJwhFh3vu5XBQ26ih/TcZVRaPyOTBVqOD0MNeVXs0JkVc/gd4mLHWgFvcIrTNmmKwR0HNSDPpYbLxol+3DbfaY9CE/sUetLA6OxO4EF38PQKPY+Ud2/yypc4uC/GT1VfEc4ALZVCZYC8Cut3hYb5ef3xdab7W4ikXIyU2vtPR/n1Ju/5nXVFX1Y0F5u0ShZkz8SI7/BLF2i4SIMiZhNTgqVj8mr2LpwSHB04m68d9GsPnD1mQQxlcfx7pbfOAjXmnTV3mQmn4TnW1KxKH0n7NJE1jvRy31n3Crs3aMJ6cuq6+gxbK4cg/X48Q16PnpuYzOiTtD5hEuu4RL4cg86Mi57DmchNwb1CsvxFJrueJB65J4efIDTqmuppDQVjkZOzH2URzRzGE2azdcQPgcV5aQQiZuiqeZ5xG9oEwB/2kNVu3hsM49ugJ9OBGHtlDoemC4YkQkJe0ejFJVc8uKOzGmXfjGcXKi21Aq5jGvEsDRb3DcuLEceOfXRpU/59OpAAACDwAAAAdzc2gtcnNhAAACACnir9y/PhoKvLNvs4fMkS24mrHyGxc24nTxaKJO9OBGr4PY3Glg90hKaZvC/cLN0wSAeIYzigp/PGm1PNIM342NqxMkIx7yUKrEbcIQaommn8kfThMIcLJo9QOpDoMAxOsfAtZrtWQKMMYM6s8hZKrb3OT8k0le/x4S2GttHMb9z006nZgyTUI4cniq+ZxwcL4l5/wzaXamQUsod/JRysEBZdLF88A50iCiSC+4NQUqquPBs76UmTpU+Lv3OEvVBwMil7Hxkzv8kqit2Xal0Ou9n/+CC1G9l/dpTO1TgaNtNERQT3rQOhkjqwmgN3wbh1sRkc1+zSenWsNrHkFBnVTJPxLwXbFeAvstDWDxTvNCpygsTfz/ejUnKfqZ1rAWfNRhzrSIz1D1+/GQbOdtM3xq3r6CVjVxE0KTLsdR1hyOd5SmszubE1UAkKWF7NRyHcgma/9hkOXc6a/4ylBcOj0yUFnjVq7Jb6C33ba0Ra6LnopZWUS7lr02dt7aYG/Qhd8OtJx7R+XiRYRnfsuJH+L18UxM34xqj9qlMPA5p1nUB2ZnklKyueLhrp0/thuWsdKCv4w66A8rOthbDtLip6TYtKwDDMBupq5ROoRHYXb6nYthHJYCX1QuDIzmoBjTlkWa7aVohggQWezYhGGo0owulURxkFNZBNi1Lc/aRuDs Test ED25519 User Key' +ED25519_USER_CERT_DEFAULTS_KEY_ID = 'ssh-keygen -s test-rsa-ca -I \'\' test-ed25519-user-all-defaults.pub'