From 67d15dbe9890d5961f67fac6bca27016f8692d30 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 8 Nov 2021 05:57:15 +0800 Subject: [PATCH] allow x25519/x448 public keys in certificatebuilder also document that we can return these key types in a certificate, although they can't be self-signed of course --- docs/x509/reference.rst | 12 +++-- .../hazmat/primitives/asymmetric/types.py | 11 ++++ src/cryptography/x509/base.py | 16 ++++-- tests/x509/test_x509.py | 52 +++++++++++++++++++ 4 files changed, 82 insertions(+), 9 deletions(-) diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 9944b08cd2cd..e28bbd4b0379 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -310,8 +310,10 @@ X.509 Certificate Object :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey` or - :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey` + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey` .. doctest:: @@ -702,8 +704,10 @@ X.509 Certificate Builder :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, - :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey` or - :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`. + :class:`~cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.x25519.X25519PublicKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.x448.X448PublicKey`. .. method:: serial_number(serial_number) diff --git a/src/cryptography/hazmat/primitives/asymmetric/types.py b/src/cryptography/hazmat/primitives/asymmetric/types.py index ef946cf4b75f..ccf339f31510 100644 --- a/src/cryptography/hazmat/primitives/asymmetric/types.py +++ b/src/cryptography/hazmat/primitives/asymmetric/types.py @@ -10,6 +10,8 @@ ed25519, ed448, rsa, + x25519, + x448, ) @@ -27,3 +29,12 @@ dsa.DSAPrivateKey, ec.EllipticCurvePrivateKey, ] +CERTIFICATE_PUBLIC_KEY_TYPES = typing.Union[ + dsa.DSAPublicKey, + rsa.RSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ed448.Ed448PublicKey, + x25519.X25519PublicKey, + x448.X448PublicKey, +] diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py index 68e31baceb9a..535db50c1390 100644 --- a/src/cryptography/x509/base.py +++ b/src/cryptography/x509/base.py @@ -17,8 +17,11 @@ ed25519, ed448, rsa, + x25519, + x448, ) from cryptography.hazmat.primitives.asymmetric.types import ( + CERTIFICATE_PUBLIC_KEY_TYPES, PRIVATE_KEY_TYPES as PRIVATE_KEY_TYPES, PUBLIC_KEY_TYPES as PUBLIC_KEY_TYPES, ) @@ -101,7 +104,7 @@ def version(self) -> Version: """ @abc.abstractmethod - def public_key(self) -> PUBLIC_KEY_TYPES: + def public_key(self) -> CERTIFICATE_PUBLIC_KEY_TYPES: """ Returns the public key """ @@ -578,7 +581,7 @@ def __init__( self, issuer_name: typing.Optional[Name] = None, subject_name: typing.Optional[Name] = None, - public_key: typing.Optional[PUBLIC_KEY_TYPES] = None, + public_key: typing.Optional[CERTIFICATE_PUBLIC_KEY_TYPES] = None, serial_number: typing.Optional[int] = None, not_valid_before: typing.Optional[datetime.datetime] = None, not_valid_after: typing.Optional[datetime.datetime] = None, @@ -631,7 +634,7 @@ def subject_name(self, name: Name) -> "CertificateBuilder": def public_key( self, - key: PUBLIC_KEY_TYPES, + key: CERTIFICATE_PUBLIC_KEY_TYPES, ) -> "CertificateBuilder": """ Sets the requestor's public key (as found in the signing request). @@ -644,12 +647,15 @@ def public_key( ec.EllipticCurvePublicKey, ed25519.Ed25519PublicKey, ed448.Ed448PublicKey, + x25519.X25519PublicKey, + x448.X448PublicKey, ), ): raise TypeError( "Expecting one of DSAPublicKey, RSAPublicKey," - " EllipticCurvePublicKey, Ed25519PublicKey or" - " Ed448PublicKey." + " EllipticCurvePublicKey, Ed25519PublicKey," + " Ed448PublicKey, X25519PublicKey, or " + "X448PublicKey." ) if self._public_key is not None: raise ValueError("The public key may only be set once.") diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index 80dfea92cbbc..e6acf45c361a 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -27,6 +27,8 @@ ed448, padding, rsa, + x25519, + x448, ) from cryptography.hazmat.primitives.asymmetric.utils import ( decode_dss_signature, @@ -2968,6 +2970,56 @@ def test_build_cert_with_public_ed448_rsa_sig(self, backend): assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) assert isinstance(cert.public_key(), ed448.Ed448PublicKey) + @pytest.mark.supported( + only_if=lambda backend: ( + backend.x25519_supported() and backend.x448_supported() + ), + skip_message="Requires OpenSSL with x25519 & x448 support", + ) + @pytest.mark.parametrize( + ("priv_key_cls", "pub_key_cls"), + [ + (x25519.X25519PrivateKey, x25519.X25519PublicKey), + (x448.X448PrivateKey, x448.X448PublicKey), + ], + ) + def test_build_cert_with_public_x25519_x448_rsa_sig( + self, priv_key_cls, pub_key_cls, backend + ): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = priv_key_cls.generate() + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = ( + x509.CertificateBuilder() + .serial_number(777) + .issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, "US")]) + ) + .public_key(subject_private_key.public_key()) + .not_valid_before(not_valid_before) + .not_valid_after(not_valid_after) + ) + + cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) + assert cert.signature_hash_algorithm is not None + issuer_private_key.public_key().verify( + cert.signature, + cert.tbs_certificate_bytes, + padding.PKCS1v15(), + cert.signature_hash_algorithm, + ) + assert cert.signature_algorithm_oid == ( + SignatureAlgorithmOID.RSA_WITH_SHA256 + ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) + assert isinstance(cert.public_key(), pub_key_cls) + def test_build_cert_with_rsa_key_too_small(self, backend): issuer_private_key = RSA_KEY_512.private_key(backend) subject_private_key = RSA_KEY_512.private_key(backend)