Skip to content

Commit 7400737

Browse files
alexclaude
andauthored
Use rust-asn1 for PKCS#1 private key serialization (#13620)
* Use rust-asn1 for PKCS#1 private key serialization Replace OpenSSL's private_key_to_der() with rust-asn1 serialization for RSA, DSA, and EC private keys in Traditional OpenSSL format. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * put this in a vector --------- Co-authored-by: Claude <[email protected]>
1 parent 9b49fe8 commit 7400737

File tree

8 files changed

+142
-37
lines changed

8 files changed

+142
-37
lines changed

docs/development/test-vectors.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,8 @@ Custom asymmetric vectors
294294
encrypted with a 20 byte salt with the password ``password``.
295295
* ``asymmetric/PKCS8/wrong-pem-delimiter-rsa.pem`` - A PKCS8 encoded RSA key
296296
with the wrong PEM delimiter.
297+
* ``asymmetric/EC/high-bit-set.pem`` - A PKCS#1 encoded EC key the private key
298+
value has its high bit set.
297299

298300
Key exchange
299301
~~~~~~~~~~~~

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
33
// for complete details.
44

5-
use crate::KeyParsingResult;
5+
use crate::{KeyParsingResult, KeySerializationResult};
66

7-
#[derive(asn1::Asn1Read)]
7+
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
88
struct DsaPrivateKey<'a> {
99
version: u8,
1010
p: asn1::BigUint<'a>,
@@ -14,6 +14,26 @@ struct DsaPrivateKey<'a> {
1414
priv_key: asn1::BigUint<'a>,
1515
}
1616

17+
pub fn serialize_pkcs1_private_key(
18+
dsa: &openssl::dsa::DsaRef<openssl::pkey::Private>,
19+
) -> KeySerializationResult<Vec<u8>> {
20+
let p_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(dsa.p())?;
21+
let q_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(dsa.q())?;
22+
let g_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(dsa.g())?;
23+
let pub_key_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(dsa.pub_key())?;
24+
let priv_key_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(dsa.priv_key())?;
25+
26+
let key = DsaPrivateKey {
27+
version: 0,
28+
p: asn1::BigUint::new(&p_bytes).unwrap(),
29+
q: asn1::BigUint::new(&q_bytes).unwrap(),
30+
g: asn1::BigUint::new(&g_bytes).unwrap(),
31+
pub_key: asn1::BigUint::new(&pub_key_bytes).unwrap(),
32+
priv_key: asn1::BigUint::new(&priv_key_bytes).unwrap(),
33+
};
34+
Ok(asn1::write_single(&key)?)
35+
}
36+
1737
pub fn parse_pkcs1_private_key(
1838
data: &[u8],
1939
) -> KeyParsingResult<openssl::pkey::PKey<openssl::pkey::Private>> {

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

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
use cryptography_x509::common::EcParameters;
66
use cryptography_x509::ec_constants;
77

8-
use crate::{KeyParsingError, KeyParsingResult};
8+
use crate::{KeyParsingError, KeyParsingResult, KeySerializationResult};
99

1010
// From RFC 5915 Section 3
11-
#[derive(asn1::Asn1Read)]
11+
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
1212
pub(crate) struct EcPrivateKey<'a> {
1313
pub(crate) version: u8,
1414
pub(crate) private_key: &'a [u8],
@@ -18,6 +18,37 @@ pub(crate) struct EcPrivateKey<'a> {
1818
pub(crate) public_key: Option<asn1::BitString<'a>>,
1919
}
2020

