Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/js/PKI.js/src/CryptoEngine/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 63 kB image not shown  

Quelle  CryptoEngine.ts   Sprache: unbekannt

 
Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import * as asn1js from "asn1js";
import * as pvutils from "pvutils";
import * as pvtsutils from "pvtsutils";
import * as common from "../common";
import { PublicKeyInfo } from "../PublicKeyInfo";
import { PrivateKeyInfo } from "../PrivateKeyInfo";
import { AlgorithmIdentifier } from "../AlgorithmIdentifier";
import { EncryptedContentInfo } from "../EncryptedContentInfo";
import { IRSASSAPSSParams, RSASSAPSSParams } from "../RSASSAPSSParams";
import { PBKDF2Params } from "../PBKDF2Params";
import { PBES2Params } from "../PBES2Params";
import { ArgumentError, AsnError, ParameterError } from "../errors";
import * as type from "./CryptoEngineInterface";
import { AbstractCryptoEngine } from "./AbstractCryptoEngine";
import { EMPTY_STRING } from "../constants";
import { ECNamedCurves } from "../ECNamedCurves";

/**
 * Making MAC key using algorithm described in B.2 of PKCS#12 standard.
 */
async function makePKCS12B2Key(cryptoEngine: CryptoEngine, hashAlgorithm: string, keyLength: number, password: ArrayBuffer, salt: ArrayBuffer, iterationCount: number) {
  //#region Initial variables
  let u: number;
  let v: number;

  const result: number[] = [];
  //#endregion

  //#region Get "u" and "v" values
  switch (hashAlgorithm.toUpperCase()) {
    case "SHA-1":
      u = 20; // 160
      v = 64; // 512
      break;
    case "SHA-256":
      u = 32; // 256
      v = 64; // 512
      break;
    case "SHA-384":
      u = 48; // 384
      v = 128; // 1024
      break;
    case "SHA-512":
      u = 64; // 512
      v = 128; // 1024
      break;
    default:
      throw new Error("Unsupported hashing algorithm");
  }
  //#endregion

  //#region Main algorithm making key
  //#region Transform password to UTF-8 like string
  const passwordViewInitial = new Uint8Array(password);

  const passwordTransformed = new ArrayBuffer((password.byteLength * 2) + 2);
  const passwordTransformedView = new Uint8Array(passwordTransformed);

  for (let i = 0; i < passwordViewInitial.length; i++) {
    passwordTransformedView[i * 2] = 0x00;
    passwordTransformedView[i * 2 + 1] = passwordViewInitial[i];
  }

  passwordTransformedView[passwordTransformedView.length - 2] = 0x00;
  passwordTransformedView[passwordTransformedView.length - 1] = 0x00;

  password = passwordTransformed.slice(0);
  //#endregion

  //#region Construct a string D (the "diversifier") by concatenating v/8 copies of ID
  const D = new ArrayBuffer(v);
  const dView = new Uint8Array(D);

  for (let i = 0; i < D.byteLength; i++)
    dView[i] = 3; // The ID value equal to "3" for MACing (see B.3 of standard)
  //#endregion

  //#region Concatenate copies of the salt together to create a string S of length v * ceil(s / v) bytes (the final copy of the salt may be trunacted to create S)
  const saltLength = salt.byteLength;

  const sLen = v * Math.ceil(saltLength / v);
  const S = new ArrayBuffer(sLen);
  const sView = new Uint8Array(S);

  const saltView = new Uint8Array(salt);

  for (let i = 0; i < sLen; i++)
    sView[i] = saltView[i % saltLength];
  //#endregion

  //#region Concatenate copies of the password together to create a string P of length v * ceil(p / v) bytes (the final copy of the password may be truncated to create P)
  const passwordLength = password.byteLength;

  const pLen = v * Math.ceil(passwordLength / v);
  const P = new ArrayBuffer(pLen);
  const pView = new Uint8Array(P);

  const passwordView = new Uint8Array(password);

  for (let i = 0; i < pLen; i++)
    pView[i] = passwordView[i % passwordLength];
  //#endregion

  //#region Set I=S||P to be the concatenation of S and P
  const sPlusPLength = S.byteLength + P.byteLength;

  let I = new ArrayBuffer(sPlusPLength);
  let iView = new Uint8Array(I);

  iView.set(sView);
  iView.set(pView, sView.length);
  //#endregion

  //#region Set c=ceil(n / u)
  const c = Math.ceil((keyLength >> 3) / u);
  //#endregion

  //#region Initial variables
  let internalSequence = Promise.resolve(I);
  //#endregion

  //#region For i=1, 2, ..., c, do the following:
  for (let i = 0; i <= c; i++) {
    internalSequence = internalSequence.then(_I => {
      //#region Create contecanetion of D and I
      const dAndI = new ArrayBuffer(D.byteLength + _I.byteLength);
      const dAndIView = new Uint8Array(dAndI);

      dAndIView.set(dView);
      dAndIView.set(iView, dView.length);
      //#endregion

      return dAndI;
    });

    //#region Make "iterationCount" rounds of hashing
    for (let j = 0; j < iterationCount; j++)
      internalSequence = internalSequence.then(roundBuffer => cryptoEngine.digest({ name: hashAlgorithm }, new Uint8Array(roundBuffer)));
    //#endregion

    internalSequence = internalSequence.then(roundBuffer => {
      //#region Concatenate copies of Ai to create a string B of length v bits (the final copy of Ai may be truncated to create B)
      const B = new ArrayBuffer(v);
      const bView = new Uint8Array(B);

      for (let j = 0; j < B.byteLength; j++)
        bView[j] = (roundBuffer as any)[j % roundBuffer.byteLength]; // TODO roundBuffer is ArrayBuffer. It doesn't have indexed values
      //#endregion

      //#region Make new I value
      const k = Math.ceil(saltLength / v) + Math.ceil(passwordLength / v);
      const iRound = [];

      let sliceStart = 0;
      let sliceLength = v;

      for (let j = 0; j < k; j++) {
        const chunk = Array.from(new Uint8Array(I.slice(sliceStart, sliceStart + sliceLength)));
        sliceStart += v;
        if ((sliceStart + v) > I.byteLength)
          sliceLength = I.byteLength - sliceStart;

        let x = 0x1ff;

        for (let l = (B.byteLength - 1); l >= 0; l--) {
          x >>= 8;
          x += bView[l] + chunk[l];
          chunk[l] = (x & 0xff);
        }

        iRound.push(...chunk);
      }

      I = new ArrayBuffer(iRound.length);
      iView = new Uint8Array(I);

      iView.set(iRound);
      //#endregion

      result.push(...(new Uint8Array(roundBuffer)));

      return I;
    });
  }
  //#endregion

  //#region Initialize final key
  internalSequence = internalSequence.then(() => {
    const resultBuffer = new ArrayBuffer(keyLength >> 3);
    const resultView = new Uint8Array(resultBuffer);

    resultView.set((new Uint8Array(result)).slice(0, keyLength >> 3));

    return resultBuffer;
  });
  //#endregion
  //#endregion

  return internalSequence;
}

function prepareAlgorithm(data: globalThis.AlgorithmIdentifier | EcdsaParams): Algorithm & { hash?: Algorithm; } {
  const res = typeof data === "string"
    ? { name: data }
    : data;

  // TODO fix type casting `as EcdsaParams`
  if ("hash" in (res as EcdsaParams)) {
    return {
      ...res,
      hash: prepareAlgorithm((res as EcdsaParams).hash)
    };
  }

  return res;
}

