tor-browser

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

helpers.js (10895B)


      1 //
      2 // helpers.js
      3 //
      4 // Helper functions used by several WebCryptoAPI tests
      5 //
      6 
      7 var registeredAlgorithmNames = [
      8    "RSASSA-PKCS1-v1_5",
      9    "RSA-PSS",
     10    "RSA-OAEP",
     11    "ECDSA",
     12    "ECDH",
     13    "AES-CTR",
     14    "AES-CBC",
     15    "AES-GCM",
     16    "AES-KW",
     17    "HMAC",
     18    "SHA-1",
     19    "SHA-256",
     20    "SHA-384",
     21    "SHA-512",
     22    "HKDF",
     23    "PBKDF2",
     24    "Ed25519",
     25    "Ed448",
     26    "X25519",
     27    "X448",
     28    "ML-DSA-44",
     29    "ML-DSA-65",
     30    "ML-DSA-87",
     31    "ML-KEM-512",
     32    "ML-KEM-768",
     33    "ML-KEM-1024",
     34    "ChaCha20-Poly1305",
     35    "Argon2i",
     36    "Argon2d",
     37    "Argon2id",
     38    "AES-OCB",
     39    "KMAC128",
     40    "KMAC256",
     41 ];
     42 
     43 
     44 // Treats an array as a set, and generates an array of all non-empty
     45 // subsets (which are themselves arrays).
     46 //
     47 // The order of members of the "subsets" is not guaranteed.
     48 function allNonemptySubsetsOf(arr) {
     49    var results = [];
     50    var firstElement;
     51    var remainingElements;
     52 
     53    for(var i=0; i<arr.length; i++) {
     54        firstElement = arr[i];
     55        remainingElements = arr.slice(i+1);
     56        results.push([firstElement]);
     57 
     58        if (remainingElements.length > 0) {
     59            allNonemptySubsetsOf(remainingElements).forEach(function(combination) {
     60                combination.push(firstElement);
     61                results.push(combination);
     62            });
     63        }
     64    }
     65 
     66    return results;
     67 }
     68 
     69 
     70 // Create a string representation of keyGeneration parameters for
     71 // test names and labels.
     72 function objectToString(obj) {
     73    var keyValuePairs = [];
     74 
     75    if (Array.isArray(obj)) {
     76        return "[" + obj.map(function(elem){return objectToString(elem);}).join(", ") + "]";
     77    } else if (typeof obj === "object") {
     78        Object.keys(obj).sort().forEach(function(keyName) {
     79            keyValuePairs.push(keyName + ": " + objectToString(obj[keyName]));
     80        });
     81        return "{" + keyValuePairs.join(", ") + "}";
     82    } else if (typeof obj === "undefined") {
     83        return "undefined";
     84    } else {
     85        return obj.toString();
     86    }
     87 
     88    var keyValuePairs = [];
     89 
     90    Object.keys(obj).sort().forEach(function(keyName) {
     91        var value = obj[keyName];
     92        if (typeof value === "object") {
     93            value = objectToString(value);
     94        } else if (typeof value === "array") {
     95            value = "[" + value.map(function(elem){return objectToString(elem);}).join(", ") + "]";
     96        } else {
     97            value = value.toString();
     98        }
     99 
    100        keyValuePairs.push(keyName + ": " + value);
    101    });
    102 
    103    return "{" + keyValuePairs.join(", ") + "}";
    104 }
    105 
    106 // Is key a CryptoKey object with correct algorithm, extractable, and usages?
    107 // Is it a secret, private, or public kind of key?
    108 function assert_goodCryptoKey(key, algorithm, extractable, usages, kind) {
    109    if (typeof algorithm === "string") {
    110        algorithm = { name: algorithm };
    111    }
    112 
    113    var correctUsages = [];
    114 
    115    var registeredAlgorithmName;
    116    registeredAlgorithmNames.forEach(function(name) {
    117        if (name.toUpperCase() === algorithm.name.toUpperCase()) {
    118            registeredAlgorithmName = name;
    119        }
    120    });
    121 
    122    assert_equals(key.constructor, CryptoKey, "Is a CryptoKey");
    123    assert_equals(key.type, kind, "Is a " + kind + " key");
    124    assert_equals(key.extractable, extractable, "Extractability is correct");
    125 
    126    assert_equals(key.algorithm.name, registeredAlgorithmName, "Correct algorithm name");
    127    if (key.algorithm.name.toUpperCase() === "HMAC" && algorithm.length === undefined) {
    128        switch (key.algorithm.hash.name.toUpperCase()) {
    129            case 'SHA-1':
    130            case 'SHA-256':
    131                assert_equals(key.algorithm.length, 512, "Correct length");
    132                break;
    133            case 'SHA-384':
    134            case 'SHA-512':
    135                assert_equals(key.algorithm.length, 1024, "Correct length");
    136                break;
    137            default:
    138                assert_unreached("Unrecognized hash");
    139        }
    140    } else if (key.algorithm.name.toUpperCase().startsWith("KMAC") && algorithm.length === undefined) {
    141        switch (key.algorithm.name.toUpperCase()) {
    142            case 'KMAC128':
    143                assert_equals(key.algorithm.length, 128, "Correct length");
    144                break;
    145            case 'KMAC256':
    146                assert_equals(key.algorithm.length, 256, "Correct length");
    147                break;
    148        }
    149    } else {
    150        assert_equals(key.algorithm.length, algorithm.length, "Correct length");
    151    }
    152    if (["HMAC", "RSASSA-PKCS1-v1_5", "RSA-PSS"].includes(registeredAlgorithmName)) {
    153        assert_equals(key.algorithm.hash.name.toUpperCase(), algorithm.hash.toUpperCase(), "Correct hash function");
    154    }
    155 
    156    if (/^(?:Ed|X)(?:25519|448)$/.test(key.algorithm.name)) {
    157        assert_false('namedCurve' in key.algorithm, "Does not have a namedCurve property");
    158    }
    159 
    160    // usages is expected to be provided for a key pair, but we are checking
    161    // only a single key. The publicKey and privateKey portions of a key pair
    162    // recognize only some of the usages appropriate for a key pair.
    163    if (key.type === "public") {
    164        ["encrypt", "verify", "wrapKey", "encapsulateBits", "encapsulateKey"].forEach(function(usage) {
    165            if (usages.includes(usage)) {
    166                correctUsages.push(usage);
    167            }
    168        });
    169    } else if (key.type === "private") {
    170        ["decrypt", "sign", "unwrapKey", "deriveKey", "deriveBits", "decapsulateBits", "decapsulateKey"].forEach(function(usage) {
    171            if (usages.includes(usage)) {
    172                correctUsages.push(usage);
    173            }
    174        });
    175    } else {
    176        correctUsages = usages;
    177    }
    178 
    179    assert_equals((typeof key.usages), "object", key.type + " key.usages is an object");
    180    assert_not_equals(key.usages, null, key.type + " key.usages isn't null");
    181 
    182    // The usages parameter could have repeats, but the usages
    183    // property of the result should not.
    184    var usageCount = 0;
    185    key.usages.forEach(function(usage) {
    186        usageCount += 1;
    187        assert_in_array(usage, correctUsages, "Has " + usage + " usage");
    188    });
    189    assert_equals(key.usages.length, usageCount, "usages property is correct");
    190    assert_equals(key[Symbol.toStringTag], 'CryptoKey', "has the expected Symbol.toStringTag");
    191 }
    192 
    193 
    194 // The algorithm parameter is an object with a name and other
    195 // properties. Given the name, generate all valid parameters.
    196 function allAlgorithmSpecifiersFor(algorithmName) {
    197    var results = [];
    198 
    199    // RSA key generation is slow. Test a minimal set of parameters
    200    var hashes = ["SHA-1", "SHA-256"];
    201 
    202    // EC key generation is a lot faster. Check all curves in the spec
    203    var curves = ["P-256", "P-384", "P-521"];
    204 
    205    if (algorithmName.toUpperCase().substring(0, 3) === "AES") {
    206        // Specifier properties are name and length
    207        [128, 192, 256].forEach(function(length) {
    208            results.push({name: algorithmName, length: length});
    209        });
    210    } else if (algorithmName.toUpperCase() === "HMAC") {
    211        [
    212            {hash: "SHA-1", length: 160},
    213            {hash: "SHA-256", length: 256},
    214            {hash: "SHA-384", length: 384},
    215            {hash: "SHA-512", length: 512},
    216            {hash: "SHA-1"},
    217            {hash: "SHA-256"},
    218            {hash: "SHA-384"},
    219            {hash: "SHA-512"},
    220        ].forEach(function(hashAlgorithm) {
    221            results.push({name: algorithmName, ...hashAlgorithm});
    222        });
    223    } else if (algorithmName.toUpperCase().substring(0, 3) === "RSA") {
    224        hashes.forEach(function(hashName) {
    225            results.push({name: algorithmName, hash: hashName, modulusLength: 2048, publicExponent: new Uint8Array([1,0,1])});
    226        });
    227    } else if (algorithmName.toUpperCase().substring(0, 2) === "EC") {
    228        curves.forEach(function(curveName) {
    229            results.push({name: algorithmName, namedCurve: curveName});
    230        });
    231    } else if (algorithmName.toUpperCase().startsWith("KMAC")) {
    232        [
    233            {length: 128},
    234            {length: 160},
    235            {length: 256},
    236        ].forEach(function(hashAlgorithm) {
    237            results.push({name: algorithmName, ...hashAlgorithm});
    238        });
    239    } else {
    240        results.push(algorithmName);
    241        results.push({ name: algorithmName });
    242    }
    243 
    244    return results;
    245 }
    246 
    247 
    248 // Create every possible valid usages parameter, given legal
    249 // usages. Note that an empty usages parameter is not always valid.
    250 //
    251 // There is an optional parameter - mandatoryUsages. If provided,
    252 // it should be an array containing those usages of which one must be
    253 // included.
    254 function allValidUsages(validUsages, emptyIsValid, mandatoryUsages) {
    255    if (typeof mandatoryUsages === "undefined") {
    256        mandatoryUsages = [];
    257    }
    258 
    259    var okaySubsets = [];
    260    allNonemptySubsetsOf(validUsages).forEach(function(subset) {
    261        if (mandatoryUsages.length === 0) {
    262            okaySubsets.push(subset);
    263        } else {
    264            for (var i=0; i<mandatoryUsages.length; i++) {
    265                if (subset.includes(mandatoryUsages[i])) {
    266                    okaySubsets.push(subset);
    267                    return;
    268                }
    269            }
    270        }
    271    });
    272 
    273    if (emptyIsValid && validUsages.length !== 0) {
    274        okaySubsets.push([]);
    275    }
    276 
    277    okaySubsets.push(validUsages.concat(mandatoryUsages).concat(validUsages)); // Repeated values are allowed
    278    return okaySubsets;
    279 }
    280 
    281 function unique(names) {
    282    return [...new Set(names)];
    283 }
    284 
    285 // Algorithm name specifiers are case-insensitive. Generate several
    286 // case variations of a given name.
    287 function allNameVariants(name, slowTest) {
    288    var upCaseName = name.toUpperCase();
    289    var lowCaseName = name.toLowerCase();
    290    var mixedCaseName = upCaseName.substring(0, 1) + lowCaseName.substring(1);
    291 
    292    // for slow tests effectively cut the amount of work in third by only
    293    // returning one variation
    294    if (slowTest) return [mixedCaseName];
    295    return unique([upCaseName, lowCaseName, mixedCaseName]);
    296 }
    297 
    298 // Builds a hex string representation for an array-like input.
    299 // "bytes" can be an Array of bytes, an ArrayBuffer, or any TypedArray.
    300 // The output looks like this:
    301 //    ab034c99
    302 function bytesToHexString(bytes)
    303 {
    304    if (!bytes)
    305        return null;
    306 
    307    bytes = new Uint8Array(bytes);
    308    var hexBytes = [];
    309 
    310    for (var i = 0; i < bytes.length; ++i) {
    311        var byteString = bytes[i].toString(16);
    312        if (byteString.length < 2)
    313            byteString = "0" + byteString;
    314        hexBytes.push(byteString);
    315    }
    316 
    317    return hexBytes.join("");
    318 }
    319 
    320 function hexStringToUint8Array(hexString)
    321 {
    322    if (hexString.length % 2 != 0)
    323        throw "Invalid hexString";
    324    var arrayBuffer = new Uint8Array(hexString.length / 2);
    325 
    326    for (var i = 0; i < hexString.length; i += 2) {
    327        var byteValue = parseInt(hexString.substr(i, 2), 16);
    328        if (byteValue == NaN)
    329            throw "Invalid hexString";
    330        arrayBuffer[i/2] = byteValue;
    331    }
    332 
    333    return arrayBuffer;
    334 }