拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Deno deno_crypto 扩展:seed 注入与密钥驻留的路径怎么走

Deno deno_crypto 扩展seed 注入与密钥驻留的路径怎么走【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/denodeno_crypto扩展负责实现 W3C Web Cryptography API你在 JS 里写的crypto.subtle.sign()最终落在哪段 Rust 上。这个 crate 的源码演进之后JS 侧被刻意做薄——所有 WebCrypto 方法体都下沉到 Rust 侧、挂在 V8 垃圾回收管理的 cppgc 包裹类上cppgc 是 V8 的 C 对象回收机制Rust 结构体实现GarbageCollected后即归 V8 GC 管理。下面沿着两条注入路径走初始化 seed 如何改写随机数行为密钥材料为什么住在 Rust 侧的 GC 对象里而不是 JSWeakMap。WebCrypto 方法怎么变成 V8 里的原生方法lib.rs 的扩展声明里现在只剩两个独立 opdeno_core::extension!(deno_crypto, deps [ deno_webidl, deno_web ], ops [ crypto::op_crypto_random_uuid_batch, op_crypto_is_seeded, ], objects [ crypto::Crypto, subtle_crypto::SubtleCrypto, crypto_key::CryptoKey, ], lazy_loaded_js [ 00_crypto.js ], options { maybe_seed: Optionu64, }, // state 闭包有 seed 则 state.put(StdRng::seed_from_u64(seed)) );deno_core::extension!宏把 ops、cppgc 类、JS 脚本和 options 一次性声明给 deno_core。objects列表是关键crypto.subtle.sign这类调用最终命中 Rust 结构体上的#[op2]方法而不是先落到 JS 函数再跨 op 边界。这个文件里你关注两处就够extension! 宏声明114 行起和CryptoError枚举136 行起后者是后面错误映射的主角。00_crypto.js 现在只剩四类簿记工作文件头部注释自己承认了 intentionally thin原型装饰给三个原型挂Deno.privateCustomInspect符号让Deno.inspect(crypto.subtle)打印SubtleCrypto {}而不是内部 cppgc 形状惰性铸造单例cppgc 堆在快照构建期没有附着到 V8 isolate所以globalThis.crypto/crypto.subtle单例必须延迟到运行时第一次读取时经Crypto.create(getSubtleSingleton())/SubtleCrypto.create()铸造铸造时顺手把 WebIDL brand 和 Node 的kKeyObject符号交给 Rust之后 Rust 侧新铸的CryptoKey都自带这两个 brandstructured-clone 复活回调core.registerCloneableResource(CryptoKey, ...)让CryptoKey能跨 Worker 结构化克隆克隆数据经CryptoKey.fromCloneData重铸 cppgc 实例Function.length修正见后面同步 throw一节。new Crypto()/new SubtleCrypto()在 Rust 侧直接返回IllegalConstructor错误——规范里这两个接口本来就不允许new。密钥材料为什么从 JS WeakMap 迁到 Rust 的 cppgc 对象旧实现里每个CryptoKey的密钥字节存在 00_crypto.js 的一个 JSWeakMap历史上叫KEY_STORE里每次 sign/encrypt/derive 都要把密钥字节序列化、跨过 JS/Rust 边界传进 op。现在密钥字节住在 key_store.rs 的CryptoKeyHandle里JS 侧只存 handle。这个文件总共 48 行看结构体定义和它的unsafe impl GarbageCollected就够了——trace是空实现因为 handle 不持有指向 V8 堆的引用。迁移的收益是双向的。性能上lib.rs 里KeyData的注释写得很直白Previously the key bytes were serialized and passed from JavaScript on every operation. 密钥字节从此只在 import/generate 时过一次边界之后的操作走 Rust 内部引用。生命周期上handle 是 V8 GC 对象CryptoKey被回收时密钥随之自动释放不需要FinalizationRegistry或手动簿记。素材的形态shared.rs 的RawKeyData枚举表达密钥素材的全部形态这个文件里你关注RawKeyData和SharedError两个定义Secret/Private/Public带用途标签的字节分别对应 HMAC 密钥、PKCS8 私钥、SPKI 公钥Raw原样存放的字节Ed25519/X25519/X448/ML-KEM 公钥不携带 secret/private/public 标签SeededPrivate { seed, private_key }FIPS 203/204 算法ML-KEM 解封装密钥、ML-DSA 签名密钥的复合素材。private_key是展开后的完整密钥seed是短种子从展开后的原始字节导入时seed为None此时导出 raw-seed/jwk/pkcs8 格式会被正确拒绝。派生密钥到KeyDatasign/verify/derive 消费的形态时SeededPrivate分支被标成unreachable!()——复合素材从不直接交给这些操作。seed 如何从 Option 走到 randomUUID 的两条分派路径扩展宏的options.maybe_seed字段接到三个注入点runtime/worker.rs636 行附近deno_crypto::deno_crypto::args(options.seed)、runtime/web_worker.rsinit(options.seed)、runtime/snapshot.rslazy_init()。seed 的语义是确定性伪随机数流主要服务测试与快照场景的可复现性。deno_crypto::args(seed) // runtime/worker.rs随扩展参数注入 │ ▼ state 闭包state.put(StdRng::seed_from_u64(seed)) // 只存一个 RNG │ ▼ JSgetCryptoSingleton() 里 op_crypto_is_seeded() 查 OpState 里有没有 StdRng→ usesSeededRng 标志 │ ├── 有 seedrandomUUID 每次走原生 cppgc 方法逐条消耗 16 字节 └── 无 seedop_crypto_random_uuid_batch 一次取 128 条JS 切片JS 侧getCryptoSingleton()铸造单例时调用op_crypto_is_seeded()记下usesSeededRng标志它只检查OpState里是否放了StdRng。randomUUID的实现function randomUUID() { if (this ! cryptoSingleton || usesSeededRng) { return FunctionPrototypeCall(cppgcRandomUUID, this); } if (uuidBatch UUID_BATCH_SIZE) { uuidBatchData op_crypto_random_uuid_batch(); // 一次取回 128 条 uuidBatch 0; } const start uuidBatch * UUID_STRING_BYTES; return StringPrototypeSlice(uuidBatchData, start, start UUID_STRING_BYTES); }00_crypto.js 里你关注randomUUID函数和getCryptoSingleton两处其余都是装饰代码。批量路径背后是 crypto.rs 的op_crypto_random_uuid_batch一次从thread_rng填 2048 字节逐 16 字节组调fast_uuid_v4_bytes拼成 4608 字节的字符串整体返回 JS后续 127 次调用只做StringPrototypeSlice不再跨边界。两条 UUID 路径的分派点fast_uuid_v4_byteslib.rs 底部在 16 字节上就地设置版本位与变体位bytes[6] (bytes[6] 0x0f) | 0x40等再用HEX_CHARS查表直接拼出 36 字符字符串绕开格式化开销同文件里test_fast_uuid_v4_correctness与uuidcrate 逐字节对拍保证这个手写转换不出错。seeded 运行时为什么放弃批量、走原生方法因为批量路径会一次消耗 2048 字节熵而 seed 场景下每次randomUUID应当恰好消耗 16 字节、保持严格的 RNG 调用顺序——测试断言依赖这个顺序可复现。JS 侧get_random_values也走同一个分派OpState里有StdRng就用它否则回落到thread_rng。一次 sign() 调用在 Rust 侧走了多远subtle_crypto.rs 是所有SubtleCrypto方法的 cppgc 入口这个文件里你关注sign/digest两个方法体就够——它们代表全部 20 多个方法的统一形态#[required(3)] #[arraybuffer] async fn sign( self, #[webidl] algorithm: SubtleSignParams, #[webidl] key: SubtleKey, #[webidl] data: BufferSource, ) - ResultVecu8, CryptoError { spawn_blocking(move || run_sign(algorithm, key, data.0)).await? }调用链分三段#[webidl]参数由各自的WebIdlConverter在 V8 栈上同步解析——算法字典解析成SubtleSignParamsSubtleKey把CryptoKey的槽位algorithm.name/hash/usages/type 以及底层CryptoKeyHandle的密钥字节拍成独立快照然后spawn_blocking把纯 Rust 的run_sign丢进 tokio 阻塞线程池在 V8 栈外执行。密钥快照在过spawn_blocking之前就是 Rust 拥有的Vecu8/Box[u8]不再触碰 V8 堆。subtle_sign.rs 的run()先校验算法名与 key 是否匹配、sign用途是否存在再把SubtleKey转成KeyData调 lib.rs 的sign_key_sync。后者按Algorithm变体分派RSASSA-PKCS1-v1_5 从 PKCS#1 DER 解私钥后按 hash 构造签名缺hash报MissingArgumentHashRSA-PSS 额外要求salt_length用OsRng的随机盐签名ECDSA 从 PKCS#8 解私钥、先算 prehash 再sign_prehash产出 rawr||sHMAC 的 SHA-3 变体走tiny-keccak其余走aws_lc_rs。verify_key_sync结构对称但多一条路径KeyType::Private的 ECDSA 验证会先从 PKCS#8 私钥推导VerifyingKey——这是规范允许的用私钥对象验证行为验证失败统一返回false而非抛错。一个容易漏掉的曲线细节P-521 的域大小是 66 字节bits2field要求字段值至少 33 字节所以 prehash 不足 33 字节时要左补零// P-521 field size is 66 bytes; bits2field requires at least // half that (33 bytes). Left-pad shorter hashes to meet the minimum. let prehash if prehash.len() 33 { let mut padded vec![0u8; 33 - prehash.len()]; padded.extend_from_slice(prehash); padded } else { prehash };这段在sign_key_sync和verify_key_sync里各出现一次。P-521 配 SHA-120 字节时不补零签名与验签就会互不相认——曲线相关的这种边界是测试里最容易漏的一类。WPT 是怎么把边界行为钉死的WPTWeb Platform TestsW3C 的 Web 标准兼容性测试集把边界行为钉得很死converter 层不能直接拒绝未知算法名是其中一条。WebIdlError是硬编码TypeError的类型而 WPTdigest.https.any.html的子测试断言错误名必须是NotSupportedError——所以DigestAlgorithm的 converter 把未登记的名字保留为Unknown(name)变体推迟到run()抛出规范要求的DOMException。sign 侧同理subtle_sign.rs 里read_required_hash的注释明确说不要在这里校验 hash 名因为 WPT bad hash name 用例断言err.name NotSupportedError未知名进入SubtleSignParams::Unknown(name)由run()末尾统一抛NotSupportedError。XOF 参数字典outputLength、domainSeparation的校验也推迟到 run 时违规统一报InvalidXofParametersOperationErrordigest.rs 里 SHA-1/SHA-2/SHA-3 之外还登记了 cSHAKE/TurboSHAKE/KangarooTwelve 七个 XOF 算法read_optional_u8负责读取可选的 u8 参数字典成员范围不合规时抛 OperationError。同步 throw 与 rejected promiseWebCrypto 规范规定每个SubtleCrypto方法返回 Promise但 op2 dispatcher 在 async 主体运行之前就同步调用WebIdlConverterconverter 层的 throw 会直接同步到达调用点而 WPT 的promise_rejects_domharness 期望 rejected Promise。00_crypto.js 用makeAsyncForwarder把所有方法包一层 async让同步错误也走 Promise rejection同时把Function.length修正为规范要求的必需参数个数unwrapKey是 7、deriveKey是 5、digest是 2。deriveBits的转发器还有第三个约束op2 宏没法声明可选参数同时保持最少参数检查直接用#[required(2)]会把Function.length压成 2且第三个用户参数会在到达 Rust 前被静默丢弃——所以 JS 侧手写一个三参转发器显式透传全部参数。如果你要继续往下读源码三个入口各有分工ext/crypto/subtle_crypto.rs 是所有SubtleCrypto方法的 cppgc 入口每个方法都是WebIDL 参数转换 →spawn_blocking→ 纯 Rustrun_*的固定五件套读懂一个就能推全部ext/crypto/digest.rs 是 converter 加推迟错误模式的完整样本含 XOF 注册表与参数校验适合对照 WPT 用例看 Deno 如何在 Rust 里复刻 JS 时代的每个边界行为runtime/worker.rs636 行附近是 seed 从WorkerOptions.seed透传到扩展 options 的唯一现场配合 tests/unit/webcrypto_test.ts 能验证前面两条路径的行为边界。【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门