21+
pub(crate) fn group_to_curve_oid(
22+
group: &openssl::ec::EcGroupRef,
23+
) -> Option<asn1::ObjectIdentifier> {
24+
let nid = group.curve_name()?;
25+
match nid {
26+
openssl::nid::Nid::X9_62_PRIME192V1 => Some(cryptography_x509::oid::EC_SECP192R1),
27+
openssl::nid::Nid::SECP224R1 => Some(cryptography_x509::oid::EC_SECP224R1),
28+
openssl::nid::Nid::X9_62_PRIME256V1 => Some(cryptography_x509::oid::EC_SECP256R1),
29+
openssl::nid::Nid::SECP384R1 => Some(cryptography_x509::oid::EC_SECP384R1),
30+
openssl::nid::Nid::SECP521R1 => Some(cryptography_x509::oid::EC_SECP521R1),
31+
openssl::nid::Nid::SECP256K1 => Some(cryptography_x509::oid::EC_SECP256K1),
32+
openssl::nid::Nid::SECT233R1 => Some(cryptography_x509::oid::EC_SECT233R1),
33+
openssl::nid::Nid::SECT283R1 => Some(cryptography_x509::oid::EC_SECT283R1),
34+
openssl::nid::Nid::SECT409R1 => Some(cryptography_x509::oid::EC_SECT409R1),
35+
openssl::nid::Nid::SECT571R1 => Some(cryptography_x509::oid::EC_SECT571R1),
36+
openssl::nid::Nid::SECT163R2 => Some(cryptography_x509::oid::EC_SECT163R2),
37+
openssl::nid::Nid::SECT163K1 => Some(cryptography_x509::oid::EC_SECT163K1),
38+
openssl::nid::Nid::SECT233K1 => Some(cryptography_x509::oid::EC_SECT233K1),
39+
openssl::nid::Nid::SECT283K1 => Some(cryptography_x509::oid::EC_SECT283K1),
40+
openssl::nid::Nid::SECT409K1 => Some(cryptography_x509::oid::EC_SECT409K1),
41+
openssl::nid::Nid::SECT571K1 => Some(cryptography_x509::oid::EC_SECT571K1),
42+
#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))]
43+
openssl::nid::Nid::BRAINPOOL_P256R1 => Some(cryptography_x509::oid::EC_BRAINPOOLP256R1),
44+
#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))]
45+
openssl::nid::Nid::BRAINPOOL_P384R1 => Some(cryptography_x509::oid::EC_BRAINPOOLP384R1),
46+
#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))]
47+
openssl::nid::Nid::BRAINPOOL_P512R1 => Some(cryptography_x509::oid::EC_BRAINPOOLP512R1),
48+
_ => None,
49+
}
50+
}
51+
2152
pub(crate) fn ec_params_to_group(
2253
params: &EcParameters<'_>,
2354
) -> KeyParsingResult<openssl::ec::EcGroup> {
@@ -88,6 +119,29 @@ pub(crate) fn ec_params_to_group(
88119
}
89120
}
90121

122+
pub fn serialize_pkcs1_private_key(
123+
ec: &openssl::ec::EcKeyRef<openssl::pkey::Private>,
124+
) -> KeySerializationResult<Vec<u8>> {
125+
let curve_oid = group_to_curve_oid(ec.group()).expect("Unknown curve");
126+
127+
let private_key_bytes = ec.private_key().to_vec();
128+
129+
let mut bn_ctx = openssl::bn::BigNumContext::new()?;
130+
let public_key_bytes = ec.public_key().to_bytes(
131+
ec.group(),
132+
openssl::ec::PointConversionForm::UNCOMPRESSED,
133+
&mut bn_ctx,
134+
)?;
135+
136+
let key = EcPrivateKey {
137+
version: 1,
138+
private_key: &private_key_bytes,
139+
parameters: Some(EcParameters::NamedCurve(curve_oid)),
140+
public_key: Some(asn1::BitString::new(&public_key_bytes, 0).unwrap()),
141+
};
142+
Ok(asn1::write_single(&key)?)
143+
}
144+
91145
pub fn parse_pkcs1_private_key(
92146
data: &[u8],
93147
ec_params: Option<EcParameters<'_>>,

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub struct Pkcs1RsaPublicKey<'a> {
1111
}
1212

1313
// RFC 8017, Section A.1.2
14-
#[derive(asn1::Asn1Read)]
14+
#[derive(asn1::Asn1Read, asn1::Asn1Write)]
1515
pub(crate) struct RsaPrivateKey<'a> {
1616
pub(crate) version: u8,
1717
pub(crate) n: asn1::BigUint<'a>,
@@ -51,6 +51,33 @@ pub fn serialize_pkcs1_public_key(
5151
Ok(asn1::write_single(&key)?)
5252
}
5353

