tor-browser

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

aes.js (14253B)


      1 function run_test() {
      2    var subtle = self.crypto.subtle; // Change to test prefixed implementations
      3 
      4    // When are all these tests really done? When all the promises they use have resolved.
      5    var all_promises = [];
      6 
      7    // Source file aes_XXX_vectors.js provides the getTestVectors method
      8    // for the AES-XXX algorithm that drives these tests.
      9    var vectors = getTestVectors();
     10    var passingVectors = vectors.passing;
     11    var failingVectors = vectors.failing;
     12    var decryptionFailingVectors = vectors.decryptionFailing;
     13 
     14    // Check for successful encryption.
     15    passingVectors.forEach(function(vector) {
     16        var promise = importVectorKey(vector, ["encrypt", "decrypt"])
     17        .then(function(vector) {
     18            promise_test(function(test) {
     19                return subtle.encrypt(vector.algorithm, vector.key, vector.plaintext)
     20                .then(function(result) {
     21                    assert_true(equalBuffers(result, vector.result), "Should return expected result");
     22                }, function(err) {
     23                    assert_unreached("encrypt error for test " + vector.name + ": " + err.message);
     24                });
     25            }, vector.name);
     26        }, function(err) {
     27            // We need a failed test if the importVectorKey operation fails, so
     28            // we know we never tested encryption
     29            promise_test(function(test) {
     30                assert_unreached("importKey failed for " + vector.name);
     31            }, "importKey step: " + vector.name);
     32        });
     33 
     34        all_promises.push(promise);
     35    });
     36 
     37    // Check for successful encryption even if the buffer is changed after calling encrypt.
     38    passingVectors.forEach(function(vector) {
     39        var plaintext = copyBuffer(vector.plaintext);
     40        var promise = importVectorKey(vector, ["encrypt", "decrypt"])
     41        .then(function(vector) {
     42            promise_test(function(test) {
     43                var operation = subtle.encrypt(vector.algorithm, vector.key, plaintext)
     44                .then(function(result) {
     45                    assert_true(equalBuffers(result, vector.result), "Should return expected result");
     46                }, function(err) {
     47                    assert_unreached("encrypt error for test " + vector.name + ": " + err.message);
     48                });
     49                plaintext[0] = 255 - plaintext[0];
     50                return operation;
     51            }, vector.name + " with altered plaintext");
     52        }, function(err) {
     53            // We need a failed test if the importVectorKey operation fails, so
     54            // we know we never tested encryption
     55            promise_test(function(test) {
     56                assert_unreached("importKey failed for " + vector.name);
     57            }, "importKey step: " + vector.name + " with altered plaintext");
     58        });
     59 
     60        all_promises.push(promise);
     61    });
     62 
     63    // Check for successful decryption.
     64    passingVectors.forEach(function(vector) {
     65        var promise = importVectorKey(vector, ["encrypt", "decrypt"])
     66        .then(function(vector) {
     67            promise_test(function(test) {
     68                return subtle.decrypt(vector.algorithm, vector.key, vector.result)
     69                .then(function(result) {
     70                    assert_true(equalBuffers(result, vector.plaintext), "Should return expected result");
     71                }, function(err) {
     72                    assert_unreached("decrypt error for test " + vector.name + ": " + err.message);
     73                });
     74            }, vector.name + " decryption");
     75        }, function(err) {
     76            // We need a failed test if the importVectorKey operation fails, so
     77            // we know we never tested encryption
     78            promise_test(function(test) {
     79                assert_unreached("importKey failed for " + vector.name);
     80            }, "importKey step for decryption: " + vector.name);
     81        });
     82 
     83        all_promises.push(promise);
     84    });
     85 
     86    // Check for successful decryption even if ciphertext is altered.
     87    passingVectors.forEach(function(vector) {
     88        var ciphertext = copyBuffer(vector.result);
     89        var promise = importVectorKey(vector, ["encrypt", "decrypt"])
     90        .then(function(vector) {
     91            promise_test(function(test) {
     92                var operation = subtle.decrypt(vector.algorithm, vector.key, ciphertext)
     93                .then(function(result) {
     94                    assert_true(equalBuffers(result, vector.plaintext), "Should return expected result");
     95                }, function(err) {
     96                    assert_unreached("decrypt error for test " + vector.name + ": " + err.message);
     97                });
     98                ciphertext[0] = 255 - ciphertext[0];
     99                return operation;
    100            }, vector.name + " decryption with altered ciphertext");
    101        }, function(err) {
    102            // We need a failed test if the importVectorKey operation fails, so
    103            // we know we never tested encryption
    104            promise_test(function(test) {
    105                assert_unreached("importKey failed for " + vector.name);
    106            }, "importKey step for decryption: " + vector.name + " with altered ciphertext");
    107        });
    108 
    109        all_promises.push(promise);
    110    });
    111 
    112    // Everything that succeeded should fail if no "encrypt" usage.
    113    passingVectors.forEach(function(vector) {
    114        // Don't want to overwrite key being used for success tests!
    115        var badVector = Object.assign({}, vector);
    116        badVector.key = null;
    117 
    118        var promise = importVectorKey(badVector, ["decrypt"])
    119        .then(function(vector) {
    120            promise_test(function(test) {
    121                return subtle.encrypt(vector.algorithm, vector.key, vector.plaintext)
    122                .then(function(result) {
    123                    assert_unreached("should have thrown exception for test " + vector.name);
    124                }, function(err) {
    125                    assert_equals(err.name, "InvalidAccessError", "Should throw an InvalidAccessError instead of " + err.message)
    126                });
    127            }, vector.name + " without encrypt usage");
    128        }, function(err) {
    129            // We need a failed test if the importVectorKey operation fails, so
    130            // we know we never tested encryption
    131            promise_test(function(test) {
    132                assert_unreached("importKey failed for " + vector.name);
    133            }, "importKey step: " + vector.name + " without encrypt usage");
    134        });
    135 
    136        all_promises.push(promise);
    137    });
    138 
    139    // Encryption should fail if algorithm of key doesn't match algorithm of function call.
    140    passingVectors.forEach(function(vector) {
    141        var algorithm = Object.assign({}, vector.algorithm);
    142        if (algorithm.name === "AES-CBC") {
    143            algorithm.name = "AES-CTR";
    144            algorithm.counter = new Uint8Array(16);
    145            algorithm.length = 64;
    146        } else {
    147            algorithm.name = "AES-CBC";
    148            algorithm.iv = new Uint8Array(16); // Need syntactically valid parameter to get to error being checked.
    149        }
    150 
    151        var promise = importVectorKey(vector, ["encrypt", "decrypt"])
    152        .then(function(vector) {
    153            promise_test(function(test) {
    154                return subtle.encrypt(algorithm, vector.key, vector.plaintext)
    155                .then(function(result) {
    156                    assert_unreached("encrypt succeeded despite mismatch " + vector.name + ": " + err.message);
    157                }, function(err) {
    158                    assert_equals(err.name, "InvalidAccessError", "Mismatch should cause InvalidAccessError instead of " + err.message);
    159                });
    160            }, vector.name + " with mismatched key and algorithm");
    161        }, function(err) {
    162            // We need a failed test if the importVectorKey operation fails, so
    163            // we know we never tested encryption
    164            promise_test(function(test) {
    165                assert_unreached("importKey failed for " + vector.name);
    166            }, "importKey step: " + vector.name + " with mismatched key and algorithm");
    167        });
    168 
    169        all_promises.push(promise);
    170    });
    171 
    172    // Everything that succeeded decrypting should fail if no "decrypt" usage.
    173    passingVectors.forEach(function(vector) {
    174        // Don't want to overwrite key being used for success tests!
    175        var badVector = Object.assign({}, vector);
    176        badVector.key = null;
    177 
    178        var promise = importVectorKey(badVector, ["encrypt"])
    179        .then(function(vector) {
    180            promise_test(function(test) {
    181                return subtle.decrypt(vector.algorithm, vector.key, vector.result)
    182                .then(function(result) {
    183                    assert_unreached("should have thrown exception for test " + vector.name);
    184                }, function(err) {
    185                    assert_equals(err.name, "InvalidAccessError", "Should throw an InvalidAccessError instead of " + err.message)
    186                });
    187            }, vector.name + " without decrypt usage");
    188        }, function(err) {
    189            // We need a failed test if the importVectorKey operation fails, so
    190            // we know we never tested encryption
    191            promise_test(function(test) {
    192                assert_unreached("importKey failed for " + vector.name);
    193            }, "importKey step: " + vector.name + " without decrypt usage");
    194        });
    195 
    196        all_promises.push(promise);
    197    });
    198 
    199    // Check for OperationError due to data lengths.
    200    failingVectors.forEach(function(vector) {
    201        var promise = importVectorKey(vector, ["encrypt", "decrypt"])
    202        .then(function(vector) {
    203            promise_test(function(test) {
    204                return subtle.encrypt(vector.algorithm, vector.key, vector.plaintext)
    205                .then(function(result) {
    206                    assert_unreached("should have thrown exception for test " + vector.name);
    207                }, function(err) {
    208                    assert_equals(err.name, "OperationError", "Should throw an OperationError instead of " + err.message)
    209                });
    210            }, vector.name);
    211        }, function(err) {
    212            // We need a failed test if the importVectorKey operation fails, so
    213            // we know we never tested encryption
    214            promise_test(function(test) {
    215                assert_unreached("importKey failed for " + vector.name);
    216            }, "importKey step: " + vector.name);
    217        });
    218 
    219        all_promises.push(promise);
    220    });
    221 
    222    // Check for OperationError due to data lengths for decryption, too.
    223    failingVectors.forEach(function(vector) {
    224        var promise = importVectorKey(vector, ["encrypt", "decrypt"])
    225        .then(function(vector) {
    226            promise_test(function(test) {
    227                return subtle.decrypt(vector.algorithm, vector.key, vector.result)
    228                .then(function(result) {
    229                    assert_unreached("should have thrown exception for test " + vector.name);
    230                }, function(err) {
    231                    assert_equals(err.name, "OperationError", "Should throw an OperationError instead of " + err.message)
    232                });
    233            }, vector.name + " decryption");
    234        }, function(err) {
    235            // We need a failed test if the importVectorKey operation fails, so
    236            // we know we never tested encryption
    237            promise_test(function(test) {
    238                assert_unreached("importKey failed for " + vector.name);
    239            }, "importKey step: decryption " + vector.name);
    240        });
    241 
    242        all_promises.push(promise);
    243    });
    244 
    245    // Check for decryption failing for algorithm-specific reasons (such as bad
    246    // padding for AES-CBC).
    247    decryptionFailingVectors.forEach(function(vector) {
    248        var promise = importVectorKey(vector, ["encrypt", "decrypt"])
    249        .then(function(vector) {
    250            promise_test(function(test) {
    251                return subtle.decrypt(vector.algorithm, vector.key, vector.result)
    252                .then(function(result) {
    253                    assert_unreached("should have thrown exception for test " + vector.name);
    254                }, function(err) {
    255                    assert_equals(err.name, "OperationError", "Should throw an OperationError instead of " + err.message)
    256                });
    257            }, vector.name);
    258        }, function(err) {
    259            // We need a failed test if the importVectorKey operation fails, so
    260            // we know we never tested encryption
    261            promise_test(function(test) {
    262                assert_unreached("importKey failed for " + vector.name);
    263            }, "importKey step: decryption " + vector.name);
    264        });
    265 
    266        all_promises.push(promise);
    267    });
    268 
    269    promise_test(function() {
    270        return Promise.all(all_promises)
    271            .then(function() {done();})
    272            .catch(function() {done();})
    273    }, "setup");
    274 
    275    // A test vector has all needed fields for encryption, EXCEPT that the
    276    // key field may be null. This function replaces that null with the Correct
    277    // CryptoKey object.
    278    //
    279    // Returns a Promise that yields an updated vector on success.
    280    function importVectorKey(vector, usages) {
    281        if (vector.key !== null) {
    282            return new Promise(function(resolve, reject) {
    283                resolve(vector);
    284            });
    285        } else {
    286            return subtle.importKey(vector.algorithm.name.toUpperCase() === "AES-OCB" ? "raw-secret" : "raw", vector.keyBuffer, {name: vector.algorithm.name}, false, usages)
    287            .then(function(key) {
    288                vector.key = key;
    289                return vector;
    290            });
    291        }
    292    }
    293 
    294    // Returns a copy of the sourceBuffer it is sent.
    295    function copyBuffer(sourceBuffer) {
    296        var source = new Uint8Array(sourceBuffer);
    297        var copy = new Uint8Array(sourceBuffer.byteLength)
    298 
    299        for (var i=0; i<source.byteLength; i++) {
    300            copy[i] = source[i];
    301        }
    302 
    303        return copy;
    304    }
    305 
    306    function equalBuffers(a, b) {
    307        if (a.byteLength !== b.byteLength) {
    308            return false;
    309        }
    310 
    311        var aBytes = new Uint8Array(a);
    312        var bBytes = new Uint8Array(b);
    313 
    314        for (var i=0; i<a.byteLength; i++) {
    315            if (aBytes[i] !== bBytes[i]) {
    316                return false;
    317            }
    318        }
    319 
    320        return true;
    321    }
    322 
    323    return;
    324 }