Skip to content

Commit 24c165d

Browse files
alexclaude
andauthored
Skip PKCS#8 round-trip when extracting ML-DSA/ML-KEM seeds on BoringSSL (#14810)
* remove PKCS#8 round-trip when extracting ML-DSA/ML-KEM seeds on BoringSSL BoringSSL exposes EVP_PKEY_get_private_seed, which returns the seed directly. Use it instead of round-tripping through PKCS#8. AWS-LC keeps the round-trip since it lacks an equivalent API. * simplify cfg_if branches in seed extraction to bare else * import ForeignTypeRef for as_ptr() on BoringSSL ML-KEM seed extraction --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1f9203e commit 24c165d

3 files changed

Lines changed: 51 additions & 21 deletions

File tree

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

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,13 @@ pub enum MlDsaPrivateKey {
4848

4949
/// Extract the ML-KEM seed from a private key.
5050
///
51-
/// For BoringSSL/AWS-LC, round-trips through PKCS#8 encoding to extract the
52-
/// seed. AWS-LC's `raw_private_key()` returns the 2400-byte expanded key, not
53-
/// the seed; since AWS-LC 1.72.0, `private_key_to_pkcs8()` produces RFC 9935
54-
/// seed-format PKCS#8 when the key was created from a seed. BoringSSL's
55-
/// private key serialization also emits RFC 9935 seed-format PKCS#8.
51+
/// For BoringSSL and OpenSSL 3.5+, calls the library's seed extraction API
52+
/// directly (`EVP_PKEY_get_private_seed` / `PKey::seed_into`).
5653
///
57-
/// For vanilla OpenSSL 3.5+, calls `PKey::seed_into` to read the seed
58-
/// directly, avoiding the PKCS#8 round-trip.
54+
/// For AWS-LC, round-trips through PKCS#8 encoding because
55+
/// `raw_private_key()` returns the 2400-byte expanded key. Since AWS-LC
56+
/// 1.72.0, `private_key_to_pkcs8()` produces RFC 9935 seed-format PKCS#8
57+
/// when the key was created from a seed.
5958
#[cfg(any(
6059
CRYPTOGRAPHY_IS_BORINGSSL,
6160
CRYPTOGRAPHY_IS_AWSLC,
@@ -65,11 +64,11 @@ pub fn mlkem_seed_from_pkey(
6564
pkey: &openssl::pkey::PKeyRef<openssl::pkey::Private>,
6665
) -> Result<MlKemPrivateKey, openssl::error::ErrorStack> {
6766
cfg_if::cfg_if! {
68-
if #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))] {
67+
if #[cfg(CRYPTOGRAPHY_IS_AWSLC)] {
6968
let pkcs8_der = pkey.private_key_to_pkcs8()?;
7069
let pki = asn1::parse_single::<PrivateKeyInfo<'_>>(&pkcs8_der).unwrap();
7170
Ok(asn1::parse_single::<MlKemPrivateKey>(pki.private_key).unwrap())
72-
} else if #[cfg(CRYPTOGRAPHY_OPENSSL_350_OR_GREATER)] {
71+
} else {
7372
let seed = cryptography_openssl::mlkem::mlkem_seed_raw(pkey)?;
7473
Ok(MlKemPrivateKey::Seed(seed))
7574
}
@@ -78,13 +77,12 @@ pub fn mlkem_seed_from_pkey(
7877

7978
/// Extract the 32-byte ML-DSA seed from a private key.
8079
///
81-
/// For BoringSSL/AWS-LC, round-trips through PKCS#8 encoding to extract the
82-
/// seed (AWS-LC's `raw_private_key()` returns the expanded key, not the seed:
83-
/// https://github.com/aws/aws-lc/issues/3072).
80+
/// For BoringSSL and OpenSSL 3.5+, calls the library's seed extraction API
81+
/// directly (`EVP_PKEY_get_private_seed` / `PKey::seed_into`).
8482
///
85-
/// For vanilla OpenSSL 3.5+, calls `PKey::seed_into` to read the seed
86-
/// directly, since OpenSSL 3.5's PKCS#8 inner encoding differs from
87-
/// BoringSSL/AWS-LC.
83+
/// For AWS-LC, round-trips through PKCS#8 encoding because
84+
/// `raw_private_key()` returns the expanded key, not the seed
85+
/// (https://github.com/aws/aws-lc/issues/3072).
8886
#[cfg(any(
8987
CRYPTOGRAPHY_IS_BORINGSSL,
9088
CRYPTOGRAPHY_IS_AWSLC,
@@ -94,11 +92,11 @@ pub fn mldsa_seed_from_pkey(
9492
pkey: &openssl::pkey::PKeyRef<openssl::pkey::Private>,
9593
) -> Result<MlDsaPrivateKey, openssl::error::ErrorStack> {
9694
cfg_if::cfg_if! {
97-
if #[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC))] {
95+
if #[cfg(CRYPTOGRAPHY_IS_AWSLC)] {
9896
let pkcs8_der = pkey.private_key_to_pkcs8()?;
9997
let pki = asn1::parse_single::<PrivateKeyInfo<'_>>(&pkcs8_der).unwrap();
10098
Ok(asn1::parse_single::<MlDsaPrivateKey>(pki.private_key).unwrap())
101-
} else if #[cfg(CRYPTOGRAPHY_OPENSSL_350_OR_GREATER)] {
99+
} else {
102100
let seed = cryptography_openssl::mldsa::mldsa_seed_raw(pkey)?;
103101
Ok(MlDsaPrivateKey::Seed(seed))
104102
}

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,27 @@ extern "C" {
206206
///
207207
/// Avoids the PKCS#8 round-trip that vanilla OpenSSL 3.5 encodes
208208
/// differently from BoringSSL/AWS-LC.
209-
#[cfg(CRYPTOGRAPHY_OPENSSL_350_OR_GREATER)]
209+
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_OPENSSL_350_OR_GREATER))]
210210
pub fn mldsa_seed_raw(
211211
pkey: &openssl::pkey::PKeyRef<openssl::pkey::Private>,
212212
) -> OpenSSLResult<[u8; 32]> {
213213
let mut seed = [0u8; 32];
214-
pkey.seed_into(&mut seed)?;
214+
cfg_if::cfg_if! {
215+
if #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] {
216+
let mut seed_len = seed.len();
217+
// SAFETY: pkey is a valid EVP_PKEY and seed is a 32-byte buffer.
218+
unsafe {
219+
cvt(ffi::EVP_PKEY_get_private_seed(
220+
pkey.as_ptr(),
221+
seed.as_mut_ptr(),
222+
&mut seed_len,
223+
))?;
224+
}
225+
assert_eq!(seed_len, 32);
226+
} else if #[cfg(CRYPTOGRAPHY_OPENSSL_350_OR_GREATER)] {
227+
pkey.seed_into(&mut seed)?;
228+
}
229+
}
215230
Ok(seed)
216231
}
217232

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// for complete details.
44

