Skip to content

Commit 2e31639

Browse files
authored
ML-DSA OpenSSL support (#14773)
* support OpenSSL with ML-DSA * Address review feedback on ML-DSA OpenSSL support Unify is_mldsa_pkey across BoringSSL/AWS-LC/OpenSSL 3.5+ and collapse the duplicated match arms in pkcs8/spki/keys. Replace silly match-with-guards with if/else chain in MlDsaVariant::from_pkey for OpenSSL 3.5+.
1 parent 5affe5a commit 2e31639

13 files changed

Lines changed: 195 additions & 46 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
- {VERSION: "3.14", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.0.20"}}
3737
- {VERSION: "3.14", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.4.5"}}
3838
- {VERSION: "3.14", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "3.5.6"}}
39-
- {VERSION: "3.14", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "4.0.0"}}
39+
- {VERSION: "3.14", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "openssl", VERSION: "4.0.0"}}
4040
- {VERSION: "3.14", NOXSESSION: "tests-ssh", OPENSSL: {TYPE: "openssl", VERSION: "3.6.2"}}
4141
- {VERSION: "3.14", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "libressl", VERSION: "4.1.2"}}
4242
- {VERSION: "3.14", NOXSESSION: "rust,tests", OPENSSL: {TYPE: "libressl", VERSION: "4.2.1"}}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ def mldsa_supported(self) -> bool:
282282
return (
283283
rust_openssl.CRYPTOGRAPHY_IS_AWSLC
284284
or rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL
285+
or rust_openssl.CRYPTOGRAPHY_OPENSSL_350_OR_GREATER
285286
)
286287

287288
def ed25519_supported(self) -> bool:

src/rust/cryptography-key-parsing/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ openssl-sys.workspace = true
1818
pem.workspace = true
1919

2020
[lints.rust]
21-
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(CRYPTOGRAPHY_IS_LIBRESSL)', 'cfg(CRYPTOGRAPHY_IS_BORINGSSL)', 'cfg(CRYPTOGRAPHY_OSSLCONF, values("OPENSSL_NO_RC2", "OPENSSL_NO_RC4"))', 'cfg(CRYPTOGRAPHY_IS_AWSLC)'] }
21+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(CRYPTOGRAPHY_IS_LIBRESSL)', 'cfg(CRYPTOGRAPHY_IS_BORINGSSL)', 'cfg(CRYPTOGRAPHY_OPENSSL_350_OR_GREATER)', 'cfg(CRYPTOGRAPHY_OSSLCONF, values("OPENSSL_NO_RC2", "OPENSSL_NO_RC4"))', 'cfg(CRYPTOGRAPHY_IS_AWSLC)'] }

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use std::env;
66

7+
#[allow(clippy::unusual_byte_groupings)]
78
fn main() {
89
if env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER").is_ok() {
910
println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_LIBRESSL");
@@ -17,6 +18,13 @@ fn main() {
1718
println!("cargo:rustc-cfg=CRYPTOGRAPHY_IS_AWSLC");
1819
}
1920

21+
if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") {
22+
let version = u64::from_str_radix(&version, 16).unwrap();
23+
if version >= 0x3_05_00_00_0 {
24+
println!("cargo:rustc-cfg=CRYPTOGRAPHY_OPENSSL_350_OR_GREATER");
25+
}
26+
}
27+
2028
if let Ok(vars) = env::var("DEP_OPENSSL_CONF") {
2129
for var in vars.split(',') {
2230
println!("cargo:rustc-cfg=CRYPTOGRAPHY_OSSLCONF=\"{var}\"");

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

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ pub enum MlKemPrivateKey {
3131
}
3232

3333
// RFC 9881 Section 6.5
34-
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
34+
#[cfg(any(
35+
CRYPTOGRAPHY_IS_BORINGSSL,
36+
CRYPTOGRAPHY_IS_AWSLC,
37+
CRYPTOGRAPHY_OPENSSL_350_OR_GREATER
38+
))]
3539
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
3640
pub enum MlDsaPrivateKey {
3741
#[implicit(0)]
@@ -55,16 +59,31 @@ pub fn mlkem_seed_from_pkey(
5559

5660
/// Extract the 32-byte ML-DSA seed from a private key.
5761
///
58-
/// AWS-LC's `raw_private_key()` returns the expanded key, not the seed.
59-
/// This function round-trips through the native PKCS#8 encoding to extract it.
60-
/// https://github.com/aws/aws-lc/issues/3072
61-
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
62+
/// For BoringSSL/AWS-LC, round-trips through PKCS#8 encoding to extract the
63+
/// seed (AWS-LC's `raw_private_key()` returns the expanded key, not the seed:
64+
/// https://github.com/aws/aws-lc/issues/3072).
65+
///
66+
/// For vanilla OpenSSL 3.5+, calls `PKey::seed_into` to read the seed
67+
/// directly, since OpenSSL 3.5's PKCS#8 inner encoding differs from
68+
/// BoringSSL/AWS-LC.
69+
#[cfg(any(
70+
CRYPTOGRAPHY_IS_BORINGSSL,
71+
CRYPTOGRAPHY_IS_AWSLC,
72+
CRYPTOGRAPHY_OPENSSL_350_OR_GREATER
73+
))]
6274
pub fn mldsa_seed_from_pkey(
6375
pkey: &openssl::pkey::PKeyRef<openssl::pkey::Private>,
6476
) -> Result<MlDsaPrivateKey, openssl::error::ErrorStack> {
65-
let pkcs8_der = pkey.private_key_to_pkcs8()?;
66-
let pki = asn1::parse_single::<PrivateKeyInfo<'_>>(&pkcs8_der).unwrap();
67-
Ok(asn1::parse_single::<MlDsaPrivateKey>(pki.private_key).unwrap())
77+
cfg_if::cfg_if! {
78+
if #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))] {
79+
let pkcs8_der = pkey.private_key_to_pkcs8()?;
80+
let pki = asn1::parse_single::<PrivateKeyInfo<'_>>(&pkcs8_der).unwrap();
81+
Ok(asn1::parse_single::<MlDsaPrivateKey>(pki.private_key).unwrap())
82+
} else if #[cfg(CRYPTOGRAPHY_OPENSSL_350_OR_GREATER)] {
83+
let seed = cryptography_openssl::mldsa::mldsa_seed_raw(pkey)?;
84+
Ok(MlDsaPrivateKey::Seed(seed))
85+
}
86+
}
6887
}
6988

