Skip to content

Commit 3391f09

Browse files
alexclaude
andauthored
Implement PKCS#8 serialization using rust-asn1 for unencrypted keys (#13624)
Replace OpenSSL's private_key_to_pkcs8() with rust-asn1 serialization for unencrypted PKCS#8 private keys. Supports RSA, EC, DSA, DH, Ed25519, X25519, Ed448, and X448 key types. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <[email protected]>
1 parent c1c9116 commit 3391f09

File tree

6 files changed

+168
-44
lines changed

6 files changed

+168
-44
lines changed

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,14 @@ pub(crate) fn ec_params_to_group(
121121

122122
pub fn serialize_pkcs1_private_key(
123123
ec: &openssl::ec::EcKeyRef<openssl::pkey::Private>,
124+
include_curve: bool,
124125
) -> KeySerializationResult<Vec<u8>> {
125-
let curve_oid = group_to_curve_oid(ec.group()).expect("Unknown curve");
126+
let parameters = if include_curve {
127+
let curve_oid = group_to_curve_oid(ec.group()).expect("Unknown curve");
128+
Some(EcParameters::NamedCurve(curve_oid))
129+
} else {
130+
None
131+
};
126132

127133
let private_key_bytes = ec.private_key().to_vec();
128134

@@ -136,7 +142,7 @@ pub fn serialize_pkcs1_private_key(
136142
let key = EcPrivateKey {
137143
version: 1,
138144
private_key: &private_key_bytes,
139-
parameters: Some(EcParameters::NamedCurve(curve_oid)),
145+
parameters,
140146
public_key: Some(asn1::BitString::new(&public_key_bytes, 0).unwrap()),
141147
};
142148
Ok(asn1::write_single(&key)?)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub mod pem;
1212
pub mod pkcs8;
1313
pub mod rsa;
1414
pub mod spki;
15+
pub(crate) mod utils;
1516

1617
pub const MIN_DH_MODULUS_SIZE: u32 = 512;
1718

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

Lines changed: 119 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ use crate::MIN_DH_MODULUS_SIZE;
1313
use crate::{ec, rsa, KeyParsingError, KeyParsingResult};
1414

1515
// RFC 5208 Section 5
16-
#[derive(asn1::Asn1Read)]
17-
struct PrivateKeyInfo<'a> {
18-
version: u8,
19-
algorithm: AlgorithmIdentifier<'a>,
20-
private_key: &'a [u8],
16+
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
17+
pub struct PrivateKeyInfo<'a> {
18+
pub version: u8,
19+
pub algorithm: AlgorithmIdentifier<'a>,
20+
pub private_key: &'a [u8],
2121
#[implicit(0)]
22-
_attributes: Option<Attributes<'a>>,
22+
pub attributes: Option<Attributes<'a>>,
2323
}
2424

2525
pub fn parse_private_key(
@@ -331,3 +331,116 @@ pub fn parse_encrypted_private_key(
331331

332332
parse_private_key(&plaintext)
333333
}
334+
335+
pub fn serialize_private_key(
336+
pkey: &openssl::pkey::PKeyRef<openssl::pkey::Private>,
337+
) -> crate::KeySerializationResult<Vec<u8>> {
338+
let p_bytes;
339+
let q_bytes;
340+
let g_bytes;
341+
let q_bytes_dh;
342+
343+
let (params, private_key_der) = if let Ok(rsa) = pkey.rsa() {
344+
let pkcs1_der = rsa::serialize_pkcs1_private_key(&rsa)?;
345+
(AlgorithmParameters::Rsa(Some(())), pkcs1_der)
346+
} else if let Ok(ec) = pkey.ec_key() {
347+
let curve_oid = ec::group_to_curve_oid(ec.group()).expect("Unknown curve");
348+
let pkcs1_der = ec::serialize_pkcs1_private_key(&ec, false)?;
349+
(
350+
AlgorithmParameters::Ec(cryptography_x509::common::EcParameters::NamedCurve(
351+
curve_oid,
352+
)),
353+
pkcs1_der,
354+
)
355+
} else if pkey.id() == openssl::pkey::Id::ED25519 {
356+
let raw_bytes = pkey.raw_private_key()?;
357+
let private_key_der = asn1::write_single(&raw_bytes.as_slice())?;
358+
(AlgorithmParameters::Ed25519, private_key_der)
359+
} else if pkey.id() == openssl::pkey::Id::X25519 {
360+
let raw_bytes = pkey.raw_private_key()?;
361+
let private_key_der = asn1::write_single(&raw_bytes.as_slice())?;
362+
(AlgorithmParameters::X25519, private_key_der)
363+
} else if crate::utils::is_ed448(pkey.id()) {
364+
let raw_bytes = pkey.raw_private_key()?;
365+
let private_key_der = asn1::write_single(&raw_bytes.as_slice())?;
366+
(AlgorithmParameters::Ed448, private_key_der)
367+
} else if crate::utils::is_x448(pkey.id()) {
368+
let raw_bytes = pkey.raw_private_key()?;
369+
let private_key_der = asn1::write_single(&raw_bytes.as_slice())?;
370+
(AlgorithmParameters::X448, private_key_der)
371+
} else if let Ok(dsa) = pkey.dsa() {
372+
p_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(dsa.p())?;
373+
q_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(dsa.q())?;
374+
g_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(dsa.g())?;
375+
376+
let priv_key_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(dsa.priv_key())?;
377+
let priv_key_int = asn1::BigUint::new(&priv_key_bytes).unwrap();
378+
let private_key_der = asn1::write_single(&priv_key_int)?;
379+
380+
let dsa_params = cryptography_x509::common::DssParams {
381+
p: asn1::BigUint::new(&p_bytes).unwrap(),
382+
q: asn1::BigUint::new(&q_bytes).unwrap(),
383+
g: asn1::BigUint::new(&g_bytes).unwrap(),
384+
};
385+
386+
(AlgorithmParameters::Dsa(dsa_params), private_key_der)
387+
} else if let Ok(dh) = pkey.dh() {
388+
p_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(dh.prime_p())?;
389+
g_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(dh.generator())?;
390+
q_bytes_dh = dh
391+
.prime_q()
392+
.map(cryptography_openssl::utils::bn_to_big_endian_bytes)
393+
.transpose()?;
394+
395+
let priv_key_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(dh.private_key())?;
396+
let priv_key_int = asn1::BigUint::new(&priv_key_bytes).unwrap();
397+
let private_key_der = asn1::write_single(&priv_key_int)?;
398+
399+
let params = if let Some(ref q_bytes) = q_bytes_dh {
400+
let dhx_params = cryptography_x509::common::DHXParams {
401+
p: asn1::BigUint::new(&p_bytes).unwrap(),
402+
g: asn1::BigUint::new(&g_bytes).unwrap(),
403+
q: asn1::BigUint::new(q_bytes).unwrap(),
404+
j: None,
405+
validation_params: None,
406+
};
407+
AlgorithmParameters::Dh(dhx_params)
408+
} else {
409+
let basic_params = cryptography_x509::common::BasicDHParams {
410+
p: asn1::BigUint::new(&p_bytes).unwrap(),
411+
g: asn1::BigUint::new(&g_bytes).unwrap(),
412+
private_value_length: None,
413+
};
414+
AlgorithmParameters::DhKeyAgreement(basic_params)
415+
};
416+
417+
(params, private_key_der)
418+
} else {
419+
unimplemented!("Unknown key type");
420+
};
421+
422+
let pki = PrivateKeyInfo {
423+
version: 0,
424+
algorithm: AlgorithmIdentifier {
425+
oid: asn1::DefinedByMarker::marker(),
426+
params,
427+
},
428+
private_key: &private_key_der,
429+
attributes: None,
430+
};
431+
Ok(asn1::write_single(&pki)?)
432+
}
433+
434+
#[cfg(test)]
435+
mod tests {
436+
use super::serialize_private_key;
437+
438+
#[cfg(not(CRYPTOGRAPHY_IS_BORINGSSL))]
439+
#[test]
440+
#[should_panic(expected = "Unknown key type")]
441+
fn test_serialize_private_key_unknown_key_type() {
442+
let pkey = openssl::pkey::PKey::hmac(&[0u8; 16]).unwrap();
443+
// Expected to panic
444+
_ = serialize_private_key(&pkey);
445+
}
446+
}

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

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -106,38 +106,6 @@ pub fn parse_public_key(
106106
}
107107
}
108108

109-
// these functions exist because you can't use conditional compilation easily
110-
// in `else if` chain.
111-
fn is_ed448(id: openssl::pkey::Id) -> bool {
112-
cfg_if::cfg_if! {
113-
if #[cfg(not(any(
114-
CRYPTOGRAPHY_IS_LIBRESSL,
115-
CRYPTOGRAPHY_IS_BORINGSSL,
116-
CRYPTOGRAPHY_IS_AWSLC
117-
)))] {
118-
id == openssl::pkey::Id::ED448
119-
} else {
120-
_ = id;
121-
false
122-
}
123-
}
124-
}
125-
126-
fn is_x448(id: openssl::pkey::Id) -> bool {
127-
cfg_if::cfg_if! {
128-
if #[cfg(not(any(
129-
CRYPTOGRAPHY_IS_LIBRESSL,
130-
CRYPTOGRAPHY_IS_BORINGSSL,
131-
CRYPTOGRAPHY_IS_AWSLC
132-
)))] {
133-
id == openssl::pkey::Id::X448
134-
} else {
135-
_ = id;
136-
false
137-
}
138-
}
139-
}
140-
141109
pub fn serialize_public_key(
142110
pkey: &openssl::pkey::PKeyRef<impl openssl::pkey::HasPublic>,
143111
) -> KeySerializationResult<Vec<u8>> {
@@ -169,10 +137,10 @@ pub fn serialize_public_key(
169137
} else if pkey.id() == openssl::pkey::Id::X25519 {
170138
let raw_bytes = pkey.raw_public_key()?;
171139
(AlgorithmParameters::X25519, raw_bytes)
172-
} else if is_ed448(pkey.id()) {
140+
} else if crate::utils::is_ed448(pkey.id()) {
173141
let raw_bytes = pkey.raw_public_key()?;
174142
(AlgorithmParameters::Ed448, raw_bytes)
175-
} else if is_x448(pkey.id()) {
143+
} else if crate::utils::is_x448(pkey.id()) {
176144
let raw_bytes = pkey.raw_public_key()?;
177145
(AlgorithmParameters::X448, raw_bytes)
178146
} else if let Ok(dsa) = pkey.dsa() {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
pub(crate) fn is_ed448(id: openssl::pkey::Id) -> bool {
6+
cfg_if::cfg_if! {
7+
if #[cfg(not(any(
8+
CRYPTOGRAPHY_IS_LIBRESSL,
9+
CRYPTOGRAPHY_IS_BORINGSSL,
10+
CRYPTOGRAPHY_IS_AWSLC
11+
)))] {
12+
id == openssl::pkey::Id::ED448
13+
} else {
14+
_ = id;
15+
false
16+
}
17+
}
18+
}
19+
20+
pub(crate) fn is_x448(id: openssl::pkey::Id) -> bool {
21+
cfg_if::cfg_if! {
22+
if #[cfg(not(any(
23+
CRYPTOGRAPHY_IS_LIBRESSL,
24+
CRYPTOGRAPHY_IS_BORINGSSL,
25+
CRYPTOGRAPHY_IS_AWSLC
26+
)))] {
27+
id == openssl::pkey::Id::X448
28+
} else {
29+
_ = id;
30+
false
31+
}
32+
}
33+
}

