tor-browser

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

ecdsa.js (24116B)


      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 [algorithm_name]_vectors.js provides the getTestVectors method
     10    // for the algorithm that drives these tests.
     11    var testVectors = getTestVectors();
     12    var invalidTestVectors = getInvalidTestVectors();
     13 
     14    // Test verification first, because signing tests rely on that working
     15    testVectors.forEach(function(vector) {
     16        var promise = importVectorKeys(vector, ["verify"], ["sign"])
     17        .then(function(vectors) {
     18            var algorithm = {name: vector.algorithmName, hash: vector.hashName};
     19            promise_test(function(test) {
     20                var operation = subtle.verify(algorithm, vector.publicKey, vector.signature, vector.plaintext)
     21                .then(function(is_verified) {
     22                    assert_true(is_verified, "Signature verified");
     23                }, function(err) {
     24                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
     25                });
     26 
     27                return operation;
     28            }, vector.name + " verification");
     29 
     30        }, function(err) {
     31            // We need a failed test if the importVectorKey operation fails, so
     32            // we know we never tested verification.
     33            promise_test(function(test) {
     34                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
     35            }, "importVectorKeys step: " + vector.name + " verification");
     36        });
     37 
     38        all_promises.push(promise);
     39    });
     40 
     41    // Test verification with an altered buffer after call
     42    testVectors.forEach(function(vector) {
     43        var promise = importVectorKeys(vector, ["verify"], ["sign"])
     44        .then(function(vectors) {
     45            var algorithm = {name: vector.algorithmName, hash: vector.hashName};
     46            promise_test(function(test) {
     47                var signature = copyBuffer(vector.signature);
     48                var operation = subtle.verify(algorithm, vector.publicKey, signature, vector.plaintext)
     49                .then(function(is_verified) {
     50                    assert_true(is_verified, "Signature verified");
     51                }, function(err) {
     52                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
     53                });
     54 
     55                signature[0] = 255 - signature[0];
     56                return operation;
     57            }, vector.name + " verification with altered signature after call");
     58        }, function(err) {
     59            promise_test(function(test) {
     60                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
     61            }, "importVectorKeys step: " + vector.name + " verification with altered signature after call");
     62        });
     63 
     64        all_promises.push(promise);
     65    });
     66 
     67    // Check for successful verification even if plaintext is altered after call.
     68    testVectors.forEach(function(vector) {
     69        var promise = importVectorKeys(vector, ["verify"], ["sign"])
     70        .then(function(vectors) {
     71            var algorithm = {name: vector.algorithmName, hash: vector.hashName};
     72            promise_test(function(test) {
     73                var plaintext = copyBuffer(vector.plaintext);
     74                var operation = subtle.verify(algorithm, vector.publicKey, vector.signature, plaintext)
     75                .then(function(is_verified) {
     76                    assert_true(is_verified, "Signature verified");
     77                }, function(err) {
     78                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
     79                });
     80 
     81                plaintext[0] = 255 - plaintext[0];
     82                return operation;
     83            }, vector.name + " with altered plaintext after call");
     84        }, function(err) {
     85            promise_test(function(test) {
     86                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
     87            }, "importVectorKeys step: " + vector.name + " with altered plaintext after call");
     88        });
     89 
     90        all_promises.push(promise);
     91    });
     92 
     93    // Check for failures due to using privateKey to verify.
     94    testVectors.forEach(function(vector) {
     95        var promise = importVectorKeys(vector, ["verify"], ["sign"])
     96        .then(function(vectors) {
     97            var algorithm = {name: vector.algorithmName, hash: vector.hashName};
     98            promise_test(function(test) {
     99                return subtle.verify(algorithm, vector.privateKey, vector.signature, vector.plaintext)
    100                .then(function(plaintext) {
    101                    assert_unreached("Should have thrown error for using privateKey to verify in " + vector.name + ": " + err.message + "'");
    102                }, function(err) {
    103                    assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'");
    104                });
    105            }, vector.name + " using privateKey to verify");
    106 
    107        }, function(err) {
    108            promise_test(function(test) {
    109                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    110            }, "importVectorKeys step: " + vector.name + " using privateKey to verify");
    111        });
    112 
    113        all_promises.push(promise);
    114    });
    115 
    116    // Check for failures due to using publicKey to sign.
    117    testVectors.forEach(function(vector) {
    118        var promise = importVectorKeys(vector, ["verify"], ["sign"])
    119        .then(function(vectors) {
    120            var algorithm = {name: vector.algorithmName, hash: vector.hashName};
    121            promise_test(function(test) {
    122                return subtle.sign(algorithm, vector.publicKey, vector.plaintext)
    123                .then(function(signature) {
    124                    assert_unreached("Should have thrown error for using publicKey to sign in " + vector.name + ": " + err.message + "'");
    125                }, function(err) {
    126                    assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'");
    127                });
    128            }, vector.name + " using publicKey to sign");
    129        }, function(err) {
    130            promise_test(function(test) {
    131                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    132            }, "importVectorKeys step: " + vector.name + " using publicKey to sign");
    133        });
    134 
    135        all_promises.push(promise);
    136    });
    137 
    138    // Check for failures due to no "verify" usage.
    139    testVectors.forEach(function(originalVector) {
    140        var vector = Object.assign({}, originalVector);
    141 
    142        var promise = importVectorKeys(vector, [], ["sign"])
    143        .then(function(vectors) {
    144            var algorithm = {name: vector.algorithmName, hash: vector.hashName};
    145            promise_test(function(test) {
    146                return subtle.verify(algorithm, vector.publicKey, vector.signature, vector.plaintext)
    147                .then(function(plaintext) {
    148                    assert_unreached("Should have thrown error for no verify usage in " + vector.name + ": " + err.message + "'");
    149                }, function(err) {
    150                    assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'");
    151                });
    152            }, vector.name + " no verify usage");
    153        }, function(err) {
    154            promise_test(function(test) {
    155                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    156            }, "importVectorKeys step: " + vector.name + " no verify usage");
    157        });
    158 
    159        all_promises.push(promise);
    160    });
    161 
    162    // Check for successful signing and verification.
    163    testVectors.forEach(function(vector) {
    164        var promise = importVectorKeys(vector, ["verify"], ["sign"])
    165        .then(function(vectors) {
    166            var algorithm = {name: vector.algorithmName, hash: vector.hashName};
    167            promise_test(function(test) {
    168                return subtle.sign(algorithm, vector.privateKey, vector.plaintext)
    169                .then(function(signature) {
    170                    // Can we verify the signature?
    171                    return subtle.verify(algorithm, vector.publicKey, signature, vector.plaintext)
    172                    .then(function(is_verified) {
    173                        assert_true(is_verified, "Round trip verification works");
    174                        return signature;
    175                    }, function(err) {
    176                        assert_unreached("verify error for test " + vector.name + ": " + err.message + "'");
    177                    });
    178                }, function(err) {
    179                    assert_unreached("sign error for test " + vector.name + ": '" + err.message + "'");
    180                });
    181            }, vector.name + " round trip");
    182 
    183        }, function(err) {
    184            // We need a failed test if the importVectorKey operation fails, so
    185            // we know we never tested signing or verifying
    186            promise_test(function(test) {
    187                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    188            }, "importVectorKeys step: " + vector.name + " round trip");
    189        });
    190 
    191        all_promises.push(promise);
    192    });
    193 
    194    // Test signing with the wrong algorithm
    195    testVectors.forEach(function(vector) {
    196        // Want to get the key for the wrong algorithm
    197        var promise = subtle.generateKey({name: "HMAC", hash: "SHA-1"}, false, ["sign", "verify"])
    198        .then(function(wrongKey) {
    199            var algorithm = {name: vector.algorithmName, hash: vector.hashName};
    200            return importVectorKeys(vector, ["verify"], ["sign"])
    201            .then(function(vectors) {
    202                promise_test(function(test) {
    203                    var operation = subtle.sign(algorithm, wrongKey, vector.plaintext)
    204                    .then(function(signature) {
    205                        assert_unreached("Signing should not have succeeded for " + vector.name);
    206                    }, function(err) {
    207                        assert_equals(err.name, "InvalidAccessError", "Should have thrown InvalidAccessError instead of '" + err.message + "'");
    208                    });
    209 
    210                    return operation;
    211                }, vector.name + " signing with wrong algorithm name");
    212 
    213            }, function(err) {
    214                // We need a failed test if the importVectorKey operation fails, so
    215                // we know we never tested verification.
    216                promise_test(function(test) {
    217                    assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    218                }, "importVectorKeys step: " + vector.name + " signing with wrong algorithm name");
    219            });
    220        }, function(err) {
    221            promise_test(function(test) {
    222                assert_unreached("Generate wrong key for test " + vector.name + " failed: '" + err.message + "'");
    223            }, "generate wrong key step: " + vector.name + " signing with wrong algorithm name");
    224        });
    225 
    226        all_promises.push(promise);
    227    });
    228 
    229    // Test verification with the wrong algorithm
    230    testVectors.forEach(function(vector) {
    231        // Want to get the key for the wrong algorithm
    232        var promise = subtle.generateKey({name: "HMAC", hash: "SHA-1"}, false, ["sign", "verify"])
    233        .then(function(wrongKey) {
    234            return importVectorKeys(vector, ["verify"], ["sign"])
    235            .then(function(vectors) {
    236                var algorithm = {name: vector.algorithmName, hash: vector.hashName};
    237                promise_test(function(test) {
    238                    var operation = subtle.verify(algorithm, wrongKey, vector.signature, vector.plaintext)
    239                    .then(function(signature) {
    240                        assert_unreached("Verifying should not have succeeded for " + vector.name);
    241                    }, function(err) {
    242                        assert_equals(err.name, "InvalidAccessError", "Should have thrown InvalidAccessError instead of '" + err.message + "'");
    243                    });
    244 
    245                    return operation;
    246                }, vector.name + " verifying with wrong algorithm name");
    247 
    248            }, function(err) {
    249                // We need a failed test if the importVectorKey operation fails, so
    250                // we know we never tested verification.
    251                promise_test(function(test) {
    252                    assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    253                }, "importVectorKeys step: " + vector.name + " verifying with wrong algorithm name");
    254            });
    255        }, function(err) {
    256            promise_test(function(test) {
    257                assert_unreached("Generate wrong key for test " + vector.name + " failed: '" + err.message + "'");
    258            }, "generate wrong key step: " + vector.name + " verifying with wrong algorithm name");
    259        });
    260 
    261        all_promises.push(promise);
    262    });
    263 
    264    // Test verification fails with wrong signature
    265    testVectors.forEach(function(vector) {
    266        var promise = importVectorKeys(vector, ["verify"], ["sign"])
    267        .then(function(vectors) {
    268            var algorithm = {name: vector.algorithmName, hash: vector.hashName};
    269            var signature = copyBuffer(vector.signature);
    270            signature[0] = 255 - signature[0];
    271            promise_test(function(test) {
    272                var operation = subtle.verify(algorithm, vector.publicKey, signature, vector.plaintext)
    273                .then(function(is_verified) {
    274                    assert_false(is_verified, "Signature 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 altered 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 altered signature");
    288        });
    289 
    290        all_promises.push(promise);
    291    });
    292 
    293    // Test verification fails with wrong hash
    294    testVectors.forEach(function(vector) {
    295        var promise = importVectorKeys(vector, ["verify"], ["sign"])
    296        .then(function(vectors) {
    297            var hashName = "SHA-1";
    298            if (vector.hashName === "SHA-1") {
    299                hashName = "SHA-256"
    300            }
    301            var algorithm = {name: vector.algorithmName, hash: hashName};
    302            promise_test(function(test) {
    303                var operation = subtle.verify(algorithm, vector.publicKey, vector.signature, vector.plaintext)
    304                .then(function(is_verified) {
    305                    assert_false(is_verified, "Signature NOT verified");
    306                }, function(err) {
    307                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
    308                });
    309 
    310                return operation;
    311            }, vector.name + " verification failure due to wrong hash");
    312 
    313        }, function(err) {
    314            // We need a failed test if the importVectorKey operation fails, so
    315            // we know we never tested verification.
    316            promise_test(function(test) {
    317                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    318            }, "importVectorKeys step: " + vector.name + " verification failure due to wrong hash");
    319        });
    320 
    321        all_promises.push(promise);
    322    });
    323 
    324    // Test verification fails with bad hash name
    325    testVectors.forEach(function(vector) {
    326        var promise = importVectorKeys(vector, ["verify"], ["sign"])
    327        .then(function(vectors) {
    328            // use the wrong name for the hash
    329            var hashName = vector.hashName.substring(0, 3) + vector.hashName.substring(4);
    330            var algorithm = {name: vector.algorithmName, hash: hashName};
    331            promise_test(function(test) {
    332                var operation = subtle.verify(algorithm, vector.publicKey, vector.signature, vector.plaintext)
    333                .then(function(is_verified) {
    334                    assert_unreached("Verification should throw an error");
    335                }, function(err) {
    336                    assert_equals(err.name, "NotSupportedError", "Correctly throws NotSupportedError for illegal hash name")
    337                });
    338 
    339                return operation;
    340            }, vector.name + " verification failure due to bad hash name");
    341 
    342        }, function(err) {
    343            // We need a failed test if the importVectorKey operation fails, so
    344            // we know we never tested verification.
    345            promise_test(function(test) {
    346                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    347            }, "importVectorKeys step: " + vector.name + " verification failure due to bad hash name");
    348        });
    349 
    350        all_promises.push(promise);
    351    });
    352 
    353    // Test verification fails with short (odd length) signature
    354    testVectors.forEach(function(vector) {
    355        var promise = importVectorKeys(vector, ["verify"], ["sign"])
    356        .then(function(vectors) {
    357            var algorithm = {name: vector.algorithmName, hash: vector.hashName};
    358            var signature = vector.signature.slice(1); // Skip the first byte
    359            promise_test(function(test) {
    360                var operation = subtle.verify(algorithm, vector.publicKey, signature, vector.plaintext)
    361                .then(function(is_verified) {
    362                    assert_false(is_verified, "Signature NOT verified");
    363                }, function(err) {
    364                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
    365                });
    366 
    367                return operation;
    368            }, vector.name + " verification failure due to shortened signature");
    369 
    370        }, function(err) {
    371            // We need a failed test if the importVectorKey operation fails, so
    372            // we know we never tested verification.
    373            promise_test(function(test) {
    374                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    375            }, "importVectorKeys step: " + vector.name + " verification failure due to shortened signature");
    376        });
    377 
    378        all_promises.push(promise);
    379    });
    380 
    381    // Test verification fails with wrong plaintext
    382    testVectors.forEach(function(vector) {
    383        var promise = importVectorKeys(vector, ["verify"], ["sign"])
    384        .then(function(vectors) {
    385            var algorithm = {name: vector.algorithmName, hash: vector.hashName};
    386            var plaintext = copyBuffer(vector.plaintext);
    387            plaintext[0] = 255 - plaintext[0];
    388            promise_test(function(test) {
    389                var operation = subtle.verify(algorithm, vector.publicKey, vector.signature, plaintext)
    390                .then(function(is_verified) {
    391                    assert_false(is_verified, "Signature NOT verified");
    392                }, function(err) {
    393                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
    394                });
    395 
    396                return operation;
    397            }, vector.name + " verification failure due to altered plaintext");
    398 
    399        }, function(err) {
    400            // We need a failed test if the importVectorKey operation fails, so
    401            // we know we never tested verification.
    402            promise_test(function(test) {
    403                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    404            }, "importVectorKeys step: " + vector.name + " verification failure due to altered plaintext");
    405        });
    406 
    407        all_promises.push(promise);
    408    });
    409 
    410    // Test invalid signatures
    411    invalidTestVectors.forEach(function(vector) {
    412        var promise = importVectorKeys(vector, ["verify"], ["sign"])
    413        .then(function(vectors) {
    414            var algorithm = {name: vector.algorithmName, hash: vector.hashName};
    415            promise_test(function(test) {
    416                var operation = subtle.verify(algorithm, vector.publicKey, vector.signature, vector.plaintext)
    417                .then(function(is_verified) {
    418                    assert_false(is_verified, "Signature unexpectedly verified");
    419                }, function(err) {
    420                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
    421                });
    422 
    423                return operation;
    424            }, vector.name + " verification");
    425 
    426        }, function(err) {
    427            // We need a failed test if the importVectorKey operation fails, so
    428            // we know we never tested verification.
    429            promise_test(function(test) {
    430                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    431            }, "importVectorKeys step: " + vector.name + " verification");
    432        });
    433 
    434        all_promises.push(promise);
    435    });
    436 
    437    promise_test(function() {
    438        return Promise.all(all_promises)
    439            .then(function() {done();})
    440            .catch(function() {done();})
    441    }, "setup");
    442 
    443    // A test vector has all needed fields for signing and verifying, EXCEPT that the
    444    // key field may be null. This function replaces that null with the Correct
    445    // CryptoKey object.
    446    //
    447    // Returns a Promise that yields an updated vector on success.
    448    function importVectorKeys(vector, publicKeyUsages, privateKeyUsages) {
    449        var publicPromise, privatePromise;
    450 
    451        if (vector.publicKey !== null) {
    452            publicPromise = new Promise(function(resolve, reject) {
    453                resolve(vector);
    454            });
    455        } else {
    456            publicPromise = subtle.importKey(vector.publicKeyFormat, vector.publicKeyBuffer, {name: vector.algorithmName, namedCurve: vector.namedCurve}, false, publicKeyUsages)
    457            .then(function(key) {
    458                vector.publicKey = key;
    459                return vector;
    460            });        // Returns a copy of the sourceBuffer it is sent.
    461        }
    462 
    463        if (vector.privateKey !== null) {
    464            privatePromise = new Promise(function(resolve, reject) {
    465                resolve(vector);
    466            });
    467        } else {
    468            privatePromise = subtle.importKey(vector.privateKeyFormat, vector.privateKeyBuffer, {name: vector.algorithmName, namedCurve: vector.namedCurve}, false, privateKeyUsages)
    469            .then(function(key) {
    470                vector.privateKey = key;
    471                return vector;
    472            });
    473        }
    474 
    475        return Promise.all([publicPromise, privatePromise]);
    476    }
    477 
    478    // Returns a copy of the sourceBuffer it is sent.
    479    function copyBuffer(sourceBuffer) {
    480        var source = new Uint8Array(sourceBuffer);
    481        var copy = new Uint8Array(sourceBuffer.byteLength)
    482 
    483        for (var i=0; i<source.byteLength; i++) {
    484            copy[i] = source[i];
    485        }
    486 
    487        return copy;
    488    }
    489 
    490    function equalBuffers(a, b) {
    491        if (a.byteLength !== b.byteLength) {
    492            return false;
    493        }
    494 
    495        var aBytes = new Uint8Array(a);
    496        var bBytes = new Uint8Array(b);
    497 
    498        for (var i=0; i<a.byteLength; i++) {
    499            if (aBytes[i] !== bBytes[i]) {
    500                return false;
    501            }
    502        }
    503 
    504        return true;
    505    }
    506 
    507    return;
    508 }