Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/js/PKI.js/src/CryptoEngine/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 62 kB image not shown  

Quelle  CryptoEngine.ts

  Sprache: JAVA
 

import { sha1 } from "@noble/hashes/sha1";
import { sha256, sha384, sha512 } from "@noble/hashes/sha2";
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(hashAlgorithm: string, keyLength: number, password: ArrayBuffer, salt: ArrayBuffer, iterationCount: number) {
  let u: number;  // Output length of the hash function
  let v: number;  // Block size of the hash function
  let md: (input: Uint8Array) => Uint8Array;  // Hash function

  // Determine the hash algorithm parameters
  switch (hashAlgorithm.toUpperCase()) {
    case "SHA-1":
      u = 20// 160 bits
      v = 64// 512 bits
      md = sha1;
      break;
    case "SHA-256":
      u = 32// 256 bits
      v = 64// 512 bits
      md = sha256;
      break;
    case "SHA-384":
      u = 48// 384 bits
      v = 128// 1024 bits
      md = sha384;
      break;
    case "SHA-512":
      u = 64// 512 bits
      v = 128// 1024 bits
      md = sha512;
      break;
    default:
      throw new Error("Unsupported hashing algorithm");
  }

  const originalPassword = new Uint8Array(password);
  let decodedPassword = new TextDecoder().decode(password);
  const encodedPassword = new TextEncoder().encode(decodedPassword);
  if (encodedPassword.some((byte, i) => byte !== originalPassword[i])) {
    decodedPassword = String.fromCharCode(...originalPassword);
  }

  // Transform the password into a byte array
  const passwordTransformed = new Uint8Array(decodedPassword.length * 2 + 2);
  const passwordView = new DataView(passwordTransformed.buffer);
  for (let i = 0; i < decodedPassword.length; i++) {
    passwordView.setUint16(i * 2, decodedPassword.charCodeAt(i), false);
  }
  // Add null-terminator
  passwordView.setUint16(decodedPassword.length * 20false);

  // Create a filled array D with the value 3 (ID for MACing)
  const D = new Uint8Array(v).fill(3);

  // Repeat the salt to fill the block size
  const saltView = new Uint8Array(salt);
  const S = new Uint8Array(v * Math.ceil(saltView.length / v)).map((_, i) => saltView[i % saltView.length]);

  // Repeat the password to fill the block size
  const P = new Uint8Array(v * Math.ceil(passwordTransformed.length / v)).map((_, i) => passwordTransformed[i % passwordTransformed.length]);

  // Concatenate S and P to form I
  let I = new Uint8Array(S.length + P.length);
  I.set(S);
  I.set(P, S.length);

  // Calculate the number of hash iterations needed
  const c = Math.ceil((keyLength >> 3) / u);
  const result: number[] = [];

  // Main loop to generate the key material
  for (let i = 0; i < c; i++) {
    // Concatenate D and I
    let A: Uint8Array = new Uint8Array(D.length + I.length);
    A.set(D);
    A.set(I, D.length);

    // Perform hash iterations
    for (let j = 0; j < iterationCount; j++) {
      A = md(A);
    }

    // Create a repeated block B from the hash output A
    const B = new Uint8Array(v).map((_, i) => A[i % A.length]);

    // Determine the number of blocks
    const k = Math.ceil(saltView.length / v) + Math.ceil(passwordTransformed.length / v);
    const iRound: number[] = [];

    // Adjust I based on B
    for (let j = 0; j < k; j++) {
      const chunk = Array.from(I.slice(j * v, (j + 1) * v));
      let x = 0x1ff;

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

      iRound.push(...chunk);
    }

    // Update I for the next iteration
    I = new Uint8Array(iRound);

    // Collect the result
    result.push(...A);
  }

  return new Uint8Array(result.slice(0, keyLength >> 3)).buffer;
}

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 {
            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 {
          throw new Error("Incorrect key data");
        }

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

        try {
          privateKeyInfo.fromJSON(jwk);
        }
        catch {
          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 {
      throw new Error("Incorrectly encoded \"pbes2Parameters\"");
    }

    let pbkdf2Params;

    try {
      pbkdf2Params = new PBKDF2Params({ schema: pbes2Parameters.keyDerivationFunc.algorithmParams });
    }
    catch {
      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(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(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 "default parameters" for the current algorithm
    const parameters = this.getAlgorithmParameters(privateKey.algorithm.name, "sign");
    if (!Object.keys(parameters.algorithm).length) {
      throw new Error("Parameter 'algorithm' is empty");
    }
    // Use the hash from the privateKey.algorithm.hash.name for keys with hash algorithms (like RSA)
    const algorithm = parameters.algorithm as any; // TODO remove `as any`
    if ("hash" in privateKey.algorithm && privateKey.algorithm.hash && (privateKey.algorithm.hash as Algorithm).name) {
      algorithm.hash.name = (privateKey.algorithm.hash as Algorithm).name;
    } else {
      algorithm.hash.name = hashAlgorithm;
    }
    //#endregion

    //#region Fill internal structures based 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 the length (in octets) of the hash function result
          switch (algorithm.hash.name.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 (algorithm.hash.name.toUpperCase() !== "SHA-1") {
            const hashAlgorithmOID = this.getOIDByAlgorithm({ name: algorithm.hash.name }, 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: Uint8Array | ArrayBuffer = 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 as BufferSource,
      data,
    );
    //#endregion
  }

}


Messung V0.5 in Prozent
C=90 H=67 G=79

¤ Dauer der Verarbeitung: 0.27 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.