char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somport webcrypto extension to typescriptdbb5c40

main
45.2 KiB585 linesraw
1mod common;
2
3#[test]
4fn webcrypto_algorithm_conversion_and_key_policy_are_preserved() {
5    let out = common::run(
6        r#"
7        let reads = 0;
8        const algorithm = {
9            get name() { reads++; return 'AES-GCM'; },
10            length: 128,
11        };
12        const key = await crypto.subtle.generateKey(algorithm, false, ['encrypt']);
13        if (reads !== 1) throw Error('Algorithm.name getter read more than once');
14        try {
15            await crypto.subtle.exportKey('raw', key);
16            throw Error('Non-extractable key exported');
17        } catch (error) {
18            if (!String(error.message).includes('not extractable')) throw error;
19        }
20        console.log('crypto algorithm conversion and key policy');
21    "#,
22        &[],
23    );
24    assert_eq!(out.stdout, b"crypto algorithm conversion and key policy\n");
25}
26
27#[test]
28fn web_crypto_random_and_uuid() {
29    let out = common::run(
30        r#"
31        if (!(crypto instanceof Crypto) || !(crypto.subtle instanceof SubtleCrypto)) throw Error('Interfaces');
32        for (const value of [new Uint8Array(8), new Uint16Array(4), new BigInt64Array(2), new Uint8ClampedArray(5)]) {
33            if (crypto.getRandomValues(value) !== value) throw Error('Return identity');
34        }
35        for (const value of [new Float32Array(1), new Float64Array(1), new DataView(new ArrayBuffer(1))]) {
36            try { crypto.getRandomValues(value); throw Error('Accepted invalid view'); } catch (error) {
37                if (error.name !== 'TypeMismatchError') throw error;
38            }
39        }
40        for (const value of [[], null, new Proxy(new Uint8Array(1), {})]) {
41            try { crypto.getRandomValues(value); throw Error('Accepted invalid view'); } catch (error) {
42                if (!(error instanceof TypeError)) throw error;
43            }
44        }
45        for (const length of [65537, 65538]) {
46            try { crypto.getRandomValues(new Uint8Array(length)); throw Error('Accepted oversized view'); } catch (error) {
47                if (error.name !== 'QuotaExceededError') throw error;
48            }
49        }
50        const shared = new Uint8Array(new SharedArrayBuffer(32));
51        try { crypto.getRandomValues(shared); throw Error('Shared buffer accepted'); } catch(error) { if (!(error instanceof TypeError)) throw error; }
52        for (const buffer of [
53            new ArrayBuffer(40),
54            new ArrayBuffer(40, {maxByteLength: 80}),
55        ]) {
56            const storage = new Uint8Array(buffer).fill(99);
57            const view = new Uint16Array(buffer, 4, 12);
58            if (crypto.getRandomValues(view) !== view ||
59                storage.slice(0, 4).some(byte => byte !== 99) ||
60                storage.slice(28).some(byte => byte !== 99)) throw Error('Random view bounds');
61        }
62        const ids = new Set(Array.from({length: 64}, () => crypto.randomUUID()));
63        if (ids.size !== 64 || [...ids].some(id => !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(id))) throw Error('UUID v4');
64        console.log('crypto-random');
65        "#,
66        &[],
67    );
68    assert_eq!(out.stdout, b"crypto-random\n");
69}
70
71#[test]
72fn jwk_dictionaries_snapshot_known_members_without_json_or_private_iterator_hooks() {
73    let out = common::run(
74        r#"
75        const subtle = crypto.subtle, sign = new Set(['sign']), signVerify = new Set(['sign','verify']);
76        let reads = 0, iteratorReads = 0;
77        const input = Object.create({
78            kty: 'oct',
79            get k() {reads++; return 'AQIDBA';},
80            ext: true,
81            key_ops: {
82                get [Symbol.iterator]() {
83                    iteratorReads++;
84                    return function() {return sign.values();};
85                },
86            },
87            toJSON() {throw Error('JWK toJSON');},
88            get unknown() {throw Error('Unknown JWK field');},
89        });
90        const imported = subtle.importKey('jwk', input, {name:'HMAC', hash:'SHA-256'}, true, sign);
91        Object.defineProperty(input, 'k', {value: 'BQYHCA'});
92        const key = await imported;
93        const bytes = new Uint8Array(await subtle.exportKey('raw', key));
94        if (reads !== 1 || iteratorReads !== 1 || bytes.join() !== '1,2,3,4') throw Error('JWK snapshot');
95        const pair = await subtle.generateKey('Ed25519', true, signVerify);
96        const privateJwk = await subtle.exportKey('jwk', pair.privateKey);
97        privateJwk.toJSON = () => {throw Error('Asymmetric JWK toJSON');};
98        privateJwk.unknown = privateJwk;
99        privateJwk.key_ops = sign;
100        const oldIterator = Array.prototype[Symbol.iterator];
101        const oldParse = JSON.parse, oldStringify = JSON.stringify;
102        try {
103            Array.prototype[Symbol.iterator] = JSON.parse = JSON.stringify = () => {throw Error('Guest hook');};
104            const copy = await subtle.importKey('jwk', privateJwk, 'Ed25519', true, sign);
105            const exported = await subtle.exportKey('jwk', copy);
106            if (exported.d !== privateJwk.d || exported.kty !== 'OKP') throw Error('JWK round trip');
107            const generated = await subtle.generateKey('Ed25519', true, signVerify);
108            if (generated.privateKey.type !== 'private') throw Error('Key metadata iterator');
109        } finally {
110            Array.prototype[Symbol.iterator] = oldIterator;
111            JSON.parse = oldParse;
112            JSON.stringify = oldStringify;
113        }
114        const reason = {};
115        try {
116            await subtle.importKey('jwk', {get k() {throw reason;}}, 'AES-GCM', false, new Set(['encrypt']));
117            throw Error('Lost conversion failure');
118        } catch (error) {if (error !== reason) throw error;}
119        for (const key_ops of [null, {}, 1]) {
120            try {
121                await subtle.importKey('jwk', {kty:'oct',k:'AQIDBA',key_ops}, {name:'HMAC',hash:'SHA-256'}, false, sign);
122                throw Error('Accepted non-iterable key_ops');
123            } catch (error) {if (!(error instanceof TypeError)) throw error;}
124        }
125        console.log('jwk dictionaries');
126        "#,
127        &[],
128    );
129    assert_eq!(out.stdout, b"jwk dictionaries\n");
130}
131
132#[test]
133fn web_crypto_symmetric_algorithms_and_keys() {
134    let out = common::run(
135        r#"
136        const bytes = hex => Uint8Array.from(hex.match(/../g).map(x => parseInt(x, 16)));
137        const hex = value => [...new Uint8Array(value)].map(x => x.toString(16).padStart(2, '0')).join('');
138        const eq = (a,b,label) => { if (a !== b) throw Error(label + ': ' + a); };
139        const subtle = crypto.subtle;
140        const aesKey = await subtle.importKey('raw', bytes('00000000000000000000000000000000'), 'AES-GCM', false, new Set(['encrypt','decrypt']));
141        if (aesKey.type !== 'secret' || aesKey.extractable || aesKey.algorithm.name !== 'AES-GCM' || aesKey.algorithm.length !== 128 || !Object.isFrozen(aesKey.usages)) throw Error('CryptoKey metadata');
142        const algorithm = aesKey.algorithm;
143        algorithm.length = 999;
144        if (aesKey.algorithm !== algorithm || aesKey.algorithm.length !== 999) throw Error('Algorithm caching');
145        if (structuredClone(aesKey).algorithm.length !== 128) throw Error('Public metadata mutated private key policy');
146        try { await subtle.exportKey('raw', aesKey); throw Error('Nonextractable export'); } catch (e) { if (e.name !== 'InvalidAccessError') throw e; }
147        const iv = bytes('000000000000000000000000');
148        const cipher = await subtle.encrypt({name:'AES-GCM',iv}, aesKey, bytes('00000000000000000000000000000000'));
149        eq(hex(cipher), '0388dace60b6a392f328c2b971b2fe78ab6e47d42cec13bdf53a67b21257bddf', 'AES-GCM vector');
150        eq(hex(await subtle.decrypt({name:'AES-GCM',iv}, aesKey, cipher)), '00000000000000000000000000000000', 'AES-GCM decrypt');
151        const aes192 = await subtle.importKey('raw', new Uint8Array(24), {name:'AES-GCM'}, true, ['encrypt','decrypt']);
152        for (const tagLength of [32, 64, 96, 104, 112, 120, 128]) {
153          const tagged = await subtle.encrypt({name:'AES-GCM',iv,tagLength}, aes192, bytes('010203'));
154          eq(hex(await subtle.decrypt({name:'AES-GCM',iv,tagLength}, aes192, tagged)), '010203', 'AES-GCM tag ' + tagLength);
155        }
156        for (const tagLength of [0, 31, 33, 48, 129]) {
157          try { await subtle.encrypt({name:'AES-GCM',iv,tagLength}, aes192, bytes('01')); throw Error('Accepted invalid GCM tag length'); }
158          catch (error) { if (error.message === 'Accepted invalid GCM tag length' || error.name !== 'OperationError') throw error; }
159        }
160        try { await subtle.encrypt({name:'AES-GCM',iv:new Uint8Array(0)}, aes192, bytes('01')); throw Error('Accepted empty GCM IV'); }
161        catch (error) { if (error.message === 'Accepted empty GCM IV' || error.name !== 'OperationError') throw error; }
162        const generated = await subtle.generateKey({name:'AES-CTR',length:192}, true, ['encrypt','encrypt','decrypt']);
163        try { await subtle.generateKey({name:'AES-GCM',length:128}, false, {0:'encrypt',length:1}); throw Error('Accepted arraylike usages'); } catch (error) { if (!(error instanceof TypeError)) throw error; }
164        if (generated.algorithm.length !== 192 || generated.usages.join(',') !== 'encrypt,decrypt' || (await subtle.exportKey('raw', generated)).byteLength !== 24) throw Error('Generated AES key');
165        const generatedHmac = await subtle.generateKey({name:'HMAC',hash:'SHA-256'}, true, ['sign']);
166        if (generatedHmac.algorithm.length !== 512 || (await subtle.exportKey('raw', generatedHmac)).byteLength !== 64) throw Error('Generated HMAC default length');
167        const cbcKey = await subtle.importKey('raw', bytes('2b7e151628aed2a6abf7158809cf4f3c'), 'AES-CBC', true, ['encrypt','decrypt']);
168        const cbcIv = bytes('000102030405060708090a0b0c0d0e0f');
169        const cbcCipher = await subtle.encrypt({name:'AES-CBC',iv:cbcIv}, cbcKey, bytes('6bc1bee22e409f96e93d7e117393172a'));
170        eq(hex(await subtle.decrypt({name:'AES-CBC',iv:cbcIv}, cbcKey, cbcCipher)), '6bc1bee22e409f96e93d7e117393172a', 'AES-CBC roundtrip');
171        const ctrKey = await subtle.importKey('raw', bytes('2b7e151628aed2a6abf7158809cf4f3c'), 'AES-CTR', true, ['encrypt','decrypt']);
172        const ctrCounter = bytes('f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff');
173        const ctrCipher = await subtle.encrypt({name:'AES-CTR',counter:ctrCounter,length:128}, ctrKey, bytes('6bc1bee22e409f96e93d7e117393172a'));
174        eq(hex(ctrCipher), '874d6191b620e3261bef6864990db6ce', 'AES-CTR vector');
175        const zeroCtr = await subtle.importKey('raw', new Uint8Array(16), 'AES-CTR', true, ['encrypt','decrypt']);
176        const rollover = await subtle.encrypt({name:'AES-CTR',counter:bytes('000000000000000000000000ffffffff'),length:32}, zeroCtr, new Uint8Array(32));
177        eq(hex(rollover), '28c16380c491088ca019f8a76853b1e866e94bd4ef8a2c3b884cfa59ca342b2e', 'AES-CTR bounded counter');
178        try { await subtle.encrypt({name:'AES-CTR',counter:new Uint8Array(16),length:1}, zeroCtr, new Uint8Array(48)); throw Error('Counter space overflow accepted'); } catch (error) { if (error.message === 'Counter space overflow accepted' || error.name !== 'OperationError') throw error; }
179        const unusualIv = bytes('0102030405060708');
180        const oddNonceCipher = await subtle.encrypt({name:'AES-GCM',iv:unusualIv}, aesKey, bytes('01'));
181        eq(hex(await subtle.decrypt({name:'AES-GCM',iv:unusualIv}, aesKey, oddNonceCipher)), '01', 'AES-GCM non-96-bit IV');
182        for (const length of [17, 32, 64]) {
183            const nonce = new Uint8Array(length).fill(length);
184            const encrypted = await subtle.encrypt({name:'AES-GCM',iv:nonce}, aesKey, bytes('abcdef'));
185            eq(hex(await subtle.decrypt({name:'AES-GCM',iv:nonce}, aesKey, encrypted)), 'abcdef', 'AES-GCM long IV ' + length);
186        }
187        const hmacKey = await subtle.importKey('raw', new TextEncoder().encode('key'), {name:'HMAC',hash:'SHA-256'}, true, ['sign','verify']);
188        const dataView = new DataView(new Uint8Array([7,97,98,99,8]).buffer, 1, 3);
189        const dataViewDigest = hex(await crypto.subtle.digest('SHA-256', dataView));
190        if (dataViewDigest !== 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad') throw Error('DataView BufferSource: ' + dataViewDigest);
191        const partialHmac = await subtle.importKey('raw', new Uint8Array([255,255]), {name:'HMAC',hash:'SHA-256',length:9}, true, ['sign']);
192        if (partialHmac.algorithm.length !== 9 || [...new Uint8Array(await subtle.exportKey('raw', partialHmac))].join(',') !== '255,128') throw Error('Partial-bit HMAC key');
193        const partialGenerated = await subtle.generateKey({name:'HMAC',hash:'SHA-256',length:9}, true, ['sign']);
194        if (partialGenerated.algorithm.length !== 9 || (await subtle.exportKey('raw', partialGenerated)).byteLength !== 2) throw Error('Partial-bit HMAC generation');
195        const message = new TextEncoder().encode('The quick brown fox jumps over the lazy dog');
196        const signature = await subtle.sign('HMAC', hmacKey, message);
197        eq(hex(signature), 'f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8', 'HMAC vector');
198        if (!await subtle.verify('HMAC', hmacKey, signature, message) || await subtle.verify('HMAC', hmacKey, new Uint8Array([0]), message)) throw Error('HMAC verify');
199        const exported = await subtle.exportKey('raw', hmacKey);
200        const jwk = await subtle.exportKey('jwk', hmacKey);
201        if (jwk.kty !== 'oct' || jwk.alg !== 'HS256' || jwk.ext !== true || !jwk.key_ops.includes('sign')) throw Error('JWK export');
202        const importedJwk = await subtle.importKey('jwk', jwk, {name:'HMAC',hash:'SHA-256'}, true, ['verify']);
203        if (!await subtle.verify('HMAC', importedJwk, signature, message)) throw Error('JWK import');
204        for (const badJwk of [{...jwk, alg:'HS512'}, {...jwk, key_ops:['sign','sign']}, {...jwk, key_ops:['execute']}, {...jwk, ext:false}]) {
205            try { await subtle.importKey('jwk', badJwk, {name:'HMAC',hash:'SHA-256'}, true, ['verify']); throw Error('Accepted invalid JWK'); } catch (error) { if (error.message === 'Accepted invalid JWK') throw error; if (error.name !== 'DataError') throw error; }
206        }
207        const importedRaw = await subtle.importKey('raw', exported, {name:'HMAC',hash:'SHA-256'}, true, ['sign']);
208        if (hex(await subtle.sign('HMAC', importedRaw, message)) !== hex(signature)) throw Error('Raw import');
209        const partialJwk = await subtle.exportKey('jwk', partialHmac);
210        const importedPartialJwk = await subtle.importKey('jwk', partialJwk, {name:'HMAC',hash:'SHA-256',length:9}, true, ['sign']);
211        const partialJwkRaw = [...new Uint8Array(await subtle.exportKey('raw', importedPartialJwk))].join(',');
212        if (importedPartialJwk.algorithm.length !== 9 || partialJwkRaw !== '255,128') throw Error('Partial-bit HMAC JWK ' + importedPartialJwk.algorithm.length + ':' + partialJwkRaw);
213        try { new CryptoKey(); throw Error('Public CryptoKey constructor'); } catch(e) { if (!(e instanceof TypeError)) throw e; }
214        const originals = [Array.prototype.includes, Array.prototype.some, Array.prototype.slice, String.prototype.toLowerCase, String.prototype.startsWith, Number.isInteger];
215        try {
216            Array.prototype.includes = Array.prototype.some = Array.prototype.slice = () => { throw Error('poisoned array'); };
217            String.prototype.toLowerCase = String.prototype.startsWith = () => { throw Error('poisoned string'); };
218            Number.isInteger = () => false;
219            const poisonKey = await subtle.importKey('raw', new Uint8Array(16), 'AES-GCM', false, ['encrypt']);
220            await subtle.encrypt({name:'AES-GCM',iv:new Uint8Array(12)}, poisonKey, new Uint8Array(1));
221        } finally {
222            [Array.prototype.includes, Array.prototype.some, Array.prototype.slice, String.prototype.toLowerCase, String.prototype.startsWith, Number.isInteger] = originals;
223        }
224        console.log('symmetric');
225        "#,
226        &[],
227    );
228    assert_eq!(out.stdout, b"symmetric\n");
229}
230
231#[test]
232fn web_crypto_asymmetric_key_generation_and_operations() {
233    let out = common::run(
234        r#"
235        const subtle = crypto.subtle;
236        const message = new TextEncoder().encode('asymmetric vector');
237        const ecdsa = await subtle.generateKey({name:'ECDSA',namedCurve:'P-256'}, true, ['sign','verify']);
238        if (ecdsa.publicKey.type !== 'public' || ecdsa.privateKey.type !== 'private' || ecdsa.publicKey.extractable !== true || ecdsa.privateKey.extractable !== true || ecdsa.publicKey.algorithm.namedCurve !== 'P-256') throw Error('ECDSA pair metadata');
239        const sig = await subtle.sign({name:'ECDSA',hash:'SHA-256'}, ecdsa.privateKey, message);
240        if (sig.byteLength !== 64 || !await subtle.verify({name:'ECDSA',hash:'SHA-256'}, ecdsa.publicKey, sig, message)) throw Error('ECDSA P1363');
241        const publicDer = await subtle.exportKey('spki', ecdsa.publicKey);
242        const publicRaw = await subtle.exportKey('raw', ecdsa.publicKey);
243        const privateDer = await subtle.exportKey('pkcs8', ecdsa.privateKey);
244        const importedPublic = await subtle.importKey('spki', publicDer, {name:'ECDSA',namedCurve:'P-256'}, false, ['verify']);
245        const importedRaw = await subtle.importKey('raw', publicRaw, {name:'ECDSA',namedCurve:'P-256'}, true, ['verify']);
246        const importedPrivate = await subtle.importKey('pkcs8', privateDer, {name:'ECDSA',namedCurve:'P-256'}, false, ['sign']);
247        if (importedPublic.extractable || !await subtle.verify({name:'ECDSA',hash:'SHA-256'}, importedPublic, sig, message) || !await subtle.verify({name:'ECDSA',hash:'SHA-256'}, importedRaw, sig, message)) throw Error('ECDSA public import/export');
248        if (await subtle.exportKey('pkcs8', importedPrivate).then(() => true, () => false)) throw Error('ECDSA private became extractable');
249        const ecdhA = await subtle.generateKey({name:'ECDH',namedCurve:'P-256'}, false, ['deriveBits','deriveKey']);
250        const ecdhB = await subtle.generateKey({name:'ECDH',namedCurve:'P-256'}, false, ['deriveBits','deriveKey']);
251        const secretA = await subtle.deriveBits({name:'ECDH',public:ecdhB.publicKey}, ecdhA.privateKey, 255);
252        const secretB = await subtle.deriveBits({name:'ECDH',public:ecdhA.publicKey}, ecdhB.privateKey, 255);
253        if (new Uint8Array(secretA).some((b, i) => b !== new Uint8Array(secretB)[i])) throw Error('ECDH');
254        const derived = await subtle.deriveKey({name:'ECDH',public:ecdhB.publicKey}, ecdhA.privateKey, {name:'AES-GCM',length:128}, true, ['encrypt']);
255        if ((await subtle.exportKey('raw', derived)).byteLength !== 16) throw Error('ECDH deriveKey');
256        const xA = await subtle.generateKey('X25519', true, ['deriveBits']);
257        const xB = await subtle.generateKey('X25519', false, ['deriveBits']);
258        const xSecret = await subtle.deriveBits({name:'X25519',public:xB.publicKey}, xA.privateKey, 256);
259        const xSecretB = await subtle.deriveBits({name:'X25519',public:xA.publicKey}, xB.privateKey, 256);
260        if (new Uint8Array(xSecret).some((b, i) => b !== new Uint8Array(xSecretB)[i])) throw Error('X25519');
261        const xPrivate = await subtle.exportKey('pkcs8', xA.privateKey);
262        const xPublic = await subtle.exportKey('spki', xB.publicKey);
263        const xImportedPrivate = await subtle.importKey('pkcs8', xPrivate, 'X25519', false, ['deriveBits']);
264        const xImportedPublic = await subtle.importKey('spki', xPublic, 'X25519', false, []);
265        if (new Uint8Array(await subtle.deriveBits({name:'X25519',public:xImportedPublic}, xImportedPrivate, 256)).some((b, i) => b !== new Uint8Array(xSecret)[i])) throw Error('X25519 DER');
266        const xJwk = await subtle.exportKey('jwk', xA.privateKey);
267        const xJwkPrivate = await subtle.importKey('jwk', xJwk, 'X25519', false, ['deriveBits']);
268        if (new Uint8Array(await subtle.deriveBits({name:'X25519',public:xB.publicKey}, xJwkPrivate, 256)).some((b, i) => b !== new Uint8Array(xSecret)[i])) throw Error('X25519 JWK');
269        for (const curve of ['P-384','P-521']) {
270          const pair = await subtle.generateKey({name:'ECDSA',namedCurve:curve}, true, ['sign','verify']);
271          const hash = curve === 'P-384' ? 'SHA-384' : 'SHA-512';
272          const signature = await subtle.sign({name:'ECDSA',hash}, pair.privateKey, message);
273          if (!await subtle.verify({name:'ECDSA',hash}, pair.publicKey, signature, message)) throw Error(curve+' ECDSA');
274          const imported = await subtle.importKey('spki', await subtle.exportKey('spki', pair.publicKey), {name:'ECDSA',namedCurve:curve}, false, ['verify']);
275          if (!await subtle.verify({name:'ECDSA',hash}, imported, signature, message)) throw Error(curve+' DER');
276          const alice = await subtle.generateKey({name:'ECDH',namedCurve:curve}, false, ['deriveBits']);
277          const bob = await subtle.generateKey({name:'ECDH',namedCurve:curve}, false, ['deriveBits']);
278          const sharedA = await subtle.deriveBits({name:'ECDH',public:bob.publicKey}, alice.privateKey, pair.publicKey.algorithm.length);
279          const sharedB = await subtle.deriveBits({name:'ECDH',public:alice.publicKey}, bob.privateKey, pair.publicKey.algorithm.length);
280          if (new Uint8Array(sharedA).some((b, i) => b !== new Uint8Array(sharedB)[i])) throw Error(curve+' ECDH');
281        }
282        const ed = await subtle.generateKey('Ed25519', true, ['sign','verify']);
283        const edSig = await subtle.sign('Ed25519', ed.privateKey, message);
284        if (ed.publicKey.algorithm.name !== 'Ed25519' || edSig.byteLength !== 64 || !await subtle.verify('Ed25519', ed.publicKey, edSig, message)) throw Error('Ed25519');
285        const edJwk = await subtle.exportKey('jwk', ed.privateKey);
286        const edImported = await subtle.importKey('jwk', edJwk, 'Ed25519', false, ['sign']);
287        const edPrivateJwk = await subtle.importKey('jwk', edJwk, 'Ed25519', true, ['sign']);
288        if (edImported.extractable || !await subtle.verify('Ed25519', ed.publicKey, await subtle.sign('Ed25519', edPrivateJwk, message), message)) throw Error('Ed25519 JWK');
289        const ecJwk = await subtle.exportKey('jwk', ecdsa.privateKey);
290        const ecPrivateJwk = await subtle.importKey('jwk', ecJwk, {name:'ECDSA',namedCurve:'P-256'}, true, ['sign']);
291        const ecPublicJwk = await subtle.importKey('jwk', await subtle.exportKey('jwk', ecdsa.publicKey), {name:'ECDSA',namedCurve:'P-256'}, true, ['verify']);
292        if (!await subtle.verify({name:'ECDSA',hash:'SHA-256'}, ecPublicJwk, await subtle.sign({name:'ECDSA',hash:'SHA-256'}, ecPrivateJwk, message), message)) throw Error('ECDSA JWK');
293        const wrapping = await subtle.generateKey({name:'AES-KW',length:128}, false, ['wrapKey','unwrapKey']);
294        const wrappedJwk = await subtle.wrapKey('jwk', ecdsa.privateKey, wrapping, 'AES-KW');
295        const unwrappedJwk = await subtle.unwrapKey('jwk', wrappedJwk, wrapping, 'AES-KW', {name:'ECDSA',namedCurve:'P-256'}, false, ['sign']);
296        if (unwrappedJwk.extractable || !await subtle.verify({name:'ECDSA',hash:'SHA-256'}, ecdsa.publicKey, await subtle.sign({name:'ECDSA',hash:'SHA-256'}, unwrappedJwk, message), message)) throw Error('asymmetric JWK unwrap');
297        const edWrappingPair = await subtle.generateKey('Ed25519', true, ['sign','verify']);
298        const wrappedPublic = await subtle.wrapKey('raw', edWrappingPair.publicKey, wrapping, 'AES-KW');
299        const unwrappedPublic = await subtle.unwrapKey('raw', wrappedPublic, wrapping, 'AES-KW', 'Ed25519', false, ['verify']);
300        const edPublicSignature = await subtle.sign('Ed25519', edWrappingPair.privateKey, message);
301        if (unwrappedPublic.type !== 'public' || unwrappedPublic.extractable || !await subtle.verify('Ed25519', unwrappedPublic, edPublicSignature, message)) throw Error('asymmetric public unwrap');
302        const rsa = await subtle.generateKey({name:'RSA-PSS',modulusLength:1024,publicExponent:new Uint8Array([1,0,1]),hash:'SHA-256'}, true, ['sign','verify']);
303        if (rsa.publicKey.algorithm.modulusLength !== 1024 || rsa.privateKey.algorithm.hash.name !== 'SHA-256') throw Error('RSA metadata');
304        const rsaSig = await subtle.sign({name:'RSA-PSS',saltLength:32}, rsa.privateKey, message);
305        if (!await subtle.verify({name:'RSA-PSS',saltLength:32}, rsa.publicKey, rsaSig, message)) throw Error('RSA-PSS');
306        const rsaPrivateJwk = await subtle.exportKey('jwk', rsa.privateKey);
307        const importedRsa = await subtle.importKey('jwk', rsaPrivateJwk, {name:'RSA-PSS',hash:'SHA-256'}, false, ['sign']);
308        if (importedRsa.extractable || !await subtle.verify({name:'RSA-PSS',saltLength:32}, rsa.publicKey, await subtle.sign({name:'RSA-PSS',saltLength:32}, importedRsa, message), message)) throw Error('RSA JWK');
309        const rsaV15 = await subtle.generateKey({name:'RSASSA-PKCS1-V1_5',modulusLength:1024,publicExponent:new Uint8Array([1,0,1]),hash:'SHA-256'}, false, ['sign','verify']);
310        if (rsaV15.publicKey.algorithm.name !== 'RSASSA-PKCS1-v1_5' || rsaV15.publicKey.algorithm.modulusLength !== 1024) throw Error('RSASSA algorithm metadata');
311        const rsaV15Signature = await subtle.sign('RSASSA-PKCS1-V1_5', rsaV15.privateKey, message);
312        if (!await subtle.verify('RSASSA-PKCS1-V1_5', rsaV15.publicKey, rsaV15Signature, message)) throw Error('RSASSA-PKCS1-v1_5');
313        const extraHashSignature = await subtle.sign({name:'RSA-PSS',get hash(){throw Error('Read ignored RSA hash');},saltLength:32}, rsa.privateKey, message);
314        if (!await subtle.verify({name:'RSA-PSS',saltLength:32}, rsa.publicKey, extraHashSignature, message)) throw Error('RSA key hash ignored');
315        if (await subtle.verify({name:'ECDSA',hash:'SHA-256'}, ecdsa.publicKey, new Uint8Array(63), message)) throw Error('Accepted malformed ECDSA signature');
316        const oaep = await subtle.generateKey({name:'RSA-OAEP',modulusLength:1024,publicExponent:new Uint8Array([1,0,1]),hash:'SHA-256'}, false, ['encrypt','decrypt']);
317        const ciphertext = await subtle.encrypt('RSA-OAEP', oaep.publicKey, message);
318        const label = new TextEncoder().encode('oaep label');
319        const labeledCiphertext = await subtle.encrypt({name:'RSA-OAEP',label}, oaep.publicKey, message);
320        if (new Uint8Array(await subtle.decrypt({name:'RSA-OAEP',label}, oaep.privateKey, labeledCiphertext)).some((b, i) => b !== message[i])) throw Error('RSA-OAEP label');
321        const binaryLabel = new Uint8Array([0,255,128,1]);
322        const binaryLabelCiphertext = await subtle.encrypt({name:'RSA-OAEP',label:binaryLabel}, oaep.publicKey, message);
323        if (new Uint8Array(await subtle.decrypt({name:'RSA-OAEP',label:binaryLabel}, oaep.privateKey, binaryLabelCiphertext)).some((b, i) => b !== message[i])) throw Error('RSA-OAEP binary label');
324        const cleartext = await subtle.decrypt('RSA-OAEP', oaep.privateKey, ciphertext);
325        if (new TextDecoder().decode(cleartext) !== 'asymmetric vector') throw Error('RSA-OAEP');
326        console.log('crypto-asymmetric');
327        "#,
328        &[],
329    );
330    assert_eq!(out.stdout, b"crypto-asymmetric\n");
331}
332
333#[test]
334fn web_crypto_kdf_kw_and_clone_policy() {
335    let out = common::run(
336        r#"
337        const bytes = hex => Uint8Array.from(hex.match(/../g).map(x => parseInt(x, 16)));
338        const hex = value => [...new Uint8Array(value)].map(x => x.toString(16).padStart(2, '0')).join('');
339        const subtle = crypto.subtle;
340        const pb = await subtle.importKey('raw', new TextEncoder().encode('password'), 'PBKDF2', false, ['deriveBits','deriveKey']);
341        if ('length' in pb.algorithm || 'hash' in pb.algorithm) throw Error('PBKDF2 key metadata');
342        const emptyPassword = await subtle.importKey('raw', new Uint8Array(0), 'PBKDF2', false, ['deriveBits']);
343        if ((await subtle.deriveBits({name:'PBKDF2',hash:'SHA-256',salt:new Uint8Array(0),iterations:1}, emptyPassword, 128)).byteLength !== 16) throw Error('Empty PBKDF2 password');
344        const pbBits = await subtle.deriveBits({name:'PBKDF2',hash:'SHA-256',salt:new TextEncoder().encode('salt'),iterations:1}, pb, 256);
345        if (hex(pbBits) !== '120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b') throw Error('PBKDF2 vector');
346        const pbSha1 = await subtle.deriveBits({name:'PBKDF2',hash:'SHA-1',salt:new TextEncoder().encode('salt'),iterations:1}, pb, 160);
347        if (hex(pbSha1) !== '0c60c80f961f0e71f3a9b524af6012062fe037a6') throw Error('PBKDF2 SHA-1 vector');
348        const hk = await subtle.importKey('raw', bytes('0b'.repeat(22)), 'HKDF', false, ['deriveBits','deriveKey']);
349        if ('length' in hk.algorithm || 'hash' in hk.algorithm) throw Error('HKDF key metadata');
350        const hkBits = await subtle.deriveBits({name:'HKDF',hash:'SHA-256',salt:bytes('000102030405060708090a0b0c'),info:bytes('f0f1f2f3f4f5f6f7f8f9')}, hk, 336);
351        if (hex(hkBits) !== '3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865') throw Error('HKDF vector');
352        const hkSha1Key = await subtle.importKey('raw', bytes('0b'.repeat(11)), 'HKDF', false, ['deriveBits']);
353        const hkSha1 = await subtle.deriveBits({name:'HKDF',hash:'SHA-1',salt:bytes('000102030405060708090a0b0c'),info:bytes('f0f1f2f3f4f5f6f7f8f9')}, hkSha1Key, 336);
354        if (hex(hkSha1) !== '085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896') throw Error('HKDF SHA-1 vector');
355        const derived = await subtle.deriveKey({name:'HKDF',hash:'SHA-256',salt:new Uint8Array(0),info:new Uint8Array(0)}, hk, {name:'AES-GCM',length:128}, false, ['encrypt','decrypt']);
356        try { await subtle.deriveKey({name:'HKDF',hash:'SHA-256',salt:new Uint8Array(0),info:new Uint8Array(0)}, hk, {name:'HMAC',hash:'SHA-256',length:9}, true, ['sign']); throw Error('Fractional HKDF output accepted'); } catch(e) { if (e.name !== 'OperationError') throw e; }
357        if (!derived || derived.algorithm.length !== 128 || derived.extractable) throw Error('deriveKey metadata');
358        const kek = await subtle.importKey('raw', bytes('000102030405060708090a0b0c0d0e0f'), 'AES-KW', true, ['wrapKey','unwrapKey']);
359        const rawKey = await subtle.importKey('raw', bytes('00112233445566778899aabbccddeeff'), {name:'AES-GCM'}, true, ['encrypt']);
360        const wrapped = await subtle.wrapKey('raw', rawKey, kek, 'AES-KW');
361        if (hex(wrapped) !== '1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5') throw Error('AES-KW vector');
362        const unwrapped = await subtle.unwrapKey('raw', wrapped, kek, 'AES-KW', {name:'AES-GCM'}, true, ['encrypt']);
363        if (hex(await subtle.exportKey('raw', unwrapped)) !== '00112233445566778899aabbccddeeff') throw Error('unwrapKey');
364        try { await subtle.unwrapKey('raw', new Uint8Array(8), kek, 'AES-KW', {name:'AES-GCM'}, true, ['encrypt']); throw Error('Accepted malformed wrapped key'); }
365        catch (error) { if (error.message === 'Accepted malformed wrapped key' || error.name !== 'OperationError') throw error; }
366        const wrappedJwk = await subtle.wrapKey('jwk', rawKey, kek, 'AES-KW');
367        const unwrappedJwk = await subtle.unwrapKey('jwk', wrappedJwk, kek, 'AES-KW', {name:'AES-GCM'}, true, ['encrypt']);
368        if (hex(await subtle.exportKey('raw', unwrappedJwk)) !== '00112233445566778899aabbccddeeff') throw Error('JWK wrapping');
369        const wrappingModes = [
370          [await subtle.generateKey({name:'AES-CBC',length:128}, false, ['wrapKey','unwrapKey']), {name:'AES-CBC',iv:new Uint8Array(16)}],
371          [await subtle.generateKey({name:'AES-CTR',length:128}, false, ['wrapKey','unwrapKey']), {name:'AES-CTR',counter:new Uint8Array(16),length:128}],
372          [await subtle.generateKey({name:'AES-GCM',length:128}, false, ['wrapKey','unwrapKey']), {name:'AES-GCM',iv:new Uint8Array(12),additionalData:new Uint8Array([1,2])}],
373        ];
374        for (const [wrappingKey, params] of wrappingModes) {
375          const wrappedBytes = await subtle.wrapKey('raw', rawKey, wrappingKey, params);
376          const unwrappedBytes = await subtle.unwrapKey('raw', wrappedBytes, wrappingKey, params, {name:'AES-GCM'}, false, ['encrypt']);
377          if (unwrappedBytes.extractable) throw Error('Nonextractable unwrap');
378          try { await subtle.exportKey('raw', unwrappedBytes); throw Error('Unwrapped key became extractable'); } catch (error) { if (error.name !== 'InvalidAccessError') throw error; }
379          const probeIv = new Uint8Array(12);
380          const originalProbe = await subtle.encrypt({name:'AES-GCM',iv:probeIv}, rawKey, new Uint8Array([7,8,9]));
381          const importedProbe = await subtle.encrypt({name:'AES-GCM',iv:probeIv}, unwrappedBytes, new Uint8Array([7,8,9]));
382          if (hex(originalProbe) !== hex(importedProbe)) throw Error('Wrapped key material mismatch');
383        }
384        const rsaWrap = await subtle.generateKey({name:'RSA-OAEP',modulusLength:1024,publicExponent:new Uint8Array([1,0,1]),hash:'SHA-256'}, false, ['wrapKey','unwrapKey']);
385        const rsaWrapped = await subtle.wrapKey('raw', rawKey, rsaWrap.publicKey, {name:'RSA-OAEP',label:new Uint8Array([4,5])});
386        const rsaUnwrapped = await subtle.unwrapKey('raw', rsaWrapped, rsaWrap.privateKey, {name:'RSA-OAEP',label:new Uint8Array([4,5])}, {name:'AES-GCM'}, false, ['encrypt']);
387        if (rsaUnwrapped.extractable || rsaUnwrapped.algorithm.length !== 128) throw Error('RSA-OAEP unwrap');
388        try { await subtle.unwrapKey('raw', rsaWrapped, rsaWrap.privateKey, {name:'RSA-OAEP',label:new Uint8Array([9])}, {name:'AES-GCM'}, false, ['encrypt']); throw Error('Accepted wrong OAEP unwrap label'); }
389        catch (error) { if (error.message === 'Accepted wrong OAEP unwrap label' || error.name !== 'OperationError') throw error; }
390        const clone = structuredClone(derived);
391        if (clone.extractable || clone.algorithm.length !== 128 || clone.usages.join(',') !== 'encrypt,decrypt') throw Error('CryptoKey clone policy');
392        try { await subtle.exportKey('raw', clone); throw Error('Clone became extractable'); } catch(e) { if (e.name !== 'InvalidAccessError') throw e; }
393        const clonedPassword = structuredClone(pb);
394        if (clonedPassword.extractable || clonedPassword.type !== 'secret' || clonedPassword.algorithm.name !== 'PBKDF2' || clonedPassword.usages.join(',') !== 'deriveBits,deriveKey') throw Error('Nonextractable CryptoKey metadata');
395        const cloneBits = await subtle.deriveBits({name:'PBKDF2',hash:'SHA-256',salt:new TextEncoder().encode('salt'),iterations:1}, clonedPassword, 256);
396        if (hex(cloneBits) !== hex(pbBits)) throw Error('CryptoKey native policy/material lost');
397        try { await subtle.exportKey('raw', clonedPassword); throw Error('Nonextractable clone became extractable'); } catch(e) { if (e.name !== 'InvalidAccessError') throw e; }
398        console.log('kdf-kw');
399        "#,
400        &[],
401    );
402    assert_eq!(out.stdout, b"kdf-kw\n");
403}
404
405#[test]
406fn web_crypto_digest_vectors_and_snapshots() {
407    let out = common::run(
408        r#"
409        const hex = async promise => [...new Uint8Array(await promise)].map(x => x.toString(16).padStart(2, '0')).join('');
410        const input = new TextEncoder().encode('abc');
411        const vectors = [
412            ['SHA-1', 'a9993e364706816aba3e25717850c26c9cd0d89d'],
413            ['SHA-256', 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'],
414            ['SHA-384', 'cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7'],
415            ['SHA-512', 'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f'],
416        ];
417        for (const [name, expected] of vectors) {
418            if (await hex(crypto.subtle.digest(name.toLowerCase(), input)) !== expected) throw Error(name);
419        }
420        const backing = new Uint8Array([97, 98, 99]);
421        const pending = crypto.subtle.digest('SHA-256', backing);
422        backing.fill(0);
423        if (await hex(pending) !== vectors[1][1]) throw Error('Input snapshot');
424        const offset = new Uint8Array([9, 97, 98, 99, 9]);
425        if (await hex(crypto.subtle.digest({name:'SHA-256'}, offset.subarray(1, 4))) !== vectors[1][1]) throw Error('View bounds');
426        try { await crypto.subtle.digest('MD5', input); throw Error('Accepted digest'); } catch (error) {
427            if (error.name !== 'NotSupportedError') throw error;
428        }
429        for (const bad of [null, {}, [], 'abc', 1]) {
430            try { await crypto.subtle.digest('SHA-256', bad); throw Error('Accepted invalid input'); } catch (error) {
431                if (!(error instanceof TypeError)) throw error;
432            }
433        }
434        try { await crypto.subtle.digest.call({}, 'SHA-256', input); throw Error('Brand'); } catch (error) {
435            if (!(error instanceof TypeError)) throw error;
436        }
437        console.log('crypto-digest');
438        "#,
439        &[],
440    );
441    assert_eq!(out.stdout, b"crypto-digest\n");
442}
443
444#[test]
445fn web_crypto_interfaces_and_public_key_policies_survive_cloning() {
446    let out = common::run(
447        r#"
448        for(const ctor of [Crypto,SubtleCrypto,CryptoKey]){
449            try{new ctor();throw Error('Constructed illegal interface');}catch(e){if(!(e instanceof TypeError))throw e;}
450        }
451        const rejected=crypto.subtle.digest({},new Uint8Array());
452        if(!(rejected instanceof Promise))throw Error('Digest did not return promise');
453        try{await rejected;throw Error('Missing algorithm accepted');}catch(e){if(!(e instanceof TypeError))throw e;}
454        try{await crypto.subtle.digest('ſHA-256',new Uint8Array());throw Error('Unicode case-folded algorithm');}catch(e){if(e.name!=='NotSupportedError')throw e;}
455        const bytes=new Uint8Array([97,98,99]);bytes.buffer.transfer();
456        if((await crypto.subtle.digest('SHA-256',bytes)).byteLength!==32||crypto.getRandomValues(bytes)!==bytes)throw Error('Empty detached views');
457        const pair=await crypto.subtle.generateKey('Ed25519',true,['sign','verify']);
458        const raw=await crypto.subtle.exportKey('raw',pair.publicKey);
459        const publicKey=await crypto.subtle.importKey('raw',raw,'Ed25519',false,['verify']);
460        const clone=structuredClone(publicKey);
461        const signature=await crypto.subtle.sign('Ed25519',pair.privateKey,new Uint8Array([1]));
462        for(const key of [publicKey,clone]){
463            if(key.extractable||key.type!=='public'||!await crypto.subtle.verify('Ed25519',key,signature,new Uint8Array([1])))throw Error('Public clone policy');
464            try{await crypto.subtle.exportKey('raw',key);throw Error('Exported nonextractable public key');}catch(e){if(e.name!=='InvalidAccessError')throw e;}
465        }
466        const aes=await crypto.subtle.generateKey({name:'AES-GCM',length:128},true,['encrypt','decrypt']);
467        const longIv=new Uint8Array(65);for(let i=0;i<65;i++)longIv[i]=i;
468        const longCipher=await crypto.subtle.encrypt({name:'AES-GCM',iv:longIv},aes,new Uint8Array([1,2,3]));
469        if([...new Uint8Array(await crypto.subtle.decrypt({name:'AES-GCM',iv:longIv},aes,longCipher))].join(',')!=='1,2,3')throw Error('Long IV roundtrip');
470        console.log('crypto policies');
471    "#,
472        &[],
473    );
474    assert_eq!(out.stdout, b"crypto policies\n");
475}
476
477#[test]
478fn crypto_counter_wraps_and_field_element_lengths_match_the_standard() {
479    let out = common::run(
480        r#"
481        const s=crypto.subtle, hex=b=>[...new Uint8Array(b)].map(x=>x.toString(16).padStart(2,'0')).join('');
482        const key=await s.importKey('raw',new Uint8Array(16).fill(1),'AES-CTR',false,['encrypt']);
483        const counter=new Uint8Array(16).fill(2);counter[15]=254;
484        if(hex(await s.encrypt({name:'AES-CTR',counter,length:8},key,new Uint8Array(48)))!=='f2a6365950d796f031b1526b9ef9468a0a32c8129e404de4f831f721158b6691e702bcca70e79b86f6b15d5de68c37e0')throw Error('Counter carried into nonce');
485        for(const bits of [1,8]){
486            try{await s.importKey('raw',new Uint8Array(2),{name:'HMAC',hash:'SHA-256',length:bits},false,['sign']);throw Error('HMAC dropped whole bytes');}catch(e){if(e.name!=='DataError')throw e;}
487        }
488        const a=await s.generateKey({name:'ECDH',namedCurve:'P-521'},false,['deriveBits']);
489        const b=await s.generateKey({name:'ECDH',namedCurve:'P-521'},false,['deriveBits']);
490        const all=await s.deriveBits({name:'ECDH',public:b.publicKey},a.privateKey);
491        const explicit=await s.deriveBits({name:'ECDH',public:b.publicKey},a.privateKey,528);
492        if(all.byteLength!==66||hex(all)!==hex(explicit)||(await s.deriveBits({name:'ECDH',public:b.publicKey},a.privateKey,0)).byteLength!==0)throw Error('Field element bit length');
493        const kdf=await s.importKey('raw',new Uint8Array(),'PBKDF2',false,['deriveBits']);
494        if((await s.deriveBits({name:'PBKDF2',hash:'SHA-256',salt:new Uint8Array(),iterations:1},kdf,0)).byteLength!==0)throw Error('Empty derived bits');
495        console.log('crypto edge vectors');
496    "#,
497        &[],
498    );
499    assert_eq!(out.stdout, b"crypto edge vectors\n");
500}
501
502#[test]
503fn crypto_export_errors_and_raw_hmac_unwrap_keep_key_policy() {
504    let out = common::run(
505        r#"
506        const s=crypto.subtle;
507        const pair=await s.generateKey({name:'ECDSA',namedCurve:'P-256'},true,['sign','verify']);
508        if(Object.getOwnPropertyNames(pair.privateKey).length!==0)throw Error('Unexpected key properties');
509        for(const [format,key] of [['raw',pair.privateKey],['spki',pair.privateKey],['pkcs8',pair.publicKey]]){
510            try{await s.exportKey(format,key);throw Error('Wrong key type exported');}catch(e){if(e.name!=='InvalidAccessError')throw e;}
511        }
512        try{await s.exportKey('RAW',pair.publicKey);throw Error('Invalid format enum accepted');}catch(e){if(!(e instanceof TypeError))throw e;}
513        const kek=await s.generateKey({name:'AES-GCM',length:128},false,['wrapKey','unwrapKey']);
514        const key=await s.importKey('raw',new Uint8Array([255,255]),{name:'HMAC',hash:'SHA-256'},true,['sign']);
515        const algorithm={name:'AES-GCM',iv:new Uint8Array(12)};
516        const wrapped=await s.wrapKey('raw',key,kek,algorithm);
517        const unwrapped=await s.unwrapKey('raw',wrapped,kek,algorithm,{name:'HMAC',hash:'SHA-256',length:9},true,['sign']);
518        if(unwrapped.algorithm.length!==9||[...new Uint8Array(await s.exportKey('raw',unwrapped))].join(',')!=='255,128')throw Error('HMAC unwrap ignored length');
519        Object.prototype.salt=new Uint8Array([255]);
520        Object.prototype.info=new Uint8Array([255]);
521        Object.prototype.counter=new Uint8Array([255]);
522        try{
523            const hmac=await s.sign('HMAC',key,new Uint8Array([0]));
524            if(hmac.byteLength!==32)throw Error('Prototype altered internal parameters');
525        }finally{delete Object.prototype.salt;delete Object.prototype.info;delete Object.prototype.counter;}
526        console.log('crypto review fixes');
527    "#,
528        &[],
529    );
530    assert_eq!(out.stdout, b"crypto review fixes\n");
531}
532
533#[test]
534fn crypto_binary_results_are_array_buffers_and_inputs_are_not_shared() {
535    let out = common::run(
536        r#"
537        const s=crypto.subtle, data=new Uint8Array([1]);
538        const aes=await s.generateKey({name:'AES-GCM',length:128},true,['encrypt','decrypt','wrapKey','unwrapKey']);
539        const mac=await s.generateKey({name:'HMAC',hash:'SHA-256'},true,['sign']);
540        const kdf=await s.importKey('raw',data,'HKDF',false,['deriveBits']);
541        const algorithm={name:'AES-GCM',iv:new Uint8Array(12)};
542        const cipher=await s.encrypt(algorithm,aes,data);
543        const outputs=[
544            await s.digest('SHA-256',data),cipher,await s.decrypt(algorithm,aes,cipher),
545            await s.sign('HMAC',mac,data),await s.exportKey('raw',aes),
546            await s.wrapKey('raw',aes,aes,algorithm),
547            await s.deriveBits({name:'HKDF',hash:'SHA-256',salt:data,info:data},kdf,8)
548        ];
549        for(const output of outputs)if(!(output instanceof ArrayBuffer)||ArrayBuffer.isView(output))throw Error('Wrong binary output type');
550        for(const source of [new SharedArrayBuffer(1),new Uint8Array(new SharedArrayBuffer(1)),new DataView(new SharedArrayBuffer(1))]){
551            try{await s.digest('SHA-256',source);throw Error('Shared BufferSource accepted');}catch(e){if(!(e instanceof TypeError))throw e;}
552        }
553        const ignored={get hash(){throw Error('Read ignored hash');},get saltLength(){throw Error('Read ignored saltLength');}};
554        ignored.name='HMAC';
555        if((await s.sign(ignored,mac,data)).byteLength!==32)throw Error('Ignored fields');
556        console.log('crypto binary types');
557    "#,
558        &[],
559    );
560    assert_eq!(out.stdout, b"crypto binary types\n");
561}
562
563#[test]
564fn rsa_pss_salt_lengths_use_enforce_range_dictionary_conversion() {
565    let out = common::run(
566        r#"
567        const subtle=crypto.subtle;
568        const pair=await subtle.generateKey({name:'RSA-PSS',modulusLength:1024,publicExponent:new Uint8Array([1,0,1]),hash:'SHA-256'},false,['sign','verify']);
569        const message=new Uint8Array([1,2,3]);
570        const signature=await subtle.sign({name:'RSA-PSS',saltLength:'32'},pair.privateKey,message);
571        if(!await subtle.verify({name:'RSA-PSS',saltLength:32.9},pair.publicKey,signature,message))throw Error('Salt conversion');
572        for(const saltLength of [-1,2**40,NaN,Infinity,-Infinity,undefined,1n,Symbol()]){
573            for(const invoke of [()=>subtle.sign({name:'RSA-PSS',saltLength},pair.privateKey,message),()=>subtle.verify({name:'RSA-PSS',saltLength},pair.publicKey,signature,message)]){
574                try{await invoke();throw Error('Invalid salt accepted');}catch(e){if(!(e instanceof TypeError))throw e;}
575            }
576        }
577        let reads=0;
578        const zero=await subtle.sign({name:'RSA-PSS',get saltLength(){reads++;return -0.5;}},pair.privateKey,message);
579        if(reads!==1||!await subtle.verify({name:'RSA-PSS',saltLength:0},pair.publicKey,zero,message))throw Error('Salt getter or truncation');
580        console.log('salt conversion');
581    "#,
582        &[],
583    );
584    assert_eq!(out.stdout, b"salt conversion\n");
585}