tor-browser

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

rsa.js (20390B)


      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 
     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(vectors) {
     17            promise_test(function(test) {
     18                var operation = subtle.verify(vector.algorithm, vector.publicKey, 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(vectors) {
     43            promise_test(function(test) {
     44                var signature = copyBuffer(vector.signature);
     45                var operation = subtle.verify(vector.algorithm, vector.publicKey, signature, vector.plaintext)
     46                .then(function(is_verified) {
     47                    assert_true(is_verified, "Signature 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(vectors) {
     68            promise_test(function(test) {
     69                var plaintext = copyBuffer(vector.plaintext);
     70                var operation = subtle.verify(vector.algorithm, vector.publicKey, 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 after call");
     84        });
     85 
     86        all_promises.push(promise);
     87    });
     88 
     89    // Check for failures due to using privateKey to verify.
     90    testVectors.forEach(function(vector) {
     91        var promise = importVectorKeys(vector, ["verify"], ["sign"])
     92        .then(function(vectors) {
     93            promise_test(function(test) {
     94                return subtle.verify(vector.algorithm, vector.privateKey, vector.signature, vector.plaintext)
     95                .then(function(plaintext) {
     96                    assert_unreached("Should have thrown error for using privateKey to verify in " + vector.name + ": " + err.message + "'");
     97                }, function(err) {
     98                    assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'");
     99                });
    100            }, vector.name + " using privateKey to verify");
    101 
    102        }, function(err) {
    103            promise_test(function(test) {
    104                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    105            }, "importVectorKeys step: " + vector.name + " using privateKey to verify");
    106        });
    107 
    108        all_promises.push(promise);
    109    });
    110 
    111    // Check for failures due to using publicKey to sign.
    112    testVectors.forEach(function(vector) {
    113        var promise = importVectorKeys(vector, ["verify"], ["sign"])
    114        .then(function(vectors) {
    115            promise_test(function(test) {
    116                return subtle.sign(vector.algorithm, vector.publicKey, vector.plaintext)
    117                .then(function(signature) {
    118                    assert_unreached("Should have thrown error for using publicKey to sign in " + vector.name + ": " + err.message + "'");
    119                }, function(err) {
    120                    assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'");
    121                });
    122            }, vector.name + " using publicKey to sign");
    123        }, function(err) {
    124            promise_test(function(test) {
    125                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    126            }, "importVectorKeys step: " + vector.name + " using publicKey to sign");
    127        });
    128 
    129        all_promises.push(promise);
    130    });
    131 
    132    // Check for failures due to no "verify" usage.
    133    testVectors.forEach(function(originalVector) {
    134        var vector = Object.assign({}, originalVector);
    135 
    136        var promise = importVectorKeys(vector, [], ["sign"])
    137        .then(function(vectors) {
    138            promise_test(function(test) {
    139                return subtle.verify(vector.algorithm, vector.publicKey, vector.signature, vector.plaintext)
    140                .then(function(plaintext) {
    141                    assert_unreached("Should have thrown error for no verify usage in " + vector.name + ": " + err.message + "'");
    142                }, function(err) {
    143                    assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'");
    144                });
    145            }, vector.name + " no verify usage");
    146        }, function(err) {
    147            promise_test(function(test) {
    148                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    149            }, "importVectorKeys step: " + vector.name + " no verify usage");
    150        });
    151 
    152        all_promises.push(promise);
    153    });
    154 
    155    // Check for successful signing and verification.
    156    testVectors.forEach(function(vector) {
    157        // RSA signing is deterministic with PKCS#1 v1.5, or PSS with zero-length salts.
    158        const isDeterministic = !("saltLength" in vector.algorithm) || vector.algorithm.saltLength == 0;
    159        var promise = importVectorKeys(vector, ["verify"], ["sign"])
    160        .then(function(vectors) {
    161            promise_test(function(test) {
    162                return subtle.sign(vector.algorithm, vector.privateKey, vector.plaintext)
    163                .then(function(signature) {
    164                    if (isDeterministic) {
    165                        // If deterministic, we can check the output matches. Otherwise, we can only check it verifies.
    166                        assert_true(equalBuffers(signature, vector.signature), "Signing did not give the expected output");
    167                    }
    168                    // Can we verify the new signature?
    169                    return subtle.verify(vector.algorithm, vector.publicKey, signature, vector.plaintext)
    170                    .then(function(is_verified) {
    171                        assert_true(is_verified, "Round trip verifies");
    172                        return signature;
    173                    }, function(err) {
    174                        assert_unreached("verify error for test " + vector.name + ": " + err.message + "'");
    175                    });
    176                })
    177                .then(function(priorSignature) {
    178                    // Will a second signing give us different signature? It should for PSS with non-empty salt
    179                    return subtle.sign(vector.algorithm, vector.privateKey, vector.plaintext)
    180                    .then(function(signature) {
    181                        if (isDeterministic) {
    182                            assert_true(equalBuffers(priorSignature, signature), "Two signings with empty salt give same signature")
    183                        } else {
    184                            assert_false(equalBuffers(priorSignature, signature), "Two signings with a salt give different signatures")
    185                        }
    186                    }, function(err) {
    187                        assert_unreached("second time verify error for test " + vector.name + ": '" + err.message + "'");
    188                    });
    189                }, function(err) {
    190                    assert_unreached("sign error for test " + vector.name + ": '" + err.message + "'");
    191                });
    192            }, vector.name + " round trip");
    193 
    194        }, function(err) {
    195            // We need a failed test if the importVectorKey operation fails, so
    196            // we know we never tested signing or verifying
    197            promise_test(function(test) {
    198                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    199            }, "importVectorKeys step: " + vector.name + " round trip");
    200        });
    201 
    202        all_promises.push(promise);
    203    });
    204 
    205 
    206    // Test signing with the wrong algorithm
    207    testVectors.forEach(function(vector) {
    208        // Want to get the key for the wrong algorithm
    209        var alteredVector = Object.assign({}, vector);
    210        alteredVector.algorithm = Object.assign({}, vector.algorithm);
    211        if (vector.algorithm.name === "RSA-PSS") {
    212            alteredVector.algorithm.name = "RSASSA-PKCS1-v1_5";
    213        } else {
    214            alteredVector.algorithm.name = "RSA-PSS";
    215        }
    216 
    217        var promise = importVectorKeys(alteredVector, ["verify"], ["sign"])
    218        .then(function(vectors) {
    219            promise_test(function(test) {
    220                var operation = subtle.sign(vector.algorithm, alteredVector.privateKey, vector.plaintext)
    221                .then(function(signature) {
    222                    assert_unreached("Signing should not have succeeded for " + vector.name);
    223                }, function(err) {
    224                    assert_equals(err.name, "InvalidAccessError", "Should have thrown InvalidAccessError instead of '" + err.message + "'");
    225                });
    226 
    227                return operation;
    228            }, vector.name + " signing with wrong algorithm name");
    229 
    230        }, function(err) {
    231            // We need a failed test if the importVectorKey operation fails, so
    232            // we know we never tested verification.
    233            promise_test(function(test) {
    234                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    235            }, "importVectorKeys step: " + vector.name + " signing with wrong algorithm name");
    236        });
    237 
    238        all_promises.push(promise);
    239    });
    240 
    241    // Test verification with the wrong algorithm
    242    testVectors.forEach(function(vector) {
    243        // Want to get the key for the wrong algorithm
    244        var alteredVector = Object.assign({}, vector);
    245        alteredVector.algorithm = Object.assign({}, vector.algorithm);
    246        if (vector.algorithm.name === "RSA-PSS") {
    247            alteredVector.algorithm.name = "RSASSA-PKCS1-v1_5";
    248        } else {
    249            alteredVector.algorithm.name = "RSA-PSS";
    250        }
    251 
    252        var promise = importVectorKeys(alteredVector, ["verify"], ["sign"])
    253        .then(function(vectors) {
    254            // Some tests are sign only
    255            if (!("signature" in vector)) {
    256                return;
    257            }
    258            promise_test(function(test) {
    259                var operation = subtle.verify(vector.algorithm, alteredVector.publicKey, vector.signature, vector.plaintext)
    260                .then(function(is_verified) {
    261                    assert_unreached("Verification should not have succeeded for " + vector.name);
    262                }, function(err) {
    263                    assert_equals(err.name, "InvalidAccessError", "Should have thrown InvalidAccessError instead of '" + err.message + "'");
    264                });
    265 
    266                return operation;
    267            }, vector.name + " verification with wrong algorithm name");
    268 
    269        }, function(err) {
    270            // We need a failed test if the importVectorKey operation fails, so
    271            // we know we never tested verification.
    272            promise_test(function(test) {
    273                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    274            }, "importVectorKeys step: " + vector.name + " verification with wrong algorithm name");
    275        });
    276 
    277        all_promises.push(promise);
    278    });
    279 
    280    // Verification should fail with wrong signature
    281    testVectors.forEach(function(vector) {
    282        var promise = importVectorKeys(vector, ["verify"], ["sign"])
    283        .then(function(vectors) {
    284            promise_test(function(test) {
    285                var signature = copyBuffer(vector.signature);
    286                signature[0] = 255 - signature[0];
    287                var operation = subtle.verify(vector.algorithm, vector.publicKey, signature, vector.plaintext)
    288                .then(function(is_verified) {
    289                    assert_false(is_verified, "Signature NOT verified");
    290                }, function(err) {
    291                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
    292                });
    293 
    294                return operation;
    295            }, vector.name + " verification failure with altered signature");
    296 
    297        }, function(err) {
    298            // We need a failed test if the importVectorKey operation fails, so
    299            // we know we never tested verification.
    300            promise_test(function(test) {
    301                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    302            }, "importVectorKeys step: " + vector.name + " verification failure with altered signature");
    303        });
    304 
    305        all_promises.push(promise);
    306    });
    307 
    308    // [RSA-PSS] Verification should fail with wrong saltLength
    309    testVectors.forEach(function(vector) {
    310        if (vector.algorithm.name === "RSA-PSS") {
    311            var promise = importVectorKeys(vector, ["verify"], ["sign"])
    312            .then(function(vectors) {
    313                promise_test(function(test) {
    314                    const saltLength = vector.algorithm.saltLength === 32 ? 48 : 32;
    315                    var operation = subtle.verify({ ...vector.algorithm, saltLength }, vector.publicKey, vector.signature, vector.plaintext)
    316                    .then(function(is_verified) {
    317                        assert_false(is_verified, "Signature NOT verified");
    318                    }, function(err) {
    319                        assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
    320                    });
    321 
    322                    return operation;
    323                }, vector.name + " verification failure with wrong saltLength");
    324 
    325            }, function(err) {
    326                // We need a failed test if the importVectorKey operation fails, so
    327                // we know we never tested verification.
    328                promise_test(function(test) {
    329                    assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    330                }, "importVectorKeys step: " + vector.name + " verification failure with wrong saltLength");
    331            });
    332 
    333            all_promises.push(promise);
    334        }
    335    });
    336 
    337    // Verification should fail with wrong plaintext
    338    testVectors.forEach(function(vector) {
    339        var promise = importVectorKeys(vector, ["verify"], ["sign"])
    340        .then(function(vectors) {
    341            promise_test(function(test) {
    342                var plaintext = copyBuffer(vector.plaintext);
    343                plaintext[0] = 255 - plaintext[0];
    344                var operation = subtle.verify(vector.algorithm, vector.publicKey, vector.signature, plaintext)
    345                .then(function(is_verified) {
    346                    assert_false(is_verified, "Signature NOT verified");
    347                }, function(err) {
    348                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
    349                });
    350 
    351                return operation;
    352            }, vector.name + " verification failure with altered plaintext");
    353 
    354        }, function(err) {
    355            // We need a failed test if the importVectorKey operation fails, so
    356            // we know we never tested verification.
    357            promise_test(function(test) {
    358                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
    359            }, "importVectorKeys step: " + vector.name + " verification failure with altered plaintext");
    360        });
    361 
    362        all_promises.push(promise);
    363    });
    364 
    365 
    366    promise_test(function() {
    367        return Promise.all(all_promises)
    368            .then(function() {done();})
    369            .catch(function() {done();})
    370    }, "setup");
    371 
    372    // A test vector has all needed fields for signing and verifying, EXCEPT that the
    373    // key field may be null. This function replaces that null with the Correct
    374    // CryptoKey object.
    375    //
    376    // Returns a Promise that yields an updated vector on success.
    377    function importVectorKeys(vector, publicKeyUsages, privateKeyUsages) {
    378        var publicPromise, privatePromise;
    379 
    380        if (vector.publicKey !== null) {
    381            publicPromise = new Promise(function(resolve, reject) {
    382                resolve(vector);
    383            });
    384        } else {
    385            publicPromise = subtle.importKey(vector.publicKeyFormat, vector.publicKeyBuffer, {name: vector.algorithm.name, hash: vector.hash}, false, publicKeyUsages)
    386            .then(function(key) {
    387                vector.publicKey = key;
    388                return vector;
    389            });        // Returns a copy of the sourceBuffer it is sent.
    390        }
    391 
    392        if (vector.privateKey !== null) {
    393            privatePromise = new Promise(function(resolve, reject) {
    394                resolve(vector);
    395            });
    396        } else {
    397            privatePromise = subtle.importKey(vector.privateKeyFormat, vector.privateKeyBuffer, {name: vector.algorithm.name, hash: vector.hash}, false, privateKeyUsages)
    398            .then(function(key) {
    399                vector.privateKey = key;
    400                return vector;
    401            });
    402        }
    403 
    404        return Promise.all([publicPromise, privatePromise]);
    405    }
    406 
    407    // Returns a copy of the sourceBuffer it is sent.
    408    function copyBuffer(sourceBuffer) {
    409        var source = new Uint8Array(sourceBuffer);
    410        var copy = new Uint8Array(sourceBuffer.byteLength)
    411 
    412        for (var i=0; i<source.byteLength; i++) {
    413            copy[i] = source[i];
    414        }
    415 
    416        return copy;
    417    }
    418 
    419    function equalBuffers(a, b) {
    420        if (a.byteLength !== b.byteLength) {
    421            return false;
    422        }
    423 
    424        var aBytes = new Uint8Array(a);
    425        var bBytes = new Uint8Array(b);
    426 
    427        for (var i=0; i<a.byteLength; i++) {
    428            if (aBytes[i] !== bBytes[i]) {
    429                return false;
    430            }
    431        }
    432 
    433        return true;
    434    }
    435 
    436    return;
    437 }