54+
pub fn serialize_pkcs1_private_key(
55+
rsa: &openssl::rsa::RsaRef<openssl::pkey::Private>,
56+
) -> KeySerializationResult<Vec<u8>> {
57+
let n_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(rsa.n())?;
58+
let e_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(rsa.e())?;
59+
let d_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(rsa.d())?;
60+
let p_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(rsa.p().unwrap())?;
61+
let q_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(rsa.q().unwrap())?;
62+
let dmp1_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(rsa.dmp1().unwrap())?;
63+
let dmq1_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(rsa.dmq1().unwrap())?;
64+
let iqmp_bytes = cryptography_openssl::utils::bn_to_big_endian_bytes(rsa.iqmp().unwrap())?;
65+
66+
let key = RsaPrivateKey {
67+
version: 0,
68+
n: asn1::BigUint::new(&n_bytes).unwrap(),
69+
e: asn1::BigUint::new(&e_bytes).unwrap(),
70+
d: asn1::BigUint::new(&d_bytes).unwrap(),
71+
p: asn1::BigUint::new(&p_bytes).unwrap(),
72+
q: asn1::BigUint::new(&q_bytes).unwrap(),
73+
dmp1: asn1::BigUint::new(&dmp1_bytes).unwrap(),
74+
dmq1: asn1::BigUint::new(&dmq1_bytes).unwrap(),
75+
iqmp: asn1::BigUint::new(&iqmp_bytes).unwrap(),
76+
other_prime_infos: None,
77+
};
78+
Ok(asn1::write_single(&key)?)
79+
}
80+
5481
pub fn parse_pkcs1_private_key(
5582
data: &[u8],
5683
) -> KeyParsingResult<openssl::pkey::PKey<openssl::pkey::Private>> {

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

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -150,32 +150,7 @@ pub fn serialize_public_key(
150150
let pkcs1_der = crate::rsa::serialize_pkcs1_public_key(&rsa)?;
151151
(AlgorithmParameters::Rsa(Some(())), pkcs1_der)
152152
} else if let Ok(ec) = pkey.ec_key() {
153-
let curve_nid = ec.group().curve_name();
154-
let curve_oid = match curve_nid {
155-
Some(openssl::nid::Nid::X9_62_PRIME192V1) => cryptography_x509::oid::EC_SECP192R1,
156-
Some(openssl::nid::Nid::SECP224R1) => cryptography_x509::oid::EC_SECP224R1,
157-
Some(openssl::nid::Nid::X9_62_PRIME256V1) => cryptography_x509::oid::EC_SECP256R1,
158-
Some(openssl::nid::Nid::SECP384R1) => cryptography_x509::oid::EC_SECP384R1,
159-
Some(openssl::nid::Nid::SECP521R1) => cryptography_x509::oid::EC_SECP521R1,
160-
Some(openssl::nid::Nid::SECP256K1) => cryptography_x509::oid::EC_SECP256K1,
161-
Some(openssl::nid::Nid::SECT233R1) => cryptography_x509::oid::EC_SECT233R1,
162-
Some(openssl::nid::Nid::SECT283R1) => cryptography_x509::oid::EC_SECT283R1,
163-
Some(openssl::nid::Nid::SECT409R1) => cryptography_x509::oid::EC_SECT409R1,
164-
Some(openssl::nid::Nid::SECT571R1) => cryptography_x509::oid::EC_SECT571R1,
165-
Some(openssl::nid::Nid::SECT163R2) => cryptography_x509::oid::EC_SECT163R2,
166-
Some(openssl::nid::Nid::SECT163K1) => cryptography_x509::oid::EC_SECT163K1,
167-
Some(openssl::nid::Nid::SECT233K1) => cryptography_x509::oid::EC_SECT233K1,
168-
Some(openssl::nid::Nid::SECT283K1) => cryptography_x509::oid::EC_SECT283K1,
169-
Some(openssl::nid::Nid::SECT409K1) => cryptography_x509::oid::EC_SECT409K1,
170-
Some(openssl::nid::Nid::SECT571K1) => cryptography_x509::oid::EC_SECT571K1,
171-
#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))]
172-
Some(openssl::nid::Nid::BRAINPOOL_P256R1) => cryptography_x509::oid::EC_BRAINPOOLP256R1,
173-
#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))]
174-
Some(openssl::nid::Nid::BRAINPOOL_P384R1) => cryptography_x509::oid::EC_BRAINPOOLP384R1,
175-
#[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))]
176-
Some(openssl::nid::Nid::BRAINPOOL_P512R1) => cryptography_x509::oid::EC_BRAINPOOLP512R1,
177-
_ => unimplemented!("Unknown curve"),
178-
};
153+
let curve_oid = crate::ec::group_to_curve_oid(ec.group()).expect("Unknown curve");
179154

180155
let mut bn_ctx = openssl::bn::BigNumContext::new()?;
181156
let point_bytes = ec.public_key().to_bytes(

src/rust/src/backend/utils.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ pub(crate) fn pkey_private_bytes<'p>(
138138
));
139139
}
140140
if let Ok(rsa) = pkey.rsa() {
141+
let der_bytes = cryptography_key_parsing::rsa::serialize_pkcs1_private_key(&rsa)?;
141142
if encoding.is(&types::ENCODING_PEM.get(py)?) {
142-
let der_bytes = rsa.private_key_to_der()?;
143143
let pem_bytes = cryptography_key_parsing::pem::encrypt_pem(
144144
"RSA PRIVATE KEY",
145145
&der_bytes,
@@ -155,12 +155,11 @@ pub(crate) fn pkey_private_bytes<'p>(
155155
));
156156
}
157157

