Skip to content

Commit

Permalink
allow x25519/x448 public keys in certificatebuilder (#6562)
Browse files Browse the repository at this point in the history
also document that we can return these key types in a certificate,
although they can't be self-signed of course
  • Loading branch information
reaperhulk authored Nov 7, 2021
1 parent 924926d commit 6f03dee
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 9 deletions.
12 changes: 8 additions & 4 deletions docs/x509/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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::

Expand Down Expand Up @@ -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)

Expand Down
11 changes: 11 additions & 0 deletions src/cryptography/hazmat/primitives/asymmetric/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
ed25519,
ed448,
rsa,
x25519,
x448,
)


Expand All @@ -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,
]
16 changes: 11 additions & 5 deletions src/cryptography/x509/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down Expand Up @@ -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
"""
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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).
Expand All @@ -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.")
Expand Down
52 changes: 52 additions & 0 deletions tests/x509/test_x509.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
ed448,
padding,
rsa,
x25519,
x448,
)
from cryptography.hazmat.primitives.asymmetric.utils import (
decode_dss_signature,
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 6f03dee

Please sign in to comment.