Skip to content

Commit 176e34b

Browse files
committed
MLDSA65 support for AWS-LC
1 parent 5645437 commit 176e34b

17 files changed

Lines changed: 1115 additions & 10 deletions

File tree

src/cryptography/hazmat/backends/openssl/backend.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,9 @@ def x448_supported(self) -> bool:
272272
and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC
273273
)
274274

275+
def mldsa_supported(self) -> bool:
276+
return rust_openssl.CRYPTOGRAPHY_IS_AWSLC
277+
275278
def ed25519_supported(self) -> bool:
276279
return not self._fips_enabled
277280

src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ from cryptography.hazmat.bindings._rust.openssl import (
1818
hpke,
1919
kdf,
2020
keys,
21+
mldsa65,
2122
poly1305,
2223
rsa,
2324
x448,
@@ -38,6 +39,7 @@ __all__ = [
3839
"hpke",
3940
"kdf",
4041
"keys",
42+
"mldsa65",
4143
"openssl_version",
4244
"openssl_version_text",
4345
"poly1305",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# This file is dual licensed under the terms of the Apache License, Version
2+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3+
# for complete details.
4+
5+
from cryptography.hazmat.primitives.asymmetric import mldsa65
6+
from cryptography.utils import Buffer
7+
8+
class MlDsa65PrivateKey: ...
9+
class MlDsa65PublicKey: ...
10+
11+
def generate_key() -> mldsa65.MlDsa65PrivateKey: ...
12+
def from_public_bytes(data: bytes) -> mldsa65.MlDsa65PublicKey: ...
13+
def from_seed_bytes(data: Buffer) -> mldsa65.MlDsa65PrivateKey: ...
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# This file is dual licensed under the terms of the Apache License, Version
2+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3+
# for complete details.
4+
5+
from __future__ import annotations
6+
7+
import abc
8+
9+
from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
10+
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
11+
from cryptography.hazmat.primitives import _serialization
12+
from cryptography.utils import Buffer
13+
14+
15+
class MlDsa65PublicKey(metaclass=abc.ABCMeta):
16+
@classmethod
17+
def from_public_bytes(cls, data: bytes) -> MlDsa65PublicKey:
18+
from cryptography.hazmat.backends.openssl.backend import backend
19+
20+
if not backend.mldsa_supported():
21+
raise UnsupportedAlgorithm(
22+
"ML-DSA-65 is not supported by this backend.",
23+
_Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM,
24+
)
25+
26+
return rust_openssl.mldsa65.from_public_bytes(data)
27+
28+
@abc.abstractmethod
29+
def public_bytes(
30+
self,
31+
encoding: _serialization.Encoding,
32+
format: _serialization.PublicFormat,
33+
) -> bytes:
34+
"""
35+
The serialized bytes of the public key.
36+
"""
37+
38+
@abc.abstractmethod
39+
def public_bytes_raw(self) -> bytes:
40+
"""
41+
The raw bytes of the public key.
42+
Equivalent to public_bytes(Raw, Raw).
43+
"""
44+
45+
@abc.abstractmethod
46+
def verify(self, signature: Buffer, data: Buffer) -> None:
47+
"""
48+
Verify the signature.
49+
"""
50+
51+
@abc.abstractmethod
52+
def verify_with_context(
53+
self, signature: Buffer, data: Buffer, context: Buffer
54+
) -> None:
55+
"""
56+
Verify the signature with a context string.
57+
"""
58+
59+
@abc.abstractmethod
60+
def __eq__(self, other: object) -> bool:
61+
"""
62+
Checks equality.
63+
"""
64+
65+
@abc.abstractmethod
66+
def __copy__(self) -> MlDsa65PublicKey:
67+
"""
68+
Returns a copy.
69+
"""
70+
71+
@abc.abstractmethod
72+
def __deepcopy__(self, memo: dict) -> MlDsa65PublicKey:
73+
"""
74+
Returns a deep copy.
75+
"""
76+
77+
78+
if hasattr(rust_openssl, "mldsa65"):
79+
MlDsa65PublicKey.register(rust_openssl.mldsa65.MlDsa65PublicKey)
80+
81+
82+
class MlDsa65PrivateKey(metaclass=abc.ABCMeta):
83+
@classmethod
84+
def generate(cls) -> MlDsa65PrivateKey:
85+
from cryptography.hazmat.backends.openssl.backend import backend
86+
87+
if not backend.mldsa_supported():
88+
raise UnsupportedAlgorithm(
89+
"ML-DSA-65 is not supported by this backend.",
90+
_Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM,
91+
)
92+
93+
return rust_openssl.mldsa65.generate_key()
94+
95+
@classmethod
96+
def from_seed_bytes(cls, data: Buffer) -> MlDsa65PrivateKey:
97+
from cryptography.hazmat.backends.openssl.backend import backend
98+
99+
if not backend.mldsa_supported():
100+
raise UnsupportedAlgorithm(
101+
"ML-DSA-65 is not supported by this backend.",
102+
_Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM,
103+
)
104+
105+
return rust_openssl.mldsa65.from_seed_bytes(data)
106+
107+
@abc.abstractmethod
108+
def public_key(self) -> MlDsa65PublicKey:
109+
"""
110+
The MlDsa65PublicKey derived from the private key.
111+
"""
112+
113+
@abc.abstractmethod
114+
def private_bytes(
115+
self,
116+
encoding: _serialization.Encoding,
117+
format: _serialization.PrivateFormat,
118+
encryption_algorithm: (_serialization.KeySerializationEncryption),
119+
) -> bytes:
120+
"""
121+
The serialized bytes of the private key.
122+
"""
123+
124+
@abc.abstractmethod
125+
def private_bytes_raw(self) -> bytes:
126+
"""
127+
The raw bytes of the private key (32-byte seed).
128+
Equivalent to private_bytes(Raw, Raw, NoEncryption()).
129+
"""
130+
131+
@abc.abstractmethod
132+
def sign(self, data: Buffer) -> bytes:
133+
"""
134+
Signs the data.
135+
"""
136+
137+
@abc.abstractmethod
138+
def sign_with_context(self, data: Buffer, context: Buffer) -> bytes:
139+
"""
140+
Signs the data with a context string.
141+
"""
142+
143+
@abc.abstractmethod
144+
def __copy__(self) -> MlDsa65PrivateKey:
145+
"""
146+
Returns a copy.
147+
"""
148+
149+
@abc.abstractmethod
150+
def __deepcopy__(self, memo: dict) -> MlDsa65PrivateKey:
151+
"""
152+
Returns a deep copy.
153+
"""
154+
155+
156+
if hasattr(rust_openssl, "mldsa65"):
157+
MlDsa65PrivateKey.register(rust_openssl.mldsa65.MlDsa65PrivateKey)

src/cryptography/hazmat/primitives/asymmetric/types.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
ec,
1414
ed448,
1515
ed25519,
16+
mldsa65,
1617
rsa,
1718
x448,
1819
x25519,
@@ -26,6 +27,7 @@
2627
ec.EllipticCurvePublicKey,
2728
ed25519.Ed25519PublicKey,
2829
ed448.Ed448PublicKey,
30+
mldsa65.MlDsa65PublicKey,
2931
x25519.X25519PublicKey,
3032
x448.X448PublicKey,
3133
]
@@ -42,6 +44,7 @@
4244
dh.DHPrivateKey,
4345
ed25519.Ed25519PrivateKey,
4446
ed448.Ed448PrivateKey,
47+
mldsa65.MlDsa65PrivateKey,
4548
rsa.RSAPrivateKey,
4649
dsa.DSAPrivateKey,
4750
ec.EllipticCurvePrivateKey,

src/rust/cryptography-key-parsing/src/pkcs8.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ pub fn parse_private_key(
108108
)?)
109109
}
110110

