From 11bc2b0dbb6aa9d07564b2429bd30724dfcbbbdd Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 14 May 2025 16:47:51 -0700 Subject: [PATCH] support explicit curves mapping to named curves We now support loading EC keys with explicit encodings of named curves. This requires that the explicit curve encoding match exactly (but allows the omission of the seed parameter to maintain compatibility with the -no_seed OpenSSL flag). This is only supported for secp256r1, secp384r1, secp521r1, and secp256k1 at this time. This is not arbitrary explicit curve support; we still have no plans to implement that. This PR also updates MSRV to 1.83 as we need that version for Option::unwrap() in const contexts. --- .github/workflows/ci.yml | 3 +- CHANGELOG.rst | 3 + Cargo.toml | 2 +- docs/installation.rst | 6 +- src/rust/cryptography-key-parsing/src/ec.rs | 33 +++- .../src/policy/extension.rs | 4 +- src/rust/cryptography-x509/src/common.rs | 54 ++++++- .../cryptography-x509/src/ec_constants.rs | 142 ++++++++++++++++++ src/rust/cryptography-x509/src/lib.rs | 1 + src/rust/cryptography-x509/src/oid.rs | 3 + src/rust/cryptography-x509/src/pkcs7.rs | 1 + src/rust/src/error.rs | 2 +- tests/hazmat/primitives/test_ec.py | 52 ++++++- tests/x509/test_x509.py | 10 +- 14 files changed, 298 insertions(+), 18 deletions(-) create mode 100644 src/rust/cryptography-x509/src/ec_constants.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bcf8bcef05a2..172856a733c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,8 +51,7 @@ jobs: # - 1.77: offset_of! in std (for pyo3), c"str" literals (replace cstr_from_literal) # - 1.80: LazyLock in std # - 1.83: const context Option::unwrap() - - {VERSION: "3.13", NOXSESSION: "rust,tests", RUST: "1.74.0"} - - {VERSION: "3.13", NOXSESSION: "rust,tests", RUST: "1.80.0"} + - {VERSION: "3.13", NOXSESSION: "rust,tests", RUST: "1.83.0"} - {VERSION: "3.13", NOXSESSION: "rust,tests", RUST: "beta"} - {VERSION: "3.13", NOXSESSION: "rust,tests", RUST: "nightly"} - {VERSION: "3.13", NOXSESSION: "tests-rust-debug"} diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 04f4d7da6ce1..144877ed5585 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ Changelog .. note:: This version is not yet released and is under active development. * **BACKWARDS INCOMPATIBLE:** Support for Python 3.7 has been removed. +* Updated the minimum supported Rust version (MSRV) to 1.83.0, from 1.74.0. +* Added support for loading elliptic curve keys that contain explicit encodings + of the curves ``secp256r1``, ``secp384r1``, ``secp521r1``, and ``secp256k1``. * Removed the deprecated ``get_attribute_for_oid`` method on :class:`~cryptography.x509.CertificateSigningRequest`. Users should use :meth:`~cryptography.x509.Attributes.get_attribute_for_oid` instead. diff --git a/Cargo.toml b/Cargo.toml index 04adc669f97b..b86bd8b064a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ authors = ["The cryptography developers "] edition = "2021" publish = false # This specifies the MSRV -rust-version = "1.74.0" +rust-version = "1.83.0" license = "Apache-2.0 OR BSD-3-Clause" [workspace.dependencies] diff --git a/docs/installation.rst b/docs/installation.rst index 7d51df587208..4baeb6ab7e0d 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -140,8 +140,8 @@ available`. .. warning:: - For RHEL and CentOS you must be on version 8.10 or newer for the command - below to install a sufficiently new Rust. If your Rust is less than 1.74.0 + For RHEL and CentOS you must be on version 9.6 or newer for the command + below to install a sufficiently new Rust. If your Rust is less than 1.83.0 please see the :ref:`Rust installation instructions ` for information about installing a newer Rust. @@ -319,7 +319,7 @@ Rust a Rust toolchain. Building ``cryptography`` requires having a working Rust toolchain. The current -minimum supported Rust version is 1.74.0. **This is newer than the Rust some +minimum supported Rust version is 1.83.0. **This is newer than the Rust some package managers ship**, so users may need to install with the instructions below. diff --git a/src/rust/cryptography-key-parsing/src/ec.rs b/src/rust/cryptography-key-parsing/src/ec.rs index fd6a195f4bfe..dbdec2af4c02 100644 --- a/src/rust/cryptography-key-parsing/src/ec.rs +++ b/src/rust/cryptography-key-parsing/src/ec.rs @@ -3,6 +3,7 @@ // for complete details. use cryptography_x509::common::EcParameters; +use cryptography_x509::ec_constants; use crate::{KeyParsingError, KeyParsingResult}; @@ -57,9 +58,37 @@ pub(crate) fn ec_params_to_group( Ok(openssl::ec::EcGroup::from_curve_name(curve_nid) .map_err(|_| KeyParsingError::UnsupportedEllipticCurve(curve_oid.clone()))?) } - EcParameters::ImplicitCurve(_) | EcParameters::SpecifiedCurve(_) => { - Err(KeyParsingError::ExplicitCurveUnsupported) + EcParameters::SpecifiedCurve(params) => { + // We do not support arbitrary explicit curves. Instead we map values + // to named curves. This currently supports only P256, P384, + // P521, and SECP256K1. No binary curves are supported. Everything must + // match, except the seed may be omitted on NIST curves since OpenSSL + // has supported a -no_seed option for over 20 years and I don't want to + // figure out whether anyone uses that or not. No one should be using + // explicit curve encoding anyway. Curves were meant to be named! + let (curve_nid, oid) = match params { + &ec_constants::P256_DOMAIN | &ec_constants::P256_DOMAIN_NO_SEED => ( + openssl::nid::Nid::X9_62_PRIME256V1, + cryptography_x509::oid::EC_SECP256R1, + ), + &ec_constants::P384_DOMAIN | &ec_constants::P384_DOMAIN_NO_SEED => ( + openssl::nid::Nid::SECP384R1, + cryptography_x509::oid::EC_SECP384R1, + ), + &ec_constants::P521_DOMAIN | &ec_constants::P521_DOMAIN_NO_SEED => ( + openssl::nid::Nid::SECP521R1, + cryptography_x509::oid::EC_SECP521R1, + ), + &ec_constants::SECP256K1_DOMAIN => ( + openssl::nid::Nid::SECP256K1, + cryptography_x509::oid::EC_SECP256K1, + ), + _ => return Err(KeyParsingError::ExplicitCurveUnsupported), + }; + Ok(openssl::ec::EcGroup::from_curve_name(curve_nid) + .map_err(|_| KeyParsingError::UnsupportedEllipticCurve(oid))?) } + EcParameters::ImplicitCurve(_) => Err(KeyParsingError::ExplicitCurveUnsupported), } } diff --git a/src/rust/cryptography-x509-verification/src/policy/extension.rs b/src/rust/cryptography-x509-verification/src/policy/extension.rs index eb0f70ffb042..6d280b284934 100644 --- a/src/rust/cryptography-x509-verification/src/policy/extension.rs +++ b/src/rust/cryptography-x509-verification/src/policy/extension.rs @@ -607,11 +607,11 @@ mod ca { let permitted_subtrees_empty = name_constraints .permitted_subtrees .as_ref() - .map_or(true, |pst| pst.is_empty()); + .is_none_or(|pst| pst.is_empty()); let excluded_subtrees_empty = name_constraints .excluded_subtrees .as_ref() - .map_or(true, |est| est.is_empty()); + .is_none_or(|est| est.is_empty()); if permitted_subtrees_empty && excluded_subtrees_empty { return Err(ValidationError::new(ValidationErrorKind::Other( diff --git a/src/rust/cryptography-x509/src/common.rs b/src/rust/cryptography-x509/src/common.rs index 183633b4a6ff..3582eecae3e9 100644 --- a/src/rust/cryptography-x509/src/common.rs +++ b/src/rust/cryptography-x509/src/common.rs @@ -449,7 +449,59 @@ pub const PSS_SHA512_MASK_GEN_ALG: MaskGenAlgorithm<'_> = MaskGenAlgorithm { pub enum EcParameters<'a> { NamedCurve(asn1::ObjectIdentifier), ImplicitCurve(asn1::Null), - SpecifiedCurve(asn1::Sequence<'a>), + SpecifiedCurve(SpecifiedECDomain<'a>), +} + +// From RFC 3279 Section 2.3.5 and RFC 5480 Appendix A +// SpecifiedECDomain ::= SEQUENCE { +// version ECPVer, +// fieldID FieldID, +// curve Curve, +// base ECPoint, +// order INTEGER, +// cofactor INTEGER OPTIONAL +// } +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, Eq, PartialEq, Debug)] +pub struct SpecifiedECDomain<'a> { + pub version: u8, + pub field_id: FieldID<'a>, + pub curve: Curve<'a>, + pub base: &'a [u8], // ECPoint can be compressed or uncompressed + pub order: asn1::BigUint<'a>, + pub cofactor: Option, +} + +// From RFC 3279 Section 2.3.5 +// FieldID ::= SEQUENCE { +// fieldType FIELD-TYPE.&id({SupportedFieldTypes}), +// parameters FIELD-TYPE.&Type({SupportedFieldTypes}{@fieldType}) +// } +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq, Debug)] +pub struct FieldID<'a> { + pub field_type: asn1::DefinedByMarker, + #[defined_by(field_type)] + pub parameters: FieldParameters<'a>, +} + +#[derive(asn1::Asn1DefinedByRead, asn1::Asn1DefinedByWrite, Hash, Clone, PartialEq, Eq, Debug)] +pub enum FieldParameters<'a> { + #[defined_by(oid::PRIME_FIELD_OID)] + PrimeField(asn1::BigUint<'a>), + #[defined_by(oid::CHARACTERISTIC_TWO_FIELD_OID)] + CharacteristicTwo(asn1::Tlv<'a>), +} + +// From RFC 3279 Section 2.3.5 +// Curve ::= SEQUENCE { +// a FieldElement, +// b FieldElement, +// seed BIT STRING OPTIONAL +// } +#[derive(asn1::Asn1Read, asn1::Asn1Write, Hash, Clone, PartialEq, Eq, Debug)] +pub struct Curve<'a> { + pub a: &'a [u8], // FieldElement + pub b: &'a [u8], // FieldElement + pub seed: Option>, } // From RFC 4055 section 3.1: diff --git a/src/rust/cryptography-x509/src/ec_constants.rs b/src/rust/cryptography-x509/src/ec_constants.rs new file mode 100644 index 000000000000..e15941a18c0f --- /dev/null +++ b/src/rust/cryptography-x509/src/ec_constants.rs @@ -0,0 +1,142 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this repository +// for complete details. + +use crate::common::{Curve, FieldID, FieldParameters, SpecifiedECDomain}; + +const P256_FIELD: FieldID<'static> = FieldID { + field_type: asn1::DefinedByMarker::marker(), + parameters: FieldParameters::PrimeField(asn1::BigUint::new(b"\x00\xff\xff\xff\xff\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff").unwrap()), +}; +const P256_CURVE_A: &[u8; 32] = b"\xff\xff\xff\xff\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfc"; +const P256_CURVE_B: &[u8; 32] = b"\x5a\xc6\x35\xd8\xaa\x3a\x93\xe7\xb3\xeb\xbd\x55\x76\x98\x86\xbc\x65\x1d\x06\xb0\xcc\x53\xb0\xf6\x3b\xce\x3c\x3e\x27\xd2\x60\x4b"; +const P256_SEED: asn1::BitString<'static> = asn1::BitString::new( + b"\xc4\x9d\x36\x08\x86\xe7\x04\x93\x6a\x66\x78\xe1\x13\x9d\x26\xb7\x81\x9f\x7e\x90", + 0, +) +.unwrap(); +const P256_UNCOMPRESSED_BASE: &[u8; 65] = b"\x04\x6b\x17\xd1\xf2\xe1\x2c\x42\x47\xf8\xbc\xe6\xe5\x63\xa4\x40\xf2\x77\x03\x7d\x81\x2d\xeb\x33\xa0\xf4\xa1\x39\x45\xd8\x98\xc2\x96\x4f\xe3\x42\xe2\xfe\x1a\x7f\x9b\x8e\xe7\xeb\x4a\x7c\x0f\x9e\x16\x2b\xce\x33\x57\x6b\x31\x5e\xce\xcb\xb6\x40\x68\x37\xbf\x51\xf5"; +const P256_ORDER: asn1::BigUint<'static> = asn1::BigUint::new(b"\x00\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xbc\xe6\xfa\xad\xa7\x17\x9e\x84\xf3\xb9\xca\xc2\xfc\x63\x25\x51").unwrap(); + +pub const P256_DOMAIN: SpecifiedECDomain<'static> = SpecifiedECDomain { + version: 1, + field_id: P256_FIELD, + curve: Curve { + a: P256_CURVE_A, + b: P256_CURVE_B, + seed: Some(P256_SEED), + }, + base: P256_UNCOMPRESSED_BASE, + order: P256_ORDER, + cofactor: Some(1), +}; + +pub const P256_DOMAIN_NO_SEED: SpecifiedECDomain<'static> = SpecifiedECDomain { + version: 1, + field_id: P256_FIELD, + curve: Curve { + a: P256_CURVE_A, + b: P256_CURVE_B, + seed: None, + }, + base: P256_UNCOMPRESSED_BASE, + order: P256_ORDER, + cofactor: Some(1), +}; + +const P384_FIELD: FieldID<'static> = FieldID { + field_type: asn1::DefinedByMarker::marker(), + parameters: FieldParameters::PrimeField(asn1::BigUint::new(b"\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff").unwrap()), +}; +const P384_CURVE_A: &[u8; 48] = b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xfc"; +const P384_CURVE_B: &[u8; 48] = b"\xb3\x31\x2f\xa7\xe2\x3e\xe7\xe4\x98\x8e\x05\x6b\xe3\xf8\x2d\x19\x18\x1d\x9c\x6e\xfe\x81\x41\x12\x03\x14\x08\x8f\x50\x13\x87\x5a\xc6\x56\x39\x8d\x8a\x2e\xd1\x9d\x2a\x85\xc8\xed\xd3\xec\x2a\xef"; +const P384_SEED: asn1::BitString<'static> = asn1::BitString::new( + b"\xa3\x35\x92\x6a\xa3\x19\xa2\x7a\x1d\x00\x89\x6a\x67\x73\xa4\x82\x7a\xcd\xac\x73", + 0, +) +.unwrap(); +const P384_UNCOMPRESSED_BASE: &[u8; 97] = b"\x04\xaa\x87\xca\x22\xbe\x8b\x05\x37\x8e\xb1\xc7\x1e\xf3\x20\xad\x74\x6e\x1d\x3b\x62\x8b\xa7\x9b\x98\x59\xf7\x41\xe0\x82\x54\x2a\x38\x55\x02\xf2\x5d\xbf\x55\x29\x6c\x3a\x54\x5e\x38\x72\x76\x0a\xb7\x36\x17\xde\x4a\x96\x26\x2c\x6f\x5d\x9e\x98\xbf\x92\x92\xdc\x29\xf8\xf4\x1d\xbd\x28\x9a\x14\x7c\xe9\xda\x31\x13\xb5\xf0\xb8\xc0\x0a\x60\xb1\xce\x1d\x7e\x81\x9d\x7a\x43\x1d\x7c\x90\xea\x0e\x5f"; +const P384_ORDER: asn1::BigUint<'static> = asn1::BigUint::new(b"\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc7\x63\x4d\x81\xf4\x37\x2d\xdf\x58\x1a\x0d\xb2\x48\xb0\xa7\x7a\xec\xec\x19\x6a\xcc\xc5\x29\x73").unwrap(); + +pub const P384_DOMAIN: SpecifiedECDomain<'static> = SpecifiedECDomain { + version: 1, + field_id: P384_FIELD, + curve: Curve { + a: P384_CURVE_A, + b: P384_CURVE_B, + seed: Some(P384_SEED), + }, + base: P384_UNCOMPRESSED_BASE, + order: P384_ORDER, + cofactor: Some(1), +}; + +pub const P384_DOMAIN_NO_SEED: SpecifiedECDomain<'static> = SpecifiedECDomain { + version: 1, + field_id: P384_FIELD, + curve: Curve { + a: P384_CURVE_A, + b: P384_CURVE_B, + seed: None, + }, + base: P384_UNCOMPRESSED_BASE, + order: P384_ORDER, + cofactor: Some(1), +}; + +const P521_FIELD: FieldID<'static> = FieldID { + field_type: asn1::DefinedByMarker::marker(), + parameters: FieldParameters::PrimeField(asn1::BigUint::new(b"\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff").unwrap()), +}; +const P521_CURVE_A: &[u8; 66] = b"\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfc"; +const P521_CURVE_B: &[u8; 66] = b"\x00\x51\x95\x3e\xb9\x61\x8e\x1c\x9a\x1f\x92\x9a\x21\xa0\xb6\x85\x40\xee\xa2\xda\x72\x5b\x99\xb3\x15\xf3\xb8\xb4\x89\x91\x8e\xf1\x09\xe1\x56\x19\x39\x51\xec\x7e\x93\x7b\x16\x52\xc0\xbd\x3b\xb1\xbf\x07\x35\x73\xdf\x88\x3d\x2c\x34\xf1\xef\x45\x1f\xd4\x6b\x50\x3f\x00"; +const P521_SEED: asn1::BitString<'static> = asn1::BitString::new( + b"\xd0\x9e\x88\x00\x29\x1c\xb8\x53\x96\xcc\x67\x17\x39\x32\x84\xaa\xa0\xda\x64\xba", + 0, +) +.unwrap(); +const P521_UNCOMPRESSED_BASE: &[u8; 133] = b"\x04\x00\xc6\x85\x8e\x06\xb7\x04\x04\xe9\xcd\x9e\x3e\xcb\x66\x23\x95\xb4\x42\x9c\x64\x81\x39\x05\x3f\xb5\x21\xf8\x28\xaf\x60\x6b\x4d\x3d\xba\xa1\x4b\x5e\x77\xef\xe7\x59\x28\xfe\x1d\xc1\x27\xa2\xff\xa8\xde\x33\x48\xb3\xc1\x85\x6a\x42\x9b\xf9\x7e\x7e\x31\xc2\xe5\xbd\x66\x01\x18\x39\x29\x6a\x78\x9a\x3b\xc0\x04\x5c\x8a\x5f\xb4\x2c\x7d\x1b\xd9\x98\xf5\x44\x49\x57\x9b\x44\x68\x17\xaf\xbd\x17\x27\x3e\x66\x2c\x97\xee\x72\x99\x5e\xf4\x26\x40\xc5\x50\xb9\x01\x3f\xad\x07\x61\x35\x3c\x70\x86\xa2\x72\xc2\x40\x88\xbe\x94\x76\x9f\xd1\x66\x50"; +const P521_ORDER: asn1::BigUint<'static> = asn1::BigUint::new(b"\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfa\x51\x86\x87\x83\xbf\x2f\x96\x6b\x7f\xcc\x01\x48\xf7\x09\xa5\xd0\x3b\xb5\xc9\xb8\x89\x9c\x47\xae\xbb\x6f\xb7\x1e\x91\x38\x64\x09").unwrap(); + +pub const P521_DOMAIN: SpecifiedECDomain<'static> = SpecifiedECDomain { + version: 1, + field_id: P521_FIELD, + curve: Curve { + a: P521_CURVE_A, + b: P521_CURVE_B, + seed: Some(P521_SEED), + }, + base: P521_UNCOMPRESSED_BASE, + order: P521_ORDER, + cofactor: Some(1), +}; + +pub const P521_DOMAIN_NO_SEED: SpecifiedECDomain<'static> = SpecifiedECDomain { + version: 1, + field_id: P521_FIELD, + curve: Curve { + a: P521_CURVE_A, + b: P521_CURVE_B, + seed: None, + }, + base: P521_UNCOMPRESSED_BASE, + order: P521_ORDER, + cofactor: Some(1), +}; + +// There is no seed for this curve so we don't need to create two domains like the previous curves +pub const SECP256K1_DOMAIN: SpecifiedECDomain<'static> = SpecifiedECDomain { + version: 1, + field_id: FieldID { + field_type: asn1::DefinedByMarker::marker(), + parameters: FieldParameters::PrimeField(asn1::BigUint::new(b"\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xfc\x2f").unwrap()), + }, + curve: Curve { + a: b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + b: b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07", + seed: None, + }, + base: b"\x04\x79\xbe\x66\x7e\xf9\xdc\xbb\xac\x55\xa0\x62\x95\xce\x87\x0b\x07\x02\x9b\xfc\xdb\x2d\xce\x28\xd9\x59\xf2\x81\x5b\x16\xf8\x17\x98\x48\x3a\xda\x77\x26\xa3\xc4\x65\x5d\xa4\xfb\xfc\x0e\x11\x08\xa8\xfd\x17\xb4\x48\xa6\x85\x54\x19\x9c\x47\xd0\x8f\xfb\x10\xd4\xb8", + order: asn1::BigUint::new(b"\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xba\xae\xdc\xe6\xaf\x48\xa0\x3b\xbf\xd2\x5e\x8c\xd0\x36\x41\x41").unwrap(), + cofactor: Some(1), +}; diff --git a/src/rust/cryptography-x509/src/lib.rs b/src/rust/cryptography-x509/src/lib.rs index b06b0a62afb3..5f633f798cec 100644 --- a/src/rust/cryptography-x509/src/lib.rs +++ b/src/rust/cryptography-x509/src/lib.rs @@ -10,6 +10,7 @@ pub mod certificate; pub mod common; pub mod crl; pub mod csr; +pub mod ec_constants; pub mod extensions; pub mod name; pub mod ocsp_req; diff --git a/src/rust/cryptography-x509/src/oid.rs b/src/rust/cryptography-x509/src/oid.rs index 55e2e4573636..cd8e331dce5a 100644 --- a/src/rust/cryptography-x509/src/oid.rs +++ b/src/rust/cryptography-x509/src/oid.rs @@ -173,3 +173,6 @@ pub const HMAC_WITH_SHA224_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 1 pub const HMAC_WITH_SHA256_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 2, 9); pub const HMAC_WITH_SHA384_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 2, 10); pub const HMAC_WITH_SHA512_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 113549, 2, 11); + +pub const PRIME_FIELD_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 1, 1); +pub const CHARACTERISTIC_TWO_FIELD_OID: asn1::ObjectIdentifier = asn1::oid!(1, 2, 840, 10045, 1, 2); diff --git a/src/rust/cryptography-x509/src/pkcs7.rs b/src/rust/cryptography-x509/src/pkcs7.rs index 7a55d48b473b..c1f2626f2f5c 100644 --- a/src/rust/cryptography-x509/src/pkcs7.rs +++ b/src/rust/cryptography-x509/src/pkcs7.rs @@ -17,6 +17,7 @@ pub struct ContentInfo<'a> { pub content: Content<'a>, } +#[allow(clippy::large_enum_variant)] #[derive(asn1::Asn1DefinedByWrite, asn1::Asn1DefinedByRead)] pub enum Content<'a> { #[defined_by(PKCS7_ENVELOPED_DATA_OID)] diff --git a/src/rust/src/error.rs b/src/rust/src/error.rs index 9495cbbe2352..139ff495203d 100644 --- a/src/rust/src/error.rs +++ b/src/rust/src/error.rs @@ -64,7 +64,7 @@ impl From for CryptographyError { } cryptography_key_parsing::KeyParsingError::ExplicitCurveUnsupported => { CryptographyError::Py(pyo3::exceptions::PyValueError::new_err( - "ECDSA keys with explicit parameters are unsupported at this time", + "ECDSA keys with explicit parameters are only supported when they map to secp256r1, secp384r1, secp521r1, or secp256k1. No custom curves are supported.", )) } cryptography_key_parsing::KeyParsingError::UnsupportedKeyType(oid) => { diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py index 2f62e46db5fb..ed3dae716dc3 100644 --- a/tests/hazmat/primitives/test_ec.py +++ b/tests/hazmat/primitives/test_ec.py @@ -1061,7 +1061,8 @@ def test_public_bytes_from_derived_public_key(self, backend): parsed_public = serialization.load_pem_public_key(pem, backend) assert parsed_public - def test_load_private_key_explicit_parameters(self): + def test_load_private_key_unsupported_explicit_parameters(self): + # This vector is P256 except the prime field value is wrong with pytest.raises(ValueError, match="explicit parameters"): load_vectors_from_file( os.path.join( @@ -1074,6 +1075,7 @@ def test_load_private_key_explicit_parameters(self): ) with pytest.raises(ValueError, match="explicit parameters"): + # This vector encodes SECT233R1 explicitly load_vectors_from_file( os.path.join( "asymmetric", @@ -1086,6 +1088,54 @@ def test_load_private_key_explicit_parameters(self): mode="rb", ) + @pytest.mark.parametrize( + ("curve", "file"), + [ + # secp256k1 has no seed value + (ec.SECP256K1, "secp256k1-explicit-no-seed.pem"), + (ec.SECP256R1, "secp256r1-explicit-seed.pem"), + (ec.SECP256R1, "secp256r1-explicit-no-seed.pem"), + (ec.SECP384R1, "secp384r1-explicit-seed.pem"), + (ec.SECP384R1, "secp384r1-explicit-no-seed.pem"), + (ec.SECP521R1, "secp521r1-explicit-seed.pem"), + (ec.SECP521R1, "secp521r1-explicit-no-seed.pem"), + ], + ) + def test_load_private_key_explicit_parameters(self, curve, file, backend): + _skip_curve_unsupported(backend, curve()) + key = load_vectors_from_file( + os.path.join("asymmetric", "EC", file), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read(), password=None + ), + mode="rb", + ) + assert isinstance(key, ec.EllipticCurvePrivateKey) + assert isinstance(key.curve, curve) + + @pytest.mark.parametrize( + ("curve", "file"), + [ + # secp256k1 has no seed value + (ec.SECP256K1, "secp256k1-pub-explicit-no-seed.pem"), + (ec.SECP256R1, "secp256r1-pub-explicit-seed.pem"), + (ec.SECP256R1, "secp256r1-pub-explicit-no-seed.pem"), + (ec.SECP384R1, "secp384r1-pub-explicit-seed.pem"), + (ec.SECP384R1, "secp384r1-pub-explicit-no-seed.pem"), + (ec.SECP521R1, "secp521r1-pub-explicit-seed.pem"), + (ec.SECP521R1, "secp521r1-pub-explicit-no-seed.pem"), + ], + ) + def test_load_public_key_explicit_parameters(self, curve, file, backend): + _skip_curve_unsupported(backend, curve()) + key = load_vectors_from_file( + os.path.join("asymmetric", "EC", file), + lambda pemfile: serialization.load_pem_public_key(pemfile.read()), + mode="rb", + ) + assert isinstance(key, ec.EllipticCurvePublicKey) + assert isinstance(key.curve, curve) + def test_load_private_key_unsupported_curve(self): with pytest.raises((ValueError, exceptions.UnsupportedAlgorithm)): load_vectors_from_file( diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index 89528ec2bcc4..274a3b3c8186 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -5899,11 +5899,11 @@ def test_load_ecdsa_no_named_curve(self, backend): os.path.join("x509", "custom", "ec_no_named_curve.pem"), x509.load_pem_x509_certificate, ) - # This test can trigger three different value errors depending - # on OpenSSL/BoringSSL and versions. Match on the text to ensure - # we are getting the right error. - with pytest.raises(ValueError, match="explicit parameters"): - cert.public_key() + # We map explicit parameters to known curves and this cert + # contains explicit params for P256, so it should load. + pk = cert.public_key() + assert isinstance(pk, ec.EllipticCurvePublicKey) + assert isinstance(pk.curve, ec.SECP256R1) def test_verify_directly_issued_by_ec(self): issuer_private_key = ec.generate_private_key(ec.SECP256R1())