| //! Functionality related to elliptic curve support. |
| |
| use super::{CurveType, KeyMaterial, OpaqueOr}; |
| use crate::{der_err, km_err, try_to_vec, Error, FallibleAllocExt}; |
| use alloc::vec::Vec; |
| use der::{AnyRef, Decode}; |
| use kmr_wire::{coset, keymint::EcCurve, rpc, KeySizeInBits}; |
| use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo}; |
| use zeroize::ZeroizeOnDrop; |
| |
| /// Size (in bytes) of a curve 25519 private key. |
| pub const CURVE25519_PRIV_KEY_LEN: usize = 32; |
| |
| /// Maximum message size for Ed25519 Signing operations. |
| pub const MAX_ED25519_MSG_SIZE: usize = 16 * 1024; |
| |
| /// Marker value used to indicate that a public key is for RKP test mode. |
| pub const RKP_TEST_KEY_CBOR_MARKER: i64 = -70000; |
| |
| /// Initial byte of SEC1 public key encoding that indicates an uncompressed point. |
| pub const SEC1_UNCOMPRESSED_PREFIX: u8 = 0x04; |
| |
| /// OID value for general-use NIST EC keys held in PKCS#8 and X.509; see RFC 5480 s2.1.1. |
| pub const X509_NIST_OID: pkcs8::ObjectIdentifier = |
| pkcs8::ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"); |
| |
| /// OID value for Ed25519 keys held in PKCS#8 and X.509; see RFC 8410 s3. |
| pub const X509_ED25519_OID: pkcs8::ObjectIdentifier = |
| pkcs8::ObjectIdentifier::new_unwrap("1.3.101.112"); |
| |
| /// OID value for X25519 keys held in PKCS#8 and X.509; see RFC 8410 s3. |
| pub const X509_X25519_OID: pkcs8::ObjectIdentifier = |
| pkcs8::ObjectIdentifier::new_unwrap("1.3.101.110"); |
| |
| /// OID value for PKCS#1 signature with SHA-256 and ECDSA, see RFC 5758 s3.2. |
| pub const ECDSA_SHA256_SIGNATURE_OID: pkcs8::ObjectIdentifier = |
| pkcs8::ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.2"); |
| |
| /// OID value in `AlgorithmIdentifier.parameters` for P-224; see RFC 5480 s2.1.1.1. |
| pub const ALGO_PARAM_P224_OID: pkcs8::ObjectIdentifier = |
| pkcs8::ObjectIdentifier::new_unwrap("1.3.132.0.33"); |
| |
| /// OID value in `AlgorithmIdentifier.parameters` for P-256; see RFC 5480 s2.1.1.1. |
| pub const ALGO_PARAM_P256_OID: pkcs8::ObjectIdentifier = |
| pkcs8::ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7"); |
| |
| /// OID value in `AlgorithmIdentifier.parameters` for P-384; see RFC 5480 s2.1.1.1. |
| pub const ALGO_PARAM_P384_OID: pkcs8::ObjectIdentifier = |
| pkcs8::ObjectIdentifier::new_unwrap("1.3.132.0.34"); |
| |
| /// OID value in `AlgorithmIdentifier.parameters` for P-521; see RFC 5480 s2.1.1.1. |
| pub const ALGO_PARAM_P521_OID: pkcs8::ObjectIdentifier = |
| pkcs8::ObjectIdentifier::new_unwrap("1.3.132.0.35"); |
| |
| /// Subset of `EcCurve` values that are NIST curves. |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] |
| #[repr(i32)] |
| pub enum NistCurve { |
| /// P-224 |
| P224 = 0, |
| /// P-256 |
| P256 = 1, |
| /// P-384 |
| P384 = 2, |
| /// P-521 |
| P521 = 3, |
| } |
| |
| impl NistCurve { |
| /// Curve coordinate size in bytes. |
| pub fn coord_len(&self) -> usize { |
| match self { |
| NistCurve::P224 => 28, |
| NistCurve::P256 => 32, |
| NistCurve::P384 => 48, |
| NistCurve::P521 => 66, |
| } |
| } |
| } |
| |
| impl From<NistCurve> for EcCurve { |
| fn from(nist: NistCurve) -> EcCurve { |
| match nist { |
| NistCurve::P224 => EcCurve::P224, |
| NistCurve::P256 => EcCurve::P256, |
| NistCurve::P384 => EcCurve::P384, |
| NistCurve::P521 => EcCurve::P521, |
| } |
| } |
| } |
| |
| impl TryFrom<EcCurve> for NistCurve { |
| type Error = Error; |
| fn try_from(curve: EcCurve) -> Result<NistCurve, Error> { |
| match curve { |
| EcCurve::P224 => Ok(NistCurve::P224), |
| EcCurve::P256 => Ok(NistCurve::P256), |
| EcCurve::P384 => Ok(NistCurve::P384), |
| EcCurve::P521 => Ok(NistCurve::P521), |
| EcCurve::Curve25519 => Err(km_err!(InvalidArgument, "curve 25519 is not a NIST curve")), |
| } |
| } |
| } |
| |
| impl OpaqueOr<Key> { |
| /// Encode into `buf` the public key information as an ASN.1 DER encodable |
| /// `SubjectPublicKeyInfo`, as described in RFC 5280 section 4.1. |
| /// |
| /// ```asn1 |
| /// SubjectPublicKeyInfo ::= SEQUENCE { |
| /// algorithm AlgorithmIdentifier, |
| /// subjectPublicKey BIT STRING } |
| /// |
| /// AlgorithmIdentifier ::= SEQUENCE { |
| /// algorithm OBJECT IDENTIFIER, |
| /// parameters ANY DEFINED BY algorithm OPTIONAL } |
| /// ``` |
| /// |
| /// For NIST curve EC keys, the contents are described in RFC 5480 section 2.1. |
| /// - The `AlgorithmIdentifier` has an `algorithm` OID of 1.2.840.10045.2.1. |
| /// - The `AlgorithmIdentifier` has `parameters` that hold an OID identifying the curve, here |
| /// one of: |
| /// - P-224: 1.3.132.0.33 |
| /// - P-256: 1.2.840.10045.3.1.7 |
| /// - P-384: 1.3.132.0.34 |
| /// - P-521: 1.3.132.0.35 |
| /// - The `subjectPublicKey` bit string holds an ASN.1 DER-encoded `OCTET STRING` that contains |
| /// a SEC-1 encoded public key. The first byte indicates the format: |
| /// - 0x04: uncompressed, followed by x || y coordinates |
| /// - 0x03: compressed, followed by x coordinate (and with a odd y coordinate) |
| /// - 0x02: compressed, followed by x coordinate (and with a even y coordinate) |
| /// |
| /// For Ed25519 keys, the contents of the `AlgorithmIdentifier` are described in RFC 8410 |
| /// section 3. |
| /// - The `algorithm` has an OID of 1.3.101.112. |
| /// - The `parameters` are absent. |
| /// |
| /// The `subjectPublicKey` holds the raw key bytes. |
| /// |
| /// For X25519 keys, the contents of the `AlgorithmIdentifier` are described in RFC 8410 |
| /// section 3. |
| /// - The `algorithm` has an OID of 1.3.101.110. |
| /// - The `parameters` are absent. |
| /// |
| /// The `subjectPublicKey` holds the raw key bytes. |
| pub fn subject_public_key_info<'a>( |
| &'a self, |
| buf: &'a mut Vec<u8>, |
| ec: &dyn super::Ec, |
| curve: &EcCurve, |
| curve_type: &CurveType, |
| ) -> Result<SubjectPublicKeyInfo<'a>, Error> { |
| buf.try_extend_from_slice(&ec.subject_public_key(self)?)?; |
| let (oid, parameters) = match curve_type { |
| CurveType::Nist => { |
| let nist_curve: NistCurve = (*curve).try_into()?; |
| let params_oid = match nist_curve { |
| NistCurve::P224 => &ALGO_PARAM_P224_OID, |
| NistCurve::P256 => &ALGO_PARAM_P256_OID, |
| NistCurve::P384 => &ALGO_PARAM_P384_OID, |
| NistCurve::P521 => &ALGO_PARAM_P521_OID, |
| }; |
| (X509_NIST_OID, Some(AnyRef::from(params_oid))) |
| } |
| CurveType::EdDsa => (X509_ED25519_OID, None), |
| CurveType::Xdh => (X509_X25519_OID, None), |
| }; |
| Ok(SubjectPublicKeyInfo { |
| algorithm: AlgorithmIdentifier { oid, parameters }, |
| subject_public_key: buf, |
| }) |
| } |
| |
| /// Generate a `COSE_Key` for the public key. |
| pub fn public_cose_key( |
| &self, |
| ec: &dyn super::Ec, |
| curve: EcCurve, |
| curve_type: CurveType, |
| purpose: CoseKeyPurpose, |
| key_id: Option<Vec<u8>>, |
| test_mode: rpc::TestMode, |
| ) -> Result<coset::CoseKey, Error> { |
| let nist_algo = match purpose { |
| CoseKeyPurpose::Agree => coset::iana::Algorithm::ECDH_ES_HKDF_256, |
| CoseKeyPurpose::Sign => coset::iana::Algorithm::ES256, |
| }; |
| |
| let pub_key = ec.subject_public_key(self)?; |
| let mut builder = match curve_type { |
| CurveType::Nist => { |
| let nist_curve: NistCurve = curve.try_into()?; |
| let (x, y) = coordinates_from_pub_key(pub_key, nist_curve)?; |
| let cose_nist_curve = match nist_curve { |
| NistCurve::P224 => { |
| // P-224 is not supported by COSE: there is no value in the COSE Elliptic |
| // Curve registry for it. |
| return Err(km_err!(Unimplemented, "no COSE support for P-224")); |
| } |
| NistCurve::P256 => coset::iana::EllipticCurve::P_256, |
| NistCurve::P384 => coset::iana::EllipticCurve::P_384, |
| NistCurve::P521 => coset::iana::EllipticCurve::P_521, |
| }; |
| coset::CoseKeyBuilder::new_ec2_pub_key(cose_nist_curve, x, y).algorithm(nist_algo) |
| } |
| CurveType::EdDsa => coset::CoseKeyBuilder::new_okp_key() |
| .param( |
| coset::iana::OkpKeyParameter::Crv as i64, |
| coset::cbor::value::Value::from(coset::iana::EllipticCurve::Ed25519 as u64), |
| ) |
| .param( |
| coset::iana::OkpKeyParameter::X as i64, |
| coset::cbor::value::Value::from(pub_key), |
| ) |
| .algorithm(coset::iana::Algorithm::EdDSA), |
| CurveType::Xdh => coset::CoseKeyBuilder::new_okp_key() |
| .param( |
| coset::iana::OkpKeyParameter::Crv as i64, |
| coset::cbor::value::Value::from(coset::iana::EllipticCurve::X25519 as u64), |
| ) |
| .param( |
| coset::iana::OkpKeyParameter::X as i64, |
| coset::cbor::value::Value::from(pub_key), |
| ) |
| .algorithm(coset::iana::Algorithm::ECDH_ES_HKDF_256), |
| }; |
| |
| if let Some(key_id) = key_id { |
| builder = builder.key_id(key_id); |
| } |
| if test_mode == rpc::TestMode(true) { |
| builder = builder.param(RKP_TEST_KEY_CBOR_MARKER, coset::cbor::value::Value::Null); |
| } |
| Ok(builder.build()) |
| } |
| } |
| |
| /// Elliptic curve private key material. |
| #[derive(Clone, PartialEq, Eq)] |
| pub enum Key { |
| /// P-224 private key. |
| P224(NistKey), |
| /// P-256 private key. |
| P256(NistKey), |
| /// P-384 private key. |
| P384(NistKey), |
| /// P-521 private key. |
| P521(NistKey), |
| /// Ed25519 private key. |
| Ed25519(Ed25519Key), |
| /// X25519 private key. |
| X25519(X25519Key), |
| } |
| |
| /// Indication of the purpose for a COSE key. |
| pub enum CoseKeyPurpose { |
| /// ECDH key agreement. |
| Agree, |
| /// ECDSA signature generation. |
| Sign, |
| } |
| |
| impl Key { |
| /// Return the private key material. |
| pub fn private_key_bytes(&self) -> &[u8] { |
| match self { |
| Key::P224(key) => &key.0, |
| Key::P256(key) => &key.0, |
| Key::P384(key) => &key.0, |
| Key::P521(key) => &key.0, |
| Key::Ed25519(key) => &key.0, |
| Key::X25519(key) => &key.0, |
| } |
| } |
| |
| /// Return the type of curve. |
| pub fn curve_type(&self) -> CurveType { |
| match self { |
| Key::P224(_) | Key::P256(_) | Key::P384(_) | Key::P521(_) => CurveType::Nist, |
| Key::Ed25519(_) => CurveType::EdDsa, |
| Key::X25519(_) => CurveType::Xdh, |
| } |
| } |
| |
| /// Return the curve. |
| pub fn curve(&self) -> EcCurve { |
| match self { |
| Key::P224(_) => EcCurve::P224, |
| Key::P256(_) => EcCurve::P256, |
| Key::P384(_) => EcCurve::P384, |
| Key::P521(_) => EcCurve::P521, |
| Key::Ed25519(_) => EcCurve::Curve25519, |
| Key::X25519(_) => EcCurve::Curve25519, |
| } |
| } |
| } |
| |
| /// A NIST EC key, in the form of an ASN.1 DER encoding of a `ECPrivateKey` structure, |
| /// as specified by RFC 5915 section 3: |
| /// |
| /// ```asn1 |
| /// ECPrivateKey ::= SEQUENCE { |
| /// version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), |
| /// privateKey OCTET STRING, |
| /// parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, |
| /// publicKey [1] BIT STRING OPTIONAL |
| /// } |
| /// ``` |
| #[derive(Clone, PartialEq, Eq, ZeroizeOnDrop)] |
| pub struct NistKey(pub Vec<u8>); |
| |
| /// Helper function to return the (x,y) coordinates, given the public key as a SEC-1 encoded |
| /// uncompressed point. 0x04: uncompressed, followed by x || y coordinates. |
| pub fn coordinates_from_pub_key( |
| pub_key: Vec<u8>, |
| curve: NistCurve, |
| ) -> Result<(Vec<u8>, Vec<u8>), Error> { |
| let coord_len = curve.coord_len(); |
| if pub_key.len() != (1 + 2 * coord_len) { |
| return Err(km_err!( |
| UnknownError, |
| "unexpected SEC1 pubkey len of {} for {:?}", |
| pub_key.len(), |
| curve |
| )); |
| } |
| if pub_key[0] != SEC1_UNCOMPRESSED_PREFIX { |
| return Err(km_err!( |
| UnknownError, |
| "unexpected SEC1 pubkey initial byte {} for {:?}", |
| pub_key[0], |
| curve |
| )); |
| } |
| Ok((try_to_vec(&pub_key[1..1 + coord_len])?, try_to_vec(&pub_key[1 + coord_len..])?)) |
| } |
| |
| /// An Ed25519 private key. |
| #[derive(Clone, PartialEq, Eq, ZeroizeOnDrop)] |
| pub struct Ed25519Key(pub [u8; CURVE25519_PRIV_KEY_LEN]); |
| |
| /// An X25519 private key. |
| #[derive(Clone, PartialEq, Eq, ZeroizeOnDrop)] |
| pub struct X25519Key(pub [u8; CURVE25519_PRIV_KEY_LEN]); |
| |
| /// Return the OID used in an `AlgorithmIdentifier` for signatures produced by this curve. |
| pub fn curve_to_signing_oid(curve: EcCurve) -> pkcs8::ObjectIdentifier { |
| match curve { |
| EcCurve::P224 | EcCurve::P256 | EcCurve::P384 | EcCurve::P521 => ECDSA_SHA256_SIGNATURE_OID, |
| EcCurve::Curve25519 => X509_ED25519_OID, |
| } |
| } |
| |
| /// Return the key size for a curve. |
| pub fn curve_to_key_size(curve: EcCurve) -> KeySizeInBits { |
| KeySizeInBits(match curve { |
| EcCurve::P224 => 224, |
| EcCurve::P256 => 256, |
| EcCurve::P384 => 384, |
| EcCurve::P521 => 521, |
| EcCurve::Curve25519 => 256, |
| }) |
| } |
| |
| /// Import an NIST EC key in SEC1 ECPrivateKey format. |
| pub fn import_sec1_private_key(data: &[u8]) -> Result<KeyMaterial, Error> { |
| let ec_key = sec1::EcPrivateKey::from_der(data) |
| .map_err(|e| der_err!(e, "failed to parse ECPrivateKey"))?; |
| let ec_parameters = ec_key.parameters.ok_or_else(|| { |
| km_err!(InvalidArgument, "sec1 formatted EC private key didn't have a parameters field") |
| })?; |
| let parameters_oid = ec_parameters.named_curve().ok_or_else(|| { |
| km_err!( |
| InvalidArgument, |
| "couldn't retrieve parameters oid from sec1 ECPrivateKey formatted ec key parameters" |
| ) |
| })?; |
| let algorithm = |
| AlgorithmIdentifier { oid: X509_NIST_OID, parameters: Some(AnyRef::from(¶meters_oid)) }; |
| let pkcs8_key = pkcs8::PrivateKeyInfo::new(algorithm, data); |
| import_pkcs8_key_impl(&pkcs8_key) |
| } |
| |
| /// Import an EC key in PKCS#8 format. |
| pub fn import_pkcs8_key(data: &[u8]) -> Result<KeyMaterial, Error> { |
| let key_info = pkcs8::PrivateKeyInfo::try_from(data) |
| .map_err(|_| km_err!(InvalidArgument, "failed to parse PKCS#8 EC key"))?; |
| import_pkcs8_key_impl(&key_info) |
| } |
| |
| /// Import a `pkcs8::PrivateKeyInfo` EC key. |
| fn import_pkcs8_key_impl(key_info: &pkcs8::PrivateKeyInfo) -> Result<KeyMaterial, Error> { |
| let algo_params = key_info.algorithm.parameters; |
| match key_info.algorithm.oid { |
| X509_NIST_OID => { |
| let algo_params = algo_params.ok_or_else(|| { |
| km_err!( |
| InvalidArgument, |
| "missing PKCS#8 parameters for NIST curve import under OID {:?}", |
| key_info.algorithm.oid |
| ) |
| })?; |
| let curve_oid = algo_params |
| .oid() |
| .map_err(|_e| km_err!(InvalidArgument, "imported key has no OID parameter"))?; |
| let (curve, key) = match curve_oid { |
| ALGO_PARAM_P224_OID => { |
| (EcCurve::P224, Key::P224(NistKey(try_to_vec(key_info.private_key)?))) |
| } |
| ALGO_PARAM_P256_OID => { |
| (EcCurve::P256, Key::P256(NistKey(try_to_vec(key_info.private_key)?))) |
| } |
| ALGO_PARAM_P384_OID => { |
| (EcCurve::P384, Key::P384(NistKey(try_to_vec(key_info.private_key)?))) |
| } |
| ALGO_PARAM_P521_OID => { |
| (EcCurve::P521, Key::P521(NistKey(try_to_vec(key_info.private_key)?))) |
| } |
| oid => { |
| return Err(km_err!( |
| ImportParameterMismatch, |
| "imported key has unknown OID {:?}", |
| oid, |
| )) |
| } |
| }; |
| Ok(KeyMaterial::Ec(curve, CurveType::Nist, key.into())) |
| } |
| X509_ED25519_OID => { |
| if algo_params.is_some() { |
| Err(km_err!(InvalidArgument, "unexpected PKCS#8 parameters for Ed25519 import")) |
| } else { |
| // For Ed25519 the PKCS#8 `privateKey` field holds a `CurvePrivateKey` |
| // (RFC 8410 s7) that is an OCTET STRING holding the raw key. As this is DER, |
| // this is just a 2 byte prefix (0x04 = OCTET STRING, 0x20 = length of raw key). |
| if key_info.private_key.len() != 2 + CURVE25519_PRIV_KEY_LEN |
| || key_info.private_key[0] != 0x04 |
| || key_info.private_key[1] != 0x20 |
| { |
| return Err(km_err!(InvalidArgument, "unexpected CurvePrivateKey contents")); |
| } |
| import_raw_ed25519_key(&key_info.private_key[2..]) |
| } |
| } |
| X509_X25519_OID => { |
| if algo_params.is_some() { |
| Err(km_err!(InvalidArgument, "unexpected PKCS#8 parameters for X25519 import",)) |
| } else { |
| // For X25519 the PKCS#8 `privateKey` field holds a `CurvePrivateKey` |
| // (RFC 8410 s7) that is an OCTET STRING holding the raw key. As this is DER, |
| // this is just a 2 byte prefix (0x04 = OCTET STRING, 0x20 = length of raw key). |
| if key_info.private_key.len() != 2 + CURVE25519_PRIV_KEY_LEN |
| || key_info.private_key[0] != 0x04 |
| || key_info.private_key[1] != 0x20 |
| { |
| return Err(km_err!(InvalidArgument, "unexpected CurvePrivateKey contents")); |
| } |
| import_raw_x25519_key(&key_info.private_key[2..]) |
| } |
| } |
| _ => Err(km_err!( |
| InvalidArgument, |
| "unexpected OID {:?} for PKCS#8 EC key import", |
| key_info.algorithm.oid, |
| )), |
| } |
| } |
| |
| /// Import a 32-byte raw Ed25519 key. |
| pub fn import_raw_ed25519_key(data: &[u8]) -> Result<KeyMaterial, Error> { |
| let key = data.try_into().map_err(|_e| { |
| km_err!(InvalidInputLength, "import Ed25519 key of incorrect len {}", data.len()) |
| })?; |
| Ok(KeyMaterial::Ec(EcCurve::Curve25519, CurveType::EdDsa, Key::Ed25519(Ed25519Key(key)).into())) |
| } |
| |
| /// Import a 32-byte raw X25519 key. |
| pub fn import_raw_x25519_key(data: &[u8]) -> Result<KeyMaterial, Error> { |
| let key = data.try_into().map_err(|_e| { |
| km_err!(InvalidInputLength, "import X25519 key of incorrect len {}", data.len()) |
| })?; |
| Ok(KeyMaterial::Ec(EcCurve::Curve25519, CurveType::Xdh, Key::X25519(X25519Key(key)).into())) |
| } |