tor-browser

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

pbkdf2.js (17992B)


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