"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var asn1js = _interopRequireWildcard(require("asn1js"));

var _pvutils = require("pvutils");

var _common = require("./common.js");

var _PublicKeyInfo = _interopRequireDefault(require("./PublicKeyInfo.js"));

var _PrivateKeyInfo = _interopRequireDefault(require("./PrivateKeyInfo.js"));

var _AlgorithmIdentifier = _interopRequireDefault(require("./AlgorithmIdentifier.js"));

var _EncryptedContentInfo = _interopRequireDefault(require("./EncryptedContentInfo.js"));

var _RSASSAPSSParams = _interopRequireDefault(require("./RSASSAPSSParams.js"));

var _PBKDF2Params = _interopRequireDefault(require("./PBKDF2Params.js"));

var _PBES2Params = _interopRequireDefault(require("./PBES2Params.js"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }

function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }

function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }

function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }

//**************************************************************************************

/**
 * Making MAC key using algorithm described in B.2 of PKCS#12 standard.
 */
function makePKCS12B2Key(cryptoEngine, hashAlgorithm, keyLength, password, salt, iterationCount) {
  //region Initial variables
  let u;
  let v;
  const result = []; //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[j % roundBuffer.length]; //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;
} //**************************************************************************************

/**
 * Default cryptographic engine for Web Cryptography API
 */


class CryptoEngine {
  //**********************************************************************************

  /**
   * Constructor for CryptoEngine class
   * @param {Object} [parameters={}]
   * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
   */
  constructor(parameters = {}) {
    //region Internal properties of the object

    /**
     * @type {Object}
     * @desc Usually here we are expecting "window.crypto" or an equivalent from custom "crypto engine"
     */
    this.crypto = (0, _pvutils.getParametersValue)(parameters, "crypto", {});
    /**
     * @type {Object}
     * @desc Usually here we are expecting "window.crypto.subtle" or an equivalent from custom "crypto engine"
     */

    this.subtle = (0, _pvutils.getParametersValue)(parameters, "subtle", {});
    /**
     * @type {string}
     * @desc Name of the "crypto engine"
     */

    this.name = (0, _pvutils.getParametersValue)(parameters, "name", ""); //endregion
  } //**********************************************************************************

  /**
   * Import WebCrypto keys from different formats
   * @param {string} format
   * @param {ArrayBuffer|Uint8Array} keyData
   * @param {Object} algorithm
   * @param {boolean} extractable
   * @param {Array} keyUsages
   * @returns {Promise}
   */


