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