|
| 1 | +From 0eebb9dbb6343d9bc1d91e5a2482ed4e054a6d8c Mon Sep 17 00:00:00 2001 |
| 2 | +From: Paul Kehrer <paul.l.kehrer@gmail.com> |
| 3 | +Date: Tue, 10 Feb 2026 12:32:06 -0600 |
| 4 | +Subject: [PATCH] EC check key on cofactor > 1 (#14287) |
| 5 | + |
| 6 | +* Refactor EC public key construction to share more code (#14285) |
| 7 | +* Run an EC check key if cofactor > 1 |
| 8 | +This only applies to the binary curves (ed25519 is cofactor 8 and ed448 is cofactor 4 but we use a different code path for eddsa) |
| 9 | +* deprecate SECT curves |
| 10 | +* add a test |
| 11 | +* more tests |
| 12 | +* code review |
| 13 | +* simplify |
| 14 | +* update with CVE and credit |
| 15 | + |
| 16 | +--------- |
| 17 | +Co-authored-by: Alex Gaynor <alex.gaynor@gmail.com> |
| 18 | +Upstream Patch Reference: https://github.com/pyca/cryptography/commit/0eebb9dbb6343d9bc1d91e5a2482ed4e054a6d8c.patch |
| 19 | +--- |
| 20 | + .../hazmat/primitives/asymmetric/ec.py | 23 +++++++++++ |
| 21 | + src/cryptography/utils.py | 1 + |
| 22 | + src/rust/src/backend/ec.rs | 41 +++++++++++++------ |
| 23 | + tests/hazmat/primitives/test_ec.py | 37 +++++++++++++++++ |
| 24 | + 4 files changed, 89 insertions(+), 13 deletions(-) |
| 25 | + |
| 26 | +diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py |
| 27 | +index b612b40..8f4dcbd 100644 |
| 28 | +--- a/src/cryptography/hazmat/primitives/asymmetric/ec.py |
| 29 | ++++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py |
| 30 | +@@ -381,3 +381,26 @@ def get_curve_for_oid(oid: ObjectIdentifier) -> type[EllipticCurve]: |
| 31 | + "The provided object identifier has no matching elliptic " |
| 32 | + "curve class" |
| 33 | + ) |
| 34 | ++ |
| 35 | ++ |
| 36 | ++_SECT_CURVES: tuple[type[EllipticCurve], ...] = ( |
| 37 | ++ SECT163K1, |
| 38 | ++ SECT163R2, |
| 39 | ++ SECT233K1, |
| 40 | ++ SECT233R1, |
| 41 | ++ SECT283K1, |
| 42 | ++ SECT283R1, |
| 43 | ++ SECT409K1, |
| 44 | ++ SECT409R1, |
| 45 | ++ SECT571K1, |
| 46 | ++ SECT571R1, |
| 47 | ++) |
| 48 | ++ |
| 49 | ++for _curve_cls in _SECT_CURVES: |
| 50 | ++ utils.deprecated( |
| 51 | ++ _curve_cls, |
| 52 | ++ __name__, |
| 53 | ++ f"{_curve_cls.__name__} will be removed in the next release.", |
| 54 | ++ utils.DeprecatedIn46, |
| 55 | ++ name=_curve_cls.__name__, |
| 56 | ++ ) |
| 57 | +diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py |
| 58 | +index a0ec7a3..e883efa 100644 |
| 59 | +--- a/src/cryptography/utils.py |
| 60 | ++++ b/src/cryptography/utils.py |
| 61 | +@@ -25,6 +25,7 @@ DeprecatedIn37 = CryptographyDeprecationWarning |
| 62 | + DeprecatedIn40 = CryptographyDeprecationWarning |
| 63 | + DeprecatedIn41 = CryptographyDeprecationWarning |
| 64 | + DeprecatedIn42 = CryptographyDeprecationWarning |
| 65 | ++DeprecatedIn46 = CryptographyDeprecationWarning |
| 66 | + |
| 67 | + |
| 68 | + def _check_bytes(name: str, value: bytes) -> None: |
| 69 | +diff --git a/src/rust/src/backend/ec.rs b/src/rust/src/backend/ec.rs |
| 70 | +index 6a224b4..381506c 100644 |
| 71 | +--- a/src/rust/src/backend/ec.rs |
| 72 | ++++ b/src/rust/src/backend/ec.rs |
| 73 | +@@ -155,12 +155,10 @@ pub(crate) fn public_key_from_pkey( |
| 74 | + ) -> CryptographyResult<ECPublicKey> { |
| 75 | + let ec = pkey.ec_key()?; |
| 76 | + let curve = py_curve_from_curve(py, ec.group())?; |
| 77 | +- check_key_infinity(&ec)?; |
| 78 | +- Ok(ECPublicKey { |
| 79 | +- pkey: pkey.to_owned(), |
| 80 | +- curve: curve.into(), |
| 81 | +- }) |
| 82 | ++ |
| 83 | ++ ECPublicKey::new(pkey.to_owned(), curve.into()) |
| 84 | + } |
| 85 | ++ |
| 86 | + #[pyo3::prelude::pyfunction] |
| 87 | + fn generate_private_key( |
| 88 | + py: pyo3::Python<'_>, |
| 89 | +@@ -215,10 +213,7 @@ fn from_public_bytes( |
| 90 | + let ec = openssl::ec::EcKey::from_public_key(&curve, &point)?; |
| 91 | + let pkey = openssl::pkey::PKey::from_ec_key(ec)?; |
| 92 | + |
| 93 | +- Ok(ECPublicKey { |
| 94 | +- pkey, |
| 95 | +- curve: py_curve.into(), |
| 96 | +- }) |
| 97 | ++ ECPublicKey::new(pkey, py_curve.into()) |
| 98 | + } |
| 99 | + |
| 100 | + #[pyo3::prelude::pymethods] |
| 101 | +@@ -357,6 +352,29 @@ impl ECPrivateKey { |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | ++impl ECPublicKey { |
| 106 | ++ fn new( |
| 107 | ++ pkey: openssl::pkey::PKey<openssl::pkey::Public>, |
| 108 | ++ curve: pyo3::Py<pyo3::PyAny>, |
| 109 | ++ ) -> CryptographyResult<ECPublicKey> { |
| 110 | ++ let ec = pkey.ec_key()?; |
| 111 | ++ check_key_infinity(&ec)?; |
| 112 | ++ let mut bn_ctx = openssl::bn::BigNumContext::new()?; |
| 113 | ++ let mut cofactor = openssl::bn::BigNum::new()?; |
| 114 | ++ ec.group().cofactor(&mut cofactor, &mut bn_ctx)?; |
| 115 | ++ let one = openssl::bn::BigNum::from_u32(1)?; |
| 116 | ++ if cofactor != one { |
| 117 | ++ ec.check_key().map_err(|_| { |
| 118 | ++ pyo3::exceptions::PyValueError::new_err( |
| 119 | ++ "Invalid EC key (key out of range, infinity, etc.)", |
| 120 | ++ ) |
| 121 | ++ })?; |
| 122 | ++ } |
| 123 | ++ |
| 124 | ++ Ok(ECPublicKey { pkey, curve }) |
| 125 | ++ } |
| 126 | ++} |
| 127 | ++ |
| 128 | + #[pyo3::prelude::pymethods] |
| 129 | + impl ECPublicKey { |
| 130 | + #[getter] |
| 131 | +@@ -591,10 +609,7 @@ impl EllipticCurvePublicNumbers { |
| 132 | + |
| 133 | + let pkey = openssl::pkey::PKey::from_ec_key(public_key)?; |
| 134 | + |
| 135 | +- Ok(ECPublicKey { |
| 136 | +- pkey, |
| 137 | +- curve: self.curve.clone_ref(py), |
| 138 | +- }) |
| 139 | ++ ECPublicKey::new(pkey, self.curve.clone_ref(py)) |
| 140 | + } |
| 141 | + |
| 142 | + fn __eq__( |
| 143 | +diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py |
| 144 | +index 334e76d..f7f2242 100644 |
| 145 | +--- a/tests/hazmat/primitives/test_ec.py |
| 146 | ++++ b/tests/hazmat/primitives/test_ec.py |
| 147 | +@@ -1340,3 +1340,40 @@ class TestECDH: |
| 148 | + |
| 149 | + with pytest.raises(ValueError): |
| 150 | + key.exchange(ec.ECDH(), public_key) |
| 151 | ++ |
| 152 | ++ |
| 153 | ++def test_invalid_sect_public_keys(backend): |
| 154 | ++ _skip_curve_unsupported(backend, ec.SECT571K1()) |
| 155 | ++ public_numbers = ec.EllipticCurvePublicNumbers(1, 1, ec.SECT571K1()) |
| 156 | ++ with pytest.raises(ValueError): |
| 157 | ++ public_numbers.public_key() |
| 158 | ++ |
| 159 | ++ point = binascii.unhexlify( |
| 160 | ++ b"0400000000000000000000000000000000000000000000000000000000000000000" |
| 161 | ++ b"0000000000000000000000000000000000000000000000000000000000000000000" |
| 162 | ++ b"0000000000010000000000000000000000000000000000000000000000000000000" |
| 163 | ++ b"0000000000000000000000000000000000000000000000000000000000000000000" |
| 164 | ++ b"0000000000000000000001" |
| 165 | ++ ) |
| 166 | ++ with pytest.raises(ValueError): |
| 167 | ++ ec.EllipticCurvePublicKey.from_encoded_point(ec.SECT571K1(), point) |
| 168 | ++ |
| 169 | ++ der = binascii.unhexlify( |
| 170 | ++ b"3081a7301006072a8648ce3d020106052b810400260381920004000000000000000" |
| 171 | ++ b"0000000000000000000000000000000000000000000000000000000000000000000" |
| 172 | ++ b"0000000000000000000000000000000000000000000000000000000000000100000" |
| 173 | ++ b"0000000000000000000000000000000000000000000000000000000000000000000" |
| 174 | ++ b"0000000000000000000000000000000000000000000000000000000000000000000" |
| 175 | ++ b"00001" |
| 176 | ++ ) |
| 177 | ++ with pytest.raises(ValueError): |
| 178 | ++ serialization.load_der_public_key(der) |
| 179 | ++ |
| 180 | ++ pem = textwrap.dedent("""-----BEGIN PUBLIC KEY----- |
| 181 | ++ MIGnMBAGByqGSM49AgEGBSuBBAAmA4GSAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |
| 182 | ++ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |
| 183 | ++ AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |
| 184 | ++ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE= |
| 185 | ++ -----END PUBLIC KEY-----""").encode() |
| 186 | ++ with pytest.raises(ValueError): |
| 187 | ++ serialization.load_pem_public_key(pem) |
| 188 | +-- |
| 189 | +2.45.4 |
| 190 | + |
0 commit comments