tor-browser

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

hkdf.js (17220B)


      1 function define_tests() {
      2    // May want to test prefixed implementations.
      3    var subtle = self.crypto.subtle;
      4 
      5    // hkdf2_vectors sets up test data with the correct derivations for each
      6    // test case.
      7    var testData = getTestData();
      8    var derivedKeys = testData.derivedKeys;
      9    var salts = testData.salts;
     10    var derivations = testData.derivations;
     11    var infos = testData.infos;
     12 
     13    // What kinds of keys can be created with deriveKey? The following:
     14    var derivedKeyTypes = testData.derivedKeyTypes;
     15 
     16    return setUpBaseKeys(derivedKeys)
     17    .then(function(allKeys) {
     18        // We get several kinds of base keys. Normal ones that can be used for
     19        // derivation operations, ones that lack the deriveBits usage, ones
     20        // that lack the deriveKeys usage, and one key that is for the wrong
     21        // algorithm (not HKDF in this case).
     22        var baseKeys = allKeys.baseKeys;
     23        var noBits = allKeys.noBits;
     24        var noKey = allKeys.noKey;
     25        var wrongKey = allKeys.wrongKey;
     26 
     27        // Test each combination of derivedKey size, salt size, hash function,
     28        // and number of iterations. The derivations object is structured in
     29        // that way, so navigate it to run tests and compare with correct results.
     30        Object.keys(derivations).forEach(function(derivedKeySize) {
     31            Object.keys(derivations[derivedKeySize]).forEach(function(saltSize) {
     32                Object.keys(derivations[derivedKeySize][saltSize]).forEach(function(hashName) {
     33                    Object.keys(derivations[derivedKeySize][saltSize][hashName]).forEach(function(infoSize) {
     34                        var testName = derivedKeySize + " derivedKey, " + saltSize + " salt, " + hashName + ", with " + infoSize + " info";
     35                        var algorithm = {name: "HKDF", salt: salts[saltSize], info: infos[infoSize], hash: hashName};
     36 
     37                        // Check for correct deriveBits result
     38                        subsetTest(promise_test, function(test) {
     39                            return subtle.deriveBits(algorithm, baseKeys[derivedKeySize], 256)
     40                            .then(function(derivation) {
     41                                assert_true(equalBuffers(derivation, derivations[derivedKeySize][saltSize][hashName][infoSize]), "Derived correct key");
     42                            }, function(err) {
     43                                assert_unreached("deriveBits failed with error " + err.name + ": " + err.message);
     44                            });
     45                        }, testName);
     46 
     47                        // 0 length
     48                        subsetTest(promise_test, function(test) {
     49                            return subtle.deriveBits(algorithm, baseKeys[derivedKeySize], 0)
     50                            .then(function(derivation) {
     51                                assert_equals(derivation.byteLength, 0, "Derived correctly empty key");
     52                            }, function(err) {
     53                                assert_unreached("deriveBits failed with error " + err.name + ": " + err.message);
     54                            });
     55                        }, testName + " with 0 length");
     56 
     57                        // Check for correct deriveKey results for every kind of
     58                        // key that can be created by the deriveKeys operation.
     59                        derivedKeyTypes.forEach(function(derivedKeyType) {
     60                            var testName = "Derived key of type ";
     61                            Object.keys(derivedKeyType.algorithm).forEach(function(prop) {
     62                                testName += prop + ": " + derivedKeyType.algorithm[prop] + " ";
     63                            });
     64                            testName += " using " + derivedKeySize + " derivedKey, " + saltSize + " salt, " + hashName + ", with " + infoSize + " info";
     65 
     66                            // Test the particular key derivation.
     67                            subsetTest(promise_test, function(test) {
     68                                return subtle.deriveKey(algorithm, baseKeys[derivedKeySize], derivedKeyType.algorithm, true, derivedKeyType.usages)
     69                                .then(function(key) {
     70                                    // Need to export the key to see that the correct bits were set.
     71                                    return subtle.exportKey("raw", key)
     72                                    .then(function(buffer) {
     73                                        assert_true(equalBuffers(buffer, derivations[derivedKeySize][saltSize][hashName][infoSize].slice(0, derivedKeyType.algorithm.length/8)), "Exported key matches correct value");
     74                                    }, function(err) {
     75                                        assert_unreached("Exporting derived key failed with error " + err.name + ": " + err.message);
     76                                    });
     77                                }, function(err) {
     78                                    assert_unreached("deriveKey failed with error " + err.name + ": " + err.message);
     79 
     80                                });
     81                            }, testName);
     82 
     83                            // Test various error conditions for deriveKey:
     84 
     85                            // - illegal name for hash algorithm (NotSupportedError)
     86                            var badHash = hashName.substring(0, 3) + hashName.substring(4);
     87                            subsetTest(promise_test, function(test) {
     88                                var badAlgorithm = {name: "HKDF", salt: salts[saltSize], hash: badHash, info: algorithm.info};
     89                                return subtle.deriveKey(badAlgorithm, baseKeys[derivedKeySize], derivedKeyType.algorithm, true, derivedKeyType.usages)
     90                                .then(function(key) {
     91                                    assert_unreached("bad hash name should have thrown an NotSupportedError");
     92                                }, function(err) {
     93                                    assert_equals(err.name, "NotSupportedError", "deriveKey with bad hash name correctly threw NotSupportedError: " + err.message);
     94                                });
     95                            }, testName + " with bad hash name " + badHash);
     96 
     97                            // - baseKey usages missing "deriveKey" (InvalidAccessError)
     98                            subsetTest(promise_test, function(test) {
     99                                return subtle.deriveKey(algorithm, noKey[derivedKeySize], derivedKeyType.algorithm, true, derivedKeyType.usages)
    100                                .then(function(key) {
    101                                    assert_unreached("missing deriveKey usage should have thrown an InvalidAccessError");
    102                                }, function(err) {
    103                                    assert_equals(err.name, "InvalidAccessError", "deriveKey with missing deriveKey usage correctly threw InvalidAccessError: " + err.message);
    104                                });
    105                            }, testName + " with missing deriveKey usage");
    106 
    107                            // - baseKey algorithm does not match HKDF (InvalidAccessError)
    108                            subsetTest(promise_test, function(test) {
    109                                return subtle.deriveKey(algorithm, wrongKey, derivedKeyType.algorithm, true, derivedKeyType.usages)
    110                                .then(function(key) {
    111                                    assert_unreached("wrong (ECDH) key should have thrown an InvalidAccessError");
    112                                }, function(err) {
    113                                    assert_equals(err.name, "InvalidAccessError", "deriveKey with wrong (ECDH) key correctly threw InvalidAccessError: " + err.message);
    114                                });
    115                            }, testName + " with wrong (ECDH) key");
    116 
    117                        });
    118 
    119                        // Test various error conditions for deriveBits below:
    120 
    121                        // missing salt (TypeError)
    122                        subsetTest(promise_test, function(test) {
    123                            return subtle.deriveBits({name: "HKDF", info: infos[infoSize], hash: hashName}, baseKeys[derivedKeySize], 0)
    124                            .then(function(derivation) {
    125                                assert_equals(derivation.byteLength, 0, "Derived even with missing salt");
    126                            }, function(err) {
    127                                assert_equals(err.name, "TypeError", "deriveBits missing salt correctly threw OperationError: " + err.message);
    128                            });
    129                        }, testName + " with missing salt");
    130 
    131                        // missing info (TypeError)
    132                        subsetTest(promise_test, function(test) {
    133                            return subtle.deriveBits({name: "HKDF", salt: salts[saltSize], hash: hashName}, baseKeys[derivedKeySize], 0)
    134                            .then(function(derivation) {
    135                                assert_equals(derivation.byteLength, 0, "Derived even with missing info");
    136                            }, function(err) {
    137                                assert_equals(err.name, "TypeError", "deriveBits missing info correctly threw OperationError: " + err.message);
    138                            });
    139                        }, testName + " with missing info");
    140 
    141                        // length not multiple of 8 (OperationError)
    142                        subsetTest(promise_test, function(test) {
    143                            return subtle.deriveBits(algorithm, baseKeys[derivedKeySize], 44)
    144                            .then(function(derivation) {
    145                                assert_unreached("non-multiple of 8 length should have thrown an OperationError");
    146                            }, function(err) {
    147                                assert_equals(err.name, "OperationError", "deriveBits with non-multiple of 8 length correctly threw OperationError: " + err.message);
    148                            });
    149                        }, testName + " with non-multiple of 8 length");
    150 
    151                        // - illegal name for hash algorithm (NotSupportedError)
    152                        var badHash = hashName.substring(0, 3) + hashName.substring(4);
    153                        subsetTest(promise_test, function(test) {
    154                            var badAlgorithm = {name: "HKDF", salt: salts[saltSize], hash: badHash, info: algorithm.info};
    155                            return subtle.deriveBits(badAlgorithm, baseKeys[derivedKeySize], 256)
    156                            .then(function(derivation) {
    157                                assert_unreached("bad hash name should have thrown an NotSupportedError");
    158                            }, function(err) {
    159                                assert_equals(err.name, "NotSupportedError", "deriveBits with bad hash name correctly threw NotSupportedError: " + err.message);
    160                            });
    161                        }, testName + " with bad hash name " + badHash);
    162 
    163                        // - baseKey usages missing "deriveBits" (InvalidAccessError)
    164                        subsetTest(promise_test, function(test) {
    165                            return subtle.deriveBits(algorithm, noBits[derivedKeySize], 256)
    166                            .then(function(derivation) {
    167                                assert_unreached("missing deriveBits usage should have thrown an InvalidAccessError");
    168                            }, function(err) {
    169                                assert_equals(err.name, "InvalidAccessError", "deriveBits with missing deriveBits usage correctly threw InvalidAccessError: " + err.message);
    170                            });
    171                        }, testName + " with missing deriveBits usage");
    172 
    173                        // - baseKey algorithm does not match HKDF (InvalidAccessError)
    174                        subsetTest(promise_test, function(test) {
    175                            return subtle.deriveBits(algorithm, wrongKey, 256)
    176                            .then(function(derivation) {
    177                                assert_unreached("wrong (ECDH) key should have thrown an InvalidAccessError");
    178                            }, function(err) {
    179                                assert_equals(err.name, "InvalidAccessError", "deriveBits with wrong (ECDH) key correctly threw InvalidAccessError: " + err.message);
    180                            });
    181                        }, testName + " with wrong (ECDH) key");
    182                    });
    183                });
    184 
    185                // - legal algorithm name but not digest one (e.g., PBKDF2) (NotSupportedError)
    186                var nonDigestHash = "PBKDF2";
    187                Object.keys(infos).forEach(function(infoSize) {
    188                    var testName = derivedKeySize + " derivedKey, " + saltSize + " salt, " + nonDigestHash + ", with " + infoSize + " info";
    189                    var algorithm = {name: "HKDF", salt: salts[saltSize], hash: nonDigestHash};
    190                    if (infoSize !== "missing") {
    191                        algorithm.info = infos[infoSize];
    192                    }
    193 
    194                    subsetTest(promise_test, function(test) {
    195                        return subtle.deriveBits(algorithm, baseKeys[derivedKeySize], 256)
    196                        .then(function(derivation) {
    197                            assert_unreached("non-digest algorithm should have thrown an NotSupportedError");
    198                        }, function(err) {
    199                            assert_equals(err.name, "NotSupportedError", "deriveBits with non-digest algorithm correctly threw NotSupportedError: " + err.message);
    200                        });
    201                    }, testName + " with non-digest algorithm " + nonDigestHash);
    202 
    203                    derivedKeyTypes.forEach(function(derivedKeyType) {
    204                        var testName = "Derived key of type ";
    205                        Object.keys(derivedKeyType.algorithm).forEach(function(prop) {
    206                            testName += prop + ": " + derivedKeyType.algorithm[prop] + " ";
    207                        });
    208                        testName += " using " + derivedKeySize + " derivedKey, " + saltSize + " salt, " + nonDigestHash + ", with " + infoSize + " info";
    209 
    210                        subsetTest(promise_test, function(test) {
    211                            return subtle.deriveKey(algorithm, baseKeys[derivedKeySize], derivedKeyType.algorithm, true, derivedKeyType.usages)
    212                            .then(function(derivation) {
    213                                assert_unreached("non-digest algorithm should have thrown an NotSupportedError");
    214                            }, function(err) {
    215                                assert_equals(err.name, "NotSupportedError", "derivekey with non-digest algorithm correctly threw NotSupportedError: " + err.message);
    216                            });
    217                        }, testName);
    218                    });
    219 
    220                });
    221 
    222            });
    223        });
    224    });
    225 
    226    // Deriving bits and keys requires starting with a base key, which is created
    227    // by importing a derivedKey. setUpBaseKeys returns a promise that yields the
    228    // necessary base keys.
    229    function setUpBaseKeys(derivedKeys) {
    230        var promises = [];
    231 
    232        var baseKeys = {};
    233        var noBits = {};
    234        var noKey = {};
    235        var wrongKey = null;
    236 
    237        Object.keys(derivedKeys).forEach(function(derivedKeySize) {
    238            var promise = subtle.importKey("raw", derivedKeys[derivedKeySize], {name: "HKDF"}, false, ["deriveKey", "deriveBits"])
    239            .then(function(baseKey) {
    240                baseKeys[derivedKeySize] = baseKey;
    241            }, function(err) {
    242                baseKeys[derivedKeySize] = null;
    243            });
    244            promises.push(promise);
    245 
    246            promise = subtle.importKey("raw", derivedKeys[derivedKeySize], {name: "HKDF"}, false, ["deriveBits"])
    247            .then(function(baseKey) {
    248                noKey[derivedKeySize] = baseKey;
    249            }, function(err) {
    250                noKey[derivedKeySize] = null;
    251            });
    252            promises.push(promise);
    253 
    254            promise = subtle.importKey("raw", derivedKeys[derivedKeySize], {name: "HKDF"}, false, ["deriveKey"])
    255            .then(function(baseKey) {
    256                noBits[derivedKeySize] = baseKey;
    257            }, function(err) {
    258                noBits[derivedKeySize] = null;
    259            });
    260            promises.push(promise);
    261        });
    262 
    263        var promise = subtle.generateKey({name: "ECDH", namedCurve: "P-256"}, false, ["deriveKey", "deriveBits"])
    264        .then(function(baseKey) {
    265            wrongKey = baseKey.privateKey;
    266        }, function(err) {
    267            wrongKey = null;
    268        });
    269        promises.push(promise);
    270 
    271 
    272        return Promise.all(promises).then(function() {
    273            return {baseKeys: baseKeys, noBits: noBits, noKey: noKey, wrongKey: wrongKey};
    274        });
    275    }
    276 
    277    function equalBuffers(a, b) {
    278        if (a.byteLength !== b.byteLength) {
    279            return false;
    280        }
    281 
    282        var aBytes = new Uint8Array(a);
    283        var bBytes = new Uint8Array(b);
    284 
    285        for (var i=0; i<a.byteLength; i++) {
    286            if (aBytes[i] !== bBytes[i]) {
    287                return false;
    288            }
    289        }
    290 
    291        return true;
    292    }
    293 
    294 }