Skip to content

Commit 6f74cf3

Browse files
committed
fixes #13672 -- emit a warning when loading an EC key that's not encoded properly
1 parent 36b7c8f commit 6f74cf3

File tree

6 files changed

+44
-11
lines changed

6 files changed

+44
-11
lines changed

docs/development/test-vectors.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,10 @@ 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.
297+
* ``asymmetric/EC/high-bit-set.pem`` - A PKCS#1 encoded EC key where the
298+
private key value has its high bit set.
299+
* ``asymmetric/EC/truncated-private-key.der`` - A PKCS#1 encoded EC key where
300+
the private key value is not encoded to the order's length.
299301

300302
Key exchange
301303
~~~~~~~~~~~~

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

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

5+
use std::ffi::CStr;
6+
57
use cryptography_x509::common::EcParameters;
68
use cryptography_x509::ec_constants;
79

@@ -153,6 +155,7 @@ pub fn serialize_pkcs1_private_key(
153155
pub fn parse_pkcs1_private_key(
154156
data: &[u8],
155157
ec_params: Option<EcParameters<'_>>,
158+
warn: impl Fn(&CStr),
156159
) -> KeyParsingResult<openssl::pkey::PKey<openssl::pkey::Private>> {
157160
let ec_private_key = asn1::parse_single::<EcPrivateKey<'_>>(data)?;
158161
if ec_private_key.version != 1 {
@@ -171,6 +174,10 @@ pub fn parse_pkcs1_private_key(
171174
(None, None) => return Err(crate::KeyParsingError::InvalidKey),
172175
};
173176

177+
if ec_private_key.private_key.len() != group.order_bits().div_ceil(8).try_into().unwrap() {
178+
warn(c"EC private key is not encoded properly: private key value is too short. Please file an issue at https://github.com/pyca/cryptography/issues explaining how your private key was created. In the future this may raise an error.");
179+
}
180+
174181
let private_number = openssl::bn::BigNum::from_slice(ec_private_key.private_key)?;
175182
let mut bn_ctx = openssl::bn::BigNumContext::new()?;
176183
let public_point = if let Some(point_bytes) = ec_private_key.public_key {

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

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

5+
use std::ffi::CStr;
6+
57
use cryptography_x509::common::{
68
AlgorithmIdentifier, AlgorithmParameters, PbeParams, Pkcs12PbeParams,
79
};
@@ -24,6 +26,7 @@ pub struct PrivateKeyInfo<'a> {
2426

2527
pub fn parse_private_key(
2628
data: &[u8],
29+
warn: impl Fn(&CStr),
2730
) -> KeyParsingResult<openssl::pkey::PKey<openssl::pkey::Private>> {
2831
let k = asn1::parse_single::<PrivateKeyInfo<'_>>(data)?;
2932
if k.version != 0 {
@@ -34,7 +37,7 @@ pub fn parse_private_key(
3437
rsa::parse_pkcs1_private_key(k.private_key)
3538
}
3639
AlgorithmParameters::Ec(ec_params) => {
37-
ec::parse_pkcs1_private_key(k.private_key, Some(ec_params))
40+
ec::parse_pkcs1_private_key(k.private_key, Some(ec_params), warn)
3841
}
3942

4043
AlgorithmParameters::Dsa(dsa_params) => {
@@ -192,6 +195,7 @@ fn pkcs5_pbe_decrypt(
192195
pub fn parse_encrypted_private_key(
193196
data: &[u8],
194197
password: Option<&[u8]>,
198+
warn: impl Fn(&CStr),
195199
) -> KeyParsingResult<openssl::pkey::PKey<openssl::pkey::Private>> {
196200
let epki = asn1::parse_single::<EncryptedPrivateKeyInfo<'_>>(data)?;
197201
let password = match password {
@@ -329,7 +333,7 @@ pub fn parse_encrypted_private_key(
329333
}
330334
};
331335

332-
parse_private_key(&plaintext)
336+
parse_private_key(&plaintext, warn)
333337
}
334338

335339
pub fn serialize_private_key(

src/rust/src/backend/keys.rs

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

5-
use pyo3::IntoPyObject;
5+
use pyo3::{IntoPyObject, PyTypeInfo};
6+
use std::ffi::CStr;
67

78
use crate::buf::CffiBuf;
89
use crate::error::{CryptographyError, CryptographyResult};
@@ -27,14 +28,22 @@ fn load_der_private_key<'p>(
2728
)
2829
}
2930

31+
fn py_warn(py: pyo3::Python<'_>) -> impl Fn(&CStr) + '_ {
32+
move |msg| {
33+
let warning_cls = pyo3::exceptions::PyUserWarning::type_object(py);
34+
// If warn fails, we ignore it.
35+
_ = pyo3::PyErr::warn(py, &warning_cls, msg, 1);
36+
}
37+
}
38+
3039
pub(crate) fn load_der_private_key_bytes<'p>(
3140
py: pyo3::Python<'p>,
3241
data: &[u8],
3342
password: Option<&[u8]>,
3443
unsafe_skip_rsa_key_validation: bool,
3544
) -> CryptographyResult<pyo3::Bound<'p, pyo3::PyAny>> {
36-
let pkey = cryptography_key_parsing::pkcs8::parse_private_key(data)
37-
.or_else(|_| cryptography_key_parsing::ec::parse_pkcs1_private_key(data, None))
45+
let pkey = cryptography_key_parsing::pkcs8::parse_private_key(data, py_warn(py))
46+
.or_else(|_| cryptography_key_parsing::ec::parse_pkcs1_private_key(data, None, py_warn(py)))
3847
.or_else(|_| cryptography_key_parsing::rsa::parse_pkcs1_private_key(data))
3948
.or_else(|_| cryptography_key_parsing::dsa::parse_pkcs1_private_key(data));
4049

@@ -49,7 +58,8 @@ pub(crate) fn load_der_private_key_bytes<'p>(
4958
return private_key_from_pkey(py, &pkey, unsafe_skip_rsa_key_validation);
5059
}
5160

52-
let pkey = cryptography_key_parsing::pkcs8::parse_encrypted_private_key(data, password)?;
61+
let pkey =
62+
cryptography_key_parsing::pkcs8::parse_encrypted_private_key(data, password, py_warn(py))?;
5363

5464
private_key_from_pkey(py, &pkey, unsafe_skip_rsa_key_validation)
5565
}
@@ -74,11 +84,11 @@ fn load_pem_private_key<'p>(
7484
let (data, mut password_used) = cryptography_key_parsing::pem::decrypt_pem(&p, password)?;
7585

7686
let pkey = match p.tag() {
77-
"PRIVATE KEY" => cryptography_key_parsing::pkcs8::parse_private_key(&data)?,
87+
"PRIVATE KEY" => cryptography_key_parsing::pkcs8::parse_private_key(&data, py_warn(py))?,
7888
"RSA PRIVATE KEY" => cryptography_key_parsing::rsa::parse_pkcs1_private_key(&data).map_err(|e| {
7989
CryptographyError::from(e).add_note(py, "If your key is in PKCS#8 format, you must use BEGIN/END PRIVATE KEY PEM delimiters")
8090
})?,
81-
"EC PRIVATE KEY" => cryptography_key_parsing::ec::parse_pkcs1_private_key(&data, None).map_err(|e| {
91+
"EC PRIVATE KEY" => cryptography_key_parsing::ec::parse_pkcs1_private_key(&data, None, py_warn(py)).map_err(|e| {
8292
CryptographyError::from(e).add_note(py, "If your key is in PKCS#8 format, you must use BEGIN/END PRIVATE KEY PEM delimiters")
8393
})?,
8494
"DSA PRIVATE KEY" => cryptography_key_parsing::dsa::parse_pkcs1_private_key(&data).map_err(|e| {
@@ -87,7 +97,7 @@ fn load_pem_private_key<'p>(
8797
_ => {
8898
assert_eq!(p.tag(), "ENCRYPTED PRIVATE KEY");
8999
password_used = true;
90-
cryptography_key_parsing::pkcs8::parse_encrypted_private_key(&data, password)?
100+
cryptography_key_parsing::pkcs8::parse_encrypted_private_key(&data, password, py_warn(py))?
91101
}
92102
};
93103
if password.is_some() && !password_used {

tests/hazmat/primitives/test_ec.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,16 @@ def test_private_bytes_small_key(self):
12481248
# length.
12491249
assert (b"\x00" * 31 + b"\x01") in der
12501250

1251+
def test_load_private_key_short_key_warngs(self):
1252+
data = load_vectors_from_file(
1253+
os.path.join("asymmetric", "EC", "truncated-private-key.der"),
1254+
lambda f: f.read(),
1255+
mode="rb",
1256+
)
1257+
1258+
with pytest.warns(UserWarning, match="private key value is too short"):
1259+
serialization.load_der_private_key(data, password=None)
1260+
12511261

12521262
class TestEllipticCurvePEMPublicKeySerialization:
12531263
@pytest.mark.parametrize(
Binary file not shown.

0 commit comments

Comments
 (0)