158-
let der_bytes = rsa.private_key_to_der()?;
159158
return Ok(pyo3::types::PyBytes::new(py, &der_bytes));
160159
}
161160
} else if let Ok(dsa) = pkey.dsa() {
161+
let der_bytes = cryptography_key_parsing::dsa::serialize_pkcs1_private_key(&dsa)?;
162162
if encoding.is(&types::ENCODING_PEM.get(py)?) {
163-
let der_bytes = dsa.private_key_to_der()?;
164163
let pem_bytes = cryptography_key_parsing::pem::encrypt_pem(
165164
"DSA PRIVATE KEY",
166165
&der_bytes,
@@ -176,12 +175,11 @@ pub(crate) fn pkey_private_bytes<'p>(
176175
));
177176
}
178177

179-
let der_bytes = dsa.private_key_to_der()?;
180178
return Ok(pyo3::types::PyBytes::new(py, &der_bytes));
181179
}
182180
} else if let Ok(ec) = pkey.ec_key() {
181+
let der_bytes = cryptography_key_parsing::ec::serialize_pkcs1_private_key(&ec)?;
183182
if encoding.is(&types::ENCODING_PEM.get(py)?) {
184-
let der_bytes = ec.private_key_to_der()?;
185183
let pem_bytes = cryptography_key_parsing::pem::encrypt_pem(
186184
"EC PRIVATE KEY",
187185
&der_bytes,
@@ -197,7 +195,6 @@ pub(crate) fn pkey_private_bytes<'p>(
197195
));
198196
}
199197

200-
let der_bytes = ec.private_key_to_der()?;
201198
return Ok(pyo3::types::PyBytes::new(py, &der_bytes));
202199
}
203200
}

tests/hazmat/primitives/test_ec.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,31 @@ def test_load_private_key_invalid_version(self):
12121212
with pytest.raises(ValueError):
12131213
serialization.load_pem_private_key(data, password=None)
12141214

1215+
def test_private_bytes_high_private_key_bit_set(self):
1216+
data = load_vectors_from_file(
1217+
os.path.join("asymmetric", "EC", "high-bit-set.pem"),
1218+
lambda f: f.read(),
1219+
mode="rb",
1220+
)
1221+
1222+
key = serialization.load_pem_private_key(data, password=None)
1223+
assert isinstance(key, ec.EllipticCurvePrivateKey)
1224+
# The high bit is set in the private key. Ensure that it's not
1225+
# serialized with an additional leading 0, as you would if serializing
1226+
# an ASN.1 integer.
1227+
expected_private_key = (
1228+
0xA07AB72DF25722849DF17FCE9AF1D2AC02EFA32C3251D8E075C29EA868D9E2A2
1229+
)
1230+
assert key.private_numbers().private_value == expected_private_key
1231+
assert (
1232+
key.private_bytes(
1233+
serialization.Encoding.PEM,
1234+
serialization.PrivateFormat.TraditionalOpenSSL,
1235+
serialization.NoEncryption(),
1236+
)
1237+
== data
1238+
)
1239+
12151240

12161241
class TestEllipticCurvePEMPublicKeySerialization:
12171242
@pytest.mark.parametrize(
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MHcCAQEEIKB6ty3yVyKEnfF/zprx0qwC76MsMlHY4HXCnqho2eKioAoGCCqGSM49
3+
AwEHoUQDQgAElI9mbdlaS+T9nHxY/59lFnn80EEecZDBHq4gLpccY8Mge5ZTMiMD
4+
ADRvOqQ5R98Sxst765CAqXmRtz8vwoD96g==
5+
-----END EC PRIVATE KEY-----

0 commit comments

Comments
 (0)