/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/crypto/shared.rs
470 строк
14 KB
Nathan Whitaker
fix(crypto): preserve RSA-OAEP label bytes (#36441)
07 авг 2026, 12:13
Не верифицирован
07 авг 2026, 12:13
3c4a4ad
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use std::borrow::Cow; use deno_core::JsBuffer; use deno_core::ToV8; use deno_core::convert::Uint8Array; use elliptic_curve::sec1::ToEncodedPoint; use p256::pkcs8::DecodePrivateKey; use rsa::RsaPrivateKey; use rsa::pkcs1::DecodeRsaPrivateKey; use rsa::pkcs1::EncodeRsaPublicKey; use serde::Deserialize; use serde::Serialize; use sha1::Sha1; use sha2::Digest; use sha2::Sha256; use sha2::Sha384; use sha2::Sha512; use sha3::Sha3_256; use sha3::Sha3_384; use sha3::Sha3_512; pub const RSA_ENCRYPTION_OID: const_oid::ObjectIdentifier = const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1"); pub const ID_SECP256R1_OID: const_oid::ObjectIdentifier = const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7"); pub const ID_SECP384R1_OID: const_oid::ObjectIdentifier = const_oid::ObjectIdentifier::new_unwrap("1.3.132.0.34"); pub const ID_SECP521R1_OID: const_oid::ObjectIdentifier = const_oid::ObjectIdentifier::new_unwrap("1.3.132.0.35"); #[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq)] pub enum ShaHash { #[serde(rename = "SHA-1")] Sha1, #[serde(rename = "SHA-256")] Sha256, #[serde(rename = "SHA-384")] Sha384, #[serde(rename = "SHA-512")] Sha512, #[serde(rename = "SHA3-256")] Sha3_256, #[serde(rename = "SHA3-384")] Sha3_384, #[serde(rename = "SHA3-512")] Sha3_512, } fn rsa_oaep_padding_for_digest<D>(label: &[u8]) -> rsa::Oaep where D: Digest + sha2::digest::DynDigest + Default + Send + Sync + 'static, { let mut digest = D::default(); Digest::update(&mut digest, label); // `rsa::Oaep` only accepts string labels. Preloading the label bytes into // the label digest and passing an empty label preserves arbitrary bytes. rsa::Oaep { digest: Box::new(digest), mgf_digest: Box::<D>::default(), label: None, } } pub(crate) fn rsa_oaep_padding(hash: ShaHash, label: &[u8]) -> rsa::Oaep { match hash { ShaHash::Sha1 => rsa_oaep_padding_for_digest::<Sha1>(label), ShaHash::Sha256 => rsa_oaep_padding_for_digest::<Sha256>(label), ShaHash::Sha384 => rsa_oaep_padding_for_digest::<Sha384>(label), ShaHash::Sha512 => rsa_oaep_padding_for_digest::<Sha512>(label), ShaHash::Sha3_256 => rsa_oaep_padding_for_digest::<Sha3_256>(label), ShaHash::Sha3_384 => rsa_oaep_padding_for_digest::<Sha3_384>(label), ShaHash::Sha3_512 => rsa_oaep_padding_for_digest::<Sha3_512>(label), } } #[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq)] pub enum EcNamedCurve { #[serde(rename = "P-256")] P256, #[serde(rename = "P-384")] P384, #[serde(rename = "P-521")] P521, } #[derive(Deserialize)] #[serde(rename_all = "lowercase", tag = "type", content = "data")] pub enum V8RawKeyData { Secret(JsBuffer), Private(JsBuffer), Public(JsBuffer), } #[derive(ToV8)] #[to_v8(tag = "type", content = "data")] pub enum RustRawKeyData { Secret(Uint8Array), Private(Uint8Array), Public(Uint8Array), } /// Owned WebCrypto key material, wrapped by a V8 garbage-collected handle /// ([`crate::key_store::CryptoKeyHandle`]). /// /// This mirrors [`V8RawKeyData`], but owns its bytes (rather than borrowing a /// `JsBuffer`) so the key material can live in Rust - held alive by the cppgc /// handle that JavaScript stores on the `CryptoKey` - instead of being /// serialized back to JavaScript and passed to every operation. /// /// The `Raw` variant is key material that is stored verbatim (e.g. /// Ed25519/X25519/X448/ML-KEM public key bytes) and carries no `secret`/ /// `private`/`public` tag. `SeededPrivate` holds the composite /// `{ seed, private_key }` material used by the FIPS 203/204 algorithms /// (ML-KEM decapsulation keys and ML-DSA signing keys), where `private_key` /// is the expanded key bytes and `seed` is the short seed used to derive them. #[derive(Debug, Clone)] pub enum RawKeyData { Secret(Box<[u8]>), Private(Box<[u8]>), Public(Box<[u8]>), Raw(Box<[u8]>), /// `seed` is `None` for keys imported from expanded raw private bytes (which /// carry no seed); exporting the `raw-seed`/`jwk`/`pkcs8` formats then /// correctly rejects. SeededPrivate { seed: Option<Box<[u8]>>, private_key: Box<[u8]>, }, } #[derive(Deserialize)] #[serde(rename_all = "lowercase")] pub enum KeyKind { Secret, Private, Public, Raw, Seeded, } /// Wire representation used by the key store insert op. JavaScript passes a /// `{ kind, data }` object (or `{ kind: "seeded", seed, privateKey }` for the /// composite ML-KEM/ML-DSA private key material). #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct InsertKeyData { kind: KeyKind, data: Option<JsBuffer>, seed: Option<JsBuffer>, private_key: Option<JsBuffer>, } /// Wire representation used by the key store get op, returned to JavaScript for /// export, structured clone and node:crypto interop. #[derive(ToV8)] pub struct StoredKeyData { kind: &'static str, data: Option<Uint8Array>, seed: Option<Uint8Array>, private_key: Option<Uint8Array>, } fn into_boxed(buf: Option<JsBuffer>) -> Box<[u8]> { buf.map(|b| b.as_ref().into()).unwrap_or_default() } impl From<InsertKeyData> for RawKeyData { fn from(data: InsertKeyData) -> Self { match data.kind { KeyKind::Secret => RawKeyData::Secret(into_boxed(data.data)), KeyKind::Private => RawKeyData::Private(into_boxed(data.data)), KeyKind::Public => RawKeyData::Public(into_boxed(data.data)), KeyKind::Raw => RawKeyData::Raw(into_boxed(data.data)), KeyKind::Seeded => RawKeyData::SeededPrivate { seed: data.seed.map(|b| b.as_ref().into()), private_key: into_boxed(data.private_key), }, } } } impl RawKeyData { pub fn to_stored_key_data(&self) -> StoredKeyData { let tagged = |kind, b: &[u8]| StoredKeyData { kind, data: Some(b.to_vec().into()), seed: None, private_key: None, }; match self { RawKeyData::Secret(b) => tagged("secret", b), RawKeyData::Private(b) => tagged("private", b), RawKeyData::Public(b) => tagged("public", b), RawKeyData::Raw(b) => tagged("raw", b), RawKeyData::SeededPrivate { seed, private_key } => StoredKeyData { kind: "seeded", data: None, seed: seed.as_ref().map(|s| s.as_ref().to_vec().into()), private_key: Some(private_key.as_ref().to_vec().into()), }, } } /// The raw key bytes, regardless of the secret/private/public/raw tag. /// /// Not valid for `SeededPrivate`; use [`Self::expanded_private_key`] / /// [`Self::seed`] for those. pub fn bytes(&self) -> &[u8] { match self { RawKeyData::Secret(b) | RawKeyData::Private(b) | RawKeyData::Public(b) | RawKeyData::Raw(b) => b, RawKeyData::SeededPrivate { .. } => unreachable!(), } } /// The expanded private key bytes of a `SeededPrivate` (ML-KEM/ML-DSA). pub fn expanded_private_key(&self) -> &[u8] { match self { RawKeyData::SeededPrivate { private_key, .. } => private_key, _ => unreachable!(), } } /// The seed of a `SeededPrivate`, or `None` for keys imported from expanded /// raw private bytes. pub fn seed(&self) -> Option<&[u8]> { match self { RawKeyData::SeededPrivate { seed, .. } => seed.as_deref(), _ => None, } } pub fn as_rsa_public_key(&self) -> Result<Cow<'_, [u8]>, SharedError> { match self { RawKeyData::Public(data) => Ok(Cow::Borrowed(data)), RawKeyData::Private(data) => { let private_key = RsaPrivateKey::from_pkcs1_der(data) .map_err(|_| SharedError::ExpectedValidPrivateKey)?; let public_key_doc = private_key .to_public_key() .to_pkcs1_der() .map_err(|_| SharedError::ExpectedValidPublicKey)?; Ok(Cow::Owned(public_key_doc.as_bytes().into())) } _ => Err(SharedError::ExpectedPublicKey), } } pub fn as_rsa_private_key(&self) -> Result<&[u8], SharedError> { match self { RawKeyData::Private(data) => Ok(data), _ => Err(SharedError::ExpectedPrivateKey), } } pub fn as_secret_key(&self) -> Result<&[u8], SharedError> { match self { RawKeyData::Secret(data) => Ok(data), _ => Err(SharedError::ExpectedSecretKey), } } pub fn as_ec_public_key_p256( &self, ) -> Result<p256::EncodedPoint, SharedError> { match self { RawKeyData::Public(data) => p256::PublicKey::from_sec1_bytes(data) .map(|p| p.to_encoded_point(false)) .map_err(|_| SharedError::ExpectedValidPublicECKey), RawKeyData::Private(data) => { let signing_key = p256::SecretKey::from_pkcs8_der(data) .map_err(|_| SharedError::ExpectedValidPrivateECKey)?; Ok(signing_key.public_key().to_encoded_point(false)) } _ => unreachable!(), } } pub fn as_ec_public_key_p384( &self, ) -> Result<p384::EncodedPoint, SharedError> { match self { RawKeyData::Public(data) => p384::PublicKey::from_sec1_bytes(data) .map(|p| p.to_encoded_point(false)) .map_err(|_| SharedError::ExpectedValidPublicECKey), RawKeyData::Private(data) => { let signing_key = p384::SecretKey::from_pkcs8_der(data) .map_err(|_| SharedError::ExpectedValidPrivateECKey)?; Ok(signing_key.public_key().to_encoded_point(false)) } _ => unreachable!(), } } pub fn as_ec_public_key_p521( &self, ) -> Result<p521::EncodedPoint, SharedError> { match self { RawKeyData::Public(data) => p521::PublicKey::from_sec1_bytes(data) .map(|p| p.to_encoded_point(false)) .map_err(|_| SharedError::ExpectedValidPublicECKey), RawKeyData::Private(data) => { let signing_key = p521::SecretKey::from_pkcs8_der(data) .map_err(|_| SharedError::ExpectedValidPrivateECKey)?; Ok(signing_key.public_key().to_encoded_point(false)) } _ => unreachable!(), } } pub fn as_ec_private_key(&self) -> Result<&[u8], SharedError> { match self { RawKeyData::Private(data) => Ok(data), _ => Err(SharedError::ExpectedPrivateKey), } } } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum SharedError { #[class(type)] #[error("expected valid private key")] ExpectedValidPrivateKey, #[class(type)] #[error("expected valid public key")] ExpectedValidPublicKey, #[class(type)] #[error("expected valid private EC key")] ExpectedValidPrivateECKey, #[class(type)] #[error("expected valid public EC key")] ExpectedValidPublicECKey, #[class(type)] #[error("expected private key")] ExpectedPrivateKey, #[class(type)] #[error("expected public key")] ExpectedPublicKey, #[class(type)] #[error("expected secret key")] ExpectedSecretKey, #[class("DOMExceptionOperationError")] #[error("failed to decode private key")] FailedDecodePrivateKey, #[class("DOMExceptionOperationError")] #[error("failed to decode public key")] FailedDecodePublicKey, #[class("DOMExceptionNotSupportedError")] #[error("unsupported format")] UnsupportedFormat, #[class(type)] #[error("invalid crypto key handle")] InvalidKeyHandle, #[class(type)] #[error("Illegal constructor")] #[property("code" = "ERR_ILLEGAL_CONSTRUCTOR")] IllegalConstructor, #[class(type)] #[error("invalid CryptoKey type")] InvalidKeyType, } #[allow( dead_code, reason = "V8RawKeyData accessors fronted the legacy op_crypto_sign/verify path; \ kept for completeness while the per-op key-handling helpers are \ still being merged into the cppgc impl" )] impl V8RawKeyData { pub fn as_rsa_public_key(&self) -> Result<Cow<'_, [u8]>, SharedError> { match self { V8RawKeyData::Public(data) => Ok(Cow::Borrowed(data)), V8RawKeyData::Private(data) => { let private_key = RsaPrivateKey::from_pkcs1_der(data) .map_err(|_| SharedError::ExpectedValidPrivateKey)?; let public_key_doc = private_key .to_public_key() .to_pkcs1_der() .map_err(|_| SharedError::ExpectedValidPublicKey)?; Ok(Cow::Owned(public_key_doc.as_bytes().into())) } _ => Err(SharedError::ExpectedPublicKey), } } pub fn as_rsa_private_key(&self) -> Result<&[u8], SharedError> { match self { V8RawKeyData::Private(data) => Ok(data), _ => Err(SharedError::ExpectedPrivateKey), } } pub fn as_secret_key(&self) -> Result<&[u8], SharedError> { match self { V8RawKeyData::Secret(data) => Ok(data), _ => Err(SharedError::ExpectedSecretKey), } } pub fn as_ec_public_key_p256( &self, ) -> Result<p256::EncodedPoint, SharedError> { match self { V8RawKeyData::Public(data) => p256::PublicKey::from_sec1_bytes(data) .map(|p| p.to_encoded_point(false)) .map_err(|_| SharedError::ExpectedValidPublicECKey), V8RawKeyData::Private(data) => { let signing_key = p256::SecretKey::from_pkcs8_der(data) .map_err(|_| SharedError::ExpectedValidPrivateECKey)?; Ok(signing_key.public_key().to_encoded_point(false)) } // Should never reach here. V8RawKeyData::Secret(_) => unreachable!(), } } pub fn as_ec_public_key_p384( &self, ) -> Result<p384::EncodedPoint, SharedError> { match self { V8RawKeyData::Public(data) => p384::PublicKey::from_sec1_bytes(data) .map(|p| p.to_encoded_point(false)) .map_err(|_| SharedError::ExpectedValidPublicECKey), V8RawKeyData::Private(data) => { let signing_key = p384::SecretKey::from_pkcs8_der(data) .map_err(|_| SharedError::ExpectedValidPrivateECKey)?; Ok(signing_key.public_key().to_encoded_point(false)) } // Should never reach here. V8RawKeyData::Secret(_) => unreachable!(), } } pub fn as_ec_public_key_p521( &self, ) -> Result<p521::EncodedPoint, SharedError> { match self { V8RawKeyData::Public(data) => { // public_key is a serialized EncodedPoint p521::PublicKey::from_sec1_bytes(data) .map(|p| p.to_encoded_point(false)) .map_err(|_| SharedError::ExpectedValidPublicECKey) } V8RawKeyData::Private(data) => { let signing_key = p521::SecretKey::from_pkcs8_der(data) .map_err(|_| SharedError::ExpectedValidPrivateECKey)?; Ok(signing_key.public_key().to_encoded_point(false)) } // Should never reach here. V8RawKeyData::Secret(_) => unreachable!(), } } pub fn as_ec_private_key(&self) -> Result<&[u8], SharedError> { match self { V8RawKeyData::Private(data) => Ok(data), _ => Err(SharedError::ExpectedPrivateKey), } } }