  importKey(format, keyData, algorithm, extractable, keyUsages) {
    //region Initial variables
    let jwk = {}; //endregion
    //region Change "keyData" type if needed

    if (keyData instanceof Uint8Array) keyData = keyData.buffer; //endregion

    switch (format.toLowerCase()) {
      case "raw":
        return this.subtle.importKey("raw", keyData, algorithm, extractable, keyUsages);

      case "spki":
        {
          const asn1 = asn1js.fromBER(keyData);
          if (asn1.offset === -1) return Promise.reject("Incorrect keyData");
          const publicKeyInfo = new _PublicKeyInfo.default();

          try {
            publicKeyInfo.fromSchema(asn1.result);
          } catch (ex) {
            return Promise.reject("Incorrect keyData");
          } // noinspection FallThroughInSwitchStatementJS


          switch (algorithm.name.toUpperCase()) {
            case "RSA-PSS":
              {
                //region Get information about used hash function
                switch (algorithm.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:
                    return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
                } //endregion

              }
            // break omitted

            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") return Promise.reject(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`); //region Get information about used hash function

                if ("alg" in jwk === false) {
                  switch (algorithm.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:
                      return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
                  }
                } //endregion
                //region Create RSA Public Key elements


                const publicKeyJSON = publicKeyInfo.toJSON();

                for (var _i = 0, _Object$keys = Object.keys(publicKeyJSON); _i < _Object$keys.length; _i++) {
                  const key = _Object$keys[_i];
                  jwk[key] = publicKeyJSON[key];
                } //endregion

              }
              break;

            case "ECDSA":
              keyUsages = ["verify"];
            // Override existing keyUsages value since the key is a public key
            // break omitted

            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") return Promise.reject(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`); //endregion
                //region Create ECDSA Public Key elements

                const publicKeyJSON = publicKeyInfo.toJSON();

                for (var _i2 = 0, _Object$keys2 = Object.keys(publicKeyJSON); _i2 < _Object$keys2.length; _i2++) {
                  const key = _Object$keys2[_i2];
                  jwk[key] = publicKeyJSON[key];
                } //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 {
                  switch (algorithm.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:
                      return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
                  }
                } //region Create ECDSA Public Key elements

                const publicKeyJSON = publicKeyInfo.toJSON();

                for (var _i3 = 0, _Object$keys3 = Object.keys(publicKeyJSON); _i3 < _Object$keys3.length; _i3++) {
                  const key = _Object$keys3[_i3];
                  jwk[key] = publicKeyJSON[key];
                } //endregion

              }
              break;

            case "RSAES-PKCS1-V1_5":
              {
                jwk.kty = "RSA";
                jwk.ext = extractable;
                jwk.key_ops = keyUsages;
                jwk.alg = "PS1";
                const publicKeyJSON = publicKeyInfo.toJSON();

                for (var _i4 = 0, _Object$keys4 = Object.keys(publicKeyJSON); _i4 < _Object$keys4.length; _i4++) {
                  const key = _Object$keys4[_i4];
                  jwk[key] = publicKeyJSON[key];
                }
              }
              break;

            default:
              return Promise.reject(`Incorrect algorithm name: ${algorithm.name.toUpperCase()}`);
          }
        }
        break;

      case "pkcs8":
        {
          const privateKeyInfo = new _PrivateKeyInfo.default(); //region Parse "PrivateKeyInfo" object

          const asn1 = asn1js.fromBER(keyData);
          if (asn1.offset === -1) return Promise.reject("Incorrect keyData");

          try {
            privateKeyInfo.fromSchema(asn1.result);
          } catch (ex) {
            return Promise.reject("Incorrect keyData");
          }

          if ("parsedKey" in privateKeyInfo === false) return Promise.reject("Incorrect keyData"); //endregion
          // noinspection FallThroughInSwitchStatementJS
          // noinspection FallThroughInSwitchStatementJS

          switch (algorithm.name.toUpperCase()) {
            case "RSA-PSS":
              {
                //region Get information about used hash function
                switch (algorithm.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:
                    return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
                } //endregion

              }
            // break omitted

            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") return Promise.reject(`Incorrect private key algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`); //endregion
                //region Get information about used hash function

                if ("alg" in jwk === false) {
                  switch (algorithm.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:
                      return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
                  }
                } //endregion
                //region Create RSA Private Key elements


                const privateKeyJSON = privateKeyInfo.toJSON();

                for (var _i5 = 0, _Object$keys5 = Object.keys(privateKeyJSON); _i5 < _Object$keys5.length; _i5++) {
                  const key = _Object$keys5[_i5];
                  jwk[key] = privateKeyJSON[key];
                } //endregion

              }
              break;

            case "ECDSA":
              keyUsages = ["sign"];
            // Override existing keyUsages value since the key is a private key
            // break omitted

            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") return Promise.reject(`Incorrect algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`); //endregion
                //region Create ECDSA Private Key elements

                const privateKeyJSON = privateKeyInfo.toJSON();

                for (var _i6 = 0, _Object$keys6 = Object.keys(privateKeyJSON); _i6 < _Object$keys6.length; _i6++) {
                  const key = _Object$keys6[_i6];
                  jwk[key] = privateKeyJSON[key];
                } //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 (algorithm.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:
                      return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
                  }
                } //endregion
                //region Create RSA Private Key elements

                const privateKeyJSON = privateKeyInfo.toJSON();

                for (var _i7 = 0, _Object$keys7 = Object.keys(privateKeyJSON); _i7 < _Object$keys7.length; _i7++) {
                  const key = _Object$keys7[_i7];
                  jwk[key] = privateKeyJSON[key];
                } //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();

                for (var _i8 = 0, _Object$keys8 = Object.keys(privateKeyJSON); _i8 < _Object$keys8.length; _i8++) {
                  const key = _Object$keys8[_i8];
                  jwk[key] = privateKeyJSON[key];
                } //endregion

              }
              break;

            default:
              return Promise.reject(`Incorrect algorithm name: ${algorithm.name.toUpperCase()}`);
          }
        }
        break;

      case "jwk":
        jwk = keyData;
        break;

      default:
        return Promise.reject(`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)
      return Promise.resolve().then(() => this.subtle.importKey("jwk", (0, _pvutils.stringToArrayBuffer)(JSON.stringify(jwk)), algorithm, extractable, keyUsages)).then(result => result, () => 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 {string} format
   * @param {Object} key
   * @returns {Promise}
   */


  exportKey(format, key) {
    let sequence = this.subtle.exportKey("jwk", key); //region Currently Safari returns ArrayBuffer as JWK thus we need an additional transformation

    if (this.name.toLowerCase() === "safari") {
      sequence = sequence.then(result => {
        // Some additional checks for Safari Technology Preview
        if (result instanceof ArrayBuffer) return JSON.parse((0, _pvutils.arrayBufferToString)(result));
        return result;
      });
    } //endregion


    switch (format.toLowerCase()) {
      case "raw":
        return this.subtle.exportKey("raw", key);

      case "spki":
        sequence = sequence.then(result => {
          const publicKeyInfo = new _PublicKeyInfo.default();

          try {
            publicKeyInfo.fromJSON(result);
          } catch (ex) {
            return Promise.reject("Incorrect key data");
          }

          return publicKeyInfo.toSchema().toBER(false);
        });
        break;

      case "pkcs8":
        sequence = sequence.then(result => {
          const privateKeyInfo = new _PrivateKeyInfo.default();

          try {
            privateKeyInfo.fromJSON(result);
          } catch (ex) {
            return Promise.reject("Incorrect key data");
          }

          return privateKeyInfo.toSchema().toBER(false);
        });
        break;

      case "jwk":
        break;

      default:
        return Promise.reject(`Incorrect format: ${format}`);
    }

    return sequence;
  } //**********************************************************************************

  /**
   * Convert WebCrypto keys between different export formats
   * @param {string} inputFormat
   * @param {string} outputFormat
   * @param {ArrayBuffer|Object} keyData
   * @param {Object} algorithm
   * @param {boolean} extractable
   * @param {Array} keyUsages
   * @returns {Promise}
   */


  convert(inputFormat, outputFormat, keyData, algorithm, extractable, keyUsages) {
    switch (inputFormat.toLowerCase()) {
      case "raw":
        switch (outputFormat.toLowerCase()) {
          case "raw":
            return Promise.resolve(keyData);

          case "spki":
            return Promise.resolve().then(() => this.importKey("raw", keyData, algorithm, extractable, keyUsages)).then(result => this.exportKey("spki", result));

          case "pkcs8":
            return Promise.resolve().then(() => this.importKey("raw", keyData, algorithm, extractable, keyUsages)).then(result => this.exportKey("pkcs8", result));

          case "jwk":
            return Promise.resolve().then(() => this.importKey("raw", keyData, algorithm, extractable, keyUsages)).then(result => this.exportKey("jwk", result));

          default:
            return Promise.reject(`Incorrect outputFormat: ${outputFormat}`);
        }

      case "spki":
        switch (outputFormat.toLowerCase()) {
          case "raw":
            return Promise.resolve().then(() => this.importKey("spki", keyData, algorithm, extractable, keyUsages)).then(result => this.exportKey("raw", result));

          case "spki":
            return Promise.resolve(keyData);

          case "pkcs8":
            return Promise.reject("Impossible to convert between SPKI/PKCS8");

          case "jwk":
            return Promise.resolve().then(() => this.importKey("spki", keyData, algorithm, extractable, keyUsages)).then(result => this.exportKey("jwk", result));

          default:
            return Promise.reject(`Incorrect outputFormat: ${outputFormat}`);
        }

      case "pkcs8":
        switch (outputFormat.toLowerCase()) {
          case "raw":
            return Promise.resolve().then(() => this.importKey("pkcs8", keyData, algorithm, extractable, keyUsages)).then(result => this.exportKey("raw", result));

          case "spki":
            return Promise.reject("Impossible to convert between SPKI/PKCS8");

          case "pkcs8":
            return Promise.resolve(keyData);

          case "jwk":
            return Promise.resolve().then(() => this.importKey("pkcs8", keyData, algorithm, extractable, keyUsages)).then(result => this.exportKey("jwk", result));

          default:
            return Promise.reject(`Incorrect outputFormat: ${outputFormat}`);
        }

      case "jwk":
        switch (outputFormat.toLowerCase()) {
          case "raw":
            return Promise.resolve().then(() => this.importKey("jwk", keyData, algorithm, extractable, keyUsages)).then(result => this.exportKey("raw", result));

          case "spki":
            return Promise.resolve().then(() => this.importKey("jwk", keyData, algorithm, extractable, keyUsages)).then(result => this.exportKey("spki", result));

          case "pkcs8":
            return Promise.resolve().then(() => this.importKey("jwk", keyData, algorithm, extractable, keyUsages)).then(result => this.exportKey("pkcs8", result));

          case "jwk":
            return Promise.resolve(keyData);

          default:
            return Promise.reject(`Incorrect outputFormat: ${outputFormat}`);
        }

      default:
        return Promise.reject(`Incorrect inputFormat: ${inputFormat}`);
    }
  } //**********************************************************************************

  /**
   * Wrapper for standard function "encrypt"
   * @param args
   * @returns {Promise}
   */


  encrypt(...args) {
    return this.subtle.encrypt(...args);
  } //**********************************************************************************

  /**
   * Wrapper for standard function "decrypt"
   * @param args
   * @returns {Promise}
   */


  decrypt(...args) {
    return this.subtle.decrypt(...args);
  } //**********************************************************************************

  /**
   * Wrapper for standard function "sign"
   * @param args
   * @returns {Promise}
   */


  sign(...args) {
    return this.subtle.sign(...args);
  } //**********************************************************************************

  /**
   * Wrapper for standard function "verify"
   * @param args
   * @returns {Promise}
   */


  verify(...args) {
    return this.subtle.verify(...args);
  } //**********************************************************************************

  /**
   * Wrapper for standard function "digest"
   * @param args
   * @returns {Promise}
   */


  digest(...args) {
    return this.subtle.digest(...args);
  } //**********************************************************************************

  /**
   * Wrapper for standard function "generateKey"
   * @param args
   * @returns {Promise}
   */


  generateKey(...args) {
    return this.subtle.generateKey(...args);
  } //**********************************************************************************

  /**
   * Wrapper for standard function "deriveKey"
   * @param args
   * @returns {Promise}
   */


  deriveKey(...args) {
    return this.subtle.deriveKey(...args);
  } //**********************************************************************************

  /**
   * Wrapper for standard function "deriveBits"
   * @param args
   * @returns {Promise}
   */


  deriveBits(...args) {
    return this.subtle.deriveBits(...args);
  } //**********************************************************************************

  /**
   * Wrapper for standard function "wrapKey"
   * @param args
   * @returns {Promise}
   */


  wrapKey(...args) {
    return this.subtle.wrapKey(...args);
  } //**********************************************************************************

  /**
   * Wrapper for standard function "unwrapKey"
   * @param args
   * @returns {Promise}
   */


  unwrapKey(...args) {
    return this.subtle.unwrapKey(...args);
  } //**********************************************************************************

  /**
   * Initialize input Uint8Array by random values (with help from current "crypto engine")
   * @param {!Uint8Array} view
   * @returns {*}
   */


  getRandomValues(view) {
    if ("getRandomValues" in this.crypto === false) throw new Error("No support for getRandomValues");
    return this.crypto.getRandomValues(view);
  } //**********************************************************************************

  /**
   * Get WebCrypto algorithm by wel-known OID
   * @param {string} oid well-known OID to search for
   * @returns {Object}
   */


  getAlgorithmByOID(oid) {
    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:
    }

    return {};
  } //**********************************************************************************

  /**
   * Get OID for each specific algorithm
   * @param {Object} algorithm
   * @returns {string}
   */


  getOIDByAlgorithm(algorithm) {
    let result = "";

    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.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.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.kdf.toUpperCase()) {
          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.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.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.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.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.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:
    }

    return result;
  } //**********************************************************************************

  /**
   * Get default algorithm parameters for each kind of operation
   * @param {string} algorithmName Algorithm name to get common parameters for
   * @param {string} operation Kind of operation: "sign", "encrypt", "generatekey", "importkey", "exportkey", "verify"
   * @returns {*}
   */


  getAlgorithmParameters(algorithmName, operation) {
    let result = {
      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 {AlgorithmIdentifier} signatureAlgorithm Signature algorithm
   * @returns {string}
   */


  getHashAlgorithm(signatureAlgorithm) {
    let result = "";

    switch (signatureAlgorithm.algorithmId) {
      case "1.2.840.10045.4.1": // ecdsa-with-SHA1

      case "1.2.840.113549.1.1.5":
        result = "SHA-1";
        break;

      case "1.2.840.10045.4.3.2": // ecdsa-with-SHA256

      case "1.2.840.113549.1.1.11":
        result = "SHA-256";
        break;

      case "1.2.840.10045.4.3.3": // ecdsa-with-SHA384

      case "1.2.840.113549.1.1.12":
        result = "SHA-384";
        break;

      case "1.2.840.10045.4.3.4": // ecdsa-with-SHA512

      case "1.2.840.113549.1.1.13":
        result = "SHA-512";
        break;

      case "1.2.840.113549.1.1.10":
        // RSA-PSS
        {
          try {
            const params = new _RSASSAPSSParams.default({
              schema: signatureAlgorithm.algorithmParams
            });

            if ("hashAlgorithm" in params) {
              const algorithm = this.getAlgorithmByOID(params.hashAlgorithm.algorithmId);
              if ("name" in algorithm === false) return "";
              result = algorithm.name;
            } else result = "SHA-1";
          } catch (ex) {}
        }
        break;

      default:
    }

    return result;
  } //**********************************************************************************

  /**
   * Specialized function encrypting "EncryptedContentInfo" object using parameters
   * @param {Object} parameters
   * @returns {Promise}
   */


  encryptEncryptedContentInfo(parameters) {
    //region Check for input parameters
    if (parameters instanceof Object === false) return Promise.reject("Parameters must have type \"Object\"");
    if ("password" in parameters === false) return Promise.reject("Absent mandatory parameter \"password\"");
    if ("contentEncryptionAlgorithm" in parameters === false) return Promise.reject("Absent mandatory parameter \"contentEncryptionAlgorithm\"");
    if ("hmacHashAlgorithm" in parameters === false) return Promise.reject("Absent mandatory parameter \"hmacHashAlgorithm\"");
    if ("iterationCount" in parameters === false) return Promise.reject("Absent mandatory parameter \"iterationCount\"");
    if ("contentToEncrypt" in parameters === false) return Promise.reject("Absent mandatory parameter \"contentToEncrypt\"");
    if ("contentType" in parameters === false) return Promise.reject("Absent mandatory parameter \"contentType\"");
    const contentEncryptionOID = this.getOIDByAlgorithm(parameters.contentEncryptionAlgorithm);
    if (contentEncryptionOID === "") return Promise.reject("Wrong \"contentEncryptionAlgorithm\" value");
    const pbkdf2OID = this.getOIDByAlgorithm({
      name: "PBKDF2"
    });
    if (pbkdf2OID === "") return Promise.reject("Can not find OID for PBKDF2");
    const hmacOID = this.getOIDByAlgorithm({
      name: "HMAC",
      hash: {
        name: parameters.hmacHashAlgorithm
      }
    });
    if (hmacOID === "") return Promise.reject(`Incorrect value for "hmacHashAlgorithm": ${parameters.hmacHashAlgorithm}`); //endregion
    //region Initial variables

    let sequence = Promise.resolve();
    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.default({
      salt: new asn1js.OctetString({
        valueHex: saltBuffer
      }),
      iterationCount: parameters.iterationCount,
      prf: new _AlgorithmIdentifier.default({
        algorithmId: hmacOID,
        algorithmParams: new asn1js.Null()
      })
    }); //endregion
    //region Derive PBKDF2 key from "password" buffer

    sequence = sequence.then(() => {
      const passwordView = new Uint8Array(parameters.password);
      return this.importKey("raw", passwordView, "PBKDF2", false, ["deriveKey"]);
    }, error => Promise.reject(error)); //endregion
    //region Derive key for "contentEncryptionAlgorithm"

    sequence = sequence.then(result => this.deriveKey({
      name: "PBKDF2",
      hash: {
        name: parameters.hmacHashAlgorithm
      },
      salt: saltView,
      iterations: parameters.iterationCount
    }, result, parameters.contentEncryptionAlgorithm, false, ["encrypt"]), error => Promise.reject(error)); //endregion
    //region Encrypt content

    sequence = sequence.then(result => this.encrypt({
      name: parameters.contentEncryptionAlgorithm.name,
      iv: ivView
    }, result, contentView), error => Promise.reject(error)); //endregion
    //region Store all parameters in EncryptedData object

    sequence = sequence.then(result => {
      const pbes2Parameters = new _PBES2Params.default({
        keyDerivationFunc: new _AlgorithmIdentifier.default({
          algorithmId: pbkdf2OID,
          algorithmParams: pbkdf2Params.toSchema()
        }),
        encryptionScheme: new _AlgorithmIdentifier.default({
          algorithmId: contentEncryptionOID,
          algorithmParams: new asn1js.OctetString({
            valueHex: ivBuffer
          })
        })
      });
      return new _EncryptedContentInfo.default({
        contentType: parameters.contentType,
        contentEncryptionAlgorithm: new _AlgorithmIdentifier.default({
          algorithmId: "1.2.840.113549.1.5.13",
          // pkcs5PBES2
          algorithmParams: pbes2Parameters.toSchema()
        }),
        encryptedContent: new asn1js.OctetString({
          valueHex: result
        })
      });
    }, error => Promise.reject(error)); //endregion

    return sequence;
  } //**********************************************************************************

  /**
   * Decrypt data stored in "EncryptedContentInfo" object using parameters
   * @param parameters
   * @return {Promise}
   */


  decryptEncryptedContentInfo(parameters) {
    //region Check for input parameters
    if (parameters instanceof Object === false) return Promise.reject("Parameters must have type \"Object\"");
    if ("password" in parameters === false) return Promise.reject("Absent mandatory parameter \"password\"");
    if ("encryptedContentInfo" in parameters === false) return Promise.reject("Absent mandatory parameter \"encryptedContentInfo\"");
    if (parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId !== "1.2.840.113549.1.5.13") // pkcs5PBES2
      return Promise.reject(`Unknown "contentEncryptionAlgorithm": ${parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId}`); //endregion
    //region Initial variables

    let sequence = Promise.resolve();
    let pbes2Parameters;

    try {
      pbes2Parameters = new _PBES2Params.default({
        schema: parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams
      });
    } catch (ex) {
      return Promise.reject("Incorrectly encoded \"pbes2Parameters\"");
    }

    let pbkdf2Params;

    try {
      pbkdf2Params = new _PBKDF2Params.default({
        schema: pbes2Parameters.keyDerivationFunc.algorithmParams
      });
    } catch (ex) {
      return Promise.reject("Incorrectly encoded \"pbkdf2Params\"");
    }

    const contentEncryptionAlgorithm = this.getAlgorithmByOID(pbes2Parameters.encryptionScheme.algorithmId);
    if ("name" in contentEncryptionAlgorithm === false) return Promise.reject(`Incorrect OID for "contentEncryptionAlgorithm": ${pbes2Parameters.encryptionScheme.algorithmId}`);
    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 ("prf" in pbkdf2Params) {
      const algorithm = this.getAlgorithmByOID(pbkdf2Params.prf.algorithmId);
      if ("name" in algorithm === false) return Promise.reject("Incorrect OID for HMAC hash algorithm");
      hmacHashAlgorithm = algorithm.hash.name;
    } //endregion
    //region Derive PBKDF2 key from "password" buffer


    sequence = sequence.then(() => this.importKey("raw", parameters.password, "PBKDF2", false, ["deriveKey"]), error => Promise.reject(error)); //endregion
    //region Derive key for "contentEncryptionAlgorithm"

    sequence = sequence.then(result => this.deriveKey({
      name: "PBKDF2",
      hash: {
        name: hmacHashAlgorithm
      },
      salt: saltView,
      iterations: iterationCount
    }, result, contentEncryptionAlgorithm, false, ["decrypt"]), error => Promise.reject(error)); //endregion
    //region Decrypt internal content using derived key

    sequence = sequence.then(result => {
      //region Create correct data block for decryption
      let dataBuffer = new ArrayBuffer(0);
      if (parameters.encryptedContentInfo.encryptedContent.idBlock.isConstructed === false) dataBuffer = parameters.encryptedContentInfo.encryptedContent.valueBlock.valueHex;else {
        var _iterator = _createForOfIteratorHelper(parameters.encryptedContentInfo.encryptedContent.valueBlock.value),
            _step;

        try {
          for (_iterator.s(); !(_step = _iterator.n()).done;) {
            const content = _step.value;
            dataBuffer = (0, _pvutils.utilConcatBuf)(dataBuffer, content.valueBlock.valueHex);
          }
        } catch (err) {
          _iterator.e(err);
        } finally {
          _iterator.f();
        }
      } //endregion

      return this.decrypt({
        name: contentEncryptionAlgorithm.name,
        iv: ivView
      }, result, dataBuffer);
    }, error => Promise.reject(error)); //endregion

    return sequence;
  } //**********************************************************************************

  /**
   * Stamping (signing) data using algorithm simular to HMAC
   * @param {Object} parameters
   * @return {Promise.<T>|Promise}
   */


  stampDataWithPassword(parameters) {
    //region Check for input parameters
    if (parameters instanceof Object === false) return Promise.reject("Parameters must have type \"Object\"");
    if ("password" in parameters === false) return Promise.reject("Absent mandatory parameter \"password\"");
    if ("hashAlgorithm" in parameters === false) return Promise.reject("Absent mandatory parameter \"hashAlgorithm\"");
    if ("salt" in parameters === false) return Promise.reject("Absent mandatory parameter \"iterationCount\"");
    if ("iterationCount" in parameters === false) return Promise.reject("Absent mandatory parameter \"salt\"");
    if ("contentToStamp" in parameters === false) return Promise.reject("Absent mandatory parameter \"contentToStamp\""); //endregion
    //region Choose correct length for HMAC key

    let length;

    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:
        return Promise.reject(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`);
    } //endregion
    //region Initial variables


    let sequence = Promise.resolve();
    const hmacAlgorithm = {
      name: "HMAC",
      length,
      hash: {
        name: parameters.hashAlgorithm
      }
    }; //endregion
    //region Create PKCS#12 key for integrity checking

    sequence = sequence.then(() => makePKCS12B2Key(this, parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount)); //endregion
    //region Import HMAC key
    // noinspection JSCheckFunctionSignatures

    sequence = sequence.then(result => this.importKey("raw", new Uint8Array(result), hmacAlgorithm, false, ["sign"])); //endregion
    //region Make signed HMAC value

    sequence = sequence.then(result => this.sign(hmacAlgorithm, result, new Uint8Array(parameters.contentToStamp)), error => Promise.reject(error)); //endregion

    return sequence;
  } //**********************************************************************************


  verifyDataStampedWithPassword(parameters) {
    //region Check for input parameters
    if (parameters instanceof Object === false) return Promise.reject("Parameters must have type \"Object\"");
    if ("password" in parameters === false) return Promise.reject("Absent mandatory parameter \"password\"");
    if ("hashAlgorithm" in parameters === false) return Promise.reject("Absent mandatory parameter \"hashAlgorithm\"");
    if ("salt" in parameters === false) return Promise.reject("Absent mandatory parameter \"iterationCount\"");
    if ("iterationCount" in parameters === false) return Promise.reject("Absent mandatory parameter \"salt\"");
    if ("contentToVerify" in parameters === false) return Promise.reject("Absent mandatory parameter \"contentToVerify\"");
    if ("signatureToVerify" in parameters === false) return Promise.reject("Absent mandatory parameter \"signatureToVerify\""); //endregion
    //region Choose correct length for HMAC key

    let length;

    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:
        return Promise.reject(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`);
    } //endregion
    //region Initial variables


    let sequence = Promise.resolve();
    const hmacAlgorithm = {
      name: "HMAC",
      length,
      hash: {
        name: parameters.hashAlgorithm
      }
    }; //endregion
    //region Create PKCS#12 key for integrity checking

    sequence = sequence.then(() => makePKCS12B2Key(this, parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount)); //endregion
    //region Import HMAC key
    // noinspection JSCheckFunctionSignatures

    sequence = sequence.then(result => this.importKey("raw", new Uint8Array(result), hmacAlgorithm, false, ["verify"])); //endregion
    //region Make signed HMAC value

    sequence = sequence.then(result => this.verify(hmacAlgorithm, result, new Uint8Array(parameters.signatureToVerify), new Uint8Array(parameters.contentToVerify)), error => Promise.reject(error)); //endregion

    return sequence;
  } //**********************************************************************************

  /**
   * Get signature parameters by analyzing private key algorithm
   * @param {Object} privateKey The private key user would like to use
   * @param {string} [hashAlgorithm="SHA-1"] Hash algorithm user would like to use
   * @return {Promise.<T>|Promise}
   */


  getSignatureParameters(privateKey, hashAlgorithm = "SHA-1") {
    //region Check hashing algorithm
    const oid = this.getOIDByAlgorithm({
      name: hashAlgorithm
    });
    if (oid === "") return Promise.reject(`Unsupported hash algorithm: ${hashAlgorithm}`); //endregion
    //region Initial variables

    const signatureAlgorithm = new _AlgorithmIdentifier.default(); //endregion
    //region Get a "default parameters" for current algorithm

    const parameters = this.getAlgorithmParameters(privateKey.algorithm.name, "sign");
    parameters.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(parameters.algorithm);
        break;

      case "RSA-PSS":
        {
          //region Set "saltLength" as a length (in octets) of hash function result
          switch (hashAlgorithm.toUpperCase()) {
            case "SHA-256":
              parameters.algorithm.saltLength = 32;
              break;

            case "SHA-384":
              parameters.algorithm.saltLength = 48;
              break;

            case "SHA-512":
              parameters.algorithm.saltLength = 64;
              break;

            default:
          } //endregion
          //region Fill "RSASSA_PSS_params" object


          const paramsObject = {};

          if (hashAlgorithm.toUpperCase() !== "SHA-1") {
            const hashAlgorithmOID = this.getOIDByAlgorithm({
              name: hashAlgorithm
            });
            if (hashAlgorithmOID === "") return Promise.reject(`Unsupported hash algorithm: ${hashAlgorithm}`);
            paramsObject.hashAlgorithm = new _AlgorithmIdentifier.default({
              algorithmId: hashAlgorithmOID,
              algorithmParams: new asn1js.Null()
            });
            paramsObject.maskGenAlgorithm = new _AlgorithmIdentifier.default({
              algorithmId: "1.2.840.113549.1.1.8",
              // MGF1
              algorithmParams: paramsObject.hashAlgorithm.toSchema()
            });
          }

          if (parameters.algorithm.saltLength !== 20) paramsObject.saltLength = parameters.algorithm.saltLength;
          const pssParameters = new _RSASSAPSSParams.default(paramsObject); //endregion
          //region Automatically set signature algorithm

          signatureAlgorithm.algorithmId = "1.2.840.113549.1.1.10";
          signatureAlgorithm.algorithmParams = pssParameters.toSchema(); //endregion
        }
        break;

      default:
        return Promise.reject(`Unsupported signature algorithm: ${privateKey.algorithm.name}`);
    } //endregion


    return Promise.resolve().then(() => ({
      signatureAlgorithm,
      parameters
    }));
  } //**********************************************************************************

  /**
   * Sign data with pre-defined private key
   * @param {ArrayBuffer} data Data to be signed
   * @param {Object} privateKey Private key to use
   * @param {Object} parameters Parameters for used algorithm
   * @return {Promise.<T>|Promise}
   */


  signWithPrivateKey(data, privateKey, parameters) {
    return this.sign(parameters.algorithm, privateKey, new Uint8Array(data)).then(result => {
      //region Special case for ECDSA algorithm
      if (parameters.algorithm.name === "ECDSA") result = (0, _common.createCMSECDSASignature)(result); //endregion

      return result;
    }, error => Promise.reject(`Signing error: ${error}`));
  } //**********************************************************************************


  fillPublicKeyParameters(publicKeyInfo, signatureAlgorithm) {
    const parameters = {}; //region Find signer's hashing algorithm

    const shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm);
    if (shaAlgorithm === "") return Promise.reject(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`); //endregion
    //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);
    if ("name" in algorithmObject === "") return Promise.reject(`Unsupported public key algorithm: ${signatureAlgorithm.algorithmId}`);
    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
      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) return Promise.reject("Incorrect type for ECDSA public key parameters");
      const curveObject = this.getAlgorithmByOID(publicKeyInfo.algorithm.algorithmParams.valueBlock.toString());
      if ("name" in curveObject === false) return Promise.reject(`Unsupported named curve algorithm: ${publicKeyInfo.algorithm.algorithmParams.valueBlock.toString()}`); //endregion

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


    return parameters;
  } //**********************************************************************************


  getPublicKey(publicKeyInfo, signatureAlgorithm, parameters = null) {
    if (parameters === null) parameters = this.fillPublicKeyParameters(publicKeyInfo, signatureAlgorithm);
    const publicKeyInfoSchema = publicKeyInfo.toSchema();
    const publicKeyInfoBuffer = publicKeyInfoSchema.toBER(false);
    const publicKeyInfoView = new Uint8Array(publicKeyInfoBuffer);
    return this.importKey("spki", publicKeyInfoView, parameters.algorithm.algorithm, true, parameters.algorithm.usages);
  } //**********************************************************************************


  verifyWithPublicKey(data, signature, publicKeyInfo, signatureAlgorithm, shaAlgorithm = null) {
    //region Initial variables
    let sequence = Promise.resolve(); //endregion
    //region Find signer's hashing algorithm

    if (shaAlgorithm === null) {
      shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm);
      if (shaAlgorithm === "") return Promise.reject(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`); //region Import public key

      sequence = sequence.then(() => this.getPublicKey(publicKeyInfo, signatureAlgorithm)); //endregion
    } else {
      const parameters = {}; //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);
      if ("name" in algorithmObject === "") return Promise.reject(`Unsupported public key algorithm: ${signatureAlgorithm.algorithmId}`);
      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
        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) return Promise.reject("Incorrect type for ECDSA public key parameters");
        const curveObject = this.getAlgorithmByOID(publicKeyInfo.algorithm.algorithmParams.valueBlock.toString());
        if ("name" in curveObject === false) return Promise.reject(`Unsupported named curve algorithm: ${publicKeyInfo.algorithm.algorithmParams.valueBlock.toString()}`); //endregion

        parameters.algorithm.algorithm.namedCurve = curveObject.name;
      } //endregion
      //endregion
      //region Import public key


      sequence = sequence.then(() => this.getPublicKey(publicKeyInfo, null, parameters)); //endregion
    } //endregion
    //region Verify signature


    sequence = sequence.then(publicKey => {
      //region Get default algorithm parameters for verification
      const algorithm = this.getAlgorithmParameters(publicKey.algorithm.name, "verify");
      if ("hash" in algorithm.algorithm) algorithm.algorithm.hash.name = shaAlgorithm; //endregion
      //region Special case for ECDSA signatures

      let signatureValue = signature.valueBlock.valueHex;

      if (publicKey.algorithm.name === "ECDSA") {
        const asn1 = asn1js.fromBER(signatureValue); // noinspection JSCheckFunctionSignatures

        signatureValue = (0, _common.createECDSASignatureFromCMS)(asn1.result);
      } //endregion
      //region Special case for RSA-PSS


      if (publicKey.algorithm.name === "RSA-PSS") {
        let pssParameters;

        try {
          pssParameters = new _RSASSAPSSParams.default({
            schema: signatureAlgorithm.algorithmParams
          });
        } catch (ex) {
          return Promise.reject(ex);
        }

        if ("saltLength" in pssParameters) algorithm.algorithm.saltLength = pssParameters.saltLength;else algorithm.algorithm.saltLength = 20;
        let hashAlgo = "SHA-1";

        if ("hashAlgorithm" in pssParameters) {
          const hashAlgorithm = this.getAlgorithmByOID(pssParameters.hashAlgorithm.algorithmId);
          if ("name" in hashAlgorithm === false) return Promise.reject(`Unrecognized hash algorithm: ${pssParameters.hashAlgorithm.algorithmId}`);
          hashAlgo = hashAlgorithm.name;
        }

        algorithm.algorithm.hash.name = hashAlgo;
      } //endregion


      return this.verify(algorithm.algorithm, publicKey, new Uint8Array(signatureValue), new Uint8Array(data));
    }); //endregion

    return sequence;
  } //**********************************************************************************


} //**************************************************************************************


exports.default = CryptoEngine;