/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/crypto/subtle_key.rs
204 строки
6 KB
em
perf(ext/crypto): port WebCrypto from JS to Rust (#34966)
13 июн 2026, 15:07
Не верифицирован
13 июн 2026, 15:07
c5fee2e
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. //! `WebIdlConverter` that extracts every field a `SubtleCrypto` method //! needs from a `CryptoKey` argument, eagerly and synchronously, so the //! async impl body can be moved into `spawn_blocking` with no v8 deps. //! //! Used by every method that takes a `CryptoKey`: `encrypt`, `decrypt`, //! `sign`, `verify`, `wrapKey`, `unwrapKey`, `deriveBits`, `deriveKey`, //! `exportKey`, `encapsulateKey`, `encapsulateBits`, `decapsulateKey`, //! `decapsulateBits`, and `getPublicKey`. use std::borrow::Cow; use deno_core::v8; use deno_core::webidl::ContextFn; use deno_core::webidl::WebIdlConverter; use deno_core::webidl::WebIdlError; use deno_core::webidl::WebIdlErrorKind; use deno_error::JsErrorBox; use crate::crypto_key::CryptoKey; use crate::crypto_key::CryptoKeyType; use crate::shared::RawKeyData; use crate::shared::ShaHash; /// A snapshot of the `CryptoKey` slots a `SubtleCrypto` method body cares /// about. Built once during argument coercion so the spec-mandated /// `InvalidAccessError` / `OperationError` checks in the impl can run /// without re-entering JS, and so the per-algorithm dispatch can hand /// the captured `RawKeyData` to `spawn_blocking`. #[allow( dead_code, reason = "fields are read by SubtleCrypto methods that are still being ported" )] pub struct SubtleKey { pub algorithm_name: String, /// AES-`*` key length in bits (`128` / `192` / `256`). `None` for any /// algorithm whose `algorithm` dictionary doesn't carry one. pub algorithm_length: Option<u32>, /// Hash for RSA-* and HMAC keys. `None` for any algorithm whose /// `algorithm` dictionary doesn't carry one. pub algorithm_hash: Option<ShaHash>, /// ECDSA / ECDH `namedCurve`. `None` for non-EC keys. pub algorithm_named_curve: Option<String>, pub usages: Vec<String>, pub key_type: CryptoKeyType, pub extractable: bool, pub raw: RawKeyData, } impl SubtleKey { pub fn has_usage(&self, usage: &str) -> bool { self.usages.iter().any(|u| u == usage) } } impl<'a> WebIdlConverter<'a> for SubtleKey { type Options = (); fn convert<'b>( scope: &mut v8::PinScope<'a, '_>, value: v8::Local<'a, v8::Value>, prefix: Cow<'static, str>, context: ContextFn<'b>, _options: &Self::Options, ) -> Result<Self, WebIdlError> { let Some(key_ptr) = deno_core::cppgc::try_unwrap_cppgc_object::<CryptoKey>(scope, value) else { return Err(WebIdlError::new( prefix, context, WebIdlErrorKind::ConvertToConverterType("CryptoKey"), )); }; let key: &CryptoKey = &key_ptr; let algorithm_name = key.algorithm_name(scope).ok_or_else(|| { WebIdlError::other( prefix.clone(), context.borrowed(), JsErrorBox::type_error("CryptoKey.algorithm.name is not a string"), ) })?; let algorithm_length = read_algorithm_field_u32(scope, key, "length"); let algorithm_hash = read_algorithm_hash(scope, key); let algorithm_named_curve = read_algorithm_field_string(scope, key, "namedCurve"); // Mirrors the `key_handle` check below: a "can't happen on a // spec-conformant instance" tampering case should surface as a // TypeError, not silently coerce to an empty list (which later // produces a less-precise usage-check failure). let usages = key.usages_as_vec(scope).ok_or_else(|| { WebIdlError::other( prefix.clone(), context.borrowed(), JsErrorBox::type_error("CryptoKey.usages has been tampered with"), ) })?; let Some(handle_ptr) = key.key_handle(scope) else { return Err(WebIdlError::other( prefix, context, JsErrorBox::type_error("CryptoKey handle has been tampered with"), )); }; let raw = handle_ptr.data().clone(); Ok(SubtleKey { algorithm_name, algorithm_length, algorithm_hash, algorithm_named_curve, usages, key_type: key.key_type(), extractable: key.extractable_(), raw, }) } } fn read_algorithm_field_u32<'s>( scope: &mut v8::PinScope<'s, '_>, key: &CryptoKey, field: &str, ) -> Option<u32> { let alg = key.algorithm_local(scope)?; let key_v8 = v8::String::new_from_one_byte( scope, field.as_bytes(), v8::NewStringType::Internalized, )?; let val = alg.get(scope, key_v8.into())?; if val.is_undefined() || val.is_null() { return None; } val.uint32_value(scope) } fn read_algorithm_field_string<'s>( scope: &mut v8::PinScope<'s, '_>, key: &CryptoKey, field: &str, ) -> Option<String> { let alg = key.algorithm_local(scope)?; let key_v8 = v8::String::new_from_one_byte( scope, field.as_bytes(), v8::NewStringType::Internalized, )?; let val = alg.get(scope, key_v8.into())?; if val.is_undefined() || val.is_null() { return None; } let s = val.to_string(scope)?; Some(s.to_rust_string_lossy(scope)) } /// RSA-* keys (`RSASSA-PKCS1-v1_5`, `RSA-PSS`, `RSA-OAEP`) and HMAC keys /// carry their hash on the `algorithm.hash` slot, which is either a /// `HashAlgorithmIdentifier` dictionary `{ name: <DOMString> }` or the /// bare name string. Both shapes are normalized into the canonical name /// here. fn read_algorithm_hash<'s>( scope: &mut v8::PinScope<'s, '_>, key: &CryptoKey, ) -> Option<ShaHash> { let alg = key.algorithm_local(scope)?; let hash_key = v8::String::new_from_one_byte( scope, b"hash", v8::NewStringType::Internalized, )?; let hash_val = alg.get(scope, hash_key.into())?; if hash_val.is_undefined() || hash_val.is_null() { return None; } let name_str = if hash_val.is_string() { hash_val.to_rust_string_lossy(scope) } else { let obj = v8::Local::<v8::Object>::try_from(hash_val).ok()?; let name_key = v8::String::new_from_one_byte( scope, b"name", v8::NewStringType::Internalized, )?; let name_val = obj.get(scope, name_key.into())?; let s = name_val.to_string(scope)?; s.to_rust_string_lossy(scope) }; match name_str.as_str() { "SHA-1" => Some(ShaHash::Sha1), "SHA-256" => Some(ShaHash::Sha256), "SHA-384" => Some(ShaHash::Sha384), "SHA-512" => Some(ShaHash::Sha512), "SHA3-256" => Some(ShaHash::Sha3_256), "SHA3-384" => Some(ShaHash::Sha3_384), "SHA3-512" => Some(ShaHash::Sha3_512), _ => None, } }