From 8db7499f5a66cf306dc4c5d37c3dc05170f9e98d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 11 Nov 2020 12:44:59 +0100 Subject: [PATCH] support AES-CTR 256 JWK keys in legacy crypto for IE11 --- src/platform/web/dom/Crypto.js | 101 ++++++++++++++++++++++++++------- 1 file changed, 79 insertions(+), 22 deletions(-) diff --git a/src/platform/web/dom/Crypto.js b/src/platform/web/dom/Crypto.js index be3e4343..35933310 100644 --- a/src/platform/web/dom/Crypto.js +++ b/src/platform/web/dom/Crypto.js @@ -241,25 +241,73 @@ class AESCrypto { async generateKey(format, length = 256) { const cryptoKey = await subtleCryptoResult(this._subtleCrypto.generateKey( {"name": "AES-CTR", length}, true, ["encrypt", "decrypt"])); - return subtleCryptoResult(this._subtleCrypto.exportKey("jwk", cryptoKey)); + return subtleCryptoResult(this._subtleCrypto.exportKey(format, cryptoKey)); } async generateIV() { - const randomBytes = this._crypto.getRandomValues(new Uint8Array(8)); - const ivArray = new Uint8Array(16); - for (let i = 0; i < randomBytes.length; i += 1) { - ivArray[i] = randomBytes[i]; - } - return ivArray; + return generateIV(this._crypto); } } +function generateIV(crypto) { + const randomBytes = crypto.getRandomValues(new Uint8Array(8)); + const ivArray = new Uint8Array(16); + for (let i = 0; i < randomBytes.length; i += 1) { + ivArray[i] = randomBytes[i]; + } + return ivArray; +} + +function jwkKeyToRaw(jwkKey) { + if (jwkKey.alg !== "A256CTR") { + throw new Error(`Unknown algorithm: ${jwkKey.alg}`); + } + if (!jwkKey.key_ops.includes("decrypt")) { + throw new Error(`decrypt missing from key_ops`); + } + if (jwkKey.kty !== "oct") { + throw new Error(`Invalid key type, "oct" expected: ${jwkKey.kty}`); + } + // convert base64-url to normal base64 + const base64UrlKey = jwkKey.k; + const base64Key = base64UrlKey.replace(/-/g, "+").replace(/_/g, "/"); + return base64.decode(base64Key); +} + +function encodeUnpaddedBase64(buffer) { + const str = base64.encode(buffer); + const paddingIdx = str.indexOf("="); + if (paddingIdx !== -1) { + return str.substr(0, paddingIdx); + } else { + return str; + } +} + +function encodeUrlBase64(buffer) { + const unpadded = encodeUnpaddedBase64(buffer); + return unpadded.replace(/\+/g, "-").replace(/\//g, "_"); +} + +function rawKeyToJwk(key) { + return { + "alg": "A256CTR", + "ext": true, + "k": encodeUrlBase64(key), + "key_ops": [ + "encrypt", + "decrypt" + ], + "kty": "oct" + }; +} import base64 from "../../../../lib/base64-arraybuffer/index.js"; class AESLegacyCrypto { - constructor(aesjs) { + constructor(aesjs, crypto) { this._aesjs = aesjs; + this._crypto = crypto; } /** * [decrypt description] @@ -274,30 +322,39 @@ class AESLegacyCrypto { throw new Error(`Unsupported counter length: ${counterLength}`); } if (jwkKey) { - if (jwkKey.alg !== "A256CTR") { - throw new Error(`Unknown algorithm: ${jwkKey.alg}`); - } - if (!jwkKey.key_ops.includes("decrypt")) { - throw new Error(`decrypt missing from key_ops`); - } - if (jwkKey.kty !== "oct") { - throw new Error(`Invalid key type, "oct" expected: ${jwkKey.kty}`); - } - // convert base64-url to normal base64 - const base64UrlKey = jwkKey.k; - const base64Key = base64UrlKey.replace(/-/g, "+").replace(/_/g, "/"); - key = base64.decode(base64Key); + key = jwkKeyToRaw(jwkKey); } const aesjs = this._aesjs; var aesCtr = new aesjs.ModeOfOperation.ctr(new Uint8Array(key), new aesjs.Counter(new Uint8Array(iv))); return aesCtr.decrypt(new Uint8Array(data)); } - async encryptCTR({key, iv, data}) { + async encryptCTR({key, jwkKey, iv, data}) { + if (jwkKey) { + key = jwkKeyToRaw(jwkKey); + } const aesjs = this._aesjs; var aesCtr = new aesjs.ModeOfOperation.ctr(new Uint8Array(key), new aesjs.Counter(new Uint8Array(iv))); return aesCtr.encrypt(new Uint8Array(data)); } + + /** + * Generate a CTR key + * @param {String} format "raw" or "jwk" + * @param {Number} length 128 or 256 + * @return {Promise} an object for jwk, or a BufferSource for raw + */ + async generateKey(format, length = 256) { + let key = crypto.getRandomValues(new Uint8Array(length / 8)); + if (format === "jwk") { + key = rawKeyToJwk(key); + } + return key; + } + + async generateIV() { + return generateIV(this._crypto); + } } function hashName(name) {