tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

wrapKey_unwrapKey.https.any.js (24728B)


      1 // META: title=WebCryptoAPI: wrapKey() and unwrapKey()
      2 // META: timeout=long
      3 // META: script=../util/helpers.js
      4 // META: script=wrapKey_unwrapKey_vectors.js
      5 
      6 // Tests for wrapKey and unwrapKey round tripping
      7 
      8    var subtle = self.crypto.subtle;
      9 
     10    var wrappers = {};  // Things we wrap (and upwrap) keys with
     11    var keys = {};      // Things to wrap and unwrap
     12 
     13    // There are five algorithms that can be used for wrapKey/unwrapKey.
     14    // Generate one key with typical parameters for each kind.
     15    //
     16    // Note: we don't need cryptographically strong parameters for things
     17    // like IV - just any legal value will do.
     18    var wrappingKeysParameters = [
     19        {
     20            name: "RSA-OAEP",
     21            importParameters: {name: "RSA-OAEP", hash: "SHA-256"},
     22            wrapParameters: {name: "RSA-OAEP", label: new Uint8Array(8)}
     23        },
     24        {
     25            name: "AES-CTR",
     26            importParameters: {name: "AES-CTR", length: 128},
     27            wrapParameters: {name: "AES-CTR", counter: new Uint8Array(16), length: 64}
     28        },
     29        {
     30            name: "AES-CBC",
     31            importParameters: {name: "AES-CBC", length: 128},
     32            wrapParameters: {name: "AES-CBC", iv: new Uint8Array(16)}
     33        },
     34        {
     35            name: "AES-GCM",
     36            importParameters: {name: "AES-GCM", length: 128},
     37            wrapParameters: {name: "AES-GCM", iv: new Uint8Array(16), additionalData: new Uint8Array(16), tagLength: 128}
     38        },
     39        {
     40            name: "AES-KW",
     41            importParameters: {name: "AES-KW", length: 128},
     42            wrapParameters: {name: "AES-KW"}
     43        }
     44    ];
     45 
     46    var keysToWrapParameters = [
     47        {algorithm: {name: "RSASSA-PKCS1-v1_5", hash: "SHA-256"}, privateUsages: ["sign"], publicUsages: ["verify"]},
     48        {algorithm: {name: "RSA-PSS", hash: "SHA-256"}, privateUsages: ["sign"], publicUsages: ["verify"]},
     49        {algorithm: {name: "RSA-OAEP", hash: "SHA-256"}, privateUsages: ["decrypt"], publicUsages: ["encrypt"]},
     50        {algorithm: {name: "ECDSA", namedCurve: "P-256"}, privateUsages: ["sign"], publicUsages: ["verify"]},
     51        {algorithm: {name: "ECDH", namedCurve: "P-256"}, privateUsages: ["deriveBits"], publicUsages: []},
     52        {algorithm: {name: "Ed25519" }, privateUsages: ["sign"], publicUsages: ["verify"]},
     53        {algorithm: {name: "Ed448" }, privateUsages: ["sign"], publicUsages: ["verify"]},
     54        {algorithm: {name: "X25519" }, privateUsages: ["deriveBits"], publicUsages: []},
     55        {algorithm: {name: "X448" }, privateUsages: ["deriveBits"], publicUsages: []},
     56        {algorithm: {name: "AES-CTR", length: 128}, usages: ["encrypt", "decrypt"]},
     57        {algorithm: {name: "AES-CBC", length: 128}, usages: ["encrypt", "decrypt"]},
     58        {algorithm: {name: "AES-GCM", length: 128}, usages: ["encrypt", "decrypt"]},
     59        {algorithm: {name: "AES-KW", length: 128}, usages: ["wrapKey", "unwrapKey"]},
     60        {algorithm: {name: "HMAC", length: 128, hash: "SHA-256"}, usages: ["sign", "verify"]}
     61    ];
     62 
     63    // Import all the keys needed, then iterate over all combinations
     64    // to test wrapping and unwrapping.
     65    promise_test(function() {
     66    return Promise.all([importWrappingKeys(), importKeysToWrap()])
     67    .then(function(results) {
     68        wrappingKeysParameters.filter((param) => Object.keys(wrappers).includes(param.name)).forEach(function(wrapperParam) {
     69            var wrapper = wrappers[wrapperParam.name];
     70            keysToWrapParameters.filter((param) => Object.keys(keys).includes(param.algorithm.name)).forEach(function(toWrapParam) {
     71                var keyData = keys[toWrapParam.algorithm.name];
     72                ["raw", "spki", "pkcs8"].filter((fmt) => Object.keys(keyData).includes(fmt)).forEach(function(keyDataFormat) {
     73                    var toWrap = keyData[keyDataFormat];
     74                    [keyDataFormat, "jwk"].forEach(function(format) {
     75                        if (wrappingIsPossible(toWrap.originalExport[format], wrapper.parameters.name)) {
     76                            testWrapping(wrapper, toWrap, format);
     77                            if (canCompareNonExtractableKeys(toWrap.key)) {
     78                                testWrappingNonExtractable(wrapper, toWrap, format);
     79                                if (format === "jwk") {
     80                                    testWrappingNonExtractableAsExtractable(wrapper, toWrap);
     81                                }
     82                            }
     83                        }
     84                    });
     85                });
     86            });
     87        });
     88        return Promise.resolve("setup done");
     89    }, function(err) {
     90        return Promise.reject("setup failed: " + err.name + ': "' + err.message + '"');
     91    });
     92    }, "setup");
     93 
     94    function importWrappingKeys() {
     95        // Using allSettled to skip unsupported test cases.
     96        var promises = [];
     97        wrappingKeysParameters.forEach(function(params) {
     98            if (params.name === "RSA-OAEP") { // we have a key pair, not just a key
     99                var algorithm = {name: "RSA-OAEP", hash: "SHA-256"};
    100                wrappers[params.name] = {wrappingKey: undefined, unwrappingKey: undefined, parameters: params};
    101                promises.push(subtle.importKey("spki", wrappingKeyData["RSA"].spki, algorithm, true, ["wrapKey"])
    102                              .then(function(key) {
    103                                  wrappers["RSA-OAEP"].wrappingKey = key;
    104                              }));
    105                promises.push(subtle.importKey("pkcs8", wrappingKeyData["RSA"].pkcs8, algorithm, true, ["unwrapKey"])
    106                              .then(function(key) {
    107                                  wrappers["RSA-OAEP"].unwrappingKey = key;
    108                              }));
    109            } else {
    110                var algorithm = {name: params.name};
    111                promises.push(subtle.importKey("raw", wrappingKeyData["SYMMETRIC"].raw, algorithm, true, ["wrapKey", "unwrapKey"])
    112                              .then(function(key) {
    113                                  wrappers[params.name] = {wrappingKey: key, unwrappingKey: key, parameters: params};
    114                              }));
    115            }
    116        });
    117        // Using allSettled to skip unsupported test cases.
    118        return Promise.allSettled(promises);
    119    }
    120 
    121    async function importAndExport(format, keyData, algorithm, keyUsages, keyType) {
    122        var importedKey;
    123        try {
    124            importedKey = await subtle.importKey(format, keyData, algorithm, true, keyUsages);
    125            keys[algorithm.name][format] = { name: algorithm.name + " " + keyType, algorithm: algorithm, usages: keyUsages, key: importedKey, originalExport: {} };
    126        } catch (err) {
    127            delete keys[algorithm.name][format];
    128            throw("Error importing " + algorithm.name + " " + keyType + " key in '" + format + "' -" + err.name + ': "' + err.message + '"');
    129        };
    130        try {
    131            var exportedKey = await subtle.exportKey(format, importedKey);
    132            keys[algorithm.name][format].originalExport[format] = exportedKey;
    133        } catch (err) {
    134            delete keys[algorithm.name][format];
    135            throw("Error exporting " + algorithm.name + " '" + format + "' key -" + err.name + ': "' + err.message + '"');
    136        };
    137        try {
    138            var jwkExportedKey = await subtle.exportKey("jwk", importedKey);
    139            keys[algorithm.name][format].originalExport["jwk"] = jwkExportedKey;
    140        } catch (err) {
    141            delete keys[algorithm.name][format];
    142            throw("Error exporting " + algorithm.name + " '" + format + "' key to 'jwk' -" + err.name + ': "' + err.message + '"');
    143        };
    144    }
    145 
    146    function importKeysToWrap() {
    147        var promises = [];
    148        keysToWrapParameters.forEach(function(params) {
    149            if ("publicUsages" in params) {
    150                keys[params.algorithm.name] = {};
    151                var keyData = toWrapKeyDataFromAlg(params.algorithm.name);
    152                promises.push(importAndExport("spki", keyData.spki, params.algorithm, params.publicUsages, "public key "));
    153                promises.push(importAndExport("pkcs8", keyData.pkcs8, params.algorithm, params.privateUsages, "private key "));
    154            } else {
    155                keys[params.algorithm.name] = {};
    156                promises.push(importAndExport("raw", toWrapKeyData["SYMMETRIC"].raw, params.algorithm, params.usages, ""));
    157            }
    158        });
    159        // Using allSettled to skip unsupported test cases.
    160        return Promise.allSettled(promises);
    161    }
    162 
    163    // Can we successfully "round-trip" (wrap, then unwrap, a key)?
    164    function testWrapping(wrapper, toWrap, fmt) {
    165        promise_test(async() => {
    166            try {
    167                var wrappedResult = await subtle.wrapKey(fmt, toWrap.key, wrapper.wrappingKey, wrapper.parameters.wrapParameters);
    168                var unwrappedResult = await subtle.unwrapKey(fmt, wrappedResult, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, true, toWrap.usages);
    169                assert_goodCryptoKey(unwrappedResult, toWrap.algorithm, true, toWrap.usages, toWrap.key.type);
    170                var roundTripExport = await subtle.exportKey(fmt, unwrappedResult);
    171                assert_true(equalExport(toWrap.originalExport[fmt], roundTripExport), "Post-wrap export matches original export");
    172            } catch (err) {
    173                if (err instanceof AssertionError) {
    174                    throw err;
    175                }
    176                assert_unreached("Round trip for extractable key threw an error - " + err.name + ': "' + err.message + '"');
    177            }
    178        }, "Can wrap and unwrap " + toWrap.name + "keys using " + fmt + " and " + wrapper.parameters.name);
    179    }
    180 
    181    function testWrappingNonExtractable(wrapper, toWrap, fmt) {
    182        promise_test(async() => {
    183            try {
    184                var wrappedResult = await subtle.wrapKey(fmt, toWrap.key, wrapper.wrappingKey, wrapper.parameters.wrapParameters);
    185                var unwrappedResult = await subtle.unwrapKey(fmt, wrappedResult, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, false, toWrap.usages);
    186                assert_goodCryptoKey(unwrappedResult, toWrap.algorithm, false, toWrap.usages, toWrap.key.type);
    187                var result = await equalKeys(toWrap.key, unwrappedResult);
    188                assert_true(result, "Unwrapped key matches original");
    189            } catch (err) {
    190                if (err instanceof AssertionError) {
    191                    throw err;
    192                }
    193                assert_unreached("Round trip for key unwrapped non-extractable threw an error - " + err.name + ': "' + err.message + '"');
    194            };
    195        }, "Can wrap and unwrap " + toWrap.name + "keys as non-extractable using " + fmt + " and " + wrapper.parameters.name);
    196    }
    197 
    198    function testWrappingNonExtractableAsExtractable(wrapper, toWrap) {
    199        promise_test(async() => {
    200            var wrappedKey;
    201            try {
    202                var wrappedResult = await wrapAsNonExtractableJwk(toWrap.key,wrapper);
    203                wrappedKey = wrappedResult;
    204                var unwrappedResult = await subtle.unwrapKey("jwk", wrappedKey, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, false, toWrap.usages);
    205                assert_false(unwrappedResult.extractable, "Unwrapped key is non-extractable");
    206                var result = await equalKeys(toWrap.key,unwrappedResult);
    207                assert_true(result, "Unwrapped key matches original");
    208            } catch (err) {
    209                if (err instanceof AssertionError) {
    210                    throw err;
    211                }
    212                assert_unreached("Round trip for non-extractable key threw an error - " + err.name + ': "' + err.message + '"');
    213            };
    214            try {
    215                var unwrappedResult = await subtle.unwrapKey("jwk", wrappedKey, wrapper.unwrappingKey, wrapper.parameters.wrapParameters, toWrap.algorithm, true, toWrap.usages);
    216                assert_unreached("Unwrapping a non-extractable JWK as extractable should fail");
    217            } catch (err) {
    218                if (err instanceof AssertionError) {
    219                    throw err;
    220                }
    221                assert_equals(err.name, "DataError", "Unwrapping a non-extractable JWK as extractable fails with DataError");
    222            }
    223        }, "Can unwrap " + toWrap.name + "non-extractable keys using jwk and " + wrapper.parameters.name);
    224    }
    225 
    226    // Implement key wrapping by hand to wrap a key as non-extractable JWK
    227    async function wrapAsNonExtractableJwk(key, wrapper) {
    228        var wrappingKey = wrapper.wrappingKey,
    229            encryptKey;
    230 
    231        var jwkWrappingKey = await subtle.exportKey("jwk",wrappingKey);
    232        // Update the key generation parameters to work as key import parameters
    233        var params = Object.create(wrapper.parameters.importParameters);
    234        if(params.name === "AES-KW") {
    235            params.name = "AES-CBC";
    236            jwkWrappingKey.alg = "A"+params.length+"CBC";
    237        } else if (params.name === "RSA-OAEP") {
    238            params.modulusLength = undefined;
    239            params.publicExponent = undefined;
    240        }
    241        jwkWrappingKey.key_ops = ["encrypt"];
    242        var importedWrappingKey = await subtle.importKey("jwk", jwkWrappingKey, params, true, ["encrypt"]);
    243        encryptKey = importedWrappingKey;
    244        var exportedKey = await subtle.exportKey("jwk",key);
    245        exportedKey.ext = false;
    246        var jwk = JSON.stringify(exportedKey)
    247        var result;
    248        if (wrappingKey.algorithm.name === "AES-KW") {
    249            result = await aeskw(encryptKey, str2ab(jwk.slice(0,-1) + " ".repeat(jwk.length%8 ? 8-jwk.length%8 : 0) + "}"));
    250        } else {
    251            result = await subtle.encrypt(wrapper.parameters.wrapParameters,encryptKey,str2ab(jwk));
    252        }
    253        return result;
    254    }
    255 
    256 
    257    // RSA-OAEP can only wrap relatively small payloads. AES-KW can only
    258    // wrap payloads a multiple of 8 bytes long.
    259    function wrappingIsPossible(exportedKey, algorithmName) {
    260        if ("byteLength" in exportedKey && algorithmName === "AES-KW") {
    261            return exportedKey.byteLength % 8 === 0;
    262        }
    263 
    264        if ("byteLength" in exportedKey && algorithmName === "RSA-OAEP") {
    265            // RSA-OAEP can only encrypt payloads with lengths shorter
    266            // than modulusLength - 2*hashLength - 1 bytes long. For
    267            // a 4096 bit modulus and SHA-256, that comes to
    268            // 4096/8 - 2*(256/8) - 1 = 512 - 2*32 - 1 = 447 bytes.
    269            return exportedKey.byteLength <= 446;
    270        }
    271 
    272        if ("kty" in exportedKey && algorithmName === "AES-KW") {
    273            return JSON.stringify(exportedKey).length % 8 == 0;
    274        }
    275 
    276        if ("kty" in exportedKey && algorithmName === "RSA-OAEP") {
    277            return JSON.stringify(exportedKey).length <= 478;
    278        }
    279 
    280        return true;
    281    }
    282 
    283 
    284    // Helper methods follow:
    285 
    286    // Are two exported keys equal
    287    function equalExport(originalExport, roundTripExport) {
    288        if ("byteLength" in originalExport) {
    289            return equalBuffers(originalExport, roundTripExport);
    290        } else {
    291            return equalJwk(originalExport, roundTripExport);
    292        }
    293    }
    294 
    295    // Are two array buffers the same?
    296    function equalBuffers(a, b) {
    297        if (a.byteLength !== b.byteLength) {
    298            return false;
    299        }
    300 
    301        var aBytes = new Uint8Array(a);
    302        var bBytes = new Uint8Array(b);
    303 
    304        for (var i=0; i<a.byteLength; i++) {
    305            if (aBytes[i] !== bBytes[i]) {
    306                return false;
    307            }
    308        }
    309 
    310        return true;
    311    }
    312 
    313    // Are two Jwk objects "the same"? That is, does the object returned include
    314    // matching values for each property that was expected? It's okay if the
    315    // returned object has extra methods; they aren't checked.
    316    function equalJwk(expected, got) {
    317        var fields = Object.keys(expected);
    318        var fieldName;
    319 
    320        for(var i=0; i<fields.length; i++) {
    321            fieldName = fields[i];
    322            if (!(fieldName in got)) {
    323                return false;
    324            }
    325            if (objectToString(expected[fieldName]) !== objectToString(got[fieldName])) {
    326                return false;
    327            }
    328        }
    329 
    330        return true;
    331    }
    332 
    333    // Character representation of any object we may use as a parameter.
    334    function objectToString(obj) {
    335        var keyValuePairs = [];
    336 
    337        if (Array.isArray(obj)) {
    338            return "[" + obj.map(function(elem){return objectToString(elem);}).join(", ") + "]";
    339        } else if (typeof obj === "object") {
    340            Object.keys(obj).sort().forEach(function(keyName) {
    341                keyValuePairs.push(keyName + ": " + objectToString(obj[keyName]));
    342            });
    343            return "{" + keyValuePairs.join(", ") + "}";
    344        } else if (typeof obj === "undefined") {
    345            return "undefined";
    346        } else {
    347            return obj.toString();
    348        }
    349 
    350        var keyValuePairs = [];
    351 
    352        Object.keys(obj).sort().forEach(function(keyName) {
    353            var value = obj[keyName];
    354            if (typeof value === "object") {
    355                value = objectToString(value);
    356            } else if (typeof value === "array") {
    357                value = "[" + value.map(function(elem){return objectToString(elem);}).join(", ") + "]";
    358            } else {
    359                value = value.toString();
    360            }
    361 
    362            keyValuePairs.push(keyName + ": " + value);
    363        });
    364 
    365        return "{" + keyValuePairs.join(", ") + "}";
    366    }
    367 
    368    // Can we compare key values by using them
    369    function canCompareNonExtractableKeys(key){
    370        if (key.usages.indexOf("decrypt") !== -1) {
    371            return true;
    372        }
    373        if (key.usages.indexOf("sign") !== -1) {
    374            return true;
    375        }
    376        if (key.usages.indexOf("wrapKey") !== -1) {
    377            return true;
    378        }
    379        if (key.usages.indexOf("deriveBits") !== -1) {
    380            return true;
    381        }
    382        return false;
    383    }
    384 
    385    // Compare two keys by using them (works for non-extractable keys)
    386    async function equalKeys(expected, got){
    387        if ( expected.algorithm.name !== got.algorithm.name ) {
    388            return false;
    389        }
    390 
    391        var cryptParams, signParams, wrapParams, deriveParams;
    392        switch(expected.algorithm.name){
    393            case "AES-CTR" :
    394                cryptParams = {name: "AES-CTR", counter: new Uint8Array(16), length: 64};
    395                break;
    396            case "AES-CBC" :
    397                cryptParams = {name: "AES-CBC", iv: new Uint8Array(16) };
    398                break;
    399            case "AES-GCM" :
    400                cryptParams = {name: "AES-GCM", iv: new Uint8Array(16) };
    401                break;
    402            case "RSA-OAEP" :
    403                cryptParams = {name: "RSA-OAEP", label: new Uint8Array(8) };
    404                break;
    405            case "RSASSA-PKCS1-v1_5" :
    406                signParams = {name: "RSASSA-PKCS1-v1_5"};
    407                break;
    408            case "RSA-PSS" :
    409                signParams = {name: "RSA-PSS", saltLength: 32 };
    410                break;
    411            case "ECDSA" :
    412                signParams = {name: "ECDSA", hash: "SHA-256"};
    413                break;
    414            case "Ed25519" :
    415                signParams = {name: "Ed25519"};
    416                break;
    417            case "Ed448" :
    418                signParams = {name: "Ed448"};
    419                break;
    420            case "X25519" :
    421                deriveParams = {name: "X25519"};
    422                break;
    423            case "X448" :
    424                deriveParams = {name: "X448"};
    425                break;
    426            case "HMAC" :
    427                signParams = {name: "HMAC"};
    428                break;
    429            case "AES-KW" :
    430                wrapParams = {name: "AES-KW"};
    431                break;
    432            case "ECDH" :
    433                deriveParams = {name: "ECDH"};
    434                break;
    435            default:
    436                throw new Error("Unsupported algorithm for key comparison");
    437        }
    438 
    439        if (cryptParams) {
    440            var jwkExpectedKey = await subtle.exportKey("jwk", expected);
    441            if (expected.algorithm.name === "RSA-OAEP") {
    442                ["d","p","q","dp","dq","qi","oth"].forEach(function(field){ delete jwkExpectedKey[field]; });
    443            }
    444            jwkExpectedKey.key_ops = ["encrypt"];
    445            var expectedEncryptKey = await subtle.importKey("jwk", jwkExpectedKey, expected.algorithm, true, ["encrypt"]);
    446            var encryptedData = await subtle.encrypt(cryptParams, expectedEncryptKey, new Uint8Array(32));
    447            var decryptedData = await subtle.decrypt(cryptParams, got, encryptedData);
    448            var result = new Uint8Array(decryptedData);
    449            return !result.some(x => x);
    450        } else if (signParams) {
    451            var verifyKey;
    452            var jwkExpectedKey = await subtle.exportKey("jwk",expected);
    453            if (expected.algorithm.name === "RSA-PSS" || expected.algorithm.name === "RSASSA-PKCS1-v1_5") {
    454                ["d","p","q","dp","dq","qi","oth"].forEach(function(field){ delete jwkExpectedKey[field]; });
    455            }
    456            if (expected.algorithm.name === "ECDSA" || expected.algorithm.name.startsWith("Ed")) {
    457                delete jwkExpectedKey["d"];
    458            }
    459            jwkExpectedKey.key_ops = ["verify"];
    460            var expectedVerifyKey = await subtle.importKey("jwk", jwkExpectedKey, expected.algorithm, true, ["verify"]);
    461            verifyKey = expectedVerifyKey;
    462            var signature = await subtle.sign(signParams, got, new Uint8Array(32));
    463            var result = await subtle.verify(signParams, verifyKey, signature, new Uint8Array(32));
    464            return result;
    465        } else if (wrapParams) {
    466            var aKeyToWrap, wrappedWithExpected;
    467            var key = await subtle.importKey("raw",new Uint8Array(16), "AES-CBC", true, ["encrypt"])
    468            aKeyToWrap = key;
    469            var wrapResult = await subtle.wrapKey("raw", aKeyToWrap, expected, wrapParams);
    470            wrappedWithExpected = Array.from((new Uint8Array(wrapResult)).values());
    471            wrapResult = await subtle.wrapKey("raw", aKeyToWrap, got, wrapParams);
    472            var wrappedWithGot = Array.from((new Uint8Array(wrapResult)).values());
    473            return wrappedWithGot.every((x,i) => x === wrappedWithExpected[i]);
    474        } else if (deriveParams) {
    475            var expectedDerivedBits;
    476            var key = await subtle.generateKey(expected.algorithm, true, ['deriveBits']);
    477            deriveParams.public = key.publicKey;
    478            var result = await subtle.deriveBits(deriveParams, expected, 128);
    479            expectedDerivedBits = Array.from((new Uint8Array(result)).values());
    480            result = await subtle.deriveBits(deriveParams, got, 128);
    481            var gotDerivedBits = Array.from((new Uint8Array(result)).values());
    482            return gotDerivedBits.every((x,i) => x === expectedDerivedBits[i]);
    483        }
    484    }
    485 
    486    // Raw AES encryption
    487    async function aes(k, p) {
    488        const ciphertext = await subtle.encrypt({ name: "AES-CBC", iv: new Uint8Array(16) }, k, p);
    489        return ciphertext.slice(0, 16);
    490    }
    491 
    492    // AES Key Wrap
    493    async function aeskw(key, data) {
    494        if (data.byteLength % 8 !== 0) {
    495            throw new Error("AES Key Wrap data must be a multiple of 8 bytes in length");
    496        }
    497 
    498        var A = Uint8Array.from([0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0, 0, 0, 0, 0, 0, 0, 0]),
    499            Av = new DataView(A.buffer),
    500            R = [],
    501            n = data.byteLength / 8;
    502 
    503        for(var i = 0; i<data.byteLength; i+=8) {
    504            R.push(new Uint8Array(data.slice(i,i+8)));
    505        }
    506 
    507        async function aeskw_step(j, i, final, B) {
    508            A.set(new Uint8Array(B.slice(0,8)));
    509            Av.setUint32(4,Av.getUint32(4) ^ (n*j+i+1));
    510            R[i] = new Uint8Array(B.slice(8,16));
    511            if (final) {
    512                R.unshift(A.slice(0,8));
    513                var result = new Uint8Array(R.length * 8);
    514                R.forEach(function(Ri,i){ result.set(Ri, i*8); });
    515                return result;
    516            } else {
    517                A.set(R[(i+1)%n],8);
    518                return aes(key,A);
    519            }
    520        }
    521 
    522        A.set(R[0], 8);
    523        let B = await aes(key, A);
    524 
    525        for(var j=0;j<6;++j) {
    526            for(var i=0;i<n;++i) {
    527                B = await aeskw_step(j, i, j === 5 && i === (n - 1), B);
    528            }
    529        }
    530 
    531        return B;
    532    }
    533 
    534    function str2ab(str)        { return Uint8Array.from( str.split(''), function(s){return s.charCodeAt(0)} ); }
    535    function ab2str(ab)         { return String.fromCharCode.apply(null, new Uint8Array(ab)); }