Skip to main content
Glama
ec.rs20.5 kB
use indexmap::{ indexset, IndexSet, }; use openssl::{ bn::{ BigNum, BigNumContext, }, ec::{ EcGroup, EcKey, EcPoint, PointConversionForm, }, ecdsa::EcdsaSig, nid::Nid, pkey::{ HasPublic, PKey, Private, Public, }, sign::{ Signer, Verifier, }, }; use serde::{ Deserialize, Serialize, }; use spki::{ der::{ asn1::BitStringRef, Decode as _, }, ObjectIdentifier, }; use super::{ check_usages_subset, CryptoHash, CryptoKey, CryptoKeyKind, CryptoKeyPair, ImportKeyInput, JsonWebKey, KeyData, KeyFormat, KeyType, KeyUsage, URL_SAFE_FORGIVING, }; use crate::{ convert_v8::{ DOMException, DOMExceptionName, }, environment::crypto_rng::CryptoRng, }; // id-ecPublicKey OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) // ansi-X9-62(10045) keyType(2) 1 } const ALGORITHM_OID: pkcs8::ObjectIdentifier = pkcs8::ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"); const ID_SECP256R1_OID: const_oid::ObjectIdentifier = const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7"); const ID_SECP384R1_OID: const_oid::ObjectIdentifier = const_oid::ObjectIdentifier::new_unwrap("1.3.132.0.34"); const ID_SECP521R1_OID: const_oid::ObjectIdentifier = const_oid::ObjectIdentifier::new_unwrap("1.3.132.0.35"); #[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Debug)] pub(crate) enum EcAlgorithm { /// The "ECDSA" algorithm identifier is used to perform signing and /// verification using the ECDSA algorithm specified in [RFC6090] and using /// the SHA hash functions and elliptic curves defined in this /// specification. #[serde(rename = "ECDSA")] Ecdsa, /// This describes using Elliptic Curve Diffie-Hellman (ECDH) for key /// generation and key agreement, as specified by [RFC6090]. #[serde(rename = "ECDH")] Ecdh, } /// The NamedCurve type represents named elliptic curves, which are a convenient /// way to specify the domain parameters of well-known elliptic curves. #[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Debug)] pub(crate) enum NamedCurve { /// NIST recommended curve P-256, also known as secp256r1. #[serde(rename = "P-256")] P256, /// NIST recommended curve P-384, also known as secp384r1. #[serde(rename = "P-384")] P384, /// NIST recommended curve P-521, also known as secp521r1. #[serde(rename = "P-521")] P521, } #[derive(Deserialize, Serialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub(crate) struct EcKeyAlgorithm { name: EcAlgorithm, /// The namedCurve member represents the named curve that the key uses. named_curve: NamedCurve, } pub(crate) type EcKeyImportParams = EcKeyAlgorithm; pub(crate) type EcKeyGenParams = EcKeyAlgorithm; #[derive(Deserialize)] pub(crate) struct EcdsaParams { #[serde(with = "super::nullary_algorithm")] hash: CryptoHash, } pub(crate) struct EcPrivateKey { private_key: PKey<Private>, } pub(crate) struct EcPublicKey { public_key: PKey<Public>, } impl NamedCurve { fn nid(&self) -> Nid { match self { NamedCurve::P256 => Nid::X9_62_PRIME256V1, NamedCurve::P384 => Nid::SECP384R1, NamedCurve::P521 => Nid::SECP521R1, } } // the smallest integer such that n * 8 is greater than the logarithm to // base 2 of the order of the base point of the elliptic curve fn private_key_size(&self) -> usize { match self { NamedCurve::P256 => 32, NamedCurve::P384 => 48, NamedCurve::P521 => 66, } } } pub(crate) fn generate_keypair( algorithm: EcKeyGenParams, _rng: &CryptoRng, extractable: bool, usages: IndexSet<KeyUsage>, ) -> anyhow::Result<CryptoKeyPair> { let (private_usages, public_usages) = match algorithm.name { EcAlgorithm::Ecdsa => { check_usages_subset(&usages, &[KeyUsage::Sign, KeyUsage::Verify])?; ( usages .intersection(&indexset![KeyUsage::Sign]) .copied() .collect(), usages .intersection(&indexset![KeyUsage::Verify]) .copied() .collect(), ) }, EcAlgorithm::Ecdh => { check_usages_subset(&usages, &[KeyUsage::DeriveKey, KeyUsage::DeriveBits])?; (usages, indexset![]) }, }; let group = EcGroup::from_curve_name(algorithm.named_curve.nid())?; let private_key = EcKey::generate(&group)?; let public_key = EcKey::from_public_key(&group, private_key.public_key())?; Ok(CryptoKeyPair { private_key: CryptoKey { kind: CryptoKeyKind::EcPrivate { algorithm: algorithm.clone(), key: EcPrivateKey { private_key: PKey::from_ec_key(private_key)?, }, }, r#type: KeyType::Private, extractable, usages: private_usages, }, public_key: CryptoKey { kind: CryptoKeyKind::EcPublic { algorithm, key: EcPublicKey { public_key: PKey::from_ec_key(public_key)?, }, }, r#type: KeyType::Public, extractable: true, // N.B.: public key is always extractable usages: public_usages, }, }) } pub(crate) fn import_key( input: ImportKeyInput, algorithm: EcKeyImportParams, extractable: bool, usages: IndexSet<KeyUsage>, ) -> anyhow::Result<CryptoKey> { let (private_usages, public_usages) = match algorithm.name { EcAlgorithm::Ecdsa => (&[KeyUsage::Sign][..], &[KeyUsage::Verify][..]), EcAlgorithm::Ecdh => (&[KeyUsage::DeriveKey, KeyUsage::DeriveBits][..], &[][..]), }; match input { ImportKeyInput::Spki(der) => { check_usages_subset(&usages, public_usages)?; let spki = spki::SubjectPublicKeyInfo::<ObjectIdentifier, BitStringRef<'_>>::from_der(&der) .map_err(|_| { DOMException::new( "invalid SubjectPublicKeyInfo document", DOMExceptionName::DataError, ) })?; // id-ecPublicKey anyhow::ensure!( spki.algorithm.oid == ALGORITHM_OID, DOMException::new( "algorithm oid is not id-ecPublicKey", DOMExceptionName::DataError, ) ); let curve = match spki.algorithm.parameters { Some(ID_SECP256R1_OID) => NamedCurve::P256, Some(ID_SECP384R1_OID) => NamedCurve::P384, Some(ID_SECP521R1_OID) => NamedCurve::P521, _ => anyhow::bail!(DOMException::new( "unknown ECParameters", DOMExceptionName::DataError, )), }; anyhow::ensure!( curve == algorithm.named_curve, DOMException::new("EC curve mismatch", DOMExceptionName::DataError) ); let group = EcGroup::from_curve_name(curve.nid())?; let mut ctx = BigNumContext::new()?; let public_key = EcPoint::from_bytes(&group, spki.subject_public_key.raw_bytes(), &mut ctx) .and_then(|p| EcKey::from_public_key(&group, &p)) .map_err(|_| { DOMException::new("invalid SubjectPublicKey", DOMExceptionName::DataError) })?; Ok(CryptoKey { kind: CryptoKeyKind::EcPublic { algorithm, key: EcPublicKey { public_key: PKey::from_ec_key(public_key)?, }, }, r#type: KeyType::Public, extractable, usages, }) }, ImportKeyInput::Pkcs8(der) => { check_usages_subset(&usages, private_usages)?; let pki = PKey::private_key_from_pkcs8(&der).map_err(|_| { DOMException::new("invalid PublicKeyInfo", DOMExceptionName::DataError) })?; let ec_key = pki .ec_key() .map_err(|_| DOMException::new("not an EC key", DOMExceptionName::DataError))?; anyhow::ensure!( ec_key.group().curve_name() == Some(algorithm.named_curve.nid()), DOMException::new("EC curve mismatch", DOMExceptionName::DataError) ); ec_key .check_key() .map_err(|_| DOMException::new("invalid EC key", DOMExceptionName::DataError))?; Ok(CryptoKey { kind: CryptoKeyKind::EcPrivate { algorithm, key: EcPrivateKey { private_key: pki }, }, r#type: KeyType::Private, extractable, usages, }) }, ImportKeyInput::Jwk(jwk) => { if jwk.d.is_some() { check_usages_subset(&usages, private_usages)?; } else { check_usages_subset(&usages, public_usages)?; } jwk.check_kty("EC")?; jwk.check_key_ops_and_use(&usages, "sig")?; jwk.check_ext(extractable)?; let curve = match jwk.crv.as_deref() { Some("P-256") => Some(NamedCurve::P256), Some("P-384") => Some(NamedCurve::P384), Some("P-521") => Some(NamedCurve::P521), _ => None, }; anyhow::ensure!( curve == Some(algorithm.named_curve), DOMException::new("EC curve mismatch", DOMExceptionName::DataError) ); jwk.check_alg(match algorithm.named_curve { NamedCurve::P256 => "ES256", NamedCurve::P384 => "ES384", NamedCurve::P521 => "ES512", })?; let group = EcGroup::from_curve_name(algorithm.named_curve.nid())?; let mut ctx = BigNumContext::new()?; let (mut p, mut _a, mut _b) = (BigNum::new()?, BigNum::new()?, BigNum::new()?); group.components_gfp(&mut p, &mut _a, &mut _b, &mut ctx)?; let expected_len = ((p.num_bits() + 7) / 8) as usize; let decode_coordinate = |coord: &Option<String>| { coord .as_ref() .and_then(|a| base64::decode_config(a, URL_SAFE_FORGIVING).ok()) .and_then(|a| { // RFC 7518 6.2.1.2, 6.2.1.3: // The length of this octet string MUST be the full size of a coordinate for // the curve specified in the "crv" parameter. if a.len() != expected_len { return None; } BigNum::from_slice(&a).ok() }) }; let coord_error = || { DOMException::new( "invalid EC public key coordinates", DOMExceptionName::DataError, ) }; let x = decode_coordinate(&jwk.x).ok_or_else(coord_error)?; let y = decode_coordinate(&jwk.y).ok_or_else(coord_error)?; // This checks: // 1. that coordinates are canonical (in the range [0, p-1]) // 2. that the cooordinates indeed lie on the curve let public_key = EcKey::from_public_key_affine_coordinates(&group, &x, &y) .map_err(|_| coord_error())?; if let Some(d) = jwk.d { let private_key = base64::decode_config(&d, URL_SAFE_FORGIVING) .ok() .and_then(|bytes| { // RFC 7518 6.2.2.1: // The length of this octet string MUST be ceiling(log-base-2(n)/8) octets // (where n is the order of the curve). if bytes.len() != group.order_bits().div_ceil(8) as usize { return None; } BigNum::from_slice(&bytes).ok() }) .and_then(|bn| { EcKey::from_private_components(&group, &bn, public_key.public_key()).ok() }) .ok_or_else(|| { DOMException::new("invalid EC private key", DOMExceptionName::DataError) })?; Ok(CryptoKey { kind: CryptoKeyKind::EcPrivate { algorithm, key: EcPrivateKey { private_key: PKey::from_ec_key(private_key)?, }, }, r#type: KeyType::Private, extractable, usages, }) } else { Ok(CryptoKey { kind: CryptoKeyKind::EcPublic { algorithm, key: EcPublicKey { public_key: PKey::from_ec_key(public_key)?, }, }, r#type: KeyType::Public, extractable, usages, }) } }, ImportKeyInput::Raw(bytes) => { check_usages_subset(&usages, public_usages)?; let group = EcGroup::from_curve_name(algorithm.named_curve.nid())?; let mut ctx = BigNumContext::new()?; let public_key = EcPoint::from_bytes(&group, &bytes, &mut ctx) .and_then(|p| EcKey::from_public_key(&group, &p)) .map_err(|_| { DOMException::new("invalid EC public key", DOMExceptionName::DataError) })?; Ok(CryptoKey { kind: CryptoKeyKind::EcPublic { algorithm, key: EcPublicKey { public_key: PKey::from_ec_key(public_key)?, }, }, r#type: KeyType::Public, extractable, usages, }) }, } } fn public_jwt<T: HasPublic>( algorithm: &EcKeyAlgorithm, ec_key: &EcKey<T>, ) -> anyhow::Result<JsonWebKey> { let group = ec_key.group(); let mut ctx = BigNumContext::new()?; let (mut p, mut _a, mut _b) = (BigNum::new()?, BigNum::new()?, BigNum::new()?); group.components_gfp(&mut p, &mut _a, &mut _b, &mut ctx)?; let coord_len = (p.num_bits() + 7) / 8; let (mut x, mut y) = (BigNum::new()?, BigNum::new()?); ec_key .public_key() .affine_coordinates(group, &mut x, &mut y, &mut ctx)?; Ok(JsonWebKey { kty: Some("EC".to_owned()), crv: Some( match algorithm.named_curve { NamedCurve::P256 => "P-256", NamedCurve::P384 => "P-384", NamedCurve::P521 => "P-521", } .to_owned(), ), x: Some(base64::encode_config( x.to_vec_padded(coord_len)?, base64::URL_SAFE_NO_PAD, )), y: Some(base64::encode_config( y.to_vec_padded(coord_len)?, base64::URL_SAFE_NO_PAD, )), ..Default::default() }) } impl EcPrivateKey { pub(crate) fn export_key( &self, algorithm: &EcKeyAlgorithm, format: KeyFormat, ) -> anyhow::Result<KeyData> { match format { KeyFormat::Pkcs8 => Ok(KeyData::Raw(self.private_key.private_key_to_pkcs8()?)), KeyFormat::Jwk => { let ec_key = self.private_key.ec_key()?; let d = ec_key.private_key(); Ok(KeyData::Jwk(JsonWebKey { d: Some(base64::encode_config( d.to_vec_padded(algorithm.named_curve.private_key_size() as i32)?, base64::URL_SAFE_NO_PAD, )), ..public_jwt(algorithm, &ec_key)? })) }, KeyFormat::Spki | KeyFormat::Raw => anyhow::bail!(DOMException::new( "invalid export format for EC private key", DOMExceptionName::InvalidAccessError )), } } pub(crate) fn sign( &self, params: EcdsaParams, algorithm: &EcKeyAlgorithm, _rng: &CryptoRng, data: &[u8], ) -> anyhow::Result<Vec<u8>> { anyhow::ensure!( algorithm.name == EcAlgorithm::Ecdsa, DOMException::new( "invalid algorithm for key", DOMExceptionName::InvalidAccessError ) ); let mut signer = Signer::new(params.hash.openssl_message_digest(), &self.private_key)?; Ok(signer .len() .and_then(|len| { let mut der = vec![0; len]; let len = signer.sign_oneshot(&mut der, data)?; der.truncate(len); // N.B.: The OpenSSL interface creates ASN.1 (DER) encoded // signatures; convert them to the fixed-length form specified // by WebCrypto let size = algorithm.named_curve.private_key_size(); let sig = EcdsaSig::from_der(&der)?; let mut fixed_sig = Vec::with_capacity(size * 2); fixed_sig.extend_from_slice(&sig.r().to_vec_padded(size as i32)?); fixed_sig.extend_from_slice(&sig.s().to_vec_padded(size as i32)?); Ok(fixed_sig) }) .map_err(|_| { DOMException::new("ECDSA signing failed", DOMExceptionName::OperationError) })?) } } impl EcPublicKey { pub(crate) fn export_key( &self, algorithm: &EcKeyAlgorithm, format: KeyFormat, ) -> anyhow::Result<KeyData> { match format { KeyFormat::Spki => Ok(KeyData::Raw(self.public_key.ec_key()?.public_key_to_der()?)), KeyFormat::Jwk => { let ec_key = self.public_key.ec_key()?; Ok(KeyData::Jwk(public_jwt(algorithm, &ec_key)?)) }, KeyFormat::Raw => { let ec_key = self.public_key.ec_key()?; let group = ec_key.group(); let mut ctx = BigNumContext::new()?; Ok(KeyData::Raw(ec_key.public_key().to_bytes( group, PointConversionForm::UNCOMPRESSED, &mut ctx, )?)) }, KeyFormat::Pkcs8 => anyhow::bail!(DOMException::new( "invalid export format for EC public key", DOMExceptionName::InvalidAccessError )), } } pub(crate) fn verify( &self, params: EcdsaParams, algorithm: &EcKeyAlgorithm, data: &[u8], signature: &[u8], ) -> anyhow::Result<bool> { anyhow::ensure!( algorithm.name == EcAlgorithm::Ecdsa, DOMException::new( "invalid algorithm for key", DOMExceptionName::InvalidAccessError ) ); // Convert the signature back to ASN.1 so that it can be verified let size = algorithm.named_curve.private_key_size(); if signature.len() != size * 2 { return Ok(false); } let (r, s) = signature.split_at(size); let sig = EcdsaSig::from_private_components(BigNum::from_slice(r)?, BigNum::from_slice(s)?)?; let der = sig.to_der()?; let mut verifier = Verifier::new(params.hash.openssl_message_digest(), &self.public_key)?; Ok(verifier.verify_oneshot(&der, data).map_err(|_| { DOMException::new( "ECDSA verification failed", DOMExceptionName::OperationError, ) })?) } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get-convex/convex-backend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server