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())