Last active
January 30, 2022 06:28
-
-
Save littledivy/2d5ed5939811a130edd760421cb5038f to your computer and use it in GitHub Desktop.
Revisions
-
littledivy renamed this gist
Jan 30, 2022 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
littledivy renamed this gist
Jan 30, 2022 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
littledivy revised this gist
Jan 30, 2022 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ Run this several times to reproduce the bug: ``` $ deno run wrapKey_unwrapKey_flaky.js ``` -
littledivy created this gist
Jan 30, 2022 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,4 @@ Run this several times to reproduce the bug: ``` $ deno run main.js ``` This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,262 @@ const subtle = crypto.subtle; function generateEcdhPeerKey() { return subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, [ "deriveBits", ]).then((k) => k.publicKey); } const wrappers = []; const keys = []; function generateWrappingKeys() { // There are five algorithms that can be used for wrapKey/unwrapKey. // Generate one key with typical parameters for each kind. // // Note: we don't need cryptographically strong parameters for things // like IV - just any legal value will do. var parameters = [ { name: "RSA-OAEP", generateParameters: { name: "RSA-OAEP", modulusLength: 4096, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256", }, wrapParameters: { name: "RSA-OAEP", label: new Uint8Array(8) }, }, { name: "AES-CTR", generateParameters: { name: "AES-CTR", length: 128 }, wrapParameters: { name: "AES-CTR", counter: new Uint8Array(16), length: 64, }, }, { name: "AES-CBC", generateParameters: { name: "AES-CBC", length: 128 }, wrapParameters: { name: "AES-CBC", iv: new Uint8Array(16) }, }, { name: "AES-GCM", generateParameters: { name: "AES-GCM", length: 128 }, wrapParameters: { name: "AES-GCM", iv: new Uint8Array(16), additionalData: new Uint8Array(16), tagLength: 64, }, }, { name: "AES-KW", generateParameters: { name: "AES-KW", length: 128 }, wrapParameters: { name: "AES-KW" }, }, ]; return Promise.all(parameters.map(function (params) { return subtle.generateKey(params.generateParameters, true, [ "wrapKey", "unwrapKey", ]) .then(function (key) { var wrapper; if (params.name === "RSA-OAEP") { // we have a key pair, not just a key wrapper = { wrappingKey: key.publicKey, unwrappingKey: key.privateKey, parameters: params, }; } else { wrapper = { wrappingKey: key, unwrappingKey: key, parameters: params, }; } wrappers.push(wrapper); return true; }); })); } function generateKeysToWrap() { var parameters = [ { algorithm: { name: "RSASSA-PKCS1-v1_5", modulusLength: 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256", }, privateUsages: ["sign"], publicUsages: ["verify"], }, { algorithm: { name: "RSA-PSS", modulusLength: 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256", }, privateUsages: ["sign"], publicUsages: ["verify"], }, { algorithm: { name: "RSA-OAEP", modulusLength: 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256", }, privateUsages: ["decrypt"], publicUsages: ["encrypt"], }, { algorithm: { name: "ECDSA", namedCurve: "P-256" }, privateUsages: ["sign"], publicUsages: ["verify"], }, { algorithm: { name: "ECDH", namedCurve: "P-256" }, privateUsages: ["deriveBits"], publicUsages: [], }, { algorithm: { name: "AES-CTR", length: 128 }, usages: ["encrypt", "decrypt"], }, { algorithm: { name: "AES-CBC", length: 128 }, usages: ["encrypt", "decrypt"], }, { algorithm: { name: "AES-GCM", length: 128 }, usages: ["encrypt", "decrypt"], }, { algorithm: { name: "AES-KW", length: 128 }, usages: ["wrapKey", "unwrapKey"], }, { algorithm: { name: "HMAC", length: 128, hash: "SHA-256" }, usages: ["sign", "verify"], }, ]; return Promise.all(parameters.map(function (params) { var usages; if ("usages" in params) { usages = params.usages; } else { usages = params.publicUsages.concat(params.privateUsages); } return subtle.generateKey(params.algorithm, true, usages) .then(function (result) { if (result.constructor === CryptoKey) { keys.push({ name: params.algorithm.name, algorithm: params.algorithm, usages: params.usages, key: result, }); } else { keys.push({ name: params.algorithm.name + " public key", algorithm: params.algorithm, usages: params.publicUsages, key: result.publicKey, }); keys.push({ name: params.algorithm.name + " private key", algorithm: params.algorithm, usages: params.privateUsages, key: result.privateKey, }); } return true; }); })); } // RSA-OAEP can only wrap relatively small payloads. AES-KW can only // wrap payloads a multiple of 8 bytes long. function wrappingIsPossible(exportedKey, algorithmName) { if ("byteLength" in exportedKey && algorithmName === "AES-KW") { return exportedKey.byteLength % 8 === 0; } if ("byteLength" in exportedKey && algorithmName === "RSA-OAEP") { // RSA-OAEP can only encrypt payloads with lengths shorter // than modulusLength - 2*hashLength - 1 bytes long. For // a 4096 bit modulus and SHA-256, that comes to // 4096/8 - 2*(256/8) - 1 = 512 - 2*32 - 1 = 447 bytes. return exportedKey.byteLength <= 446; } if ("kty" in exportedKey && algorithmName === "AES-KW") { return JSON.stringify(exportedKey).length % 8 == 0; } if ("kty" in exportedKey && algorithmName === "RSA-OAEP") { return JSON.stringify(exportedKey).length <= 478; } return true; } const ecdhPeerKey = await generateEcdhPeerKey(); await generateWrappingKeys(); await generateKeysToWrap(); for (const wrapper of wrappers) { for (const key of keys) { var formats; if (key.name.includes("private")) { formats = ["pkcs8", "jwk"]; } else if (key.name.includes("public")) { formats = ["spki", "jwk"]; } else { formats = ["raw", "jwk"]; } console.log(`Wrapping ${key.name} with ${wrapper.parameters.name};`); for (const format of formats) { console.log(` ${format}`); try { const exportedKey = await subtle.exportKey(format, key.key); if (!wrappingIsPossible(exportedKey, wrapper.parameters.name)) { console.log(` Skipping ${format} format for ${key.name}`); continue; } const wrappedResult = await subtle.wrapKey( format, key.key, wrapper.wrappingKey, wrapper.parameters.wrapParameters, ); // This is flaky. await subtle.unwrapKey( format, wrappedResult, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, key.algorithm, false, key.usages, ); } catch (e) { if (e.message.includes("Initialization vector")) { continue; } if (e.message.includes("expected private key")) { continue; } throw e; } } } }