tor-browser

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

CryptoEngine.ts (63909B)


      1 import { sha1 } from "@noble/hashes/sha1";
      2 import { sha256, sha384, sha512 } from "@noble/hashes/sha2";
      3 import * as asn1js from "asn1js";
      4 import * as pvutils from "pvutils";
      5 import * as pvtsutils from "pvtsutils";
      6 import * as common from "../common";
      7 import { PublicKeyInfo } from "../PublicKeyInfo";
      8 import { PrivateKeyInfo } from "../PrivateKeyInfo";
      9 import { AlgorithmIdentifier } from "../AlgorithmIdentifier";
     10 import { EncryptedContentInfo } from "../EncryptedContentInfo";
     11 import { IRSASSAPSSParams, RSASSAPSSParams } from "../RSASSAPSSParams";
     12 import { PBKDF2Params } from "../PBKDF2Params";
     13 import { PBES2Params } from "../PBES2Params";
     14 import { ArgumentError, AsnError, ParameterError } from "../errors";
     15 import * as type from "./CryptoEngineInterface";
     16 import { AbstractCryptoEngine } from "./AbstractCryptoEngine";
     17 import { EMPTY_STRING } from "../constants";
     18 import { ECNamedCurves } from "../ECNamedCurves";
     19 
     20 /**
     21 * Making MAC key using algorithm described in B.2 of PKCS#12 standard.
     22 */
     23 async function makePKCS12B2Key(hashAlgorithm: string, keyLength: number, password: ArrayBuffer, salt: ArrayBuffer, iterationCount: number) {
     24  let u: number;  // Output length of the hash function
     25  let v: number;  // Block size of the hash function
     26  let md: (input: Uint8Array) => Uint8Array;  // Hash function
     27 
     28  // Determine the hash algorithm parameters
     29  switch (hashAlgorithm.toUpperCase()) {
     30    case "SHA-1":
     31      u = 20; // 160 bits
     32      v = 64; // 512 bits
     33      md = sha1;
     34      break;
     35    case "SHA-256":
     36      u = 32; // 256 bits
     37      v = 64; // 512 bits
     38      md = sha256;
     39      break;
     40    case "SHA-384":
     41      u = 48; // 384 bits
     42      v = 128; // 1024 bits
     43      md = sha384;
     44      break;
     45    case "SHA-512":
     46      u = 64; // 512 bits
     47      v = 128; // 1024 bits
     48      md = sha512;
     49      break;
     50    default:
     51      throw new Error("Unsupported hashing algorithm");
     52  }
     53 
     54  const originalPassword = new Uint8Array(password);
     55  let decodedPassword = new TextDecoder().decode(password);
     56  const encodedPassword = new TextEncoder().encode(decodedPassword);
     57  if (encodedPassword.some((byte, i) => byte !== originalPassword[i])) {
     58    decodedPassword = String.fromCharCode(...originalPassword);
     59  }
     60 
     61  // Transform the password into a byte array
     62  const passwordTransformed = new Uint8Array(decodedPassword.length * 2 + 2);
     63  const passwordView = new DataView(passwordTransformed.buffer);
     64  for (let i = 0; i < decodedPassword.length; i++) {
     65    passwordView.setUint16(i * 2, decodedPassword.charCodeAt(i), false);
     66  }
     67  // Add null-terminator
     68  passwordView.setUint16(decodedPassword.length * 2, 0, false);
     69 
     70  // Create a filled array D with the value 3 (ID for MACing)
     71  const D = new Uint8Array(v).fill(3);
     72 
     73  // Repeat the salt to fill the block size
     74  const saltView = new Uint8Array(salt);
     75  const S = new Uint8Array(v * Math.ceil(saltView.length / v)).map((_, i) => saltView[i % saltView.length]);
     76 
     77  // Repeat the password to fill the block size
     78  const P = new Uint8Array(v * Math.ceil(passwordTransformed.length / v)).map((_, i) => passwordTransformed[i % passwordTransformed.length]);
     79 
     80  // Concatenate S and P to form I
     81  let I = new Uint8Array(S.length + P.length);
     82  I.set(S);
     83  I.set(P, S.length);
     84 
     85  // Calculate the number of hash iterations needed
     86  const c = Math.ceil((keyLength >> 3) / u);
     87  const result: number[] = [];
     88 
     89  // Main loop to generate the key material
     90  for (let i = 0; i < c; i++) {
     91    // Concatenate D and I
     92    let A: Uint8Array = new Uint8Array(D.length + I.length);
     93    A.set(D);
     94    A.set(I, D.length);
     95 
     96    // Perform hash iterations
     97    for (let j = 0; j < iterationCount; j++) {
     98      A = md(A);
     99    }
    100 
    101    // Create a repeated block B from the hash output A
    102    const B = new Uint8Array(v).map((_, i) => A[i % A.length]);
    103 
    104    // Determine the number of blocks
    105    const k = Math.ceil(saltView.length / v) + Math.ceil(passwordTransformed.length / v);
    106    const iRound: number[] = [];
    107 
    108    // Adjust I based on B
    109    for (let j = 0; j < k; j++) {
    110      const chunk = Array.from(I.slice(j * v, (j + 1) * v));
    111      let x = 0x1ff;
    112 
    113      for (let l = B.length - 1; l >= 0; l--) {
    114        x >>= 8;
    115        x += B[l] + (chunk[l] || 0);
    116        chunk[l] = x & 0xff;
    117      }
    118 
    119      iRound.push(...chunk);
    120    }
    121 
    122    // Update I for the next iteration
    123    I = new Uint8Array(iRound);
    124 
    125    // Collect the result
    126    result.push(...A);
    127  }
    128 
    129  return new Uint8Array(result.slice(0, keyLength >> 3)).buffer;
    130 }
    131 
    132 function prepareAlgorithm(data: globalThis.AlgorithmIdentifier | EcdsaParams): Algorithm & { hash?: Algorithm; } {
    133  const res = typeof data === "string"
    134    ? { name: data }
    135    : data;
    136 
    137  // TODO fix type casting `as EcdsaParams`
    138  if ("hash" in (res as EcdsaParams)) {
    139    return {
    140      ...res,
    141      hash: prepareAlgorithm((res as EcdsaParams).hash)
    142    };
    143  }
    144 
    145  return res;
    146 }
    147 
    148 /**
    149 * Default cryptographic engine for Web Cryptography API
    150 */
    151 export class CryptoEngine extends AbstractCryptoEngine {
    152 
    153  public override async importKey(format: KeyFormat, keyData: BufferSource | JsonWebKey, algorithm: globalThis.AlgorithmIdentifier, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey> {
    154    //#region Initial variables
    155    let jwk: JsonWebKey = {};
    156    //#endregion
    157 
    158    const alg = prepareAlgorithm(algorithm);
    159 
    160    switch (format.toLowerCase()) {
    161      case "raw":
    162        return this.subtle.importKey("raw", keyData as BufferSource, algorithm, extractable, keyUsages);
    163      case "spki":
    164        {
    165          const asn1 = asn1js.fromBER(pvtsutils.BufferSourceConverter.toArrayBuffer(keyData as BufferSource));
    166          AsnError.assert(asn1, "keyData");
    167 
    168          const publicKeyInfo = new PublicKeyInfo();
    169          try {
    170            publicKeyInfo.fromSchema(asn1.result);
    171          } catch {
    172            throw new ArgumentError("Incorrect keyData");
    173          }
    174 
    175          switch (alg.name.toUpperCase()) {
    176            case "RSA-PSS":
    177              {
    178                //#region Get information about used hash function
    179                if (!alg.hash) {
    180                  throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed");
    181                }
    182                switch (alg.hash.name.toUpperCase()) {
    183                  case "SHA-1":
    184                    jwk.alg = "PS1";
    185                    break;
    186                  case "SHA-256":
    187                    jwk.alg = "PS256";
    188                    break;
    189                  case "SHA-384":
    190                    jwk.alg = "PS384";
    191                    break;
    192                  case "SHA-512":
    193                    jwk.alg = "PS512";
    194                    break;
    195                  default:
    196                    throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`);
    197                }
    198                //#endregion
    199              }
    200            // break omitted
    201            // eslint-disable-next-line no-fallthrough
    202            case "RSASSA-PKCS1-V1_5":
    203              {
    204                keyUsages = ["verify"]; // Override existing keyUsages value since the key is a public key
    205 
    206                jwk.kty = "RSA";
    207                jwk.ext = extractable;
    208                jwk.key_ops = keyUsages;
    209 
    210                if (publicKeyInfo.algorithm.algorithmId !== "1.2.840.113549.1.1.1")
    211                  throw new Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`);
    212 
    213                //#region Get information about used hash function
    214                if (!jwk.alg) {
    215                  if (!alg.hash) {
    216                    throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed");
    217                  }
    218                  switch (alg.hash.name.toUpperCase()) {
    219                    case "SHA-1":
    220                      jwk.alg = "RS1";
    221                      break;
    222                    case "SHA-256":
    223                      jwk.alg = "RS256";
    224                      break;
    225                    case "SHA-384":
    226                      jwk.alg = "RS384";
    227                      break;
    228                    case "SHA-512":
    229                      jwk.alg = "RS512";
    230                      break;
    231                    default:
    232                      throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`);
    233                  }
    234                }
    235                //#endregion
    236 
    237                //#region Create RSA Public Key elements
    238                const publicKeyJSON = publicKeyInfo.toJSON();
    239                Object.assign(jwk, publicKeyJSON);
    240                //#endregion
    241              }
    242              break;
    243            case "ECDSA":
    244              keyUsages = ["verify"]; // Override existing keyUsages value since the key is a public key
    245            // break omitted
    246            // eslint-disable-next-line no-fallthrough
    247            case "ECDH":
    248              {
    249                //#region Initial variables
    250                jwk = {
    251                  kty: "EC",
    252                  ext: extractable,
    253                  key_ops: keyUsages
    254                };
    255                //#endregion
    256 
    257                //#region Get information about algorithm
    258                if (publicKeyInfo.algorithm.algorithmId !== "1.2.840.10045.2.1") {
    259                  throw new Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`);
    260                }
    261                //#endregion
    262 
    263                //#region Create ECDSA Public Key elements
    264                const publicKeyJSON = publicKeyInfo.toJSON();
    265                Object.assign(jwk, publicKeyJSON);
    266                //#endregion
    267              }
    268              break;
    269            case "RSA-OAEP":
    270              {
    271                jwk.kty = "RSA";
    272                jwk.ext = extractable;
    273                jwk.key_ops = keyUsages;
    274 
    275                if (this.name.toLowerCase() === "safari")
    276                  jwk.alg = "RSA-OAEP";
    277                else {
    278                  if (!alg.hash) {
    279                    throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed");
    280                  }
    281                  switch (alg.hash.name.toUpperCase()) {
    282                    case "SHA-1":
    283                      jwk.alg = "RSA-OAEP";
    284                      break;
    285                    case "SHA-256":
    286                      jwk.alg = "RSA-OAEP-256";
    287                      break;
    288                    case "SHA-384":
    289                      jwk.alg = "RSA-OAEP-384";
    290                      break;
    291                    case "SHA-512":
    292                      jwk.alg = "RSA-OAEP-512";
    293                      break;
    294                    default:
    295                      throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`);
    296                  }
    297                }
    298 
    299                //#region Create ECDSA Public Key elements
    300                const publicKeyJSON = publicKeyInfo.toJSON();
    301                Object.assign(jwk, publicKeyJSON);
    302                //#endregion
    303              }
    304              break;
    305            case "RSAES-PKCS1-V1_5":
    306              {
    307                jwk.kty = "RSA";
    308                jwk.ext = extractable;
    309                jwk.key_ops = keyUsages;
    310                jwk.alg = "PS1";
    311 
    312                const publicKeyJSON = publicKeyInfo.toJSON();
    313                Object.assign(jwk, publicKeyJSON);
    314              }
    315              break;
    316            default:
    317              throw new Error(`Incorrect algorithm name: ${alg.name.toUpperCase()}`);
    318          }
    319        }
    320        break;
    321      case "pkcs8":
    322        {
    323          const privateKeyInfo = new PrivateKeyInfo();
    324 
    325          //#region Parse "PrivateKeyInfo" object
    326          const asn1 = asn1js.fromBER(pvtsutils.BufferSourceConverter.toArrayBuffer(keyData as BufferSource));
    327          AsnError.assert(asn1, "keyData");
    328 
    329          try {
    330            privateKeyInfo.fromSchema(asn1.result);
    331          }
    332          catch {
    333            throw new Error("Incorrect keyData");
    334          }
    335 
    336          if (!privateKeyInfo.parsedKey)
    337            throw new Error("Incorrect keyData");
    338          //#endregion
    339 
    340          switch (alg.name.toUpperCase()) {
    341            case "RSA-PSS":
    342              {
    343                //#region Get information about used hash function
    344                switch (alg.hash?.name.toUpperCase()) {
    345                  case "SHA-1":
    346                    jwk.alg = "PS1";
    347                    break;
    348                  case "SHA-256":
    349                    jwk.alg = "PS256";
    350                    break;
    351                  case "SHA-384":
    352                    jwk.alg = "PS384";
    353                    break;
    354                  case "SHA-512":
    355                    jwk.alg = "PS512";
    356                    break;
    357                  default:
    358                    throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`);
    359                }
    360                //#endregion
    361              }
    362            // break omitted
    363            // eslint-disable-next-line no-fallthrough
    364            case "RSASSA-PKCS1-V1_5":
    365              {
    366                keyUsages = ["sign"]; // Override existing keyUsages value since the key is a private key
    367 
    368                jwk.kty = "RSA";
    369                jwk.ext = extractable;
    370                jwk.key_ops = keyUsages;
    371 
    372                //#region Get information about used hash function
    373                if (privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.113549.1.1.1")
    374                  throw new Error(`Incorrect private key algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`);
    375                //#endregion
    376 
    377                //#region Get information about used hash function
    378                if (("alg" in jwk) === false) {
    379                  switch (alg.hash?.name.toUpperCase()) {
    380                    case "SHA-1":
    381                      jwk.alg = "RS1";
    382                      break;
    383                    case "SHA-256":
    384                      jwk.alg = "RS256";
    385                      break;
    386                    case "SHA-384":
    387                      jwk.alg = "RS384";
    388                      break;
    389                    case "SHA-512":
    390                      jwk.alg = "RS512";
    391                      break;
    392                    default:
    393                      throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`);
    394                  }
    395                }
    396                //#endregion
    397 
    398                //#region Create RSA Private Key elements
    399                const privateKeyJSON = privateKeyInfo.toJSON();
    400                Object.assign(jwk, privateKeyJSON);
    401                //#endregion
    402              }
    403              break;
    404            case "ECDSA":
    405              keyUsages = ["sign"]; // Override existing keyUsages value since the key is a private key
    406            // break omitted
    407            // eslint-disable-next-line no-fallthrough
    408            case "ECDH":
    409              {
    410                //#region Initial variables
    411                jwk = {
    412                  kty: "EC",
    413                  ext: extractable,
    414                  key_ops: keyUsages
    415                };
    416                //#endregion
    417 
    418                //#region Get information about used hash function
    419                if (privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.10045.2.1")
    420                  throw new Error(`Incorrect algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`);
    421                //#endregion
    422 
    423                //#region Create ECDSA Private Key elements
    424                const privateKeyJSON = privateKeyInfo.toJSON();
    425                Object.assign(jwk, privateKeyJSON);
    426                //#endregion
    427              }
    428              break;
    429            case "RSA-OAEP":
    430              {
    431                jwk.kty = "RSA";
    432                jwk.ext = extractable;
    433                jwk.key_ops = keyUsages;
    434 
    435                //#region Get information about used hash function
    436                if (this.name.toLowerCase() === "safari")
    437                  jwk.alg = "RSA-OAEP";
    438                else {
    439                  switch (alg.hash?.name.toUpperCase()) {
    440                    case "SHA-1":
    441                      jwk.alg = "RSA-OAEP";
    442                      break;
    443                    case "SHA-256":
    444                      jwk.alg = "RSA-OAEP-256";
    445                      break;
    446                    case "SHA-384":
    447                      jwk.alg = "RSA-OAEP-384";
    448                      break;
    449                    case "SHA-512":
    450                      jwk.alg = "RSA-OAEP-512";
    451                      break;
    452                    default:
    453                      throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`);
    454                  }
    455                }
    456                //#endregion
    457 
    458                //#region Create RSA Private Key elements
    459                const privateKeyJSON = privateKeyInfo.toJSON();
    460                Object.assign(jwk, privateKeyJSON);
    461                //#endregion
    462              }
    463              break;
    464            case "RSAES-PKCS1-V1_5":
    465              {
    466                keyUsages = ["decrypt"]; // Override existing keyUsages value since the key is a private key
    467 
    468                jwk.kty = "RSA";
    469                jwk.ext = extractable;
    470                jwk.key_ops = keyUsages;
    471                jwk.alg = "PS1";
    472 
    473                //#region Create RSA Private Key elements
    474                const privateKeyJSON = privateKeyInfo.toJSON();
    475                Object.assign(jwk, privateKeyJSON);
    476                //#endregion
    477              }
    478              break;
    479            default:
    480              throw new Error(`Incorrect algorithm name: ${alg.name.toUpperCase()}`);
    481          }
    482        }
    483        break;
    484      case "jwk":
    485        jwk = keyData as JsonWebKey;
    486        break;
    487      default:
    488        throw new Error(`Incorrect format: ${format}`);
    489    }
    490 
    491    //#region Special case for Safari browser (since its acting not as WebCrypto standard describes)
    492    if (this.name.toLowerCase() === "safari") {
    493      // Try to use both ways - import using ArrayBuffer and pure JWK (for Safari Technology Preview)
    494      try {
    495        return this.subtle.importKey("jwk", pvutils.stringToArrayBuffer(JSON.stringify(jwk)) as any, algorithm, extractable, keyUsages);
    496      } catch {
    497        return this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages);
    498      }
    499    }
    500    //#endregion
    501 
    502    return this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages);
    503  }
    504 
    505  /**
    506   * Export WebCrypto keys to different formats
    507   * @param format
    508   * @param key
    509   */
    510  public override exportKey(format: "jwk", key: CryptoKey): Promise<JsonWebKey>;
    511  public override exportKey(format: Exclude<KeyFormat, "jwk">, key: CryptoKey): Promise<ArrayBuffer>;
    512  public override exportKey(format: string, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey>;
    513  public override async exportKey(format: KeyFormat, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey> {
    514    let jwk = await this.subtle.exportKey("jwk", key);
    515 
    516    //#region Currently Safari returns ArrayBuffer as JWK thus we need an additional transformation
    517    if (this.name.toLowerCase() === "safari") {
    518      // Some additional checks for Safari Technology Preview
    519      if (jwk instanceof ArrayBuffer) {
    520        jwk = JSON.parse(pvutils.arrayBufferToString(jwk));
    521      }
    522    }
    523    //#endregion
    524 
    525    switch (format.toLowerCase()) {
    526      case "raw":
    527        return this.subtle.exportKey("raw", key);
    528      case "spki": {
    529        const publicKeyInfo = new PublicKeyInfo();
    530 
    531        try {
    532          publicKeyInfo.fromJSON(jwk);
    533        }
    534        catch {
    535          throw new Error("Incorrect key data");
    536        }
    537 
    538        return publicKeyInfo.toSchema().toBER(false);
    539      }
    540      case "pkcs8": {
    541        const privateKeyInfo = new PrivateKeyInfo();
    542 
    543        try {
    544          privateKeyInfo.fromJSON(jwk);
    545        }
    546        catch {
    547          throw new Error("Incorrect key data");
    548        }
    549 
    550        return privateKeyInfo.toSchema().toBER(false);
    551      }
    552      case "jwk":
    553        return jwk;
    554      default:
    555        throw new Error(`Incorrect format: ${format}`);
    556    }
    557  }
    558 
    559  /**
    560   * Convert WebCrypto keys between different export formats
    561   * @param  inputFormat
    562   * @param  outputFormat
    563   * @param  keyData
    564   * @param  algorithm
    565   * @param  extractable
    566   * @param  keyUsages
    567   */
    568  public async convert(inputFormat: KeyFormat, outputFormat: KeyFormat, keyData: ArrayBuffer | JsonWebKey, algorithm: Algorithm, extractable: boolean, keyUsages: KeyUsage[]) {
    569    if (inputFormat.toLowerCase() === outputFormat.toLowerCase()) {
    570      return keyData;
    571    }
    572 
    573    const key = await this.importKey(inputFormat, keyData, algorithm, extractable, keyUsages);
    574    return this.exportKey(outputFormat, key);
    575  }
    576 
    577  /**
    578   * Gets WebCrypto algorithm by wel-known OID
    579   * @param oid algorithm identifier
    580   * @param safety if `true` throws exception on unknown algorithm identifier
    581   * @param target name of the target
    582   * @returns Returns WebCrypto algorithm or an empty object
    583   */
    584  public getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety?: boolean, target?: string): T | object;
    585  /**
    586   * Gets WebCrypto algorithm by wel-known OID
    587   * @param oid algorithm identifier
    588   * @param safety if `true` throws exception on unknown algorithm identifier
    589   * @param target name of the target
    590   * @returns Returns WebCrypto algorithm
    591   * @throws Throws {@link Error} exception if unknown algorithm identifier
    592   */
    593  public getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety: true, target?: string): T;
    594  public getAlgorithmByOID(oid: string, safety = false, target?: string): any {
    595    switch (oid) {
    596      case "1.2.840.113549.1.1.1":
    597        return {
    598          name: "RSAES-PKCS1-v1_5"
    599        };
    600      case "1.2.840.113549.1.1.5":
    601        return {
    602          name: "RSASSA-PKCS1-v1_5",
    603          hash: {
    604            name: "SHA-1"
    605          }
    606        };
    607      case "1.2.840.113549.1.1.11":
    608        return {
    609          name: "RSASSA-PKCS1-v1_5",
    610          hash: {
    611            name: "SHA-256"
    612          }
    613        };
    614      case "1.2.840.113549.1.1.12":
    615        return {
    616          name: "RSASSA-PKCS1-v1_5",
    617          hash: {
    618            name: "SHA-384"
    619          }
    620        };
    621      case "1.2.840.113549.1.1.13":
    622        return {
    623          name: "RSASSA-PKCS1-v1_5",
    624          hash: {
    625            name: "SHA-512"
    626          }
    627        };
    628      case "1.2.840.113549.1.1.10":
    629        return {
    630          name: "RSA-PSS"
    631        };
    632      case "1.2.840.113549.1.1.7":
    633        return {
    634          name: "RSA-OAEP"
    635        };
    636      case "1.2.840.10045.2.1":
    637      case "1.2.840.10045.4.1":
    638        return {
    639          name: "ECDSA",
    640          hash: {
    641            name: "SHA-1"
    642          }
    643        };
    644      case "1.2.840.10045.4.3.2":
    645        return {
    646          name: "ECDSA",
    647          hash: {
    648            name: "SHA-256"
    649          }
    650        };
    651      case "1.2.840.10045.4.3.3":
    652        return {
    653          name: "ECDSA",
    654          hash: {
    655            name: "SHA-384"
    656          }
    657        };
    658      case "1.2.840.10045.4.3.4":
    659        return {
    660          name: "ECDSA",
    661          hash: {
    662            name: "SHA-512"
    663          }
    664        };
    665      case "1.3.133.16.840.63.0.2":
    666        return {
    667          name: "ECDH",
    668          kdf: "SHA-1"
    669        };
    670      case "1.3.132.1.11.1":
    671        return {
    672          name: "ECDH",
    673          kdf: "SHA-256"
    674        };
    675      case "1.3.132.1.11.2":
    676        return {
    677          name: "ECDH",
    678          kdf: "SHA-384"
    679        };
    680      case "1.3.132.1.11.3":
    681        return {
    682          name: "ECDH",
    683          kdf: "SHA-512"
    684        };
    685      case "2.16.840.1.101.3.4.1.2":
    686        return {
    687          name: "AES-CBC",
    688          length: 128
    689        };
    690      case "2.16.840.1.101.3.4.1.22":
    691        return {
    692          name: "AES-CBC",
    693          length: 192
    694        };
    695      case "2.16.840.1.101.3.4.1.42":
    696        return {
    697          name: "AES-CBC",
    698          length: 256
    699        };
    700      case "2.16.840.1.101.3.4.1.6":
    701        return {
    702          name: "AES-GCM",
    703          length: 128
    704        };
    705      case "2.16.840.1.101.3.4.1.26":
    706        return {
    707          name: "AES-GCM",
    708          length: 192
    709        };
    710      case "2.16.840.1.101.3.4.1.46":
    711        return {
    712          name: "AES-GCM",
    713          length: 256
    714        };
    715      case "2.16.840.1.101.3.4.1.4":
    716        return {
    717          name: "AES-CFB",
    718          length: 128
    719        };
    720      case "2.16.840.1.101.3.4.1.24":
    721        return {
    722          name: "AES-CFB",
    723          length: 192
    724        };
    725      case "2.16.840.1.101.3.4.1.44":
    726        return {
    727          name: "AES-CFB",
    728          length: 256
    729        };
    730      case "2.16.840.1.101.3.4.1.5":
    731        return {
    732          name: "AES-KW",
    733          length: 128
    734        };
    735      case "2.16.840.1.101.3.4.1.25":
    736        return {
    737          name: "AES-KW",
    738          length: 192
    739        };
    740      case "2.16.840.1.101.3.4.1.45":
    741        return {
    742          name: "AES-KW",
    743          length: 256
    744        };
    745      case "1.2.840.113549.2.7":
    746        return {
    747          name: "HMAC",
    748          hash: {
    749            name: "SHA-1"
    750          }
    751        };
    752      case "1.2.840.113549.2.9":
    753        return {
    754          name: "HMAC",
    755          hash: {
    756            name: "SHA-256"
    757          }
    758        };
    759      case "1.2.840.113549.2.10":
    760        return {
    761          name: "HMAC",
    762          hash: {
    763            name: "SHA-384"
    764          }
    765        };
    766      case "1.2.840.113549.2.11":
    767        return {
    768          name: "HMAC",
    769          hash: {
    770            name: "SHA-512"
    771          }
    772        };
    773      case "1.2.840.113549.1.9.16.3.5":
    774        return {
    775          name: "DH"
    776        };
    777      case "1.3.14.3.2.26":
    778        return {
    779          name: "SHA-1"
    780        };
    781      case "2.16.840.1.101.3.4.2.1":
    782        return {
    783          name: "SHA-256"
    784        };
    785      case "2.16.840.1.101.3.4.2.2":
    786        return {
    787          name: "SHA-384"
    788        };
    789      case "2.16.840.1.101.3.4.2.3":
    790        return {
    791          name: "SHA-512"
    792        };
    793      case "1.2.840.113549.1.5.12":
    794        return {
    795          name: "PBKDF2"
    796        };
    797      //#region Special case - OIDs for ECC curves
    798      case "1.2.840.10045.3.1.7":
    799        return {
    800          name: "P-256"
    801        };
    802      case "1.3.132.0.34":
    803        return {
    804          name: "P-384"
    805        };
    806      case "1.3.132.0.35":
    807        return {
    808          name: "P-521"
    809        };
    810      //#endregion
    811      default:
    812    }
    813 
    814    if (safety) {
    815      throw new Error(`Unsupported algorithm identifier ${target ? `for ${target} ` : EMPTY_STRING}: ${oid}`);
    816    }
    817 
    818    return {};
    819  }
    820 
    821  public getOIDByAlgorithm(algorithm: Algorithm, safety = false, target?: string): string {
    822    let result = EMPTY_STRING;
    823 
    824    switch (algorithm.name.toUpperCase()) {
    825      case "RSAES-PKCS1-V1_5":
    826        result = "1.2.840.113549.1.1.1";
    827        break;
    828      case "RSASSA-PKCS1-V1_5":
    829        switch ((algorithm as any).hash.name.toUpperCase()) {
    830          case "SHA-1":
    831            result = "1.2.840.113549.1.1.5";
    832            break;
    833          case "SHA-256":
    834            result = "1.2.840.113549.1.1.11";
    835            break;
    836          case "SHA-384":
    837            result = "1.2.840.113549.1.1.12";
    838            break;
    839          case "SHA-512":
    840            result = "1.2.840.113549.1.1.13";
    841            break;
    842          default:
    843        }
    844        break;
    845      case "RSA-PSS":
    846        result = "1.2.840.113549.1.1.10";
    847        break;
    848      case "RSA-OAEP":
    849        result = "1.2.840.113549.1.1.7";
    850        break;
    851      case "ECDSA":
    852        switch ((algorithm as any).hash.name.toUpperCase()) {
    853          case "SHA-1":
    854            result = "1.2.840.10045.4.1";
    855            break;
    856          case "SHA-256":
    857            result = "1.2.840.10045.4.3.2";
    858            break;
    859          case "SHA-384":
    860            result = "1.2.840.10045.4.3.3";
    861            break;
    862          case "SHA-512":
    863            result = "1.2.840.10045.4.3.4";
    864            break;
    865          default:
    866        }
    867        break;
    868      case "ECDH":
    869        switch ((algorithm as any).kdf.toUpperCase()) // Non-standard addition - hash algorithm of KDF function
    870        {
    871          case "SHA-1":
    872            result = "1.3.133.16.840.63.0.2"; // dhSinglePass-stdDH-sha1kdf-scheme
    873            break;
    874          case "SHA-256":
    875            result = "1.3.132.1.11.1"; // dhSinglePass-stdDH-sha256kdf-scheme
    876            break;
    877          case "SHA-384":
    878            result = "1.3.132.1.11.2"; // dhSinglePass-stdDH-sha384kdf-scheme
    879            break;
    880          case "SHA-512":
    881            result = "1.3.132.1.11.3"; // dhSinglePass-stdDH-sha512kdf-scheme
    882            break;
    883          default:
    884        }
    885        break;
    886      case "AES-CTR":
    887        break;
    888      case "AES-CBC":
    889        switch ((algorithm as any).length) {
    890          case 128:
    891            result = "2.16.840.1.101.3.4.1.2";
    892            break;
    893          case 192:
    894            result = "2.16.840.1.101.3.4.1.22";
    895            break;
    896          case 256:
    897            result = "2.16.840.1.101.3.4.1.42";
    898            break;
    899          default:
    900        }
    901        break;
    902      case "AES-CMAC":
    903        break;
    904      case "AES-GCM":
    905        switch ((algorithm as any).length) {
    906          case 128:
    907            result = "2.16.840.1.101.3.4.1.6";
    908            break;
    909          case 192:
    910            result = "2.16.840.1.101.3.4.1.26";
    911            break;
    912          case 256:
    913            result = "2.16.840.1.101.3.4.1.46";
    914            break;
    915          default:
    916        }
    917        break;
    918      case "AES-CFB":
    919        switch ((algorithm as any).length) {
    920          case 128:
    921            result = "2.16.840.1.101.3.4.1.4";
    922            break;
    923          case 192:
    924            result = "2.16.840.1.101.3.4.1.24";
    925            break;
    926          case 256:
    927            result = "2.16.840.1.101.3.4.1.44";
    928            break;
    929          default:
    930        }
    931        break;
    932      case "AES-KW":
    933        switch ((algorithm as any).length) {
    934          case 128:
    935            result = "2.16.840.1.101.3.4.1.5";
    936            break;
    937          case 192:
    938            result = "2.16.840.1.101.3.4.1.25";
    939            break;
    940          case 256:
    941            result = "2.16.840.1.101.3.4.1.45";
    942            break;
    943          default:
    944        }
    945        break;
    946      case "HMAC":
    947        switch ((algorithm as any).hash.name.toUpperCase()) {
    948          case "SHA-1":
    949            result = "1.2.840.113549.2.7";
    950            break;
    951          case "SHA-256":
    952            result = "1.2.840.113549.2.9";
    953            break;
    954          case "SHA-384":
    955            result = "1.2.840.113549.2.10";
    956            break;
    957          case "SHA-512":
    958            result = "1.2.840.113549.2.11";
    959            break;
    960          default:
    961        }
    962        break;
    963      case "DH":
    964        result = "1.2.840.113549.1.9.16.3.5";
    965        break;
    966      case "SHA-1":
    967        result = "1.3.14.3.2.26";
    968        break;
    969      case "SHA-256":
    970        result = "2.16.840.1.101.3.4.2.1";
    971        break;
    972      case "SHA-384":
    973        result = "2.16.840.1.101.3.4.2.2";
    974        break;
    975      case "SHA-512":
    976        result = "2.16.840.1.101.3.4.2.3";
    977        break;
    978      case "CONCAT":
    979        break;
    980      case "HKDF":
    981        break;
    982      case "PBKDF2":
    983        result = "1.2.840.113549.1.5.12";
    984        break;
    985      //#region Special case - OIDs for ECC curves
    986      case "P-256":
    987        result = "1.2.840.10045.3.1.7";
    988        break;
    989      case "P-384":
    990        result = "1.3.132.0.34";
    991        break;
    992      case "P-521":
    993        result = "1.3.132.0.35";
    994        break;
    995      //#endregion
    996      default:
    997    }
    998 
    999    if (!result && safety) {
   1000      throw new Error(`Unsupported algorithm ${target ? `for ${target} ` : EMPTY_STRING}: ${algorithm.name}`);
   1001    }
   1002 
   1003    return result;
   1004  }
   1005 
   1006  public getAlgorithmParameters(algorithmName: string, operation: type.CryptoEngineAlgorithmOperation): type.CryptoEngineAlgorithmParams {
   1007    let result: type.CryptoEngineAlgorithmParams = {
   1008      algorithm: {},
   1009      usages: []
   1010    };
   1011 
   1012    switch (algorithmName.toUpperCase()) {
   1013      case "RSAES-PKCS1-V1_5":
   1014      case "RSASSA-PKCS1-V1_5":
   1015        switch (operation.toLowerCase()) {
   1016          case "generatekey":
   1017            result = {
   1018              algorithm: {
   1019                name: "RSASSA-PKCS1-v1_5",
   1020                modulusLength: 2048,
   1021                publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
   1022                hash: {
   1023                  name: "SHA-256"
   1024                }
   1025              },
   1026              usages: ["sign", "verify"]
   1027            };
   1028            break;
   1029          case "verify":
   1030          case "sign":
   1031          case "importkey":
   1032            result = {
   1033              algorithm: {
   1034                name: "RSASSA-PKCS1-v1_5",
   1035                hash: {
   1036                  name: "SHA-256"
   1037                }
   1038              },
   1039              usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only
   1040            };
   1041            break;
   1042          case "exportkey":
   1043          default:
   1044            return {
   1045              algorithm: {
   1046                name: "RSASSA-PKCS1-v1_5"
   1047              },
   1048              usages: []
   1049            };
   1050        }
   1051        break;
   1052      case "RSA-PSS":
   1053        switch (operation.toLowerCase()) {
   1054          case "sign":
   1055          case "verify":
   1056            result = {
   1057              algorithm: {
   1058                name: "RSA-PSS",
   1059                hash: {
   1060                  name: "SHA-1"
   1061                },
   1062                saltLength: 20
   1063              },
   1064              usages: ["sign", "verify"]
   1065            };
   1066            break;
   1067          case "generatekey":
   1068            result = {
   1069              algorithm: {
   1070                name: "RSA-PSS",
   1071                modulusLength: 2048,
   1072                publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
   1073                hash: {
   1074                  name: "SHA-1"
   1075                }
   1076              },
   1077              usages: ["sign", "verify"]
   1078            };
   1079            break;
   1080          case "importkey":
   1081            result = {
   1082              algorithm: {
   1083                name: "RSA-PSS",
   1084                hash: {
   1085                  name: "SHA-1"
   1086                }
   1087              },
   1088              usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only
   1089            };
   1090            break;
   1091          case "exportkey":
   1092          default:
   1093            return {
   1094              algorithm: {
   1095                name: "RSA-PSS"
   1096              },
   1097              usages: []
   1098            };
   1099        }
   1100        break;
   1101      case "RSA-OAEP":
   1102        switch (operation.toLowerCase()) {
   1103          case "encrypt":
   1104          case "decrypt":
   1105            result = {
   1106              algorithm: {
   1107                name: "RSA-OAEP"
   1108              },
   1109              usages: ["encrypt", "decrypt"]
   1110            };
   1111            break;
   1112          case "generatekey":
   1113            result = {
   1114              algorithm: {
   1115                name: "RSA-OAEP",
   1116                modulusLength: 2048,
   1117                publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
   1118                hash: {
   1119                  name: "SHA-256"
   1120                }
   1121              },
   1122              usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
   1123            };
   1124            break;
   1125          case "importkey":
   1126            result = {
   1127              algorithm: {
   1128                name: "RSA-OAEP",
   1129                hash: {
   1130                  name: "SHA-256"
   1131                }
   1132              },
   1133              usages: ["encrypt"] // encrypt for "spki" and decrypt for "pkcs8"
   1134            };
   1135            break;
   1136          case "exportkey":
   1137          default:
   1138            return {
   1139              algorithm: {
   1140                name: "RSA-OAEP"
   1141              },
   1142              usages: []
   1143            };
   1144        }
   1145        break;
   1146      case "ECDSA":
   1147        switch (operation.toLowerCase()) {
   1148          case "generatekey":
   1149            result = {
   1150              algorithm: {
   1151                name: "ECDSA",
   1152                namedCurve: "P-256"
   1153              },
   1154              usages: ["sign", "verify"]
   1155            };
   1156            break;
   1157          case "importkey":
   1158            result = {
   1159              algorithm: {
   1160                name: "ECDSA",
   1161                namedCurve: "P-256"
   1162              },
   1163              usages: ["verify"] // "sign" for "pkcs8"
   1164            };
   1165            break;
   1166          case "verify":
   1167          case "sign":
   1168            result = {
   1169              algorithm: {
   1170                name: "ECDSA",
   1171                hash: {
   1172                  name: "SHA-256"
   1173                }
   1174              },
   1175              usages: ["sign"]
   1176            };
   1177            break;
   1178          default:
   1179            return {
   1180              algorithm: {
   1181                name: "ECDSA"
   1182              },
   1183              usages: []
   1184            };
   1185        }
   1186        break;
   1187      case "ECDH":
   1188        switch (operation.toLowerCase()) {
   1189          case "exportkey":
   1190          case "importkey":
   1191          case "generatekey":
   1192            result = {
   1193              algorithm: {
   1194                name: "ECDH",
   1195                namedCurve: "P-256"
   1196              },
   1197              usages: ["deriveKey", "deriveBits"]
   1198            };
   1199            break;
   1200          case "derivekey":
   1201          case "derivebits":
   1202            result = {
   1203              algorithm: {
   1204                name: "ECDH",
   1205                namedCurve: "P-256",
   1206                public: [] // Must be a "publicKey"
   1207              },
   1208              usages: ["encrypt", "decrypt"]
   1209            };
   1210            break;
   1211          default:
   1212            return {
   1213              algorithm: {
   1214                name: "ECDH"
   1215              },
   1216              usages: []
   1217            };
   1218        }
   1219        break;
   1220      case "AES-CTR":
   1221        switch (operation.toLowerCase()) {
   1222          case "importkey":
   1223          case "exportkey":
   1224          case "generatekey":
   1225            result = {
   1226              algorithm: {
   1227                name: "AES-CTR",
   1228                length: 256
   1229              },
   1230              usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
   1231            };
   1232            break;
   1233          case "decrypt":
   1234          case "encrypt":
   1235            result = {
   1236              algorithm: {
   1237                name: "AES-CTR",
   1238                counter: new Uint8Array(16),
   1239                length: 10
   1240              },
   1241              usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
   1242            };
   1243            break;
   1244          default:
   1245            return {
   1246              algorithm: {
   1247                name: "AES-CTR"
   1248              },
   1249              usages: []
   1250            };
   1251        }
   1252        break;
   1253      case "AES-CBC":
   1254        switch (operation.toLowerCase()) {
   1255          case "importkey":
   1256          case "exportkey":
   1257          case "generatekey":
   1258            result = {
   1259              algorithm: {
   1260                name: "AES-CBC",
   1261                length: 256
   1262              },
   1263              usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
   1264            };
   1265            break;
   1266          case "decrypt":
   1267          case "encrypt":
   1268            result = {
   1269              algorithm: {
   1270                name: "AES-CBC",
   1271                iv: this.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step
   1272              },
   1273              usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
   1274            };
   1275            break;
   1276          default:
   1277            return {
   1278              algorithm: {
   1279                name: "AES-CBC"
   1280              },
   1281              usages: []
   1282            };
   1283        }
   1284        break;
   1285      case "AES-GCM":
   1286        switch (operation.toLowerCase()) {
   1287          case "importkey":
   1288          case "exportkey":
   1289          case "generatekey":
   1290            result = {
   1291              algorithm: {
   1292                name: "AES-GCM",
   1293                length: 256
   1294              },
   1295              usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
   1296            };
   1297            break;
   1298          case "decrypt":
   1299          case "encrypt":
   1300            result = {
   1301              algorithm: {
   1302                name: "AES-GCM",
   1303                iv: this.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step
   1304              },
   1305              usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
   1306            };
   1307            break;
   1308          default:
   1309            return {
   1310              algorithm: {
   1311                name: "AES-GCM"
   1312              },
   1313              usages: []
   1314            };
   1315        }
   1316        break;
   1317      case "AES-KW":
   1318        switch (operation.toLowerCase()) {
   1319          case "importkey":
   1320          case "exportkey":
   1321          case "generatekey":
   1322          case "wrapkey":
   1323          case "unwrapkey":
   1324            result = {
   1325              algorithm: {
   1326                name: "AES-KW",
   1327                length: 256
   1328              },
   1329              usages: ["wrapKey", "unwrapKey"]
   1330            };
   1331            break;
   1332          default:
   1333            return {
   1334              algorithm: {
   1335                name: "AES-KW"
   1336              },
   1337              usages: []
   1338            };
   1339        }
   1340        break;
   1341      case "HMAC":
   1342        switch (operation.toLowerCase()) {
   1343          case "sign":
   1344          case "verify":
   1345            result = {
   1346              algorithm: {
   1347                name: "HMAC"
   1348              },
   1349              usages: ["sign", "verify"]
   1350            };
   1351            break;
   1352          case "importkey":
   1353          case "exportkey":
   1354          case "generatekey":
   1355            result = {
   1356              algorithm: {
   1357                name: "HMAC",
   1358                length: 32,
   1359                hash: {
   1360                  name: "SHA-256"
   1361                }
   1362              },
   1363              usages: ["sign", "verify"]
   1364            };
   1365            break;
   1366          default:
   1367            return {
   1368              algorithm: {
   1369                name: "HMAC"
   1370              },
   1371              usages: []
   1372            };
   1373        }
   1374        break;
   1375      case "HKDF":
   1376        switch (operation.toLowerCase()) {
   1377          case "derivekey":
   1378            result = {
   1379              algorithm: {
   1380                name: "HKDF",
   1381                hash: "SHA-256",
   1382                salt: new Uint8Array([]),
   1383                info: new Uint8Array([])
   1384              },
   1385              usages: ["encrypt", "decrypt"]
   1386            };
   1387            break;
   1388          default:
   1389            return {
   1390              algorithm: {
   1391                name: "HKDF"
   1392              },
   1393              usages: []
   1394            };
   1395        }
   1396        break;
   1397      case "PBKDF2":
   1398        switch (operation.toLowerCase()) {
   1399          case "derivekey":
   1400            result = {
   1401              algorithm: {
   1402                name: "PBKDF2",
   1403                hash: { name: "SHA-256" },
   1404                salt: new Uint8Array([]),
   1405                iterations: 10000
   1406              },
   1407              usages: ["encrypt", "decrypt"]
   1408            };
   1409            break;
   1410          default:
   1411            return {
   1412              algorithm: {
   1413                name: "PBKDF2"
   1414              },
   1415              usages: []
   1416            };
   1417        }
   1418        break;
   1419      default:
   1420    }
   1421 
   1422    return result;
   1423  }
   1424 
   1425  /**
   1426   * Getting hash algorithm by signature algorithm
   1427   * @param signatureAlgorithm Signature algorithm
   1428   */
   1429  // TODO use safety
   1430  getHashAlgorithm(signatureAlgorithm: AlgorithmIdentifier): string {
   1431    let result = EMPTY_STRING;
   1432 
   1433    switch (signatureAlgorithm.algorithmId) {
   1434      case "1.2.840.10045.4.1": // ecdsa-with-SHA1
   1435      case "1.2.840.113549.1.1.5": // rsa-encryption-with-SHA1
   1436        result = "SHA-1";
   1437        break;
   1438      case "1.2.840.10045.4.3.2": // ecdsa-with-SHA256
   1439      case "1.2.840.113549.1.1.11": // rsa-encryption-with-SHA256
   1440        result = "SHA-256";
   1441        break;
   1442      case "1.2.840.10045.4.3.3": // ecdsa-with-SHA384
   1443      case "1.2.840.113549.1.1.12": // rsa-encryption-with-SHA384
   1444        result = "SHA-384";
   1445        break;
   1446      case "1.2.840.10045.4.3.4": // ecdsa-with-SHA512
   1447      case "1.2.840.113549.1.1.13": // rsa-encryption-with-SHA512
   1448        result = "SHA-512";
   1449        break;
   1450      case "1.2.840.113549.1.1.10": // RSA-PSS
   1451        {
   1452          try {
   1453            const params = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams });
   1454            if (params.hashAlgorithm) {
   1455              const algorithm = this.getAlgorithmByOID(params.hashAlgorithm.algorithmId);
   1456              if ("name" in algorithm) {
   1457                result = algorithm.name;
   1458              }
   1459              else {
   1460                return EMPTY_STRING;
   1461              }
   1462            }
   1463            else
   1464              result = "SHA-1";
   1465          }
   1466          catch {
   1467            // nothing
   1468          }
   1469        }
   1470        break;
   1471      default:
   1472    }
   1473 
   1474    return result;
   1475  }
   1476 
   1477  public async encryptEncryptedContentInfo(parameters: type.CryptoEngineEncryptParams): Promise<EncryptedContentInfo> {
   1478    //#region Check for input parameters
   1479    ParameterError.assert(parameters,
   1480      "password", "contentEncryptionAlgorithm", "hmacHashAlgorithm",
   1481      "iterationCount", "contentToEncrypt", "contentToEncrypt", "contentType");
   1482 
   1483    const contentEncryptionOID = this.getOIDByAlgorithm(parameters.contentEncryptionAlgorithm, true, "contentEncryptionAlgorithm");
   1484 
   1485    const pbkdf2OID = this.getOIDByAlgorithm({
   1486      name: "PBKDF2"
   1487    }, true, "PBKDF2");
   1488    const hmacOID = this.getOIDByAlgorithm({
   1489      name: "HMAC",
   1490      hash: {
   1491        name: parameters.hmacHashAlgorithm
   1492      }
   1493    } as Algorithm, true, "hmacHashAlgorithm");
   1494    //#endregion
   1495 
   1496    //#region Initial variables
   1497 
   1498    // TODO Should we reuse iv from parameters.contentEncryptionAlgorithm or use it's length for ivBuffer?
   1499    const ivBuffer = new ArrayBuffer(16); // For AES we need IV 16 bytes long
   1500    const ivView = new Uint8Array(ivBuffer);
   1501    this.getRandomValues(ivView);
   1502 
   1503    const saltBuffer = new ArrayBuffer(64);
   1504    const saltView = new Uint8Array(saltBuffer);
   1505    this.getRandomValues(saltView);
   1506 
   1507    const contentView = new Uint8Array(parameters.contentToEncrypt);
   1508 
   1509    const pbkdf2Params = new PBKDF2Params({
   1510      salt: new asn1js.OctetString({ valueHex: saltBuffer }),
   1511      iterationCount: parameters.iterationCount,
   1512      prf: new AlgorithmIdentifier({
   1513        algorithmId: hmacOID,
   1514        algorithmParams: new asn1js.Null()
   1515      })
   1516    });
   1517    //#endregion
   1518 
   1519    //#region Derive PBKDF2 key from "password" buffer
   1520    const passwordView = new Uint8Array(parameters.password);
   1521 
   1522    const pbkdfKey = await this.importKey("raw",
   1523      passwordView,
   1524      "PBKDF2",
   1525      false,
   1526      ["deriveKey"]);
   1527 
   1528    //#endregion
   1529 
   1530    //#region Derive key for "contentEncryptionAlgorithm"
   1531    const derivedKey = await this.deriveKey({
   1532      name: "PBKDF2",
   1533      hash: {
   1534        name: parameters.hmacHashAlgorithm
   1535      },
   1536      salt: saltView,
   1537      iterations: parameters.iterationCount
   1538    },
   1539      pbkdfKey,
   1540      parameters.contentEncryptionAlgorithm,
   1541      false,
   1542      ["encrypt"]);
   1543    //#endregion
   1544 
   1545    //#region Encrypt content
   1546    // TODO encrypt doesn't use all parameters from parameters.contentEncryptionAlgorithm (eg additionalData and tagLength for AES-GCM)
   1547    const encryptedData = await this.encrypt(
   1548      {
   1549        name: parameters.contentEncryptionAlgorithm.name,
   1550        iv: ivView
   1551      },
   1552      derivedKey,
   1553      contentView);
   1554    //#endregion
   1555 
   1556    //#region Store all parameters in EncryptedData object
   1557    const pbes2Parameters = new PBES2Params({
   1558      keyDerivationFunc: new AlgorithmIdentifier({
   1559        algorithmId: pbkdf2OID,
   1560        algorithmParams: pbkdf2Params.toSchema()
   1561      }),
   1562      encryptionScheme: new AlgorithmIdentifier({
   1563        algorithmId: contentEncryptionOID,
   1564        algorithmParams: new asn1js.OctetString({ valueHex: ivBuffer })
   1565      })
   1566    });
   1567 
   1568    return new EncryptedContentInfo({
   1569      contentType: parameters.contentType,
   1570      contentEncryptionAlgorithm: new AlgorithmIdentifier({
   1571        algorithmId: "1.2.840.113549.1.5.13", // pkcs5PBES2
   1572        algorithmParams: pbes2Parameters.toSchema()
   1573      }),
   1574      encryptedContent: new asn1js.OctetString({ valueHex: encryptedData })
   1575    });
   1576    //#endregion
   1577  }
   1578 
   1579  /**
   1580   * Decrypt data stored in "EncryptedContentInfo" object using parameters
   1581   * @param parameters
   1582   */
   1583  public async decryptEncryptedContentInfo(parameters: type.CryptoEngineDecryptParams): Promise<ArrayBuffer> {
   1584    //#region Check for input parameters
   1585    ParameterError.assert(parameters, "password", "encryptedContentInfo");
   1586 
   1587    if (parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId !== "1.2.840.113549.1.5.13") // pkcs5PBES2
   1588      throw new Error(`Unknown "contentEncryptionAlgorithm": ${parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId}`);
   1589    //#endregion
   1590 
   1591    //#region Initial variables
   1592    let pbes2Parameters: PBES2Params;
   1593 
   1594    try {
   1595      pbes2Parameters = new PBES2Params({ schema: parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams });
   1596    }
   1597    catch {
   1598      throw new Error("Incorrectly encoded \"pbes2Parameters\"");
   1599    }
   1600 
   1601    let pbkdf2Params;
   1602 
   1603    try {
   1604      pbkdf2Params = new PBKDF2Params({ schema: pbes2Parameters.keyDerivationFunc.algorithmParams });
   1605    }
   1606    catch {
   1607      throw new Error("Incorrectly encoded \"pbkdf2Params\"");
   1608    }
   1609 
   1610    const contentEncryptionAlgorithm = this.getAlgorithmByOID(pbes2Parameters.encryptionScheme.algorithmId, true);
   1611 
   1612    const ivBuffer = pbes2Parameters.encryptionScheme.algorithmParams.valueBlock.valueHex;
   1613    const ivView = new Uint8Array(ivBuffer);
   1614 
   1615    const saltBuffer = pbkdf2Params.salt.valueBlock.valueHex;
   1616    const saltView = new Uint8Array(saltBuffer);
   1617 
   1618    const iterationCount = pbkdf2Params.iterationCount;
   1619 
   1620    let hmacHashAlgorithm = "SHA-1";
   1621 
   1622    if (pbkdf2Params.prf) {
   1623      const algorithm = this.getAlgorithmByOID<any>(pbkdf2Params.prf.algorithmId, true);
   1624      hmacHashAlgorithm = algorithm.hash.name;
   1625    }
   1626    //#endregion
   1627 
   1628    //#region Derive PBKDF2 key from "password" buffer
   1629    const pbkdfKey = await this.importKey("raw",
   1630      parameters.password,
   1631      "PBKDF2",
   1632      false,
   1633      ["deriveKey"]);
   1634    //#endregion
   1635 
   1636    //#region Derive key for "contentEncryptionAlgorithm"
   1637    const result = await this.deriveKey(
   1638      {
   1639        name: "PBKDF2",
   1640        hash: {
   1641          name: hmacHashAlgorithm
   1642        },
   1643        salt: saltView,
   1644        iterations: iterationCount
   1645      },
   1646      pbkdfKey,
   1647      contentEncryptionAlgorithm as any,
   1648      false,
   1649      ["decrypt"]);
   1650    //#endregion
   1651 
   1652    //#region Decrypt internal content using derived key
   1653    //#region Create correct data block for decryption
   1654    const dataBuffer = parameters.encryptedContentInfo.getEncryptedContent();
   1655    //#endregion
   1656 
   1657    return this.decrypt({
   1658      name: contentEncryptionAlgorithm.name,
   1659      iv: ivView
   1660    },
   1661      result,
   1662      dataBuffer);
   1663    //#endregion
   1664  }
   1665 
   1666  public async stampDataWithPassword(parameters: type.CryptoEngineStampDataWithPasswordParams): Promise<ArrayBuffer> {
   1667    //#region Check for input parameters
   1668    if ((parameters instanceof Object) === false)
   1669      throw new Error("Parameters must have type \"Object\"");
   1670 
   1671    ParameterError.assert(parameters, "password", "hashAlgorithm", "iterationCount", "salt", "contentToStamp");
   1672    //#endregion
   1673 
   1674    //#region Choose correct length for HMAC key
   1675    let length: number;
   1676 
   1677    switch (parameters.hashAlgorithm.toLowerCase()) {
   1678      case "sha-1":
   1679        length = 160;
   1680        break;
   1681      case "sha-256":
   1682        length = 256;
   1683        break;
   1684      case "sha-384":
   1685        length = 384;
   1686        break;
   1687      case "sha-512":
   1688        length = 512;
   1689        break;
   1690      default:
   1691        throw new Error(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`);
   1692    }
   1693    //#endregion
   1694 
   1695    //#region Initial variables
   1696    const hmacAlgorithm = {
   1697      name: "HMAC",
   1698      length,
   1699      hash: {
   1700        name: parameters.hashAlgorithm
   1701      }
   1702    };
   1703    //#endregion
   1704 
   1705    //#region Create PKCS#12 key for integrity checking
   1706    const pkcsKey = await makePKCS12B2Key(parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount);
   1707    //#endregion
   1708 
   1709    //#region Import HMAC key
   1710 
   1711    const hmacKey = await this.importKey("raw",
   1712      new Uint8Array(pkcsKey),
   1713      hmacAlgorithm,
   1714      false,
   1715      ["sign"]);
   1716    //#endregion
   1717 
   1718    //#region Make signed HMAC value
   1719    return this.sign(hmacAlgorithm, hmacKey, new Uint8Array(parameters.contentToStamp));
   1720    //#endregion
   1721  }
   1722 
   1723  public async verifyDataStampedWithPassword(parameters: type.CryptoEngineVerifyDataStampedWithPasswordParams): Promise<boolean> {
   1724    //#region Check for input parameters
   1725    ParameterError.assert(parameters,
   1726      "password", "hashAlgorithm", "salt",
   1727      "iterationCount", "contentToVerify", "signatureToVerify");
   1728    //#endregion
   1729 
   1730    //#region Choose correct length for HMAC key
   1731    let length = 0;
   1732 
   1733    switch (parameters.hashAlgorithm.toLowerCase()) {
   1734      case "sha-1":
   1735        length = 160;
   1736        break;
   1737      case "sha-256":
   1738        length = 256;
   1739        break;
   1740      case "sha-384":
   1741        length = 384;
   1742        break;
   1743      case "sha-512":
   1744        length = 512;
   1745        break;
   1746      default:
   1747        throw new Error(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`);
   1748    }
   1749    //#endregion
   1750 
   1751    //#region Initial variables
   1752    const hmacAlgorithm = {
   1753      name: "HMAC",
   1754      length,
   1755      hash: {
   1756        name: parameters.hashAlgorithm
   1757      }
   1758    };
   1759    //#endregion
   1760 
   1761    //#region Create PKCS#12 key for integrity checking
   1762    const pkcsKey = await makePKCS12B2Key(parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount);
   1763    //#endregion
   1764 
   1765    //#region Import HMAC key
   1766    const hmacKey = await this.importKey("raw",
   1767      new Uint8Array(pkcsKey),
   1768      hmacAlgorithm,
   1769      false,
   1770      ["verify"]);
   1771    //#endregion
   1772 
   1773    //#region Make signed HMAC value
   1774    return this.verify(hmacAlgorithm, hmacKey, new Uint8Array(parameters.signatureToVerify), new Uint8Array(parameters.contentToVerify));
   1775    //#endregion
   1776  }
   1777 
   1778  public async getSignatureParameters(privateKey: CryptoKey, hashAlgorithm = "SHA-1"): Promise<type.CryptoEngineSignatureParams> {
   1779    // Check hashing algorithm
   1780    this.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm");
   1781 
   1782    // Initial variables
   1783    const signatureAlgorithm = new AlgorithmIdentifier();
   1784 
   1785    //#region Get "default parameters" for the current algorithm
   1786    const parameters = this.getAlgorithmParameters(privateKey.algorithm.name, "sign");
   1787    if (!Object.keys(parameters.algorithm).length) {
   1788      throw new Error("Parameter 'algorithm' is empty");
   1789    }
   1790    // Use the hash from the privateKey.algorithm.hash.name for keys with hash algorithms (like RSA)
   1791    const algorithm = parameters.algorithm as any; // TODO remove `as any`
   1792    if ("hash" in privateKey.algorithm && privateKey.algorithm.hash && (privateKey.algorithm.hash as Algorithm).name) {
   1793      algorithm.hash.name = (privateKey.algorithm.hash as Algorithm).name;
   1794    } else {
   1795      algorithm.hash.name = hashAlgorithm;
   1796    }
   1797    //#endregion
   1798 
   1799    //#region Fill internal structures based on "privateKey" and "hashAlgorithm"
   1800    switch (privateKey.algorithm.name.toUpperCase()) {
   1801      case "RSASSA-PKCS1-V1_5":
   1802      case "ECDSA":
   1803        signatureAlgorithm.algorithmId = this.getOIDByAlgorithm(algorithm, true);
   1804        break;
   1805      case "RSA-PSS":
   1806        {
   1807          //#region Set "saltLength" as the length (in octets) of the hash function result
   1808          switch (algorithm.hash.name.toUpperCase()) {
   1809            case "SHA-256":
   1810              algorithm.saltLength = 32;
   1811              break;
   1812            case "SHA-384":
   1813              algorithm.saltLength = 48;
   1814              break;
   1815            case "SHA-512":
   1816              algorithm.saltLength = 64;
   1817              break;
   1818            default:
   1819          }
   1820          //#endregion
   1821 
   1822          //#region Fill "RSASSA_PSS_params" object
   1823          const paramsObject: Partial<IRSASSAPSSParams> = {};
   1824 
   1825          if (algorithm.hash.name.toUpperCase() !== "SHA-1") {
   1826            const hashAlgorithmOID = this.getOIDByAlgorithm({ name: algorithm.hash.name }, true, "hashAlgorithm");
   1827 
   1828            paramsObject.hashAlgorithm = new AlgorithmIdentifier({
   1829              algorithmId: hashAlgorithmOID,
   1830              algorithmParams: new asn1js.Null()
   1831            });
   1832 
   1833            paramsObject.maskGenAlgorithm = new AlgorithmIdentifier({
   1834              algorithmId: "1.2.840.113549.1.1.8", // MGF1
   1835              algorithmParams: paramsObject.hashAlgorithm.toSchema()
   1836            });
   1837          }
   1838 
   1839          if (algorithm.saltLength !== 20)
   1840            paramsObject.saltLength = algorithm.saltLength;
   1841 
   1842          const pssParameters = new RSASSAPSSParams(paramsObject);
   1843          //#endregion
   1844 
   1845          //#region Automatically set signature algorithm
   1846          signatureAlgorithm.algorithmId = "1.2.840.113549.1.1.10";
   1847          signatureAlgorithm.algorithmParams = pssParameters.toSchema();
   1848          //#endregion
   1849        }
   1850        break;
   1851      default:
   1852        throw new Error(`Unsupported signature algorithm: ${privateKey.algorithm.name}`);
   1853    }
   1854    //#endregion
   1855 
   1856    return {
   1857      signatureAlgorithm,
   1858      parameters
   1859    };
   1860  }
   1861 
   1862  public async signWithPrivateKey(data: BufferSource, privateKey: CryptoKey, parameters: type.CryptoEngineSignWithPrivateKeyParams): Promise<ArrayBuffer> {
   1863    const signature = await this.sign(parameters.algorithm,
   1864      privateKey,
   1865      data);
   1866 
   1867    //#region Special case for ECDSA algorithm
   1868    if (parameters.algorithm.name === "ECDSA") {
   1869      return common.createCMSECDSASignature(signature);
   1870    }
   1871    //#endregion
   1872 
   1873    return signature;
   1874  }
   1875 
   1876  public fillPublicKeyParameters(publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier): type.CryptoEnginePublicKeyParams {
   1877    const parameters = {} as any;
   1878 
   1879    //#region Find signer's hashing algorithm
   1880    const shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm);
   1881    if (shaAlgorithm === EMPTY_STRING)
   1882      throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`);
   1883    //#endregion
   1884 
   1885    //#region Get information about public key algorithm and default parameters for import
   1886    let algorithmId: string;
   1887    if (signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10")
   1888      algorithmId = signatureAlgorithm.algorithmId;
   1889    else
   1890      algorithmId = publicKeyInfo.algorithm.algorithmId;
   1891 
   1892    const algorithmObject = this.getAlgorithmByOID(algorithmId, true);
   1893 
   1894    parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importKey");
   1895    if ("hash" in parameters.algorithm.algorithm)
   1896      parameters.algorithm.algorithm.hash.name = shaAlgorithm;
   1897 
   1898    //#region Special case for ECDSA
   1899    if (algorithmObject.name === "ECDSA") {
   1900      //#region Get information about named curve
   1901      const publicKeyAlgorithm = publicKeyInfo.algorithm;
   1902      if (!publicKeyAlgorithm.algorithmParams) {
   1903        throw new Error("Algorithm parameters for ECDSA public key are missed");
   1904      }
   1905      const publicKeyAlgorithmParams = publicKeyAlgorithm.algorithmParams;
   1906      if ("idBlock" in publicKeyAlgorithm.algorithmParams) {
   1907        if (!((publicKeyAlgorithmParams.idBlock.tagClass === 1) && (publicKeyAlgorithmParams.idBlock.tagNumber === 6))) {
   1908          throw new Error("Incorrect type for ECDSA public key parameters");
   1909        }
   1910      }
   1911 
   1912      const curveObject = this.getAlgorithmByOID(publicKeyAlgorithmParams.valueBlock.toString(), true);
   1913      //#endregion
   1914 
   1915      parameters.algorithm.algorithm.namedCurve = curveObject.name;
   1916    }
   1917    //#endregion
   1918    //#endregion
   1919 
   1920    return parameters;
   1921  }
   1922 
   1923  public async getPublicKey(publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, parameters?: type.CryptoEnginePublicKeyParams): Promise<CryptoKey> {
   1924    if (!parameters) {
   1925      parameters = this.fillPublicKeyParameters(publicKeyInfo, signatureAlgorithm);
   1926    }
   1927 
   1928    const publicKeyInfoBuffer = publicKeyInfo.toSchema().toBER(false);
   1929 
   1930    return this.importKey("spki",
   1931      publicKeyInfoBuffer,
   1932      parameters.algorithm.algorithm as Algorithm,
   1933      true,
   1934      parameters.algorithm.usages
   1935    );
   1936  }
   1937 
   1938  public async verifyWithPublicKey(data: BufferSource, signature: asn1js.BitString | asn1js.OctetString, publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, shaAlgorithm?: string): Promise<boolean> {
   1939    //#region Find signer's hashing algorithm
   1940    let publicKey: CryptoKey;
   1941    if (!shaAlgorithm) {
   1942      shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm);
   1943      if (!shaAlgorithm)
   1944        throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`);
   1945 
   1946      //#region Import public key
   1947      publicKey = await this.getPublicKey(publicKeyInfo, signatureAlgorithm);
   1948      //#endregion
   1949    } else {
   1950      const parameters = {} as type.CryptoEnginePublicKeyParams;
   1951 
   1952      //#region Get information about public key algorithm and default parameters for import
   1953      let algorithmId;
   1954      if (signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10")
   1955        algorithmId = signatureAlgorithm.algorithmId;
   1956      else
   1957        algorithmId = publicKeyInfo.algorithm.algorithmId;
   1958 
   1959      const algorithmObject = this.getAlgorithmByOID(algorithmId, true);
   1960 
   1961      parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importKey");
   1962      if ("hash" in parameters.algorithm.algorithm)
   1963        (parameters.algorithm.algorithm as any).hash.name = shaAlgorithm;
   1964 
   1965      //#region Special case for ECDSA
   1966      if (algorithmObject.name === "ECDSA") {
   1967        //#region Get information about named curve
   1968        let algorithmParamsChecked = false;
   1969 
   1970        if (("algorithmParams" in publicKeyInfo.algorithm) === true) {
   1971          if ("idBlock" in publicKeyInfo.algorithm.algorithmParams) {
   1972            if ((publicKeyInfo.algorithm.algorithmParams.idBlock.tagClass === 1) && (publicKeyInfo.algorithm.algorithmParams.idBlock.tagNumber === 6))
   1973              algorithmParamsChecked = true;
   1974          }
   1975        }
   1976 
   1977        if (algorithmParamsChecked === false) {
   1978          throw new Error("Incorrect type for ECDSA public key parameters");
   1979        }
   1980 
   1981        const curveObject = this.getAlgorithmByOID(publicKeyInfo.algorithm.algorithmParams.valueBlock.toString(), true);
   1982        //#endregion
   1983 
   1984        (parameters.algorithm.algorithm as any).namedCurve = curveObject.name;
   1985      }
   1986      //#endregion
   1987      //#endregion
   1988 
   1989      //#region Import public key
   1990 
   1991      publicKey = await this.getPublicKey(publicKeyInfo, null as any, parameters); // TODO null!!!
   1992      //#endregion
   1993    }
   1994    //#endregion
   1995 
   1996    //#region Verify signature
   1997    //#region Get default algorithm parameters for verification
   1998    const algorithm = this.getAlgorithmParameters(publicKey.algorithm.name, "verify");
   1999    if ("hash" in algorithm.algorithm)
   2000      (algorithm.algorithm as any).hash.name = shaAlgorithm;
   2001    //#endregion
   2002 
   2003    //#region Special case for ECDSA signatures
   2004    let signatureValue: Uint8Array | ArrayBuffer = signature.valueBlock.valueHexView;
   2005 
   2006    if (publicKey.algorithm.name === "ECDSA") {
   2007      const namedCurve = ECNamedCurves.find((publicKey.algorithm as EcKeyAlgorithm).namedCurve);
   2008      if (!namedCurve) {
   2009        throw new Error("Unsupported named curve in use");
   2010      }
   2011      const asn1 = asn1js.fromBER(signatureValue);
   2012      AsnError.assert(asn1, "Signature value");
   2013      signatureValue = common.createECDSASignatureFromCMS(asn1.result, namedCurve.size);
   2014    }
   2015    //#endregion
   2016 
   2017    //#region Special case for RSA-PSS
   2018    if (publicKey.algorithm.name === "RSA-PSS") {
   2019      const pssParameters = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams });
   2020 
   2021      if ("saltLength" in pssParameters)
   2022        (algorithm.algorithm as any).saltLength = pssParameters.saltLength;
   2023      else
   2024        (algorithm.algorithm as any).saltLength = 20;
   2025 
   2026      let hashAlgo = "SHA-1";
   2027 
   2028      if ("hashAlgorithm" in pssParameters) {
   2029        const hashAlgorithm = this.getAlgorithmByOID(pssParameters.hashAlgorithm.algorithmId, true);
   2030 
   2031        hashAlgo = hashAlgorithm.name;
   2032      }
   2033 
   2034      (algorithm.algorithm as any).hash.name = hashAlgo;
   2035    }
   2036    //#endregion
   2037 
   2038    return this.verify((algorithm.algorithm as any),
   2039      publicKey,
   2040      signatureValue as BufferSource,
   2041      data,
   2042    );
   2043    //#endregion
   2044  }
   2045 
   2046 }