7089
pub fn parse_private_key(data: &[u8]) -> KeyParsingResult<ParsedPrivateKey> {
@@ -174,8 +193,11 @@ pub fn parse_private_key(data: &[u8]) -> KeyParsingResult<ParsedPrivateKey> {
174193
)?;
175194
Ok(ParsedPrivateKey::Pkey(pkey))
176195
}
177-
178-
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
196+
#[cfg(any(
197+
CRYPTOGRAPHY_IS_BORINGSSL,
198+
CRYPTOGRAPHY_IS_AWSLC,
199+
CRYPTOGRAPHY_OPENSSL_350_OR_GREATER
200+
))]
179201
AlgorithmParameters::MlDsa44 => {
180202
let MlDsaPrivateKey::Seed(seed) = asn1::parse_single::<MlDsaPrivateKey>(k.private_key)?;
181203
Ok(ParsedPrivateKey::Pkey(
@@ -186,7 +208,11 @@ pub fn parse_private_key(data: &[u8]) -> KeyParsingResult<ParsedPrivateKey> {
186208
))
187209
}
188210

189-
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
211+
#[cfg(any(
212+
CRYPTOGRAPHY_IS_BORINGSSL,
213+
CRYPTOGRAPHY_IS_AWSLC,
214+
CRYPTOGRAPHY_OPENSSL_350_OR_GREATER
215+
))]
190216
AlgorithmParameters::MlDsa65 => {
191217
let MlDsaPrivateKey::Seed(seed) = asn1::parse_single::<MlDsaPrivateKey>(k.private_key)?;
192218
Ok(ParsedPrivateKey::Pkey(
@@ -197,7 +223,11 @@ pub fn parse_private_key(data: &[u8]) -> KeyParsingResult<ParsedPrivateKey> {
197223
))
198224
}
199225

200-
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
226+
#[cfg(any(
227+
CRYPTOGRAPHY_IS_BORINGSSL,
228+
CRYPTOGRAPHY_IS_AWSLC,
229+
CRYPTOGRAPHY_OPENSSL_350_OR_GREATER
230+
))]
201231
AlgorithmParameters::MlDsa87 => {
202232
let MlDsaPrivateKey::Seed(seed) = asn1::parse_single::<MlDsaPrivateKey>(k.private_key)?;
203233
Ok(ParsedPrivateKey::Pkey(
@@ -555,8 +585,12 @@ pub fn serialize_private_key(key: &ParsedPrivateKey) -> crate::KeySerializationR
555585
};
556586
(params, private_key_der)
557587
}
558-
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
559-
id if cryptography_openssl::mldsa::is_mldsa_pkey_type(id) => {
588+
#[cfg(any(
589+
CRYPTOGRAPHY_IS_BORINGSSL,
590+
CRYPTOGRAPHY_IS_AWSLC,
591+
CRYPTOGRAPHY_OPENSSL_350_OR_GREATER
592+
))]
593+
_ if cryptography_openssl::mldsa::is_mldsa_pkey(pkey) => {
560594
let private_key_der = asn1::write_single(&mldsa_seed_from_pkey(pkey)?)?;
561595
let params = match cryptography_openssl::mldsa::MlDsaVariant::from_pkey(pkey) {
562596
cryptography_openssl::mldsa::MlDsaVariant::MlDsa44 => {

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

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,23 +125,35 @@ pub fn parse_public_key(data: &[u8]) -> KeyParsingResult<ParsedPublicKey> {
125125
)
126126
.map_err(|_| KeyParsingError::InvalidKey)?,
127127
)),
128-
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
128+
#[cfg(any(
129+
CRYPTOGRAPHY_IS_BORINGSSL,
130+
CRYPTOGRAPHY_IS_AWSLC,
131+
CRYPTOGRAPHY_OPENSSL_350_OR_GREATER
132+
))]
129133
AlgorithmParameters::MlDsa44 => Ok(ParsedPublicKey::Pkey(
130134
cryptography_openssl::mldsa::new_raw_public_key(
131135
cryptography_openssl::mldsa::MlDsaVariant::MlDsa44,
132136
k.subject_public_key.as_bytes(),
133137
)
134138
.map_err(|_| KeyParsingError::InvalidKey)?,
135139
)),
136-
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
140+
#[cfg(any(
141+
CRYPTOGRAPHY_IS_BORINGSSL,
142+
CRYPTOGRAPHY_IS_AWSLC,
143+
CRYPTOGRAPHY_OPENSSL_350_OR_GREATER
144+
))]
137145
AlgorithmParameters::MlDsa65 => Ok(ParsedPublicKey::Pkey(
138146
cryptography_openssl::mldsa::new_raw_public_key(
139147
cryptography_openssl::mldsa::MlDsaVariant::MlDsa65,
140148
k.subject_public_key.as_bytes(),
141149
)
142150
.map_err(|_| KeyParsingError::InvalidKey)?,
143151
)),
144-
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
152+
#[cfg(any(
153+
CRYPTOGRAPHY_IS_BORINGSSL,
154+
CRYPTOGRAPHY_IS_AWSLC,
155+
CRYPTOGRAPHY_OPENSSL_350_OR_GREATER
156+
))]
145157
AlgorithmParameters::MlDsa87 => Ok(ParsedPublicKey::Pkey(
146158
cryptography_openssl::mldsa::new_raw_public_key(
147159
cryptography_openssl::mldsa::MlDsaVariant::MlDsa87,
@@ -277,8 +289,12 @@ pub fn serialize_public_key(
277289
};
278290
(params, raw_bytes)
279291
}
280-
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
281-
id if cryptography_openssl::mldsa::is_mldsa_pkey_type(id) => {
292+
#[cfg(any(
293+
CRYPTOGRAPHY_IS_BORINGSSL,
294+
CRYPTOGRAPHY_IS_AWSLC,
295+
CRYPTOGRAPHY_OPENSSL_350_OR_GREATER
296+
))]
297+
_ if cryptography_openssl::mldsa::is_mldsa_pkey(pkey) => {
282298
let raw_bytes = pkey.raw_public_key()?;
283299
let params = match cryptography_openssl::mldsa::MlDsaVariant::from_pkey(pkey) {
284300
cryptography_openssl::mldsa::MlDsaVariant::MlDsa44 => AlgorithmParameters::MlDsa44,

src/rust/cryptography-openssl/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ openssl.workspace = true
1515
openssl-sys.workspace = true
1616

1717
[lints.rust]
18-
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)', 'cfg(CRYPTOGRAPHY_IS_LIBRESSL)', 'cfg(CRYPTOGRAPHY_IS_BORINGSSL)', 'cfg(CRYPTOGRAPHY_IS_AWSLC)'] }
18+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(CRYPTOGRAPHY_OPENSSL_320_OR_GREATER)', 'cfg(CRYPTOGRAPHY_OPENSSL_350_OR_GREATER)', 'cfg(CRYPTOGRAPHY_IS_LIBRESSL)', 'cfg(CRYPTOGRAPHY_IS_BORINGSSL)', 'cfg(CRYPTOGRAPHY_IS_AWSLC)'] }

src/rust/cryptography-openssl/build.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ fn main() {
1212
if version >= 0x3_02_00_00_0 {
1313
println!("cargo:rustc-cfg=CRYPTOGRAPHY_OPENSSL_320_OR_GREATER");
1414
}
15+
if version >= 0x3_05_00_00_0 {
16+
println!("cargo:rustc-cfg=CRYPTOGRAPHY_OPENSSL_350_OR_GREATER");
17+
}
1518
}
1619

1720
if env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER").is_ok() {

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ pub mod aead;
99
pub mod cmac;
1010
pub mod fips;
1111
pub mod hmac;
12-
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
12+
#[cfg(any(
13+
CRYPTOGRAPHY_IS_BORINGSSL,
14+
CRYPTOGRAPHY_IS_AWSLC,
15+
CRYPTOGRAPHY_OPENSSL_350_OR_GREATER
16+
))]
1317
pub mod mldsa;
1418
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))]
1519
pub mod mlkem;

0 commit comments

Comments
 (0)