src/rust/src/backend/utils.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,10 @@ pub(crate) fn pkey_private_bytes<'p>(
115115

116116
if format.is(&types::PRIVATE_FORMAT_PKCS8.get(py)?) {
117117
let (tag, der_bytes) = if password.is_empty() {
118-
("PRIVATE KEY", pkey.private_key_to_pkcs8()?)
118+
(
119+
"PRIVATE KEY",
120+
cryptography_key_parsing::pkcs8::serialize_private_key(pkey)?,
121+
)
119122
} else {
120123
(
121124
"ENCRYPTED PRIVATE KEY",
@@ -178,7 +181,7 @@ pub(crate) fn pkey_private_bytes<'p>(
178181
return Ok(pyo3::types::PyBytes::new(py, &der_bytes));
179182
}
180183
} else if let Ok(ec) = pkey.ec_key() {
181-
let der_bytes = cryptography_key_parsing::ec::serialize_pkcs1_private_key(&ec)?;
184+
let der_bytes = cryptography_key_parsing::ec::serialize_pkcs1_private_key(&ec, true)?;
182185
if encoding.is(&types::ENCODING_PEM.get(py)?) {
183186
let pem_bytes = cryptography_key_parsing::pem::encrypt_pem(
184187
"EC PRIVATE KEY",

0 commit comments

Comments
 (0)