111+
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
112+
AlgorithmParameters::MlDsa65 => Ok(openssl::pkey::PKey::private_key_from_der(data)?),
113+
111114
_ => Err(KeyParsingError::UnsupportedKeyType(
112115
k.algorithm.oid().clone(),
113116
)),
@@ -440,6 +443,10 @@ pub fn serialize_private_key(
440443

441444
(params, private_key_der)
442445
}
446+
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
447+
id if id == openssl::pkey::Id::from_raw(cryptography_openssl::mldsa::NID_PQDSA) => {
448+
return Ok(pkey.private_key_to_pkcs8()?);
449+
}
443450
_ => {
444451
unimplemented!("Unknown key type");
445452
}

src/rust/cryptography-key-parsing/src/spki.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ pub fn parse_public_key(
100100

101101
Ok(openssl::pkey::PKey::from_dh(dh)?)
102102
}
103+
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
104+
AlgorithmParameters::MlDsa65 => Ok(cryptography_openssl::mldsa::new_raw_public_key(
105+
k.subject_public_key.as_bytes(),
106+
)
107+
.map_err(|_| KeyParsingError::InvalidKey)?),
108+
103109
_ => Err(KeyParsingError::UnsupportedKeyType(
104110
k.algorithm.oid().clone(),
105111
)),
@@ -214,6 +220,15 @@ pub fn serialize_public_key(
214220

215221
(params, pub_key_der)
216222
}
223+
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
224+
id if id == openssl::pkey::Id::from_raw(cryptography_openssl::mldsa::NID_PQDSA) => {
225+
let raw_bytes = pkey.raw_public_key()?;
226+
if raw_bytes.len() == cryptography_openssl::mldsa::MLDSA65_PUBLIC_KEY_BYTES {
227+
(AlgorithmParameters::MlDsa65, raw_bytes)
228+
} else {
229+
unimplemented!("Unsupported ML-DSA variant");
230+
}
231+
}
217232
_ => {
218233
unimplemented!("Unknown key type");
219234
}

src/rust/cryptography-openssl/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub mod aead;
99
pub mod cmac;
1010
pub mod fips;
1111
pub mod hmac;
12+
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
13+
pub mod mldsa;
1214
#[cfg(any(
1315
CRYPTOGRAPHY_IS_BORINGSSL,
1416
CRYPTOGRAPHY_IS_LIBRESSL,

0 commit comments

Comments
 (0)