tor-browser

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

common.ts (13278B)


      1 import * as asn1js from "asn1js";
      2 import * as pvutils from "pvutils";
      3 import { AlgorithmIdentifier } from "./AlgorithmIdentifier";
      4 import { EMPTY_BUFFER } from "./constants";
      5 import type { CryptoEngineAlgorithmOperation, CryptoEngineAlgorithmParams, ICryptoEngine } from "./CryptoEngine/CryptoEngineInterface";
      6 import { ArgumentError } from "./errors";
      7 
      8 //#region Crypto engine related function
      9 export { ICryptoEngine } from "./CryptoEngine/CryptoEngineInterface";
     10 
     11 export interface GlobalCryptoEngine {
     12  name: string;
     13  crypto: ICryptoEngine | null;
     14 }
     15 export let engine: GlobalCryptoEngine = {
     16  name: "none",
     17  crypto: null,
     18 };
     19 
     20 function isCryptoEngine(engine: unknown): engine is ICryptoEngine {
     21  return engine
     22    && typeof engine === "object"
     23    && "crypto" in engine
     24    ? true
     25    : false;
     26 }
     27 
     28 /**
     29 * Sets global crypto engine
     30 * @param name Name of the crypto engine
     31 * @param crypto
     32 * @param subtle
     33 * @deprecated Since version 3.0.0
     34 */
     35 export function setEngine(name: string, crypto: ICryptoEngine | Crypto, subtle: ICryptoEngine | SubtleCrypto): void;
     36 /**
     37 * Sets global crypto engine
     38 * @param name Name of the crypto engine
     39 * @param crypto Crypto engine. If the parameter is omitted, `CryptoEngine` with `self.crypto` are used
     40 * @since 3.0.0
     41 */
     42 export function setEngine(name: string, crypto?: ICryptoEngine): void;
     43 export function setEngine(name: string, ...args: any[]): void {
     44  let crypto: ICryptoEngine | null = null;
     45  if (args.length < 2) {
     46    // v3.0.0 implementation
     47    if (args.length) {
     48      crypto = args[0];
     49    } else {
     50      // crypto param is omitted, use CryptoEngine(self.crypto)
     51      crypto = typeof self !== "undefined" && self.crypto ? new CryptoEngine({ name: "browser", crypto: self.crypto }) : null;
     52    }
     53  } else {
     54    // prev implementation
     55    const cryptoArg = args[0];
     56    const subtleArg = args[1];
     57    if (isCryptoEngine(subtleArg)) {
     58      crypto = subtleArg;
     59    } else if (isCryptoEngine(cryptoArg)) {
     60      crypto = cryptoArg;
     61    } else if ("subtle" in cryptoArg && "getRandomValues" in cryptoArg) {
     62      crypto = new CryptoEngine({
     63        crypto: cryptoArg,
     64      });
     65    }
     66  }
     67 
     68  if ((typeof process !== "undefined") && ("pid" in process) && (typeof global !== "undefined") && (typeof window === "undefined")) {
     69    // We are in Node
     70    if (typeof (global as any)[process.pid] === "undefined") {
     71      (global as any)[process.pid] = {};
     72    }
     73    else {
     74      if (typeof (global as any)[process.pid] !== "object") {
     75        throw new Error(`Name global.${process.pid} already exists and it is not an object`);
     76      }
     77    }
     78 
     79    if (typeof (global as any)[process.pid].pkijs === "undefined") {
     80      (global as any)[process.pid].pkijs = {};
     81    }
     82    else {
     83      if (typeof (global as any)[process.pid].pkijs !== "object") {
     84        throw new Error(`Name global.${process.pid}.pkijs already exists and it is not an object`);
     85      }
     86    }
     87 
     88    (global as any)[process.pid].pkijs.engine = {
     89      name: name,
     90      crypto,
     91    };
     92  } else {
     93    // We are in browser
     94    engine = {
     95      name: name,
     96      crypto,
     97    };
     98  }
     99 }
    100 
    101 export function getEngine(): GlobalCryptoEngine {
    102  //#region We are in Node
    103  if ((typeof process !== "undefined") && ("pid" in process) && (typeof global !== "undefined") && (typeof window === "undefined")) {
    104    let _engine;
    105 
    106    try {
    107      _engine = (global as any)[process.pid].pkijs.engine;
    108    }
    109    catch {
    110      throw new Error("Please call 'setEngine' before call to 'getEngine'");
    111    }
    112 
    113    return _engine;
    114  }
    115  //#endregion
    116 
    117  return engine;
    118 }
    119 //#endregion
    120 
    121 //#region Declaration of common functions
    122 
    123 /**
    124 * Gets crypto subtle from the current "crypto engine"
    125 * @param safety
    126 * @returns Reruns {@link ICryptoEngine} or `null`
    127 */
    128 export function getCrypto(safety?: boolean): ICryptoEngine | null;
    129 /**
    130 * Gets crypto subtle from the current "crypto engine"
    131 * @param safety
    132 * @returns Reruns {@link ICryptoEngine} or throws en exception
    133 * @throws Throws {@link Error} if `subtle` is empty
    134 */
    135 export function getCrypto(safety: true): ICryptoEngine;
    136 export function getCrypto(safety = false): ICryptoEngine | null {
    137  const _engine = getEngine();
    138 
    139  if (!_engine.crypto && safety) {
    140    throw new Error("Unable to create WebCrypto object");
    141  }
    142 
    143  return _engine.crypto;
    144 }
    145 
    146 /**
    147 * Initialize input Uint8Array by random values (with help from current "crypto engine")
    148 * @param view
    149 */
    150 export function getRandomValues(view: Uint8Array) {
    151  return getCrypto(true).getRandomValues(view);
    152 }
    153 
    154 /**
    155 * Get OID for each specific algorithm
    156 * @param algorithm WebCrypto Algorithm
    157 * @param safety if `true` throws exception on unknown algorithm
    158 * @param target name of the target
    159 * @throws Throws {@link Error} exception if unknown WebCrypto algorithm
    160 */
    161 export function getOIDByAlgorithm(algorithm: Algorithm, safety?: boolean, target?: string) {
    162  return getCrypto(true).getOIDByAlgorithm(algorithm, safety, target);
    163 }
    164 
    165 /**
    166 * Get default algorithm parameters for each kind of operation
    167 * @param algorithmName Algorithm name to get common parameters for
    168 * @param operation Kind of operation: "sign", "encrypt", "generateKey", "importKey", "exportKey", "verify"
    169 */
    170 // TODO Add safety
    171 export function getAlgorithmParameters(algorithmName: string, operation: CryptoEngineAlgorithmOperation): CryptoEngineAlgorithmParams {
    172  return getCrypto(true).getAlgorithmParameters(algorithmName, operation);
    173 }
    174 
    175 /**
    176 * Create CMS ECDSA signature from WebCrypto ECDSA signature
    177 * @param signatureBuffer WebCrypto result of "sign" function
    178 */
    179 export function createCMSECDSASignature(signatureBuffer: ArrayBuffer): ArrayBuffer {
    180  //#region Initial check for correct length
    181  if ((signatureBuffer.byteLength % 2) !== 0)
    182    return EMPTY_BUFFER;
    183  //#endregion
    184 
    185  //#region Initial variables
    186  const length = signatureBuffer.byteLength / 2; // There are two equal parts inside incoming ArrayBuffer
    187 
    188  const rBuffer = new ArrayBuffer(length);
    189  const rView = new Uint8Array(rBuffer);
    190  rView.set(new Uint8Array(signatureBuffer, 0, length));
    191 
    192  const rInteger = new asn1js.Integer({ valueHex: rBuffer });
    193 
    194  const sBuffer = new ArrayBuffer(length);
    195  const sView = new Uint8Array(sBuffer);
    196  sView.set(new Uint8Array(signatureBuffer, length, length));
    197 
    198  const sInteger = new asn1js.Integer({ valueHex: sBuffer });
    199  //#endregion
    200 
    201  return (new asn1js.Sequence({
    202    value: [
    203      rInteger.convertToDER(),
    204      sInteger.convertToDER()
    205    ]
    206  })).toBER(false);
    207 }
    208 
    209 /**
    210 * Create a single ArrayBuffer from CMS ECDSA signature
    211 * @param cmsSignature ASN.1 SEQUENCE contains CMS ECDSA signature
    212 * @param pointSize Size of EC point. Use {@link ECNamedCurves.find} to get correct point size
    213 * @returns WebCrypto signature
    214 */
    215 export function createECDSASignatureFromCMS(cmsSignature: asn1js.AsnType, pointSize: number): ArrayBuffer {
    216  // Check input variables
    217  if (!(cmsSignature instanceof asn1js.Sequence
    218    && cmsSignature.valueBlock.value.length === 2
    219    && cmsSignature.valueBlock.value[0] instanceof asn1js.Integer
    220    && cmsSignature.valueBlock.value[1] instanceof asn1js.Integer))
    221    return EMPTY_BUFFER;
    222 
    223  const rValueView = cmsSignature.valueBlock.value[0].convertFromDER().valueBlock.valueHexView;
    224  const sValueView = cmsSignature.valueBlock.value[1].convertFromDER().valueBlock.valueHexView;
    225 
    226  const res = new Uint8Array(pointSize * 2);
    227 
    228  res.set(rValueView, pointSize - rValueView.byteLength);
    229  res.set(sValueView, (2 * pointSize) - sValueView.byteLength);
    230 
    231  return res.buffer;
    232 }
    233 
    234 /**
    235 * Gets WebCrypto algorithm by well-known OID
    236 * @param oid algorithm identifier
    237 * @param safety if true throws exception on unknown algorithm identifier
    238 * @param target name of the target
    239 * @returns WebCrypto algorithm or an empty object
    240 */
    241 export function getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety?: boolean, target?: string): T | object;
    242 export function getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety: true, target?: string): T;
    243 export function getAlgorithmByOID(oid: string, safety = false, target?: string): any {
    244  return getCrypto(true).getAlgorithmByOID(oid, safety, target);
    245 }
    246 
    247 /**
    248 * Getting hash algorithm by signature algorithm
    249 * @param signatureAlgorithm Signature algorithm
    250 */
    251 export function getHashAlgorithm(signatureAlgorithm: AlgorithmIdentifier): string {
    252  return getCrypto(true).getHashAlgorithm(signatureAlgorithm);
    253 }
    254 
    255 /**
    256 * ANS X9.63 Key Derivation Function having a "Counter" as a parameter
    257 * @param hashFunction Used hash function
    258 * @param zBuffer ArrayBuffer containing ECDH shared secret to derive from
    259 * @param Counter
    260 * @param SharedInfo Usually DER encoded "ECC_CMS_SharedInfo" structure
    261 * @param crypto Crypto engine
    262 */
    263 async function kdfWithCounter(hashFunction: string, zBuffer: ArrayBuffer, Counter: number, SharedInfo: ArrayBuffer, crypto: ICryptoEngine): Promise<{ counter: number; result: ArrayBuffer; }> {
    264  //#region Check of input parameters
    265  switch (hashFunction.toUpperCase()) {
    266    case "SHA-1":
    267    case "SHA-256":
    268    case "SHA-384":
    269    case "SHA-512":
    270      break;
    271    default:
    272      throw new ArgumentError(`Unknown hash function: ${hashFunction}`);
    273  }
    274 
    275  ArgumentError.assert(zBuffer, "zBuffer", "ArrayBuffer");
    276  if (zBuffer.byteLength === 0)
    277    throw new ArgumentError("'zBuffer' has zero length, error");
    278 
    279  ArgumentError.assert(SharedInfo, "SharedInfo", "ArrayBuffer");
    280  if (Counter > 255)
    281    throw new ArgumentError("Please set 'Counter' argument to value less or equal to 255");
    282  //#endregion
    283 
    284  //#region Initial variables
    285  const counterBuffer = new ArrayBuffer(4);
    286  const counterView = new Uint8Array(counterBuffer);
    287  counterView[0] = 0x00;
    288  counterView[1] = 0x00;
    289  counterView[2] = 0x00;
    290  counterView[3] = Counter;
    291 
    292  let combinedBuffer = EMPTY_BUFFER;
    293  //#endregion
    294 
    295  //#region Create a combined ArrayBuffer for digesting
    296  combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, zBuffer);
    297  combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, counterBuffer);
    298  combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, SharedInfo);
    299  //#endregion
    300 
    301  //#region Return digest of combined ArrayBuffer and information about current counter
    302  const result = await crypto.digest(
    303    { name: hashFunction },
    304    combinedBuffer);
    305 
    306  return {
    307    counter: Counter,
    308    result
    309  };
    310  //#endregion
    311 }
    312 
    313 /**
    314 * ANS X9.63 Key Derivation Function
    315 * @param hashFunction Used hash function
    316 * @param Zbuffer ArrayBuffer containing ECDH shared secret to derive from
    317 * @param keydatalen Length (!!! in BITS !!!) of used kew derivation function
    318 * @param SharedInfo Usually DER encoded "ECC_CMS_SharedInfo" structure
    319 * @param crypto Crypto engine
    320 */
    321 export async function kdf(hashFunction: string, Zbuffer: ArrayBuffer, keydatalen: number, SharedInfo: ArrayBuffer, crypto = getCrypto(true)) {
    322  //#region Initial variables
    323  let hashLength = 0;
    324  let maxCounter = 1;
    325  //#endregion
    326 
    327  //#region Check of input parameters
    328  switch (hashFunction.toUpperCase()) {
    329    case "SHA-1":
    330      hashLength = 160; // In bits
    331      break;
    332    case "SHA-256":
    333      hashLength = 256; // In bits
    334      break;
    335    case "SHA-384":
    336      hashLength = 384; // In bits
    337      break;
    338    case "SHA-512":
    339      hashLength = 512; // In bits
    340      break;
    341    default:
    342      throw new ArgumentError(`Unknown hash function: ${hashFunction}`);
    343  }
    344 
    345  ArgumentError.assert(Zbuffer, "Zbuffer", "ArrayBuffer");
    346  if (Zbuffer.byteLength === 0)
    347    throw new ArgumentError("'Zbuffer' has zero length, error");
    348  ArgumentError.assert(SharedInfo, "SharedInfo", "ArrayBuffer");
    349  //#endregion
    350 
    351  //#region Calculated maximum value of "Counter" variable
    352  const quotient = keydatalen / hashLength;
    353 
    354  if (Math.floor(quotient) > 0) {
    355    maxCounter = Math.floor(quotient);
    356 
    357    if ((quotient - maxCounter) > 0)
    358      maxCounter++;
    359  }
    360  //#endregion
    361 
    362  //#region Create an array of "kdfWithCounter"
    363  const incomingResult = [];
    364  for (let i = 1; i <= maxCounter; i++)
    365    incomingResult.push(await kdfWithCounter(hashFunction, Zbuffer, i, SharedInfo, crypto));
    366  //#endregion
    367 
    368  //#region Return combined digest with specified length
    369  //#region Initial variables
    370  let combinedBuffer = EMPTY_BUFFER;
    371  let currentCounter = 1;
    372  let found = true;
    373  //#endregion
    374 
    375  //#region Combine all buffer together
    376  while (found) {
    377    found = false;
    378 
    379    for (const result of incomingResult) {
    380      if (result.counter === currentCounter) {
    381        combinedBuffer = pvutils.utilConcatBuf(combinedBuffer, result.result);
    382        found = true;
    383        break;
    384      }
    385    }
    386 
    387    currentCounter++;
    388  }
    389  //#endregion
    390 
    391  //#region Create output buffer with specified length
    392  keydatalen >>= 3; // Divide by 8 since "keydatalen" is in bits
    393 
    394  if (combinedBuffer.byteLength > keydatalen) {
    395    const newBuffer = new ArrayBuffer(keydatalen);
    396    const newView = new Uint8Array(newBuffer);
    397    const combinedView = new Uint8Array(combinedBuffer);
    398 
    399    for (let i = 0; i < keydatalen; i++)
    400      newView[i] = combinedView[i];
    401 
    402    return newBuffer;
    403  }
    404 
    405  return combinedBuffer; // Since the situation when "combinedBuffer.byteLength < keydatalen" here we have only "combinedBuffer.byteLength === keydatalen"
    406  //#endregion
    407  //#endregion
    408 }
    409 //#endregion
    410 
    411 import { CryptoEngine } from "./CryptoEngine/CryptoEngine";