1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
// Copyright 2018-2026 the Deno authors. MIT license.
// This module is intentionally thin: every WebCrypto algorithm body
// (`SubtleCrypto.{digest,encrypt,decrypt,sign,verify,deriveBits,deriveKey,
// importKey,exportKey,wrapKey,unwrapKey,generateKey,getPublicKey,
// encapsulateKey,encapsulateBits,decapsulateKey,decapsulateBits,supports}`,
// and the `Crypto.{getRandomValues,randomUUID,subtle}` members) is
// implemented natively on the cppgc-wrapped Rust classes in
// `ext/crypto/{crypto,subtle_crypto,crypto_key}.rs` and the per-algorithm
// modules they delegate to. What remains in JS is bookkeeping the v8 layer
// requires us to do outside cppgc: the privateCustomInspect decoration on
// the three prototypes, the lazy minting of the `Crypto` / `SubtleCrypto`
// singletons (cppgc allocations can't run at snapshot-build time), the
// structured-clone resurrection callback, and the small `deriveBits`
// forwarder that gives the spec-mandated `Function.length === 2`.
(function () {
const { core, primordials, internals } = __bootstrap;
const {
op_crypto_is_seeded,
op_crypto_random_uuid_batch,
Crypto,
CryptoKey,
SubtleCrypto,
} = core.ops;
const {
FunctionPrototypeCall,
ObjectAssign,
ObjectDefineProperty,
ObjectPrototypeIsPrototypeOf,
SafeArrayIterator,
StringPrototypeSlice,
SymbolFor,
} = primordials;
const webidl = core.loadExtScript("ext:deno_webidl/00_webidl.js");
const { createFilteredInspectProxy } = core.loadExtScript(
"ext:deno_web/01_console.js",
);
const { kKeyObject } = internals;
// op2-generated interface constructors expose the macro's internal
// new-target signal (`_: bool`) as a formal parameter, giving the
// constructor's `.length` a value of 1. Per Web IDL, the interface
// object's `.length` is the minimum-overload required-argument count -- 0
// for all three classes (`Crypto`, `CryptoKey`, `SubtleCrypto` have no
// constructor exposed). Also pin the `prototype` slot to non-writable:
// V8's FunctionTemplate-derived constructors default to a writable
// prototype, but Web IDL requires
// `{ writable: false, enumerable: false, configurable: false }`. The
// `configurable: false` slot is already set, and ECMAScript permits
// downgrading `writable: true -> false` on a non-configurable property.
function applyWebIdlInterfaceShape(interface_) {
ObjectDefineProperty(interface_, "length", {
__proto__: null,
value: 0,
writable: false,
enumerable: false,
configurable: true,
});
ObjectDefineProperty(interface_, "prototype", {
__proto__: null,
writable: false,
});
}
// `CryptoKey` is the cppgc-wrapped Rust class imported above; the `type`,
// `extractable`, `usages` and `algorithm` getters and the underlying state
// all live in Rust (`ext/crypto/crypto_key.rs`). The JS shim only attaches
// the `Deno.privateCustomInspect` symbol to the prototype.
const CryptoKeyPrototype = CryptoKey.prototype;
ObjectDefineProperty(
CryptoKeyPrototype,
SymbolFor("Deno.privateCustomInspect"),
{
__proto__: null,
value: function (inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(CryptoKeyPrototype, this),
keys: [
"type",
"extractable",
"algorithm",
"usages",
],
}),
inspectOptions,
);
},
enumerable: false,
configurable: true,
writable: true,
},
);
webidl.configureInterface(CryptoKey);
applyWebIdlInterfaceShape(CryptoKey);
// Structured-clone resurrection. The host-object brand stamped onto every
// CryptoKey by `make_crypto_key` (`ext/crypto/make_key.rs`) returns a
// snapshot with shape `{ type: "CryptoKey", keyType, extractable, usages,
// algorithm, keyData }`; the static method `CryptoKey.fromCloneData(data)`
// (`ext/crypto/node_interop.rs::from_clone_data`) parses the snapshot back
// into a freshly-minted cppgc instance.
core.registerCloneableResource(
"CryptoKey",
(data) => CryptoKey.fromCloneData(data),
);
// `SubtleCrypto.prototype.deriveBits` is a cppgc method declared with three
// formal params (`algorithm`, `baseKey`, `length`). The op2 macro has no
// way to declare an *optional* param while keeping the macro-level
// minimum-arg check, so we cannot use `#[required(2)]` (it would also cap
// the `Function.length` slot to 2 and route through `async_op_2`, which
// silently drops the third user argument before it reaches Rust -- see
// `setUpAsyncStub` in `libs/core/00_infra.js`). The spec (and WebIDL idl
// harness) requires `Function.length === 2` for
// `deriveBits(AlgorithmIdentifier, CryptoKey, optional unsigned long?)`,
// so wrap the cppgc method in a small forwarder whose declared params
// give it `length === 2` (the `length` default doesn't count) and
// explicitly pass all three args through.
const SubtleCryptoPrototype = SubtleCrypto.prototype;
const cppgcDeriveBits = SubtleCryptoPrototype.deriveBits;
const deriveBitsForwarder = {
async deriveBits(algorithm, baseKey, length = undefined) {
return await FunctionPrototypeCall(
cppgcDeriveBits,
this,
algorithm,
baseKey,
length,
);
},
}.deriveBits;
ObjectDefineProperty(SubtleCryptoPrototype, "deriveBits", {
__proto__: null,
value: deriveBitsForwarder,
writable: true,
enumerable: true,
configurable: true,
});
// The WebCrypto spec declares `importKey`, `getPublicKey`, `unwrapKey`,
// `encapsulateKey`, and `decapsulateKey` as returning Promises. The cppgc
// impls (`subtle_crypto.rs`) run their bodies synchronously because the
// per-algorithm work is bounded (no IO, no large key derivation off-CPU),
// but a sync error path would propagate to JS as a synchronous `throw`.
// That breaks `assertRejects` callers in the test suite and any
// `.catch()`-only consumer. The async wrappers below coerce both the
// success and error paths through a Promise, matching the legacy JS
// async-fn shape.
function makeAsyncForwarder(name, methodName, arity) {
const cppgc = SubtleCryptoPrototype[methodName];
// The `name` parameter is captured in the wrapper's `Function.name`
// slot via a property assignment because async-arrow `function.name`
// would otherwise be `"makeAsyncForwarder"` from the surrounding fn.
const wrapper = {
async [methodName](...args) {
// `await` keeps `dlint require-await` happy and is a no-op when the
// underlying cppgc method returns a non-thenable (the WebCrypto
// surface guarantees a CryptoKey/array/dict, not a Promise).
return await FunctionPrototypeCall(
cppgc,
this,
...new SafeArrayIterator(args),
);
},
}[methodName];
// `Function.length` of `(...args) => ...` is 0, but the WebIDL idl-harness
// test (`SubtleCrypto interface: operation <name>(...)`) requires it to
// match the operation's required-argument count per the spec.
ObjectDefineProperty(wrapper, "length", {
__proto__: null,
value: arity,
configurable: true,
});
ObjectDefineProperty(SubtleCryptoPrototype, name, {
__proto__: null,
value: wrapper,
writable: true,
enumerable: true,
configurable: true,
});
}
// Per WebCrypto spec, every SubtleCrypto method returns a Promise. The
// op2-generated dispatchers invoke `WebIdlConverter`s synchronously before
// the async body runs, so a converter-level throw (`TypeError: Missing
// 'modulusLength'`, `Unrecognized algorithm`, etc.) reaches the call site
// as a synchronous exception. WPT's `promise_rejects_dom` wraps the call
// in `fn.call(undefined)`, which then surfaces the throw as
// `TypeError: Failed to execute 'call' on 'SubtleCrypto': ...` -- a wrong
// shape compared to the spec's "rejected promise". Forward every method
// through `async` so the throw becomes a Promise rejection.
// The third argument is the required-arg count from the WebCrypto IDL,
// applied to the wrapper's `Function.length` for idlharness compliance.
makeAsyncForwarder("digest", "digest", 2);
makeAsyncForwarder("encrypt", "encrypt", 3);
makeAsyncForwarder("decrypt", "decrypt", 3);
makeAsyncForwarder("sign", "sign", 3);
makeAsyncForwarder("verify", "verify", 4);
makeAsyncForwarder("deriveKey", "deriveKey", 5);
makeAsyncForwarder("importKey", "importKey", 5);
makeAsyncForwarder("exportKey", "exportKey", 2);
makeAsyncForwarder("generateKey", "generateKey", 3);
makeAsyncForwarder("getPublicKey", "getPublicKey", 2);
makeAsyncForwarder("wrapKey", "wrapKey", 4);
makeAsyncForwarder("unwrapKey", "unwrapKey", 7);
makeAsyncForwarder("encapsulateBits", "encapsulateBits", 2);
makeAsyncForwarder("encapsulateKey", "encapsulateKey", 5);
makeAsyncForwarder("decapsulateBits", "decapsulateBits", 3);
makeAsyncForwarder("decapsulateKey", "decapsulateKey", 6);
// `SubtleCrypto`'s prototype keeps a single privateCustomInspect helper so
// `Deno.inspect(crypto.subtle)` prints `SubtleCrypto {}` rather than the
// internal cppgc shape.
ObjectAssign(SubtleCryptoPrototype, {
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
return `${this.constructor.name} ${inspect({}, inspectOptions)}`;
},
});
webidl.configureInterface(SubtleCrypto);
applyWebIdlInterfaceShape(SubtleCrypto);
// The `SubtleCrypto` singleton (reachable as `globalThis.crypto.subtle`) is
// minted lazily: `SubtleCrypto.create()` (a static method on the cppgc
// class) allocates the cppgc-wrapped instance, because the cppgc heap
// isn't attached to the V8 isolate at snapshot-build time. The first
// runtime read of `crypto.subtle` calls `getSubtleSingleton`, which also
// stamps the `webidl.brand` symbol onto the instance so the
// `assertBranded` checks at the top of every method body pass. The same
// call hands `webidl.brand` and `kKeyObject` to Rust so freshly-minted
// `CryptoKey`s carry both brands.
let subtleSingleton;
function getSubtleSingleton() {
if (subtleSingleton === undefined) {
Crypto.registerSymbols(webidl.brand, kKeyObject);
subtleSingleton = SubtleCrypto.create();
subtleSingleton[webidl.brand] = webidl.brand;
}
return subtleSingleton;
}
// `Crypto` is the cppgc-wrapped Rust class imported above. `getRandomValues`
// and the `subtle` getter are implemented natively in `crypto.rs`. The normal
// `randomUUID` path batches complete UUID strings so calls after a refill stay
// in JS; seeded runtimes use the native method to preserve exact RNG call order.
const CryptoPrototype = Crypto.prototype;
const cppgcRandomUUID = CryptoPrototype.randomUUID;
const UUID_STRING_BYTES = 36;
const UUID_BATCH_SIZE = 128;
let uuidBatchData;
let uuidBatch = UUID_BATCH_SIZE;
function randomUUID() {
if (this !== cryptoSingleton || usesSeededRng) {
return FunctionPrototypeCall(cppgcRandomUUID, this);
}
if (uuidBatch === UUID_BATCH_SIZE) {
uuidBatchData = op_crypto_random_uuid_batch();
uuidBatch = 0;
}
const start = uuidBatch++ * UUID_STRING_BYTES;
return StringPrototypeSlice(
uuidBatchData,
start,
start + UUID_STRING_BYTES,
);
}
ObjectDefineProperty(CryptoPrototype, "randomUUID", {
__proto__: null,
value: randomUUID,
writable: true,
enumerable: true,
configurable: true,
});
ObjectDefineProperty(CryptoPrototype, SymbolFor("Deno.privateCustomInspect"), {
__proto__: null,
value: function (inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(CryptoPrototype, this),
keys: ["subtle"],
}),
inspectOptions,
);
},
enumerable: false,
configurable: true,
writable: true,
});
webidl.configureInterface(Crypto);
applyWebIdlInterfaceShape(Crypto);
let cryptoSingleton;
let usesSeededRng = false;
function getCryptoSingleton() {
if (cryptoSingleton === undefined) {
cryptoSingleton = Crypto.create(getSubtleSingleton());
usesSeededRng = op_crypto_is_seeded();
// Stamp the WebIDL brand so `Reflect.getPrototypeOf(crypto)` and
// the IDL `Crypto interface: operation randomUUID()` invariants
// resolve through the same brand-check path as `SubtleCrypto`.
cryptoSingleton[webidl.brand] = webidl.brand;
}
return cryptoSingleton;
}
// Bridge functions for Node.js KeyObject interop -- thin trampolines onto
// the cppgc static methods declared on the `CryptoKey` class in
// `ext/crypto/crypto_key.rs` (which delegate to `node_interop.rs`).
function cryptoKeyExportNodeKeyMaterial(cryptoKey) {
return CryptoKey.exportNodeMaterial(cryptoKey);
}
function importCryptoKeySync(format, keyData, algorithm, extractable, usages) {
return CryptoKey.importSync(format, keyData, algorithm, extractable, usages);
}
return {
Crypto,
get crypto() {
return getCryptoSingleton();
},
CryptoKey,
cryptoKeyExportNodeKeyMaterial,
importCryptoKeySync,
SubtleCrypto,
};
})();