55
use foreign_types_shared::ForeignType;
6+
#[cfg(CRYPTOGRAPHY_IS_BORINGSSL)]
7+
use foreign_types_shared::ForeignTypeRef;
68
use openssl_sys as ffi;
79
#[cfg(CRYPTOGRAPHY_IS_AWSLC)]
810
use std::os::raw::c_int;
@@ -121,12 +123,27 @@ extern "C" {
121123
///
122124
/// Avoids the PKCS#8 round-trip that vanilla OpenSSL 3.5 encodes
123125
/// differently from BoringSSL/AWS-LC.
124-
#[cfg(CRYPTOGRAPHY_OPENSSL_350_OR_GREATER)]
126+
#[cfg(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_OPENSSL_350_OR_GREATER))]
125127
pub fn mlkem_seed_raw(
126128
pkey: &openssl::pkey::PKeyRef<openssl::pkey::Private>,
127129
) -> OpenSSLResult<[u8; 64]> {
128130
let mut seed = [0u8; 64];
129-
pkey.seed_into(&mut seed)?;
131+
cfg_if::cfg_if! {
132+
if #[cfg(CRYPTOGRAPHY_IS_BORINGSSL)] {
133+
let mut seed_len = seed.len();
134+
// SAFETY: pkey is a valid EVP_PKEY and seed is a 64-byte buffer.
135+
unsafe {
136+
cvt(ffi::EVP_PKEY_get_private_seed(
137+
pkey.as_ptr(),
138+
seed.as_mut_ptr(),
139+
&mut seed_len,
140+
))?;
141+
}
142+
assert_eq!(seed_len, 64);
143+
} else if #[cfg(CRYPTOGRAPHY_OPENSSL_350_OR_GREATER)] {
144+
pkey.seed_into(&mut seed)?;
145+
}
146+
}
130147
Ok(seed)
131148
}
132149

0 commit comments

Comments
 (0)