/**
 * Default cryptographic engine for Web Cryptography API
 */
export class CryptoEngine extends AbstractCryptoEngine {

  public override async importKey(format: KeyFormat, keyData: BufferSource | JsonWebKey, algorithm: globalThis.AlgorithmIdentifier, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKey> {
    //#region Initial variables
    let jwk: JsonWebKey = {};
    //#endregion

    const alg = prepareAlgorithm(algorithm);

    switch (format.toLowerCase()) {
      case "raw":
        return this.subtle.importKey("raw", keyData as BufferSource, algorithm, extractable, keyUsages);
      case "spki":
        {
          const asn1 = asn1js.fromBER(pvtsutils.BufferSourceConverter.toArrayBuffer(keyData as BufferSource));
          AsnError.assert(asn1, "keyData");

          const publicKeyInfo = new PublicKeyInfo();
          try {
            publicKeyInfo.fromSchema(asn1.result);
          } catch {
            throw new ArgumentError("Incorrect keyData");
          }

          switch (alg.name.toUpperCase()) {
            case "RSA-PSS":
              {
                //#region Get information about used hash function
                if (!alg.hash) {
                  throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed");
                }
                switch (alg.hash.name.toUpperCase()) {
                  case "SHA-1":
                    jwk.alg = "PS1";
                    break;
                  case "SHA-256":
                    jwk.alg = "PS256";
                    break;
                  case "SHA-384":
                    jwk.alg = "PS384";
                    break;
                  case "SHA-512":
                    jwk.alg = "PS512";
                    break;
                  default:
                    throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`);
                }
                //#endregion
              }
            // break omitted
            // eslint-disable-next-line no-fallthrough
            case "RSASSA-PKCS1-V1_5":
              {
                keyUsages = ["verify"]; // Override existing keyUsages value since the key is a public key

                jwk.kty = "RSA";
                jwk.ext = extractable;
                jwk.key_ops = keyUsages;

                if (publicKeyInfo.algorithm.algorithmId !== "1.2.840.113549.1.1.1")
                  throw new Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`);

                //#region Get information about used hash function
                if (!jwk.alg) {
                  if (!alg.hash) {
                    throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed");
                  }
                  switch (alg.hash.name.toUpperCase()) {
                    case "SHA-1":
                      jwk.alg = "RS1";
                      break;
                    case "SHA-256":
                      jwk.alg = "RS256";
                      break;
                    case "SHA-384":
                      jwk.alg = "RS384";
                      break;
                    case "SHA-512":
                      jwk.alg = "RS512";
                      break;
                    default:
                      throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`);
                  }
                }
                //#endregion

                //#region Create RSA Public Key elements
                const publicKeyJSON = publicKeyInfo.toJSON();
                Object.assign(jwk, publicKeyJSON);
                //#endregion
              }
              break;
            case "ECDSA":
              keyUsages = ["verify"]; // Override existing keyUsages value since the key is a public key
            // break omitted
            // eslint-disable-next-line no-fallthrough
            case "ECDH":
              {
                //#region Initial variables
                jwk = {
                  kty: "EC",
                  ext: extractable,
                  key_ops: keyUsages
                };
                //#endregion

                //#region Get information about algorithm
                if (publicKeyInfo.algorithm.algorithmId !== "1.2.840.10045.2.1") {
                  throw new Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`);
                }
                //#endregion

                //#region Create ECDSA Public Key elements
                const publicKeyJSON = publicKeyInfo.toJSON();
                Object.assign(jwk, publicKeyJSON);
                //#endregion
              }
              break;
            case "RSA-OAEP":
              {
                jwk.kty = "RSA";
                jwk.ext = extractable;
                jwk.key_ops = keyUsages;

                if (this.name.toLowerCase() === "safari")
                  jwk.alg = "RSA-OAEP";
                else {
                  if (!alg.hash) {
                    throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed");
                  }
                  switch (alg.hash.name.toUpperCase()) {
                    case "SHA-1":
                      jwk.alg = "RSA-OAEP";
                      break;
                    case "SHA-256":
                      jwk.alg = "RSA-OAEP-256";
                      break;
                    case "SHA-384":
                      jwk.alg = "RSA-OAEP-384";
                      break;
                    case "SHA-512":
                      jwk.alg = "RSA-OAEP-512";
                      break;
                    default:
                      throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`);
                  }
                }

                //#region Create ECDSA Public Key elements
                const publicKeyJSON = publicKeyInfo.toJSON();
                Object.assign(jwk, publicKeyJSON);
                //#endregion
              }
              break;
            case "RSAES-PKCS1-V1_5":
              {
                jwk.kty = "RSA";
                jwk.ext = extractable;
                jwk.key_ops = keyUsages;
                jwk.alg = "PS1";

                const publicKeyJSON = publicKeyInfo.toJSON();
                Object.assign(jwk, publicKeyJSON);
              }
              break;
            default:
              throw new Error(`Incorrect algorithm name: ${alg.name.toUpperCase()}`);
          }
        }
        break;
      case "pkcs8":
        {
          const privateKeyInfo = new PrivateKeyInfo();

          //#region Parse "PrivateKeyInfo" object
          const asn1 = asn1js.fromBER(pvtsutils.BufferSourceConverter.toArrayBuffer(keyData as BufferSource));
          AsnError.assert(asn1, "keyData");

          try {
            privateKeyInfo.fromSchema(asn1.result);
          }
          catch (ex) {
            throw new Error("Incorrect keyData");
          }

          if (!privateKeyInfo.parsedKey)
            throw new Error("Incorrect keyData");
          //#endregion

          switch (alg.name.toUpperCase()) {
            case "RSA-PSS":
              {
                //#region Get information about used hash function
                switch (alg.hash?.name.toUpperCase()) {
                  case "SHA-1":
                    jwk.alg = "PS1";
                    break;
                  case "SHA-256":
                    jwk.alg = "PS256";
                    break;
                  case "SHA-384":
                    jwk.alg = "PS384";
                    break;
                  case "SHA-512":
                    jwk.alg = "PS512";
                    break;
                  default:
                    throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`);
                }
                //#endregion
              }
            // break omitted
            // eslint-disable-next-line no-fallthrough
            case "RSASSA-PKCS1-V1_5":
              {
                keyUsages = ["sign"]; // Override existing keyUsages value since the key is a private key

                jwk.kty = "RSA";
                jwk.ext = extractable;
                jwk.key_ops = keyUsages;

                //#region Get information about used hash function
                if (privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.113549.1.1.1")
                  throw new Error(`Incorrect private key algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`);
                //#endregion

                //#region Get information about used hash function
                if (("alg" in jwk) === false) {
                  switch (alg.hash?.name.toUpperCase()) {
                    case "SHA-1":
                      jwk.alg = "RS1";
                      break;
                    case "SHA-256":
                      jwk.alg = "RS256";
                      break;
                    case "SHA-384":
                      jwk.alg = "RS384";
                      break;
                    case "SHA-512":
                      jwk.alg = "RS512";
                      break;
                    default:
                      throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`);
                  }
                }
                //#endregion

                //#region Create RSA Private Key elements
                const privateKeyJSON = privateKeyInfo.toJSON();
                Object.assign(jwk, privateKeyJSON);
                //#endregion
              }
              break;
            case "ECDSA":
              keyUsages = ["sign"]; // Override existing keyUsages value since the key is a private key
            // break omitted
            // eslint-disable-next-line no-fallthrough
            case "ECDH":
              {
                //#region Initial variables
                jwk = {
                  kty: "EC",
                  ext: extractable,
                  key_ops: keyUsages
                };
                //#endregion

                //#region Get information about used hash function
                if (privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.10045.2.1")
                  throw new Error(`Incorrect algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`);
                //#endregion

                //#region Create ECDSA Private Key elements
                const privateKeyJSON = privateKeyInfo.toJSON();
                Object.assign(jwk, privateKeyJSON);
                //#endregion
              }
              break;
            case "RSA-OAEP":
              {
                jwk.kty = "RSA";
                jwk.ext = extractable;
                jwk.key_ops = keyUsages;

                //#region Get information about used hash function
                if (this.name.toLowerCase() === "safari")
                  jwk.alg = "RSA-OAEP";
                else {
                  switch (alg.hash?.name.toUpperCase()) {
                    case "SHA-1":
                      jwk.alg = "RSA-OAEP";
                      break;
                    case "SHA-256":
                      jwk.alg = "RSA-OAEP-256";
                      break;
                    case "SHA-384":
                      jwk.alg = "RSA-OAEP-384";
                      break;
                    case "SHA-512":
                      jwk.alg = "RSA-OAEP-512";
                      break;
                    default:
                      throw new Error(`Incorrect hash algorithm: ${alg.hash?.name.toUpperCase()}`);
                  }
                }
                //#endregion

                //#region Create RSA Private Key elements
                const privateKeyJSON = privateKeyInfo.toJSON();
                Object.assign(jwk, privateKeyJSON);
                //#endregion
              }
              break;
            case "RSAES-PKCS1-V1_5":
              {
                keyUsages = ["decrypt"]; // Override existing keyUsages value since the key is a private key

                jwk.kty = "RSA";
                jwk.ext = extractable;
                jwk.key_ops = keyUsages;
                jwk.alg = "PS1";

                //#region Create RSA Private Key elements
                const privateKeyJSON = privateKeyInfo.toJSON();
                Object.assign(jwk, privateKeyJSON);
                //#endregion
              }
              break;
            default:
              throw new Error(`Incorrect algorithm name: ${alg.name.toUpperCase()}`);
          }
        }
        break;
      case "jwk":
        jwk = keyData as JsonWebKey;
        break;
      default:
        throw new Error(`Incorrect format: ${format}`);
    }

    //#region Special case for Safari browser (since its acting not as WebCrypto standard describes)
    if (this.name.toLowerCase() === "safari") {
      // Try to use both ways - import using ArrayBuffer and pure JWK (for Safari Technology Preview)
      try {
        return this.subtle.importKey("jwk", pvutils.stringToArrayBuffer(JSON.stringify(jwk)) as any, algorithm, extractable, keyUsages);
      } catch {
        return this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages);
      }
    }
    //#endregion

    return this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages);
  }

  /**
   * Export WebCrypto keys to different formats
   * @param format
   * @param key
   */
  public override exportKey(format: "jwk", key: CryptoKey): Promise<JsonWebKey>;
  public override exportKey(format: Exclude<KeyFormat, "jwk">, key: CryptoKey): Promise<ArrayBuffer>;
  public override exportKey(format: string, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey>;
  public override async exportKey(format: KeyFormat, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey> {
    let jwk = await this.subtle.exportKey("jwk", key);

    //#region Currently Safari returns ArrayBuffer as JWK thus we need an additional transformation
    if (this.name.toLowerCase() === "safari") {
      // Some additional checks for Safari Technology Preview
      if (jwk instanceof ArrayBuffer) {
        jwk = JSON.parse(pvutils.arrayBufferToString(jwk));
      }
    }
    //#endregion

    switch (format.toLowerCase()) {
      case "raw":
        return this.subtle.exportKey("raw", key);
      case "spki": {
        const publicKeyInfo = new PublicKeyInfo();

        try {
          publicKeyInfo.fromJSON(jwk);
        }
        catch (ex) {
          throw new Error("Incorrect key data");
        }

        return publicKeyInfo.toSchema().toBER(false);
      }
      case "pkcs8": {
        const privateKeyInfo = new PrivateKeyInfo();

        try {
          privateKeyInfo.fromJSON(jwk);
        }
        catch (ex) {
          throw new Error("Incorrect key data");
        }

        return privateKeyInfo.toSchema().toBER(false);
      }
      case "jwk":
        return jwk;
      default:
        throw new Error(`Incorrect format: ${format}`);
    }
  }

  /**
   * Convert WebCrypto keys between different export formats
   * @param  inputFormat
   * @param  outputFormat
   * @param  keyData
   * @param  algorithm
   * @param  extractable
   * @param  keyUsages
   */
  public async convert(inputFormat: KeyFormat, outputFormat: KeyFormat, keyData: ArrayBuffer | JsonWebKey, algorithm: Algorithm, extractable: boolean, keyUsages: KeyUsage[]) {
    if (inputFormat.toLowerCase() === outputFormat.toLowerCase()) {
      return keyData;
    }

    const key = await this.importKey(inputFormat, keyData, algorithm, extractable, keyUsages);
    return this.exportKey(outputFormat, key);
  }

  /**
   * Gets WebCrypto algorithm by wel-known OID
   * @param oid algorithm identifier
   * @param safety if `true` throws exception on unknown algorithm identifier
   * @param target name of the target
   * @returns Returns WebCrypto algorithm or an empty object
   */
  public getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety?: boolean, target?: string): T | object;
  /**
   * Gets WebCrypto algorithm by wel-known OID
   * @param oid algorithm identifier
   * @param safety if `true` throws exception on unknown algorithm identifier
   * @param target name of the target
   * @returns Returns WebCrypto algorithm
   * @throws Throws {@link Error} exception if unknown algorithm identifier
   */
  public getAlgorithmByOID<T extends Algorithm = Algorithm>(oid: string, safety: true, target?: string): T;
  public getAlgorithmByOID(oid: string, safety = false, target?: string): any {
    switch (oid) {
      case "1.2.840.113549.1.1.1":
        return {
          name: "RSAES-PKCS1-v1_5"
        };
      case "1.2.840.113549.1.1.5":
        return {
          name: "RSASSA-PKCS1-v1_5",
          hash: {
            name: "SHA-1"
          }
        };
      case "1.2.840.113549.1.1.11":
        return {
          name: "RSASSA-PKCS1-v1_5",
          hash: {
            name: "SHA-256"
          }
        };
      case "1.2.840.113549.1.1.12":
        return {
          name: "RSASSA-PKCS1-v1_5",
          hash: {
            name: "SHA-384"
          }
        };
      case "1.2.840.113549.1.1.13":
        return {
          name: "RSASSA-PKCS1-v1_5",
          hash: {
            name: "SHA-512"
          }
        };
      case "1.2.840.113549.1.1.10":
        return {
          name: "RSA-PSS"
        };
      case "1.2.840.113549.1.1.7":
        return {
          name: "RSA-OAEP"
        };
      case "1.2.840.10045.2.1":
      case "1.2.840.10045.4.1":
        return {
          name: "ECDSA",
          hash: {
            name: "SHA-1"
          }
        };
      case "1.2.840.10045.4.3.2":
        return {
          name: "ECDSA",
          hash: {
            name: "SHA-256"
          }
        };
      case "1.2.840.10045.4.3.3":
        return {
          name: "ECDSA",
          hash: {
            name: "SHA-384"
          }
        };
      case "1.2.840.10045.4.3.4":
        return {
          name: "ECDSA",
          hash: {
            name: "SHA-512"
          }
        };
      case "1.3.133.16.840.63.0.2":
        return {
          name: "ECDH",
          kdf: "SHA-1"
        };
      case "1.3.132.1.11.1":
        return {
          name: "ECDH",
          kdf: "SHA-256"
        };
      case "1.3.132.1.11.2":
        return {
          name: "ECDH",
          kdf: "SHA-384"
        };
      case "1.3.132.1.11.3":
        return {
          name: "ECDH",
          kdf: "SHA-512"
        };
      case "2.16.840.1.101.3.4.1.2":
        return {
          name: "AES-CBC",
          length: 128
        };
      case "2.16.840.1.101.3.4.1.22":
        return {
          name: "AES-CBC",
          length: 192
        };
      case "2.16.840.1.101.3.4.1.42":
        return {
          name: "AES-CBC",
          length: 256
        };
      case "2.16.840.1.101.3.4.1.6":
        return {
          name: "AES-GCM",
          length: 128
        };
      case "2.16.840.1.101.3.4.1.26":
        return {
          name: "AES-GCM",
          length: 192
        };
      case "2.16.840.1.101.3.4.1.46":
        return {
          name: "AES-GCM",
          length: 256
        };
      case "2.16.840.1.101.3.4.1.4":
        return {
          name: "AES-CFB",
          length: 128
        };
      case "2.16.840.1.101.3.4.1.24":
        return {
          name: "AES-CFB",
          length: 192
        };
      case "2.16.840.1.101.3.4.1.44":
        return {
          name: "AES-CFB",
          length: 256
        };
      case "2.16.840.1.101.3.4.1.5":
        return {
          name: "AES-KW",
          length: 128
        };
      case "2.16.840.1.101.3.4.1.25":
        return {
          name: "AES-KW",
          length: 192
        };
      case "2.16.840.1.101.3.4.1.45":
        return {
          name: "AES-KW",
          length: 256
        };
      case "1.2.840.113549.2.7":
        return {
          name: "HMAC",
          hash: {
            name: "SHA-1"
          }
        };
      case "1.2.840.113549.2.9":
        return {
          name: "HMAC",
          hash: {
            name: "SHA-256"
          }
        };
      case "1.2.840.113549.2.10":
        return {
          name: "HMAC",
          hash: {
            name: "SHA-384"
          }
        };
      case "1.2.840.113549.2.11":
        return {
          name: "HMAC",
          hash: {
            name: "SHA-512"
          }
        };
      case "1.2.840.113549.1.9.16.3.5":
        return {
          name: "DH"
        };
      case "1.3.14.3.2.26":
        return {
          name: "SHA-1"
        };
      case "2.16.840.1.101.3.4.2.1":
        return {
          name: "SHA-256"
        };
      case "2.16.840.1.101.3.4.2.2":
        return {
          name: "SHA-384"
        };
      case "2.16.840.1.101.3.4.2.3":
        return {
          name: "SHA-512"
        };
      case "1.2.840.113549.1.5.12":
        return {
          name: "PBKDF2"
        };
      //#region Special case - OIDs for ECC curves
      case "1.2.840.10045.3.1.7":
        return {
          name: "P-256"
        };
      case "1.3.132.0.34":
        return {
          name: "P-384"
        };
      case "1.3.132.0.35":
        return {
          name: "P-521"
        };
      //#endregion
      default:
    }

    if (safety) {
      throw new Error(`Unsupported algorithm identifier ${target ? `for ${target} ` : EMPTY_STRING}: ${oid}`);
    }

    return {};
  }

  public getOIDByAlgorithm(algorithm: Algorithm, safety = false, target?: string): string {
    let result = EMPTY_STRING;

    switch (algorithm.name.toUpperCase()) {
      case "RSAES-PKCS1-V1_5":
        result = "1.2.840.113549.1.1.1";
        break;
      case "RSASSA-PKCS1-V1_5":
        switch ((algorithm as any).hash.name.toUpperCase()) {
          case "SHA-1":
            result = "1.2.840.113549.1.1.5";
            break;
          case "SHA-256":
            result = "1.2.840.113549.1.1.11";
            break;
          case "SHA-384":
            result = "1.2.840.113549.1.1.12";
            break;
          case "SHA-512":
            result = "1.2.840.113549.1.1.13";
            break;
          default:
        }
        break;
      case "RSA-PSS":
        result = "1.2.840.113549.1.1.10";
        break;
      case "RSA-OAEP":
        result = "1.2.840.113549.1.1.7";
        break;
      case "ECDSA":
        switch ((algorithm as any).hash.name.toUpperCase()) {
          case "SHA-1":
            result = "1.2.840.10045.4.1";
            break;
          case "SHA-256":
            result = "1.2.840.10045.4.3.2";
            break;
          case "SHA-384":
            result = "1.2.840.10045.4.3.3";
            break;
          case "SHA-512":
            result = "1.2.840.10045.4.3.4";
            break;
          default:
        }
        break;
      case "ECDH":
        switch ((algorithm as any).kdf.toUpperCase()) // Non-standard addition - hash algorithm of KDF function
        {
          case "SHA-1":
            result = "1.3.133.16.840.63.0.2"; // dhSinglePass-stdDH-sha1kdf-scheme
            break;
          case "SHA-256":
            result = "1.3.132.1.11.1"; // dhSinglePass-stdDH-sha256kdf-scheme
            break;
          case "SHA-384":
            result = "1.3.132.1.11.2"; // dhSinglePass-stdDH-sha384kdf-scheme
            break;
          case "SHA-512":
            result = "1.3.132.1.11.3"; // dhSinglePass-stdDH-sha512kdf-scheme
            break;
          default:
        }
        break;
      case "AES-CTR":
        break;
      case "AES-CBC":
        switch ((algorithm as any).length) {
          case 128:
            result = "2.16.840.1.101.3.4.1.2";
            break;
          case 192:
            result = "2.16.840.1.101.3.4.1.22";
            break;
          case 256:
            result = "2.16.840.1.101.3.4.1.42";
            break;
          default:
        }
        break;
      case "AES-CMAC":
        break;
      case "AES-GCM":
        switch ((algorithm as any).length) {
          case 128:
            result = "2.16.840.1.101.3.4.1.6";
            break;
          case 192:
            result = "2.16.840.1.101.3.4.1.26";
            break;
          case 256:
            result = "2.16.840.1.101.3.4.1.46";
            break;
          default:
        }
        break;
      case "AES-CFB":
        switch ((algorithm as any).length) {
          case 128:
            result = "2.16.840.1.101.3.4.1.4";
            break;
          case 192:
            result = "2.16.840.1.101.3.4.1.24";
            break;
          case 256:
            result = "2.16.840.1.101.3.4.1.44";
            break;
          default:
        }
        break;
      case "AES-KW":
        switch ((algorithm as any).length) {
          case 128:
            result = "2.16.840.1.101.3.4.1.5";
            break;
          case 192:
            result = "2.16.840.1.101.3.4.1.25";
            break;
          case 256:
            result = "2.16.840.1.101.3.4.1.45";
            break;
          default:
        }
        break;
      case "HMAC":
        switch ((algorithm as any).hash.name.toUpperCase()) {
          case "SHA-1":
            result = "1.2.840.113549.2.7";
            break;
          case "SHA-256":
            result = "1.2.840.113549.2.9";
            break;
          case "SHA-384":
            result = "1.2.840.113549.2.10";
            break;
          case "SHA-512":
            result = "1.2.840.113549.2.11";
            break;
          default:
        }
        break;
      case "DH":
        result = "1.2.840.113549.1.9.16.3.5";
        break;
      case "SHA-1":
        result = "1.3.14.3.2.26";
        break;
      case "SHA-256":
        result = "2.16.840.1.101.3.4.2.1";
        break;
      case "SHA-384":
        result = "2.16.840.1.101.3.4.2.2";
        break;
      case "SHA-512":
        result = "2.16.840.1.101.3.4.2.3";
        break;
      case "CONCAT":
        break;
      case "HKDF":
        break;
      case "PBKDF2":
        result = "1.2.840.113549.1.5.12";
        break;
      //#region Special case - OIDs for ECC curves
      case "P-256":
        result = "1.2.840.10045.3.1.7";
        break;
      case "P-384":
        result = "1.3.132.0.34";
        break;
      case "P-521":
        result = "1.3.132.0.35";
        break;
      //#endregion
      default:
    }

    if (!result && safety) {
      throw new Error(`Unsupported algorithm ${target ? `for ${target} ` : EMPTY_STRING}: ${algorithm.name}`);
    }

    return result;
  }

  public getAlgorithmParameters(algorithmName: string, operation: type.CryptoEngineAlgorithmOperation): type.CryptoEngineAlgorithmParams {
    let result: type.CryptoEngineAlgorithmParams = {
      algorithm: {},
      usages: []
    };

    switch (algorithmName.toUpperCase()) {
      case "RSAES-PKCS1-V1_5":
      case "RSASSA-PKCS1-V1_5":
        switch (operation.toLowerCase()) {
          case "generatekey":
            result = {
              algorithm: {
                name: "RSASSA-PKCS1-v1_5",
                modulusLength: 2048,
                publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
                hash: {
                  name: "SHA-256"
                }
              },
              usages: ["sign", "verify"]
            };
            break;
          case "verify":
          case "sign":
          case "importkey":
            result = {
              algorithm: {
                name: "RSASSA-PKCS1-v1_5",
                hash: {
                  name: "SHA-256"
                }
              },
              usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only
            };
            break;
          case "exportkey":
          default:
            return {
              algorithm: {
                name: "RSASSA-PKCS1-v1_5"
              },
              usages: []
            };
        }
        break;
      case "RSA-PSS":
        switch (operation.toLowerCase()) {
          case "sign":
          case "verify":
            result = {
              algorithm: {
                name: "RSA-PSS",
                hash: {
                  name: "SHA-1"
                },
                saltLength: 20
              },
              usages: ["sign", "verify"]
            };
            break;
          case "generatekey":
            result = {
              algorithm: {
                name: "RSA-PSS",
                modulusLength: 2048,
                publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
                hash: {
                  name: "SHA-1"
                }
              },
              usages: ["sign", "verify"]
            };
            break;
          case "importkey":
            result = {
              algorithm: {
                name: "RSA-PSS",
                hash: {
                  name: "SHA-1"
                }
              },
              usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only
            };
            break;
          case "exportkey":
          default:
            return {
              algorithm: {
                name: "RSA-PSS"
              },
              usages: []
            };
        }
        break;
      case "RSA-OAEP":
        switch (operation.toLowerCase()) {
          case "encrypt":
          case "decrypt":
            result = {
              algorithm: {
                name: "RSA-OAEP"
              },
              usages: ["encrypt", "decrypt"]
            };
            break;
          case "generatekey":
            result = {
              algorithm: {
                name: "RSA-OAEP",
                modulusLength: 2048,
                publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
                hash: {
                  name: "SHA-256"
                }
              },
              usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
            };
            break;
          case "importkey":
            result = {
              algorithm: {
                name: "RSA-OAEP",
                hash: {
                  name: "SHA-256"
                }
              },
              usages: ["encrypt"] // encrypt for "spki" and decrypt for "pkcs8"
            };
            break;
          case "exportkey":
          default:
            return {
              algorithm: {
                name: "RSA-OAEP"
              },
              usages: []
            };
        }
        break;
      case "ECDSA":
        switch (operation.toLowerCase()) {
          case "generatekey":
            result = {
              algorithm: {
                name: "ECDSA",
                namedCurve: "P-256"
              },
              usages: ["sign", "verify"]
            };
            break;
          case "importkey":
            result = {
              algorithm: {
                name: "ECDSA",
                namedCurve: "P-256"
              },
              usages: ["verify"] // "sign" for "pkcs8"
            };
            break;
          case "verify":
          case "sign":
            result = {
              algorithm: {
                name: "ECDSA",
                hash: {
                  name: "SHA-256"
                }
              },
              usages: ["sign"]
            };
            break;
          default:
            return {
              algorithm: {
                name: "ECDSA"
              },
              usages: []
            };
        }
        break;
      case "ECDH":
        switch (operation.toLowerCase()) {
          case "exportkey":
          case "importkey":
          case "generatekey":
            result = {
              algorithm: {
                name: "ECDH",
                namedCurve: "P-256"
              },
              usages: ["deriveKey", "deriveBits"]
            };
            break;
          case "derivekey":
          case "derivebits":
            result = {
              algorithm: {
                name: "ECDH",
                namedCurve: "P-256",
                public: [] // Must be a "publicKey"
              },
              usages: ["encrypt", "decrypt"]
            };
            break;
          default:
            return {
              algorithm: {
                name: "ECDH"
              },
              usages: []
            };
        }
        break;
      case "AES-CTR":
        switch (operation.toLowerCase()) {
          case "importkey":
          case "exportkey":
          case "generatekey":
            result = {
              algorithm: {
                name: "AES-CTR",
                length: 256
              },
              usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
            };
            break;
          case "decrypt":
          case "encrypt":
            result = {
              algorithm: {
                name: "AES-CTR",
                counter: new Uint8Array(16),
                length: 10
              },
              usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
            };
            break;
          default:
            return {
              algorithm: {
                name: "AES-CTR"
              },
              usages: []
            };
        }
        break;
      case "AES-CBC":
        switch (operation.toLowerCase()) {
          case "importkey":
          case "exportkey":
          case "generatekey":
            result = {
              algorithm: {
                name: "AES-CBC",
                length: 256
              },
              usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
            };
            break;
          case "decrypt":
          case "encrypt":
            result = {
              algorithm: {
                name: "AES-CBC",
                iv: this.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step
              },
              usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
            };
            break;
          default:
            return {
              algorithm: {
                name: "AES-CBC"
              },
              usages: []
            };
        }
        break;
      case "AES-GCM":
        switch (operation.toLowerCase()) {
          case "importkey":
          case "exportkey":
          case "generatekey":
            result = {
              algorithm: {
                name: "AES-GCM",
                length: 256
              },
              usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
            };
            break;
          case "decrypt":
          case "encrypt":
            result = {
              algorithm: {
                name: "AES-GCM",
                iv: this.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step
              },
              usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
            };
            break;
          default:
            return {
              algorithm: {
                name: "AES-GCM"
              },
              usages: []
            };
        }
        break;
      case "AES-KW":
        switch (operation.toLowerCase()) {
          case "importkey":
          case "exportkey":
          case "generatekey":
          case "wrapkey":
          case "unwrapkey":
            result = {
              algorithm: {
                name: "AES-KW",
                length: 256
              },
              usages: ["wrapKey", "unwrapKey"]
            };
            break;
          default:
            return {
              algorithm: {
                name: "AES-KW"
              },
              usages: []
            };
        }
        break;
      case "HMAC":
        switch (operation.toLowerCase()) {
          case "sign":
          case "verify":
            result = {
              algorithm: {
                name: "HMAC"
              },
              usages: ["sign", "verify"]
            };
            break;
          case "importkey":
          case "exportkey":
          case "generatekey":
            result = {
              algorithm: {
                name: "HMAC",
                length: 32,
                hash: {
                  name: "SHA-256"
                }
              },
              usages: ["sign", "verify"]
            };
            break;
          default:
            return {
              algorithm: {
                name: "HMAC"
              },
              usages: []
            };
        }
        break;
      case "HKDF":
        switch (operation.toLowerCase()) {
          case "derivekey":
            result = {
              algorithm: {
                name: "HKDF",
                hash: "SHA-256",
                salt: new Uint8Array([]),
                info: new Uint8Array([])
              },
              usages: ["encrypt", "decrypt"]
            };
            break;
          default:
            return {
              algorithm: {
                name: "HKDF"
              },
              usages: []
            };
        }
        break;
      case "PBKDF2":
        switch (operation.toLowerCase()) {
          case "derivekey":
            result = {
              algorithm: {
                name: "PBKDF2",
                hash: { name: "SHA-256" },
                salt: new Uint8Array([]),
                iterations: 10000
              },
              usages: ["encrypt", "decrypt"]
            };
            break;
          default:
            return {
              algorithm: {
                name: "PBKDF2"
              },
              usages: []
            };
        }
        break;
      default:
    }

    return result;
  }

  /**
   * Getting hash algorithm by signature algorithm
   * @param signatureAlgorithm Signature algorithm
   */
  // TODO use safety
  getHashAlgorithm(signatureAlgorithm: AlgorithmIdentifier): string {
    let result = EMPTY_STRING;

    switch (signatureAlgorithm.algorithmId) {
      case "1.2.840.10045.4.1": // ecdsa-with-SHA1
      case "1.2.840.113549.1.1.5": // rsa-encryption-with-SHA1
        result = "SHA-1";
        break;
      case "1.2.840.10045.4.3.2": // ecdsa-with-SHA256
      case "1.2.840.113549.1.1.11": // rsa-encryption-with-SHA256
        result = "SHA-256";
        break;
      case "1.2.840.10045.4.3.3": // ecdsa-with-SHA384
      case "1.2.840.113549.1.1.12": // rsa-encryption-with-SHA384
        result = "SHA-384";
        break;
      case "1.2.840.10045.4.3.4": // ecdsa-with-SHA512
      case "1.2.840.113549.1.1.13": // rsa-encryption-with-SHA512
        result = "SHA-512";
        break;
      case "1.2.840.113549.1.1.10": // RSA-PSS
        {
          try {
            const params = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams });
            if (params.hashAlgorithm) {
              const algorithm = this.getAlgorithmByOID(params.hashAlgorithm.algorithmId);
              if ("name" in algorithm) {
                result = algorithm.name;
              }
              else {
                return EMPTY_STRING;
              }
            }
            else
              result = "SHA-1";
          }
          catch {
            // nothing
          }
        }
        break;
      default:
    }

    return result;
  }

  public async encryptEncryptedContentInfo(parameters: type.CryptoEngineEncryptParams): Promise<EncryptedContentInfo> {
    //#region Check for input parameters
    ParameterError.assert(parameters,
      "password", "contentEncryptionAlgorithm", "hmacHashAlgorithm",
      "iterationCount", "contentToEncrypt", "contentToEncrypt", "contentType");

    const contentEncryptionOID = this.getOIDByAlgorithm(parameters.contentEncryptionAlgorithm, true, "contentEncryptionAlgorithm");

    const pbkdf2OID = this.getOIDByAlgorithm({
      name: "PBKDF2"
    }, true, "PBKDF2");
    const hmacOID = this.getOIDByAlgorithm({
      name: "HMAC",
      hash: {
        name: parameters.hmacHashAlgorithm
      }
    } as Algorithm, true, "hmacHashAlgorithm");
    //#endregion

    //#region Initial variables

    // TODO Should we reuse iv from parameters.contentEncryptionAlgorithm or use it's length for ivBuffer?
    const ivBuffer = new ArrayBuffer(16); // For AES we need IV 16 bytes long
    const ivView = new Uint8Array(ivBuffer);
    this.getRandomValues(ivView);

    const saltBuffer = new ArrayBuffer(64);
    const saltView = new Uint8Array(saltBuffer);
    this.getRandomValues(saltView);

    const contentView = new Uint8Array(parameters.contentToEncrypt);

    const pbkdf2Params = new PBKDF2Params({
      salt: new asn1js.OctetString({ valueHex: saltBuffer }),
      iterationCount: parameters.iterationCount,
      prf: new AlgorithmIdentifier({
        algorithmId: hmacOID,
        algorithmParams: new asn1js.Null()
      })
    });
    //#endregion

    //#region Derive PBKDF2 key from "password" buffer
    const passwordView = new Uint8Array(parameters.password);

    const pbkdfKey = await this.importKey("raw",
      passwordView,
      "PBKDF2",
      false,
      ["deriveKey"]);

    //#endregion

    //#region Derive key for "contentEncryptionAlgorithm"
    const derivedKey = await this.deriveKey({
      name: "PBKDF2",
      hash: {
        name: parameters.hmacHashAlgorithm
      },
      salt: saltView,
      iterations: parameters.iterationCount
    },
      pbkdfKey,
      parameters.contentEncryptionAlgorithm,
      false,
      ["encrypt"]);
    //#endregion

    //#region Encrypt content
    // TODO encrypt doesn't use all parameters from parameters.contentEncryptionAlgorithm (eg additionalData and tagLength for AES-GCM)
    const encryptedData = await this.encrypt(
      {
        name: parameters.contentEncryptionAlgorithm.name,
        iv: ivView
      },
      derivedKey,
      contentView);
    //#endregion

    //#region Store all parameters in EncryptedData object
    const pbes2Parameters = new PBES2Params({
      keyDerivationFunc: new AlgorithmIdentifier({
        algorithmId: pbkdf2OID,
        algorithmParams: pbkdf2Params.toSchema()
      }),
      encryptionScheme: new AlgorithmIdentifier({
        algorithmId: contentEncryptionOID,
        algorithmParams: new asn1js.OctetString({ valueHex: ivBuffer })
      })
    });

    return new EncryptedContentInfo({
      contentType: parameters.contentType,
      contentEncryptionAlgorithm: new AlgorithmIdentifier({
        algorithmId: "1.2.840.113549.1.5.13", // pkcs5PBES2
        algorithmParams: pbes2Parameters.toSchema()
      }),
      encryptedContent: new asn1js.OctetString({ valueHex: encryptedData })
    });
    //#endregion
  }

  /**
   * Decrypt data stored in "EncryptedContentInfo" object using parameters
   * @param parameters
   */
  public async decryptEncryptedContentInfo(parameters: type.CryptoEngineDecryptParams): Promise<ArrayBuffer> {
    //#region Check for input parameters
    ParameterError.assert(parameters, "password", "encryptedContentInfo");

    if (parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId !== "1.2.840.113549.1.5.13") // pkcs5PBES2
      throw new Error(`Unknown "contentEncryptionAlgorithm": ${parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId}`);
    //#endregion

    //#region Initial variables
    let pbes2Parameters: PBES2Params;

    try {
      pbes2Parameters = new PBES2Params({ schema: parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams });
    }
    catch (ex) {
      throw new Error("Incorrectly encoded \"pbes2Parameters\"");
    }

    let pbkdf2Params;

    try {
      pbkdf2Params = new PBKDF2Params({ schema: pbes2Parameters.keyDerivationFunc.algorithmParams });
    }
    catch (ex) {
      throw new Error("Incorrectly encoded \"pbkdf2Params\"");
    }

    const contentEncryptionAlgorithm = this.getAlgorithmByOID(pbes2Parameters.encryptionScheme.algorithmId, true);

    const ivBuffer = pbes2Parameters.encryptionScheme.algorithmParams.valueBlock.valueHex;
    const ivView = new Uint8Array(ivBuffer);

    const saltBuffer = pbkdf2Params.salt.valueBlock.valueHex;
    const saltView = new Uint8Array(saltBuffer);

    const iterationCount = pbkdf2Params.iterationCount;

    let hmacHashAlgorithm = "SHA-1";

    if (pbkdf2Params.prf) {
      const algorithm = this.getAlgorithmByOID<any>(pbkdf2Params.prf.algorithmId, true);
      hmacHashAlgorithm = algorithm.hash.name;
    }
    //#endregion

    //#region Derive PBKDF2 key from "password" buffer
    const pbkdfKey = await this.importKey("raw",
      parameters.password,
      "PBKDF2",
      false,
      ["deriveKey"]);
    //#endregion

    //#region Derive key for "contentEncryptionAlgorithm"
    const result = await this.deriveKey(
      {
        name: "PBKDF2",
        hash: {
          name: hmacHashAlgorithm
        },
        salt: saltView,
        iterations: iterationCount
      },
      pbkdfKey,
      contentEncryptionAlgorithm as any,
      false,
      ["decrypt"]);
    //#endregion

    //#region Decrypt internal content using derived key
    //#region Create correct data block for decryption
    const dataBuffer = parameters.encryptedContentInfo.getEncryptedContent();
    //#endregion

    return this.decrypt({
      name: contentEncryptionAlgorithm.name,
      iv: ivView
    },
      result,
      dataBuffer);
    //#endregion
  }

  public async stampDataWithPassword(parameters: type.CryptoEngineStampDataWithPasswordParams): Promise<ArrayBuffer> {
    //#region Check for input parameters
    if ((parameters instanceof Object) === false)
      throw new Error("Parameters must have type \"Object\"");

    ParameterError.assert(parameters, "password", "hashAlgorithm", "iterationCount", "salt", "contentToStamp");
    //#endregion

    //#region Choose correct length for HMAC key
    let length: number;

    switch (parameters.hashAlgorithm.toLowerCase()) {
      case "sha-1":
        length = 160;
        break;
      case "sha-256":
        length = 256;
        break;
      case "sha-384":
        length = 384;
        break;
      case "sha-512":
        length = 512;
        break;
      default:
        throw new Error(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`);
    }
    //#endregion

    //#region Initial variables
    const hmacAlgorithm = {
      name: "HMAC",
      length,
      hash: {
        name: parameters.hashAlgorithm
      }
    };
    //#endregion

    //#region Create PKCS#12 key for integrity checking
    const pkcsKey = await makePKCS12B2Key(this, parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount);
    //#endregion

    //#region Import HMAC key

    const hmacKey = await this.importKey("raw",
      new Uint8Array(pkcsKey),
      hmacAlgorithm,
      false,
      ["sign"]);
    //#endregion

    //#region Make signed HMAC value
    return this.sign(hmacAlgorithm, hmacKey, new Uint8Array(parameters.contentToStamp));
    //#endregion
  }

  public async verifyDataStampedWithPassword(parameters: type.CryptoEngineVerifyDataStampedWithPasswordParams): Promise<boolean> {
    //#region Check for input parameters
    ParameterError.assert(parameters,
      "password", "hashAlgorithm", "salt",
      "iterationCount", "contentToVerify", "signatureToVerify");
    //#endregion

    //#region Choose correct length for HMAC key
    let length = 0;

    switch (parameters.hashAlgorithm.toLowerCase()) {
      case "sha-1":
        length = 160;
        break;
      case "sha-256":
        length = 256;
        break;
      case "sha-384":
        length = 384;
        break;
      case "sha-512":
        length = 512;
        break;
      default:
        throw new Error(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`);
    }
    //#endregion

    //#region Initial variables
    const hmacAlgorithm = {
      name: "HMAC",
      length,
      hash: {
        name: parameters.hashAlgorithm
      }
    };
    //#endregion

    //#region Create PKCS#12 key for integrity checking
    const pkcsKey = await makePKCS12B2Key(this, parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount);
    //#endregion

    //#region Import HMAC key
    const hmacKey = await this.importKey("raw",
      new Uint8Array(pkcsKey),
      hmacAlgorithm,
      false,
      ["verify"]);
    //#endregion

    //#region Make signed HMAC value
    return this.verify(hmacAlgorithm, hmacKey, new Uint8Array(parameters.signatureToVerify), new Uint8Array(parameters.contentToVerify));
    //#endregion
  }

  public async getSignatureParameters(privateKey: CryptoKey, hashAlgorithm = "SHA-1"): Promise<type.CryptoEngineSignatureParams> {
    // Check hashing algorithm
    this.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm");

    // Initial variables
    const signatureAlgorithm = new AlgorithmIdentifier();

    //#region Get a "default parameters" for current algorithm
    const parameters = this.getAlgorithmParameters(privateKey.algorithm.name, "sign");
    if (!Object.keys(parameters.algorithm).length) {
      throw new Error("Parameter 'algorithm' is empty");
    }
    const algorithm = parameters.algorithm as any; // TODO remove `as any`
    algorithm.hash.name = hashAlgorithm;
    //#endregion

    //#region Fill internal structures base on "privateKey" and "hashAlgorithm"
    switch (privateKey.algorithm.name.toUpperCase()) {
      case "RSASSA-PKCS1-V1_5":
      case "ECDSA":
        signatureAlgorithm.algorithmId = this.getOIDByAlgorithm(algorithm, true);
        break;
      case "RSA-PSS":
        {
          //#region Set "saltLength" as a length (in octets) of hash function result
          switch (hashAlgorithm.toUpperCase()) {
            case "SHA-256":
              algorithm.saltLength = 32;
              break;
            case "SHA-384":
              algorithm.saltLength = 48;
              break;
            case "SHA-512":
              algorithm.saltLength = 64;
              break;
            default:
          }
          //#endregion

          //#region Fill "RSASSA_PSS_params" object
          const paramsObject: Partial<IRSASSAPSSParams> = {};

          if (hashAlgorithm.toUpperCase() !== "SHA-1") {
            const hashAlgorithmOID = this.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm");

            paramsObject.hashAlgorithm = new AlgorithmIdentifier({
              algorithmId: hashAlgorithmOID,
              algorithmParams: new asn1js.Null()
            });

            paramsObject.maskGenAlgorithm = new AlgorithmIdentifier({
              algorithmId: "1.2.840.113549.1.1.8", // MGF1
              algorithmParams: paramsObject.hashAlgorithm.toSchema()
            });
          }

          if (algorithm.saltLength !== 20)
            paramsObject.saltLength = algorithm.saltLength;

          const pssParameters = new RSASSAPSSParams(paramsObject);
          //#endregion

          //#region Automatically set signature algorithm
          signatureAlgorithm.algorithmId = "1.2.840.113549.1.1.10";
          signatureAlgorithm.algorithmParams = pssParameters.toSchema();
          //#endregion
        }
        break;
      default:
        throw new Error(`Unsupported signature algorithm: ${privateKey.algorithm.name}`);
    }
    //#endregion

    return {
      signatureAlgorithm,
      parameters
    };
  }

  public async signWithPrivateKey(data: BufferSource, privateKey: CryptoKey, parameters: type.CryptoEngineSignWithPrivateKeyParams): Promise<ArrayBuffer> {
    const signature = await this.sign(parameters.algorithm,
      privateKey,
      data);

    //#region Special case for ECDSA algorithm
    if (parameters.algorithm.name === "ECDSA") {
      return common.createCMSECDSASignature(signature);
    }
    //#endregion

    return signature;
  }

  public fillPublicKeyParameters(publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier): type.CryptoEnginePublicKeyParams {
    const parameters = {} as any;

    //#region Find signer's hashing algorithm
    const shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm);
    if (shaAlgorithm === EMPTY_STRING)
      throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`);
    //#endregion

    //#region Get information about public key algorithm and default parameters for import
    let algorithmId: string;
    if (signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10")
      algorithmId = signatureAlgorithm.algorithmId;
    else
      algorithmId = publicKeyInfo.algorithm.algorithmId;

    const algorithmObject = this.getAlgorithmByOID(algorithmId, true);

    parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importKey");
    if ("hash" in parameters.algorithm.algorithm)
      parameters.algorithm.algorithm.hash.name = shaAlgorithm;

    //#region Special case for ECDSA
    if (algorithmObject.name === "ECDSA") {
      //#region Get information about named curve
      const publicKeyAlgorithm = publicKeyInfo.algorithm;
      if (!publicKeyAlgorithm.algorithmParams) {
        throw new Error("Algorithm parameters for ECDSA public key are missed");
      }
      const publicKeyAlgorithmParams = publicKeyAlgorithm.algorithmParams;
      if ("idBlock" in publicKeyAlgorithm.algorithmParams) {
        if (!((publicKeyAlgorithmParams.idBlock.tagClass === 1) && (publicKeyAlgorithmParams.idBlock.tagNumber === 6))) {
          throw new Error("Incorrect type for ECDSA public key parameters");
        }
      }

      const curveObject = this.getAlgorithmByOID(publicKeyAlgorithmParams.valueBlock.toString(), true);
      //#endregion

      parameters.algorithm.algorithm.namedCurve = curveObject.name;
    }
    //#endregion
    //#endregion

    return parameters;
  }

  public async getPublicKey(publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, parameters?: type.CryptoEnginePublicKeyParams): Promise<CryptoKey> {
    if (!parameters) {
      parameters = this.fillPublicKeyParameters(publicKeyInfo, signatureAlgorithm);
    }

    const publicKeyInfoBuffer = publicKeyInfo.toSchema().toBER(false);

    return this.importKey("spki",
      publicKeyInfoBuffer,
      parameters.algorithm.algorithm as Algorithm,
      true,
      parameters.algorithm.usages
    );
  }

  public async verifyWithPublicKey(data: BufferSource, signature: asn1js.BitString | asn1js.OctetString, publicKeyInfo: PublicKeyInfo, signatureAlgorithm: AlgorithmIdentifier, shaAlgorithm?: string): Promise<boolean> {
    //#region Find signer's hashing algorithm
    let publicKey: CryptoKey;
    if (!shaAlgorithm) {
      shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm);
      if (!shaAlgorithm)
        throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`);

      //#region Import public key
      publicKey = await this.getPublicKey(publicKeyInfo, signatureAlgorithm);
      //#endregion
    } else {
      const parameters = {} as type.CryptoEnginePublicKeyParams;

      //#region Get information about public key algorithm and default parameters for import
      let algorithmId;
      if (signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10")
        algorithmId = signatureAlgorithm.algorithmId;
      else
        algorithmId = publicKeyInfo.algorithm.algorithmId;

      const algorithmObject = this.getAlgorithmByOID(algorithmId, true);

      parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importKey");
      if ("hash" in parameters.algorithm.algorithm)
        (parameters.algorithm.algorithm as any).hash.name = shaAlgorithm;

      //#region Special case for ECDSA
      if (algorithmObject.name === "ECDSA") {
        //#region Get information about named curve
        let algorithmParamsChecked = false;

        if (("algorithmParams" in publicKeyInfo.algorithm) === true) {
          if ("idBlock" in publicKeyInfo.algorithm.algorithmParams) {
            if ((publicKeyInfo.algorithm.algorithmParams.idBlock.tagClass === 1) && (publicKeyInfo.algorithm.algorithmParams.idBlock.tagNumber === 6))
              algorithmParamsChecked = true;
          }
        }

        if (algorithmParamsChecked === false) {
          throw new Error("Incorrect type for ECDSA public key parameters");
        }

        const curveObject = this.getAlgorithmByOID(publicKeyInfo.algorithm.algorithmParams.valueBlock.toString(), true);
        //#endregion

        (parameters.algorithm.algorithm as any).namedCurve = curveObject.name;
      }
      //#endregion
      //#endregion

      //#region Import public key

      publicKey = await this.getPublicKey(publicKeyInfo, null as any, parameters); // TODO null!!!
      //#endregion
    }
    //#endregion

    //#region Verify signature
    //#region Get default algorithm parameters for verification
    const algorithm = this.getAlgorithmParameters(publicKey.algorithm.name, "verify");
    if ("hash" in algorithm.algorithm)
      (algorithm.algorithm as any).hash.name = shaAlgorithm;
    //#endregion

    //#region Special case for ECDSA signatures
    let signatureValue: BufferSource = signature.valueBlock.valueHexView;

    if (publicKey.algorithm.name === "ECDSA") {
      const namedCurve = ECNamedCurves.find((publicKey.algorithm as EcKeyAlgorithm).namedCurve);
      if (!namedCurve) {
        throw new Error("Unsupported named curve in use");
      }
      const asn1 = asn1js.fromBER(signatureValue);
      AsnError.assert(asn1, "Signature value");
      signatureValue = common.createECDSASignatureFromCMS(asn1.result, namedCurve.size);
    }
    //#endregion

    //#region Special case for RSA-PSS
    if (publicKey.algorithm.name === "RSA-PSS") {
      const pssParameters = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams });

      if ("saltLength" in pssParameters)
        (algorithm.algorithm as any).saltLength = pssParameters.saltLength;
      else
        (algorithm.algorithm as any).saltLength = 20;

      let hashAlgo = "SHA-1";

      if ("hashAlgorithm" in pssParameters) {
        const hashAlgorithm = this.getAlgorithmByOID(pssParameters.hashAlgorithm.algorithmId, true);

        hashAlgo = hashAlgorithm.name;
      }

      (algorithm.algorithm as any).hash.name = hashAlgo;
    }
    //#endregion

    return this.verify((algorithm.algorithm as any),
      publicKey,
      signatureValue,
      data,
    );
    //#endregion
  }

}


[ zur Elbe Produktseite wechseln0.60Quellennavigators  ]