tor-browser

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

hmac.js (16177B)


      1 function run_test() {
      2    setup({explicit_done: true});
      3 
      4    var subtle = self.crypto.subtle; // Change to test prefixed implementations
      5 
      6    // When are all these tests really done? When all the promises they use have resolved.
      7    var all_promises = [];
      8 
      9    // Source file hmac_vectors.js provides the getTestVectors method
     10    // for the algorithm that drives these tests.
     11    var testVectors = getTestVectors();
     12 
     13    // Test verification first, because signing tests rely on that working
     14    testVectors.forEach(function(vector) {
     15        var promise = importVectorKeys(vector, ["verify", "sign"])
     16        .then(function(vector) {
     17            promise_test(function(test) {
     18                var operation = subtle.verify({name: "HMAC", hash: vector.hash}, vector.key, vector.signature, vector.plaintext)
     19                .then(function(is_verified) {
     20                    assert_true(is_verified, "Signature verified");
     21                }, function(err) {
     22                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
     23                });
     24 
     25                return operation;
     26            }, vector.name + " verification");
     27 
     28        }, function(err) {
     29            // We need a failed test if the importVectorKey operation fails, so
     30            // we know we never tested verification.
     31            promise_test(function(test) {
     32                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
     33            }, "importVectorKeys step: " + vector.name + " verification");
     34        });
     35 
     36        all_promises.push(promise);
     37    });
     38 
     39    // Test verification with an altered buffer after call
     40    testVectors.forEach(function(vector) {
     41        var promise = importVectorKeys(vector, ["verify", "sign"])
     42        .then(function(vector) {
     43            promise_test(function(test) {
     44                var signature = copyBuffer(vector.signature);
     45                var operation = subtle.verify({name: "HMAC", hash: vector.hash}, vector.key, signature, vector.plaintext)
     46                .then(function(is_verified) {
     47                    assert_true(is_verified, "Signature is not verified");
     48                }, function(err) {
     49                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
     50                });
     51 
     52                signature[0] = 255 - signature[0];
     53                return operation;
     54            }, vector.name + " verification with altered signature after call");
     55        }, function(err) {
     56            promise_test(function(test) {
     57                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
     58            }, "importVectorKeys step: " + vector.name + " verification with altered signature after call");
     59        });
     60 
     61        all_promises.push(promise);
     62    });
     63 
     64    // Check for successful verification even if plaintext is altered after call.
     65    testVectors.forEach(function(vector) {
     66        var promise = importVectorKeys(vector, ["verify", "sign"])
     67        .then(function(vector) {
     68            promise_test(function(test) {
     69                var plaintext = copyBuffer(vector.plaintext);
     70                var operation = subtle.verify({name: "HMAC", hash: vector.hash}, vector.key, vector.signature, plaintext)
     71                .then(function(is_verified) {
     72                    assert_true(is_verified, "Signature verified");
     73                }, function(err) {
     74                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
     75                });
     76 
     77                plaintext[0] = 255 - plaintext[0];
     78                return operation;
     79            }, vector.name + " with altered plaintext after call");
     80        }, function(err) {
     81            promise_test(function(test) {
     82                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
     83            }, "importVectorKeys step: " + vector.name + " with altered plaintext");
     84        });
     85 
     86        all_promises.push(promise);
     87    });
     88 
     89    // Check for failures due to no "verify" usage.
     90    testVectors.forEach(function(originalVector) {
     91        var vector = Object.assign({}, originalVector);
     92 
     93        var promise = importVectorKeys(vector, ["sign"])
     94        .then(function(vector) {
     95            promise_test(function(test) {
     96                return subtle.verify({name: "HMAC", hash: vector.hash}, vector.key, vector.signature, vector.plaintext)
     97                .then(function(plaintext) {
     98                    assert_unreached("Should have thrown error for no verify usage in " + vector.name + ": " + err.message + "'");
     99                }, function(err) {
    100                    assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'");
    101                });
    102            }, vector.name + " no verify usage");
    103        }, function(err) {
    104            promise_test(function(test) {
    105                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    106            }, "importVectorKeys step: " + vector.name + " no verify usage");
    107        });
    108 
    109        all_promises.push(promise);
    110    });
    111 
    112    // Check for successful signing and verification.
    113    testVectors.forEach(function(vector) {
    114        var promise = importVectorKeys(vector, ["verify", "sign"])
    115        .then(function(vectors) {
    116            promise_test(function(test) {
    117                return subtle.sign({name: "HMAC", hash: vector.hash}, vector.key, vector.plaintext)
    118                .then(function(signature) {
    119                    assert_true(equalBuffers(signature, vector.signature), "Signing did not give the expected output");
    120                    // Can we get the verify the new signature?
    121                    return subtle.verify({name: "HMAC", hash: vector.hash}, vector.key, signature, vector.plaintext)
    122                    .then(function(is_verified) {
    123                        assert_true(is_verified, "Round trip verifies");
    124                        return signature;
    125                    }, function(err) {
    126                        assert_unreached("verify error for test " + vector.name + ": " + err.message + "'");
    127                    });
    128                });
    129            }, vector.name + " round trip");
    130 
    131        }, function(err) {
    132            // We need a failed test if the importVectorKey operation fails, so
    133            // we know we never tested signing or verifying
    134            promise_test(function(test) {
    135                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    136            }, "importVectorKeys step: " + vector.name + " round trip");
    137        });
    138 
    139        all_promises.push(promise);
    140    });
    141 
    142    // Test signing with the wrong algorithm
    143    testVectors.forEach(function(vector) {
    144        // Want to get the key for the wrong algorithm
    145        var promise = subtle.generateKey({name: "ECDSA", namedCurve: "P-256", hash: "SHA-256"}, false, ["sign", "verify"])
    146        .then(function(wrongKey) {
    147            return importVectorKeys(vector, ["verify", "sign"])
    148            .then(function(vectors) {
    149                promise_test(function(test) {
    150                    var operation = subtle.sign({name: "HMAC", hash: vector.hash}, wrongKey.privateKey, vector.plaintext)
    151                    .then(function(signature) {
    152                        assert_unreached("Signing should not have succeeded for " + vector.name);
    153                    }, function(err) {
    154                        assert_equals(err.name, "InvalidAccessError", "Should have thrown InvalidAccessError instead of '" + err.message + "'");
    155                    });
    156 
    157                    return operation;
    158                }, vector.name + " signing with wrong algorithm name");
    159 
    160            }, function(err) {
    161                // We need a failed test if the importVectorKey operation fails, so
    162                // we know we never tested verification.
    163                promise_test(function(test) {
    164                    assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    165                }, "importVectorKeys step: " + vector.name + " signing with wrong algorithm name");
    166            });
    167        }, function(err) {
    168            promise_test(function(test) {
    169                assert_unreached("Generate wrong key for test " + vector.name + " failed: '" + err.message + "'");
    170            }, "generate wrong key step: " + vector.name + " signing with wrong algorithm name");
    171        });
    172 
    173        all_promises.push(promise);
    174    });
    175 
    176    // Test verification with the wrong algorithm
    177    testVectors.forEach(function(vector) {
    178        // Want to get the key for the wrong algorithm
    179        var promise = subtle.generateKey({name: "ECDSA", namedCurve: "P-256", hash: "SHA-256"}, false, ["sign", "verify"])
    180        .then(function(wrongKey) {
    181            return importVectorKeys(vector, ["verify", "sign"])
    182            .then(function(vector) {
    183                promise_test(function(test) {
    184                    var operation = subtle.verify({name: "HMAC", hash: vector.hash}, wrongKey.publicKey, vector.signature, vector.plaintext)
    185                    .then(function(signature) {
    186                        assert_unreached("Verifying should not have succeeded for " + vector.name);
    187                    }, function(err) {
    188                        assert_equals(err.name, "InvalidAccessError", "Should have thrown InvalidAccessError instead of '" + err.message + "'");
    189                    });
    190 
    191                    return operation;
    192                }, vector.name + " verifying with wrong algorithm name");
    193 
    194            }, function(err) {
    195                // We need a failed test if the importVectorKey operation fails, so
    196                // we know we never tested verification.
    197                promise_test(function(test) {
    198                    assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    199                }, "importVectorKeys step: " + vector.name + " verifying with wrong algorithm name");
    200            });
    201        }, function(err) {
    202            promise_test(function(test) {
    203                assert_unreached("Generate wrong key for test " + vector.name + " failed: '" + err.message + "'");
    204            }, "generate wrong key step: " + vector.name + " verifying with wrong algorithm name");
    205        });
    206 
    207        all_promises.push(promise);
    208    });
    209 
    210    // Verification should fail if the plaintext is changed
    211    testVectors.forEach(function(vector) {
    212        var promise = importVectorKeys(vector, ["verify", "sign"])
    213        .then(function(vector) {
    214            var plaintext = copyBuffer(vector.plaintext);
    215            plaintext[0] = 255 - plaintext[0];
    216            promise_test(function(test) {
    217                var operation = subtle.verify({name: "HMAC", hash: vector.hash}, vector.key, vector.signature, plaintext)
    218                .then(function(is_verified) {
    219                    assert_false(is_verified, "Signature is NOT verified");
    220                }, function(err) {
    221                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
    222                });
    223 
    224                return operation;
    225            }, vector.name + " verification failure due to wrong plaintext");
    226 
    227        }, function(err) {
    228            // We need a failed test if the importVectorKey operation fails, so
    229            // we know we never tested verification.
    230            promise_test(function(test) {
    231                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    232            }, "importVectorKeys step: " + vector.name + " verification failure due to wrong plaintext");
    233        });
    234 
    235        all_promises.push(promise);
    236    });
    237 
    238    // Verification should fail if the signature is changed
    239    testVectors.forEach(function(vector) {
    240        var promise = importVectorKeys(vector, ["verify", "sign"])
    241        .then(function(vector) {
    242            var signature = copyBuffer(vector.signature);
    243            signature[0] = 255 - signature[0];
    244            promise_test(function(test) {
    245                var operation = subtle.verify({name: "HMAC", hash: vector.hash}, vector.key, signature, vector.plaintext)
    246                .then(function(is_verified) {
    247                    assert_false(is_verified, "Signature is NOT verified");
    248                }, function(err) {
    249                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
    250                });
    251 
    252                return operation;
    253            }, vector.name + " verification failure due to wrong signature");
    254 
    255        }, function(err) {
    256            // We need a failed test if the importVectorKey operation fails, so
    257            // we know we never tested verification.
    258            promise_test(function(test) {
    259                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    260            }, "importVectorKeys step: " + vector.name + " verification failure due to wrong signature");
    261        });
    262 
    263        all_promises.push(promise);
    264    });
    265 
    266    // Verification should fail if the signature is wrong length
    267    testVectors.forEach(function(vector) {
    268        var promise = importVectorKeys(vector, ["verify", "sign"])
    269        .then(function(vector) {
    270            var signature = vector.signature.slice(1); // Drop first byte
    271            promise_test(function(test) {
    272                var operation = subtle.verify({name: "HMAC", hash: vector.hash}, vector.key, signature, vector.plaintext)
    273                .then(function(is_verified) {
    274                    assert_false(is_verified, "Signature is NOT verified");
    275                }, function(err) {
    276                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
    277                });
    278 
    279                return operation;
    280            }, vector.name + " verification failure due to short signature");
    281 
    282        }, function(err) {
    283            // We need a failed test if the importVectorKey operation fails, so
    284            // we know we never tested verification.
    285            promise_test(function(test) {
    286                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    287            }, "importVectorKeys step: " + vector.name + " verification failure due to short signature");
    288        });
    289 
    290        all_promises.push(promise);
    291    });
    292 
    293 
    294 
    295    promise_test(function() {
    296        return Promise.all(all_promises)
    297            .then(function() {done();})
    298            .catch(function() {done();})
    299    }, "setup");
    300 
    301    // A test vector has all needed fields for signing and verifying, EXCEPT that the
    302    // key field may be null. This function replaces that null with the Correct
    303    // CryptoKey object.
    304    //
    305    // Returns a Promise that yields an updated vector on success.
    306    function importVectorKeys(vector, keyUsages) {
    307        if (vector.key !== null) {
    308            return new Promise(function(resolve, reject) {
    309                resolve(vector);
    310            });
    311        } else {
    312            return subtle.importKey("raw", vector.keyBuffer, {name: "HMAC", hash: vector.hash}, false, keyUsages)
    313            .then(function(key) {
    314                vector.key = key;
    315                return vector;
    316            });
    317        }
    318    }
    319 
    320    // Returns a copy of the sourceBuffer it is sent.
    321    function copyBuffer(sourceBuffer) {
    322        var source = new Uint8Array(sourceBuffer);
    323        var copy = new Uint8Array(sourceBuffer.byteLength)
    324 
    325        for (var i=0; i<source.byteLength; i++) {
    326            copy[i] = source[i];
    327        }
    328 
    329        return copy;
    330    }
    331 
    332    function equalBuffers(a, b) {
    333        if (a.byteLength !== b.byteLength) {
    334            return false;
    335        }
    336 
    337        var aBytes = new Uint8Array(a);
    338        var bBytes = new Uint8Array(b);
    339 
    340        for (var i=0; i<a.byteLength; i++) {
    341            if (aBytes[i] !== bBytes[i]) {
    342                return false;
    343            }
    344        }
    345 
    346        return true;
    347    }
    348 
    349    return;
    350 }