1313import idna
1414from cryptography import x509
1515from cryptography .hazmat .primitives import hashes
16- from cryptography .hazmat .primitives .asymmetric import ec , rsa
16+ from cryptography .hazmat .primitives .asymmetric import ec , mldsa , rsa
1717from cryptography .hazmat .primitives .serialization import (
1818 Encoding ,
1919 NoEncryption ,
2727if TYPE_CHECKING : # pragma: no cover
2828 import OpenSSL .SSL
2929
30- CERTIFICATE_PUBLIC_KEY_TYPES = Union [rsa .RSAPublicKey , ec .EllipticCurvePublicKey ]
31- CERTIFICATE_PRIVATE_KEY_TYPES = Union [rsa .RSAPrivateKey , ec .EllipticCurvePrivateKey ]
30+ CERTIFICATE_PUBLIC_KEY_TYPES = Union [
31+ rsa .RSAPublicKey ,
32+ ec .EllipticCurvePublicKey ,
33+ mldsa .MLDSA44PublicKey ,
34+ mldsa .MLDSA65PublicKey ,
35+ mldsa .MLDSA87PublicKey ,
36+ ]
37+ CERTIFICATE_PRIVATE_KEY_TYPES = Union [
38+ rsa .RSAPrivateKey ,
39+ ec .EllipticCurvePrivateKey ,
40+ mldsa .MLDSA44PrivateKey ,
41+ mldsa .MLDSA65PrivateKey ,
42+ mldsa .MLDSA87PrivateKey ,
43+ ]
3244
3345__all__ = ["CA" ]
3446
@@ -214,6 +226,9 @@ class KeyType(Enum):
214226
215227 RSA = 0
216228 ECDSA = 1
229+ MLDSA44 = 2
230+ MLDSA65 = 3
231+ MLDSA87 = 4
217232
218233 def _generate_key (self ) -> CERTIFICATE_PRIVATE_KEY_TYPES :
219234 if self is KeyType .RSA :
@@ -223,9 +238,44 @@ def _generate_key(self) -> CERTIFICATE_PRIVATE_KEY_TYPES:
223238 return rsa .generate_private_key (public_exponent = 65537 , key_size = 2048 )
224239 elif self is KeyType .ECDSA :
225240 return ec .generate_private_key (ec .SECP256R1 ())
241+ elif self is KeyType .MLDSA44 :
242+ return mldsa .MLDSA44PrivateKey .generate ()
243+ elif self is KeyType .MLDSA65 :
244+ return mldsa .MLDSA65PrivateKey .generate ()
245+ elif self is KeyType .MLDSA87 :
246+ return mldsa .MLDSA87PrivateKey .generate ()
226247 else : # pragma: no cover
227248 raise ValueError ("Unknown key type" )
228249
250+ @property
251+ def _hash_algorithm (self ) -> Optional [hashes .SHA256 ]:
252+ """ML-DSA uses intrinsic hashing; RSA/ECDSA use SHA-256."""
253+ if self in (KeyType .MLDSA44 , KeyType .MLDSA65 , KeyType .MLDSA87 ):
254+ return None
255+ return hashes .SHA256 ()
256+
257+ @property
258+ def _private_key_format (self ) -> PrivateFormat :
259+ """ML-DSA keys don't support TraditionalOpenSSL format."""
260+ if self in (KeyType .MLDSA44 , KeyType .MLDSA65 , KeyType .MLDSA87 ):
261+ return PrivateFormat .PKCS8
262+ return PrivateFormat .TraditionalOpenSSL
263+
264+
265+ def _detect_key_type (private_key : CERTIFICATE_PRIVATE_KEY_TYPES ) -> KeyType :
266+ if isinstance (private_key , rsa .RSAPrivateKey ):
267+ return KeyType .RSA
268+ elif isinstance (private_key , ec .EllipticCurvePrivateKey ):
269+ return KeyType .ECDSA
270+ elif isinstance (private_key , mldsa .MLDSA44PrivateKey ):
271+ return KeyType .MLDSA44
272+ elif isinstance (private_key , mldsa .MLDSA65PrivateKey ):
273+ return KeyType .MLDSA65
274+ elif isinstance (private_key , mldsa .MLDSA87PrivateKey ):
275+ return KeyType .MLDSA87
276+ else :
277+ raise TypeError (f"Unsupported key type: { type (private_key )} " )
278+
229279
230280class CA :
231281 """A certificate authority."""
@@ -241,6 +291,7 @@ def __init__(
241291 key_type : KeyType = KeyType .ECDSA ,
242292 ) -> None :
243293 self .parent_cert = parent_cert
294+ self ._key_type = key_type
244295 self ._private_key = key_type ._generate_key ()
245296 self ._path_length = path_length
246297
@@ -250,9 +301,11 @@ def __init__(
250301 )
251302 issuer = name
252303 sign_key = self ._private_key
304+ sign_key_type = key_type
253305 aki : Optional [x509 .AuthorityKeyIdentifier ]
254306 if parent_cert is not None :
255307 sign_key = parent_cert ._private_key
308+ sign_key_type = parent_cert ._key_type
256309 parent_certificate = parent_cert ._certificate
257310 issuer = parent_certificate .subject
258311 ski_ext = parent_certificate .extensions .get_extension_for_class (
@@ -286,7 +339,7 @@ def __init__(
286339 critical = True ,
287340 ).sign (
288341 private_key = sign_key ,
289- algorithm = hashes . SHA256 () ,
342+ algorithm = sign_key_type . _hash_algorithm ,
290343 )
291344
292345 @property
@@ -301,7 +354,7 @@ def private_key_pem(self) -> Blob:
301354 other certificates from this CA."""
302355 return Blob (
303356 self ._private_key .private_bytes (
304- Encoding .PEM , PrivateFormat . TraditionalOpenSSL , NoEncryption ()
357+ Encoding .PEM , self . _key_type . _private_key_format , NoEncryption ()
305358 )
306359 )
307360
@@ -440,7 +493,7 @@ def issue_cert(
440493 )
441494 .sign (
442495 private_key = self ._private_key ,
443- algorithm = hashes . SHA256 () ,
496+ algorithm = self . _key_type . _hash_algorithm ,
444497 )
445498 )
446499
@@ -453,7 +506,7 @@ def issue_cert(
453506 return LeafCert (
454507 key .private_bytes (
455508 Encoding .PEM ,
456- PrivateFormat . TraditionalOpenSSL ,
509+ key_type . _private_key_format ,
457510 NoEncryption (),
458511 ),
459512 cert .public_bytes (Encoding .PEM ),
@@ -499,6 +552,7 @@ def from_pem(cls, cert_bytes: bytes, private_key_bytes: bytes) -> "CA":
499552 ca .parent_cert = None
500553 ca ._certificate = x509 .load_pem_x509_certificate (cert_bytes )
501554 ca ._private_key = load_pem_private_key (private_key_bytes , password = None ) # type: ignore[assignment]
555+ ca ._key_type = _detect_key_type (ca ._private_key )
502556
503557 return ca
504558
0 commit comments