tor-browser

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

EnvelopedData.ts (58045B)


      1 import * as asn1js from "asn1js";
      2 import * as pvutils from "pvutils";
      3 import { BufferSourceConverter } from "pvtsutils";
      4 import * as common from "./common";
      5 import { OriginatorInfo, OriginatorInfoJson } from "./OriginatorInfo";
      6 import { RecipientInfo, RecipientInfoJson } from "./RecipientInfo";
      7 import { EncryptedContentInfo, EncryptedContentInfoJson, EncryptedContentInfoSchema, EncryptedContentInfoSplit } from "./EncryptedContentInfo";
      8 import { Attribute, AttributeJson } from "./Attribute";
      9 import { AlgorithmIdentifier, AlgorithmIdentifierParameters } from "./AlgorithmIdentifier";
     10 import { RSAESOAEPParams } from "./RSAESOAEPParams";
     11 import { KeyTransRecipientInfo } from "./KeyTransRecipientInfo";
     12 import { IssuerAndSerialNumber } from "./IssuerAndSerialNumber";
     13 import { RecipientKeyIdentifier } from "./RecipientKeyIdentifier";
     14 import { RecipientEncryptedKey } from "./RecipientEncryptedKey";
     15 import { KeyAgreeRecipientIdentifier } from "./KeyAgreeRecipientIdentifier";
     16 import { KeyAgreeRecipientInfo, KeyAgreeRecipientInfoParameters } from "./KeyAgreeRecipientInfo";
     17 import { RecipientEncryptedKeys } from "./RecipientEncryptedKeys";
     18 import { KEKRecipientInfo } from "./KEKRecipientInfo";
     19 import { KEKIdentifier } from "./KEKIdentifier";
     20 import { PBKDF2Params } from "./PBKDF2Params";
     21 import { PasswordRecipientinfo } from "./PasswordRecipientinfo";
     22 import { ECCCMSSharedInfo } from "./ECCCMSSharedInfo";
     23 import { OriginatorIdentifierOrKey } from "./OriginatorIdentifierOrKey";
     24 import { OriginatorPublicKey } from "./OriginatorPublicKey";
     25 import * as Schema from "./Schema";
     26 import { Certificate } from "./Certificate";
     27 import { ArgumentError, AsnError } from "./errors";
     28 import { PkiObject, PkiObjectParameters } from "./PkiObject";
     29 import { EMPTY_STRING } from "./constants";
     30 
     31 const VERSION = "version";
     32 const ORIGINATOR_INFO = "originatorInfo";
     33 const RECIPIENT_INFOS = "recipientInfos";
     34 const ENCRYPTED_CONTENT_INFO = "encryptedContentInfo";
     35 const UNPROTECTED_ATTRS = "unprotectedAttrs";
     36 const CLEAR_PROPS = [
     37  VERSION,
     38  ORIGINATOR_INFO,
     39  RECIPIENT_INFOS,
     40  ENCRYPTED_CONTENT_INFO,
     41  UNPROTECTED_ATTRS
     42 ];
     43 
     44 const defaultEncryptionParams = {
     45  kdfAlgorithm: "SHA-512",
     46  kekEncryptionLength: 256
     47 };
     48 const curveLengthByName: Record<string, number> = {
     49  "P-256": 256,
     50  "P-384": 384,
     51  "P-521": 528
     52 };
     53 
     54 export interface IEnvelopedData {
     55  /**
     56   * Version number.
     57   *
     58   * The appropriate value depends on `originatorInfo`, `RecipientInfo`, and `unprotectedAttrs`.
     59   *
     60   * The version MUST be assigned as follows:
     61   * ```
     62   * IF (originatorInfo is present) AND
     63   *    ((any certificates with a type of other are present) OR
     64   *    (any crls with a type of other are present))
     65   * THEN version is 4
     66   * ELSE
     67   *    IF ((originatorInfo is present) AND
     68   *       (any version 2 attribute certificates are present)) OR
     69   *       (any RecipientInfo structures include pwri) OR
     70   *       (any RecipientInfo structures include ori)
     71   *    THEN version is 3
     72   *    ELSE
     73   *       IF (originatorInfo is absent) AND
     74   *          (unprotectedAttrs is absent) AND
     75   *          (all RecipientInfo structures are version 0)
     76   *       THEN version is 0
     77   *       ELSE version is 2
     78   * ```
     79   */
     80  version: number;
     81  /**
     82   * Optionally provides information about the originator. It is present only if required by the key management algorithm.
     83   * It may contain certificates and CRLs.
     84   */
     85  originatorInfo?: OriginatorInfo;
     86  /**
     87   * Collection of per-recipient information. There MUST be at least one element in the collection.
     88   */
     89  recipientInfos: RecipientInfo[];
     90  /**
     91   * Encrypted content information
     92   */
     93  encryptedContentInfo: EncryptedContentInfo;
     94  /**
     95   * Collection of attributes that are not encrypted
     96   */
     97  unprotectedAttrs?: Attribute[];
     98 }
     99 
    100 /**
    101 * JSON representation of {@link EnvelopedData}
    102 */
    103 export interface EnvelopedDataJson {
    104  version: number;
    105  originatorInfo?: OriginatorInfoJson;
    106  recipientInfos: RecipientInfoJson[];
    107  encryptedContentInfo: EncryptedContentInfoJson;
    108  unprotectedAttrs?: AttributeJson[];
    109 }
    110 
    111 export type EnvelopedDataParameters = PkiObjectParameters & Partial<IEnvelopedData> & EncryptedContentInfoSplit;
    112 
    113 export interface EnvelopedDataEncryptionParams {
    114  kekEncryptionLength: number;
    115  kdfAlgorithm: string;
    116 }
    117 
    118 export interface EnvelopedDataDecryptBaseParams {
    119  preDefinedData?: BufferSource;
    120  recipientCertificate?: Certificate;
    121 }
    122 
    123 export interface EnvelopedDataDecryptKeyParams extends EnvelopedDataDecryptBaseParams {
    124  recipientPrivateKey: CryptoKey;
    125  /**
    126   * Crypto provider assigned to `recipientPrivateKey`. If the filed is empty uses default crypto provider.
    127   */
    128  crypto?: Crypto;
    129 }
    130 
    131 export interface EnvelopedDataDecryptBufferParams extends EnvelopedDataDecryptBaseParams {
    132  recipientPrivateKey?: BufferSource;
    133 }
    134 
    135 export type EnvelopedDataDecryptParams = EnvelopedDataDecryptBufferParams | EnvelopedDataDecryptKeyParams;
    136 
    137 /**
    138 * Represents the EnvelopedData structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652)
    139 *
    140 * @example The following example demonstrates how to create and encrypt CMS Enveloped Data
    141 * ```js
    142 * const cmsEnveloped = new pkijs.EnvelopedData();
    143 *
    144 * // Add recipient
    145 * cmsEnveloped.addRecipientByCertificate(cert, { oaepHashAlgorithm: "SHA-256" });
    146 *
    147 * // Secret key algorithm
    148 * const alg = {
    149 *   name: "AES-GCM",
    150 *   length: 256,
    151 * }
    152 * await cmsEnveloped.encrypt(alg, dataToEncrypt);
    153 *
    154 * // Add Enveloped Data into CMS Content Info
    155 * const cmsContent = new pkijs.ContentInfo();
    156 * cmsContent.contentType = pkijs.ContentInfo.ENVELOPED_DATA;
    157 * cmsContent.content = cmsEnveloped.toSchema();
    158 *
    159 * const cmsContentRaw = cmsContent.toSchema().toBER();
    160 * ```
    161 *
    162 * @example The following example demonstrates how to decrypt CMS Enveloped Data
    163 * ```js
    164 * // Get a "crypto" extension
    165 * const crypto = pkijs.getCrypto();
    166 *
    167 * // Parse CMS Content Info
    168 * const cmsContent = pkijs.ContentInfo.fromBER(cmsContentRaw);
    169 * if (cmsContent.contentType !== pkijs.ContentInfo.ENVELOPED_DATA) {
    170 *   throw new Error("CMS is not Enveloped Data");
    171 * }
    172 * // Parse CMS Enveloped Data
    173 * const cmsEnveloped = new pkijs.EnvelopedData({ schema: cmsContent.content });
    174 *
    175 * // Export private key to PKCS#8
    176 * const pkcs8 = await crypto.exportKey("pkcs8", keys.privateKey);
    177 *
    178 * // Decrypt data
    179 * const decryptedData = await cmsEnveloped.decrypt(0, {
    180 *   recipientCertificate: cert,
    181 *   recipientPrivateKey: pkcs8,
    182 * });
    183 * ```
    184 */
    185 export class EnvelopedData extends PkiObject implements IEnvelopedData {
    186 
    187  public static override CLASS_NAME = "EnvelopedData";
    188 
    189  public version!: number;
    190  public originatorInfo?: OriginatorInfo;
    191  public recipientInfos!: RecipientInfo[];
    192  public encryptedContentInfo!: EncryptedContentInfo;
    193  public unprotectedAttrs?: Attribute[];
    194 
    195  public policy: Required<EncryptedContentInfoSplit>;
    196 
    197  /**
    198   * Initializes a new instance of the {@link EnvelopedData} class
    199   * @param parameters Initialization parameters
    200   */
    201  constructor(parameters: EnvelopedDataParameters = {}) {
    202    super();
    203 
    204    this.version = pvutils.getParametersValue(parameters, VERSION, EnvelopedData.defaultValues(VERSION));
    205    if (ORIGINATOR_INFO in parameters) {
    206      this.originatorInfo = pvutils.getParametersValue(parameters, ORIGINATOR_INFO, EnvelopedData.defaultValues(ORIGINATOR_INFO));
    207    }
    208    this.recipientInfos = pvutils.getParametersValue(parameters, RECIPIENT_INFOS, EnvelopedData.defaultValues(RECIPIENT_INFOS));
    209    this.encryptedContentInfo = pvutils.getParametersValue(parameters, ENCRYPTED_CONTENT_INFO, EnvelopedData.defaultValues(ENCRYPTED_CONTENT_INFO));
    210    if (UNPROTECTED_ATTRS in parameters) {
    211      this.unprotectedAttrs = pvutils.getParametersValue(parameters, UNPROTECTED_ATTRS, EnvelopedData.defaultValues(UNPROTECTED_ATTRS));
    212    }
    213    this.policy = {
    214      disableSplit: !!parameters.disableSplit,
    215    };
    216 
    217    if (parameters.schema) {
    218      this.fromSchema(parameters.schema);
    219    }
    220  }
    221 
    222  /**
    223   * Returns default values for all class members
    224   * @param memberName String name for a class member
    225   * @returns Default value
    226   */
    227  public static override defaultValues(memberName: typeof VERSION): number;
    228  public static override defaultValues(memberName: typeof ORIGINATOR_INFO): OriginatorInfo;
    229  public static override defaultValues(memberName: typeof RECIPIENT_INFOS): RecipientInfo[];
    230  public static override defaultValues(memberName: typeof ENCRYPTED_CONTENT_INFO): EncryptedContentInfo;
    231  public static override defaultValues(memberName: typeof UNPROTECTED_ATTRS): Attribute[];
    232  public static override defaultValues(memberName: string): any {
    233    switch (memberName) {
    234      case VERSION:
    235        return 0;
    236      case ORIGINATOR_INFO:
    237        return new OriginatorInfo();
    238      case RECIPIENT_INFOS:
    239        return [];
    240      case ENCRYPTED_CONTENT_INFO:
    241        return new EncryptedContentInfo();
    242      case UNPROTECTED_ATTRS:
    243        return [];
    244      default:
    245        return super.defaultValues(memberName);
    246    }
    247  }
    248 
    249  /**
    250   * Compare values with default values for all class members
    251   * @param memberName String name for a class member
    252   * @param memberValue Value to compare with default value
    253   */
    254  public static compareWithDefault(memberName: string, memberValue: any): boolean {
    255    switch (memberName) {
    256      case VERSION:
    257        return (memberValue === EnvelopedData.defaultValues(memberName));
    258      case ORIGINATOR_INFO:
    259        return ((memberValue.certs.certificates.length === 0) && (memberValue.crls.crls.length === 0));
    260      case RECIPIENT_INFOS:
    261      case UNPROTECTED_ATTRS:
    262        return (memberValue.length === 0);
    263      case ENCRYPTED_CONTENT_INFO:
    264        return ((EncryptedContentInfo.compareWithDefault("contentType", memberValue.contentType)) &&
    265          (EncryptedContentInfo.compareWithDefault("contentEncryptionAlgorithm", memberValue.contentEncryptionAlgorithm) &&
    266            (EncryptedContentInfo.compareWithDefault("encryptedContent", memberValue.encryptedContent))));
    267      default:
    268        return super.defaultValues(memberName);
    269    }
    270  }
    271 
    272  /**
    273   * @inheritdoc
    274   * @asn ASN.1 schema
    275   * ```asn
    276   * EnvelopedData ::= SEQUENCE {
    277   *    version CMSVersion,
    278   *    originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
    279   *    recipientInfos RecipientInfos,
    280   *    encryptedContentInfo EncryptedContentInfo,
    281   *    unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }
    282   *```
    283   */
    284  public static override schema(parameters: Schema.SchemaParameters<{
    285    version?: string;
    286    originatorInfo?: string;
    287    recipientInfos?: string;
    288    encryptedContentInfo?: EncryptedContentInfoSchema;
    289    unprotectedAttrs?: string;
    290  }> = {}): Schema.SchemaType {
    291    const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {});
    292 
    293    return (new asn1js.Sequence({
    294      name: (names.blockName || EMPTY_STRING),
    295      value: [
    296        new asn1js.Integer({ name: (names.version || EMPTY_STRING) }),
    297        new asn1js.Constructed({
    298          name: (names.originatorInfo || EMPTY_STRING),
    299          optional: true,
    300          idBlock: {
    301            tagClass: 3, // CONTEXT-SPECIFIC
    302            tagNumber: 0 // [0]
    303          },
    304          value: OriginatorInfo.schema().valueBlock.value
    305        }),
    306        new asn1js.Set({
    307          value: [
    308            new asn1js.Repeated({
    309              name: (names.recipientInfos || EMPTY_STRING),
    310              value: RecipientInfo.schema()
    311            })
    312          ]
    313        }),
    314        EncryptedContentInfo.schema(names.encryptedContentInfo || {}),
    315        new asn1js.Constructed({
    316          optional: true,
    317          idBlock: {
    318            tagClass: 3, // CONTEXT-SPECIFIC
    319            tagNumber: 1 // [1]
    320          },
    321          value: [
    322            new asn1js.Repeated({
    323              name: (names.unprotectedAttrs || EMPTY_STRING),
    324              value: Attribute.schema()
    325            })
    326          ]
    327        })
    328      ]
    329    }));
    330  }
    331 
    332  public fromSchema(schema: Schema.SchemaType): void {
    333    // Clear input data first
    334    pvutils.clearProps(schema, CLEAR_PROPS);
    335 
    336    // Check the schema is valid
    337    const asn1 = asn1js.compareSchema(schema,
    338      schema,
    339      EnvelopedData.schema({
    340        names: {
    341          version: VERSION,
    342          originatorInfo: ORIGINATOR_INFO,
    343          recipientInfos: RECIPIENT_INFOS,
    344          encryptedContentInfo: {
    345            names: {
    346              blockName: ENCRYPTED_CONTENT_INFO
    347            }
    348          },
    349          unprotectedAttrs: UNPROTECTED_ATTRS
    350        }
    351      })
    352    );
    353    AsnError.assertSchema(asn1, this.className);
    354 
    355    // Get internal properties from parsed schema
    356    this.version = asn1.result.version.valueBlock.valueDec;
    357 
    358    if (ORIGINATOR_INFO in asn1.result) {
    359      this.originatorInfo = new OriginatorInfo({
    360        schema: new asn1js.Sequence({
    361          value: asn1.result.originatorInfo.valueBlock.value
    362        })
    363      });
    364    }
    365 
    366    this.recipientInfos = Array.from(asn1.result.recipientInfos, o => new RecipientInfo({ schema: o }));
    367    this.encryptedContentInfo = new EncryptedContentInfo({ schema: asn1.result.encryptedContentInfo });
    368 
    369    if (UNPROTECTED_ATTRS in asn1.result)
    370      this.unprotectedAttrs = Array.from(asn1.result.unprotectedAttrs, o => new Attribute({ schema: o }));
    371  }
    372 
    373  public toSchema(): asn1js.Sequence {
    374    //#region Create array for output sequence
    375    const outputArray = [];
    376 
    377    outputArray.push(new asn1js.Integer({ value: this.version }));
    378 
    379    if (this.originatorInfo) {
    380      outputArray.push(new asn1js.Constructed({
    381        optional: true,
    382        idBlock: {
    383          tagClass: 3, // CONTEXT-SPECIFIC
    384          tagNumber: 0 // [0]
    385        },
    386        value: this.originatorInfo.toSchema().valueBlock.value
    387      }));
    388    }
    389 
    390    outputArray.push(new asn1js.Set({
    391      value: Array.from(this.recipientInfos, o => o.toSchema())
    392    }));
    393 
    394    outputArray.push(this.encryptedContentInfo.toSchema());
    395 
    396    if (this.unprotectedAttrs) {
    397      outputArray.push(new asn1js.Constructed({
    398        optional: true,
    399        idBlock: {
    400          tagClass: 3, // CONTEXT-SPECIFIC
    401          tagNumber: 1 // [1]
    402        },
    403        value: Array.from(this.unprotectedAttrs, o => o.toSchema())
    404      }));
    405    }
    406    //#endregion
    407 
    408    //#region Construct and return new ASN.1 schema for this object
    409    return (new asn1js.Sequence({
    410      value: outputArray
    411    }));
    412    //#endregion
    413  }
    414 
    415  public toJSON(): EnvelopedDataJson {
    416    const res: EnvelopedDataJson = {
    417      version: this.version,
    418      recipientInfos: Array.from(this.recipientInfos, o => o.toJSON()),
    419      encryptedContentInfo: this.encryptedContentInfo.toJSON(),
    420    };
    421 
    422    if (this.originatorInfo)
    423      res.originatorInfo = this.originatorInfo.toJSON();
    424 
    425    if (this.unprotectedAttrs)
    426      res.unprotectedAttrs = Array.from(this.unprotectedAttrs, o => o.toJSON());
    427 
    428    return res;
    429  }
    430 
    431  /**
    432   * Helpers function for filling "RecipientInfo" based on recipient's certificate.
    433   * Problem with WebCrypto is that for RSA certificates we have only one option - "key transport" and
    434   * for ECC certificates we also have one option - "key agreement". As soon as Google will implement
    435   * DH algorithm it would be possible to use "key agreement" also for RSA certificates.
    436   * @param certificate Recipient's certificate
    437   * @param parameters Additional parameters necessary for "fine tunning" of encryption process
    438   * @param variant Variant = 1 is for "key transport", variant = 2 is for "key agreement". In fact the "variant" is unnecessary now because Google has no DH algorithm implementation. Thus key encryption scheme would be choosen by certificate type only: "key transport" for RSA and "key agreement" for ECC certificates.
    439   * @param crypto Crypto engine
    440   */
    441  public addRecipientByCertificate(certificate: Certificate, parameters?: object, variant?: number, crypto = common.getCrypto(true)): boolean {
    442    //#region Initialize encryption parameters
    443    const encryptionParameters = Object.assign(
    444      { useOAEP: true, oaepHashAlgorithm: "SHA-512" },
    445      defaultEncryptionParams,
    446      parameters || {}
    447    );
    448    //#endregion
    449 
    450    //#region Check type of certificate
    451    if (certificate.subjectPublicKeyInfo.algorithm.algorithmId.indexOf("1.2.840.113549") !== (-1))
    452      variant = 1; // For the moment it is the only variant for RSA-based certificates
    453    else {
    454      if (certificate.subjectPublicKeyInfo.algorithm.algorithmId.indexOf("1.2.840.10045") !== (-1))
    455        variant = 2; // For the moment it is the only variant for ECC-based certificates
    456      else
    457        throw new Error(`Unknown type of certificate's public key: ${certificate.subjectPublicKeyInfo.algorithm.algorithmId}`);
    458    }
    459    //#endregion
    460 
    461    //#region Add new "recipient" depends on "variant" and certificate type
    462    switch (variant) {
    463      case 1: // Key transport scheme
    464        {
    465          let algorithmId;
    466          let algorithmParams;
    467 
    468          if (encryptionParameters.useOAEP === true) {
    469            // keyEncryptionAlgorithm
    470            algorithmId = crypto.getOIDByAlgorithm({
    471              name: "RSA-OAEP"
    472            }, true, "keyEncryptionAlgorithm");
    473 
    474            //#region RSAES-OAEP-params
    475            const hashOID = crypto.getOIDByAlgorithm({
    476              name: encryptionParameters.oaepHashAlgorithm
    477            }, true, "RSAES-OAEP-params");
    478 
    479            const hashAlgorithm = new AlgorithmIdentifier({
    480              algorithmId: hashOID,
    481              algorithmParams: new asn1js.Null()
    482            });
    483 
    484            const rsaOAEPParams = new RSAESOAEPParams({
    485              hashAlgorithm,
    486              maskGenAlgorithm: new AlgorithmIdentifier({
    487                algorithmId: "1.2.840.113549.1.1.8", // id-mgf1
    488                algorithmParams: hashAlgorithm.toSchema()
    489              })
    490            });
    491 
    492            algorithmParams = rsaOAEPParams.toSchema();
    493            //#endregion
    494          }
    495          else // Use old RSAES-PKCS1-v1_5 schema instead
    496          {
    497            //#region keyEncryptionAlgorithm
    498            algorithmId = crypto.getOIDByAlgorithm({
    499              name: "RSAES-PKCS1-v1_5"
    500            });
    501            if (algorithmId === EMPTY_STRING)
    502              throw new Error("Can not find OID for RSAES-PKCS1-v1_5");
    503            //#endregion
    504 
    505            algorithmParams = new asn1js.Null();
    506          }
    507 
    508          //#region KeyTransRecipientInfo
    509          const keyInfo = new KeyTransRecipientInfo({
    510            version: 0,
    511            rid: new IssuerAndSerialNumber({
    512              issuer: certificate.issuer,
    513              serialNumber: certificate.serialNumber
    514            }),
    515            keyEncryptionAlgorithm: new AlgorithmIdentifier({
    516              algorithmId,
    517              algorithmParams
    518            }),
    519            recipientCertificate: certificate,
    520            // "encryptedKey" will be calculated in "encrypt" function
    521          });
    522          //#endregion
    523 
    524          //#region Final values for "CMS_ENVELOPED_DATA"
    525          this.recipientInfos.push(new RecipientInfo({
    526            variant: 1,
    527            value: keyInfo
    528          }));
    529          //#endregion
    530        }
    531        break;
    532      case 2: // Key agreement scheme
    533        {
    534          const recipientIdentifier = new KeyAgreeRecipientIdentifier({
    535            variant: 1,
    536            value: new IssuerAndSerialNumber({
    537              issuer: certificate.issuer,
    538              serialNumber: certificate.serialNumber
    539            })
    540          });
    541          this._addKeyAgreeRecipientInfo(
    542            recipientIdentifier,
    543            encryptionParameters,
    544            { recipientCertificate: certificate },
    545            crypto,
    546          );
    547        }
    548        break;
    549      default:
    550        throw new Error(`Unknown "variant" value: ${variant}`);
    551    }
    552    //#endregion
    553 
    554    return true;
    555  }
    556 
    557  /**
    558   * Add recipient based on pre-defined data like password or KEK
    559   * @param preDefinedData ArrayBuffer with pre-defined data
    560   * @param parameters Additional parameters necessary for "fine tunning" of encryption process
    561   * @param variant Variant = 1 for pre-defined "key encryption key" (KEK). Variant = 2 for password-based encryption.
    562   * @param crypto Crypto engine
    563   */
    564  public addRecipientByPreDefinedData(preDefinedData: ArrayBuffer, parameters: {
    565    keyIdentifier?: ArrayBuffer;
    566    hmacHashAlgorithm?: string;
    567    iterationCount?: number;
    568    keyEncryptionAlgorithm?: AesKeyGenParams;
    569    keyEncryptionAlgorithmParams?: any;
    570  } = {}, variant: number, crypto = common.getCrypto(true)) {
    571    //#region Check initial parameters
    572    ArgumentError.assert(preDefinedData, "preDefinedData", "ArrayBuffer");
    573    if (!preDefinedData.byteLength) {
    574      throw new Error("Pre-defined data could have zero length");
    575    }
    576    //#endregion
    577 
    578    //#region Initialize encryption parameters
    579    if (!parameters.keyIdentifier) {
    580      const keyIdentifierBuffer = new ArrayBuffer(16);
    581      const keyIdentifierView = new Uint8Array(keyIdentifierBuffer);
    582      crypto.getRandomValues(keyIdentifierView);
    583 
    584      parameters.keyIdentifier = keyIdentifierBuffer;
    585    }
    586 
    587    if (!parameters.hmacHashAlgorithm)
    588      parameters.hmacHashAlgorithm = "SHA-512";
    589 
    590    if (parameters.iterationCount === undefined) {
    591      parameters.iterationCount = 2048;
    592    }
    593 
    594    if (!parameters.keyEncryptionAlgorithm) {
    595      parameters.keyEncryptionAlgorithm = {
    596        name: "AES-KW",
    597        length: 256
    598      };
    599    }
    600 
    601    if (!parameters.keyEncryptionAlgorithmParams)
    602      parameters.keyEncryptionAlgorithmParams = new asn1js.Null();
    603    //#endregion
    604 
    605    //#region Add new recipient based on passed variant
    606    switch (variant) {
    607      case 1: // KEKRecipientInfo
    608        {
    609          // keyEncryptionAlgorithm
    610          const kekOID = crypto.getOIDByAlgorithm(parameters.keyEncryptionAlgorithm, true, "keyEncryptionAlgorithm");
    611 
    612          //#region KEKRecipientInfo
    613          const keyInfo = new KEKRecipientInfo({
    614            version: 4,
    615            kekid: new KEKIdentifier({
    616              keyIdentifier: new asn1js.OctetString({ valueHex: parameters.keyIdentifier })
    617            }),
    618            keyEncryptionAlgorithm: new AlgorithmIdentifier({
    619              algorithmId: kekOID,
    620              /*
    621               For AES-KW params are NULL, but for other algorithm could another situation.
    622               */
    623              algorithmParams: parameters.keyEncryptionAlgorithmParams
    624            }),
    625            preDefinedKEK: preDefinedData
    626            // "encryptedKey" would be set in "ecrypt" function
    627          });
    628          //#endregion
    629 
    630          //#region Final values for "CMS_ENVELOPED_DATA"
    631          this.recipientInfos.push(new RecipientInfo({
    632            variant: 3,
    633            value: keyInfo
    634          }));
    635          //#endregion
    636        }
    637        break;
    638      case 2: // PasswordRecipientinfo
    639        {
    640          // keyDerivationAlgorithm
    641          const pbkdf2OID = crypto.getOIDByAlgorithm({ name: "PBKDF2" }, true, "keyDerivationAlgorithm");
    642 
    643          //#region Salt
    644          const saltBuffer = new ArrayBuffer(64);
    645          const saltView = new Uint8Array(saltBuffer);
    646          crypto.getRandomValues(saltView);
    647          //#endregion
    648 
    649          //#region HMAC-based algorithm
    650          const hmacOID = crypto.getOIDByAlgorithm({
    651            name: "HMAC",
    652            hash: {
    653              name: parameters.hmacHashAlgorithm
    654            }
    655          } as Algorithm, true, "hmacHashAlgorithm");
    656          //#endregion
    657 
    658          //#region PBKDF2-params
    659          const pbkdf2Params = new PBKDF2Params({
    660            salt: new asn1js.OctetString({ valueHex: saltBuffer }),
    661            iterationCount: parameters.iterationCount,
    662            prf: new AlgorithmIdentifier({
    663              algorithmId: hmacOID,
    664              algorithmParams: new asn1js.Null()
    665            })
    666          });
    667          //#endregion
    668 
    669          // keyEncryptionAlgorithm
    670          const kekOID = crypto.getOIDByAlgorithm(parameters.keyEncryptionAlgorithm, true, "keyEncryptionAlgorithm");
    671 
    672          //#region PasswordRecipientinfo
    673          const keyInfo = new PasswordRecipientinfo({
    674            version: 0,
    675            keyDerivationAlgorithm: new AlgorithmIdentifier({
    676              algorithmId: pbkdf2OID,
    677              algorithmParams: pbkdf2Params.toSchema()
    678            }),
    679            keyEncryptionAlgorithm: new AlgorithmIdentifier({
    680              algorithmId: kekOID,
    681              /*
    682               For AES-KW params are NULL, but for other algorithm could be another situation.
    683               */
    684              algorithmParams: parameters.keyEncryptionAlgorithmParams
    685            }),
    686            password: preDefinedData
    687            // "encryptedKey" would be set in "encrypt" function
    688          });
    689          //#endregion
    690 
    691          //#region Final values for "CMS_ENVELOPED_DATA"
    692          this.recipientInfos.push(new RecipientInfo({
    693            variant: 4,
    694            value: keyInfo
    695          }));
    696          //#endregion
    697        }
    698        break;
    699      default:
    700        throw new Error(`Unknown value for "variant": ${variant}`);
    701    }
    702    //#endregion
    703  }
    704 
    705  /**
    706   * Add a "RecipientInfo" using a KeyAgreeRecipientInfo of type RecipientKeyIdentifier.
    707   * @param key Recipient's public key
    708   * @param keyId The id for the recipient's public key
    709   * @param parameters Additional parameters for "fine tuning" the encryption process
    710   * @param crypto Crypto engine
    711   */
    712  addRecipientByKeyIdentifier(key?: CryptoKey, keyId?: ArrayBuffer, parameters?: any, crypto = common.getCrypto(true)) {
    713    //#region Initialize encryption parameters
    714    const encryptionParameters = Object.assign({}, defaultEncryptionParams, parameters || {});
    715    //#endregion
    716 
    717    const recipientIdentifier = new KeyAgreeRecipientIdentifier({
    718      variant: 2,
    719      value: new RecipientKeyIdentifier({
    720        subjectKeyIdentifier: new asn1js.OctetString({ valueHex: keyId }),
    721      })
    722    });
    723    this._addKeyAgreeRecipientInfo(
    724      recipientIdentifier,
    725      encryptionParameters,
    726      { recipientPublicKey: key },
    727      crypto,
    728    );
    729  }
    730 
    731  /**
    732   * Add a "RecipientInfo" using a KeyAgreeRecipientInfo of type RecipientKeyIdentifier.
    733   * @param recipientIdentifier Recipient identifier
    734   * @param encryptionParameters Additional parameters for "fine tuning" the encryption process
    735   * @param extraRecipientInfoParams Additional params for KeyAgreeRecipientInfo
    736   * @param crypto Crypto engine
    737   */
    738  private _addKeyAgreeRecipientInfo(recipientIdentifier: KeyAgreeRecipientIdentifier, encryptionParameters: EnvelopedDataEncryptionParams, extraRecipientInfoParams: KeyAgreeRecipientInfoParameters, crypto = common.getCrypto(true)) {
    739    //#region RecipientEncryptedKey
    740    const encryptedKey = new RecipientEncryptedKey({
    741      rid: recipientIdentifier
    742      // "encryptedKey" will be calculated in "encrypt" function
    743    });
    744    //#endregion
    745 
    746    //#region keyEncryptionAlgorithm
    747    const aesKWoid = crypto.getOIDByAlgorithm({
    748      name: "AES-KW",
    749      length: encryptionParameters.kekEncryptionLength
    750    } as Algorithm, true, "keyEncryptionAlgorithm");
    751 
    752    const aesKW = new AlgorithmIdentifier({
    753      algorithmId: aesKWoid,
    754    });
    755    //#endregion
    756 
    757    //#region KeyAgreeRecipientInfo
    758    const ecdhOID = crypto.getOIDByAlgorithm({
    759      name: "ECDH",
    760      kdf: encryptionParameters.kdfAlgorithm
    761    } as Algorithm, true, "KeyAgreeRecipientInfo");
    762 
    763    // In fact there is no need in so long UKM, but RFC2631
    764    // has requirement that "UserKeyMaterial" must be 512 bits long
    765    const ukmBuffer = new ArrayBuffer(64);
    766    const ukmView = new Uint8Array(ukmBuffer);
    767    crypto.getRandomValues(ukmView); // Generate random values in 64 bytes long buffer
    768 
    769    const recipientInfoParams = {
    770      version: 3,
    771      // "originator" will be calculated in "encrypt" function because ephemeral key would be generated there
    772      ukm: new asn1js.OctetString({ valueHex: ukmBuffer }),
    773      keyEncryptionAlgorithm: new AlgorithmIdentifier({
    774        algorithmId: ecdhOID,
    775        algorithmParams: aesKW.toSchema()
    776      }),
    777      recipientEncryptedKeys: new RecipientEncryptedKeys({
    778        encryptedKeys: [encryptedKey]
    779      })
    780    };
    781    const keyInfo = new KeyAgreeRecipientInfo(Object.assign(recipientInfoParams, extraRecipientInfoParams));
    782    //#endregion
    783 
    784    //#region Final values for "CMS_ENVELOPED_DATA"
    785    this.recipientInfos.push(new RecipientInfo({
    786      variant: 2,
    787      value: keyInfo
    788    }));
    789    //#endregion
    790  }
    791 
    792  /**
    793   * Creates a new CMS Enveloped Data content with encrypted data
    794   * @param contentEncryptionAlgorithm WebCrypto algorithm. For the moment here could be only "AES-CBC" or "AES-GCM" algorithms.
    795   * @param contentToEncrypt Content to encrypt
    796   * @param crypto Crypto engine
    797   */
    798  public async encrypt(contentEncryptionAlgorithm: Algorithm, contentToEncrypt: ArrayBuffer, crypto = common.getCrypto(true)): Promise<(void | { ecdhPrivateKey: CryptoKey; })[]> {
    799    //#region Initial variables
    800    const ivBuffer = new ArrayBuffer(16); // For AES we need IV 16 bytes long
    801    const ivView = new Uint8Array(ivBuffer);
    802    crypto.getRandomValues(ivView);
    803 
    804    const contentView = new Uint8Array(contentToEncrypt);
    805    //#endregion
    806 
    807    // Check for input parameters
    808    const contentEncryptionOID = crypto.getOIDByAlgorithm(contentEncryptionAlgorithm, true, "contentEncryptionAlgorithm");
    809 
    810    //#region Generate new content encryption key
    811    const sessionKey = await crypto.generateKey(contentEncryptionAlgorithm as AesKeyAlgorithm, true, ["encrypt"]);
    812    //#endregion
    813    //#region Encrypt content
    814 
    815    const encryptedContent = await crypto.encrypt({
    816      name: contentEncryptionAlgorithm.name,
    817      iv: ivView
    818    },
    819      sessionKey,
    820      contentView);
    821    //#endregion
    822    //#region Export raw content of content encryption key
    823    const exportedSessionKey = await crypto.exportKey("raw", sessionKey);
    824 
    825    //#endregion
    826    //#region Append common information to CMS_ENVELOPED_DATA
    827    this.version = 2;
    828    this.encryptedContentInfo = new EncryptedContentInfo({
    829      disableSplit: this.policy.disableSplit,
    830      contentType: "1.2.840.113549.1.7.1", // "data"
    831      contentEncryptionAlgorithm: new AlgorithmIdentifier({
    832        algorithmId: contentEncryptionOID,
    833        algorithmParams: new asn1js.OctetString({ valueHex: ivBuffer })
    834      }),
    835      encryptedContent: new asn1js.OctetString({ valueHex: encryptedContent })
    836    });
    837    //#endregion
    838 
    839    //#region Special sub-functions to work with each recipient's type
    840    const SubKeyAgreeRecipientInfo = async (index: number) => {
    841      //#region Initial variables
    842      const recipientInfo = this.recipientInfos[index].value as KeyAgreeRecipientInfo;
    843      let recipientCurve: string;
    844      //#endregion
    845 
    846      //#region Get public key and named curve from recipient's certificate or public key
    847      let recipientPublicKey: CryptoKey;
    848      if (recipientInfo.recipientPublicKey) {
    849        recipientCurve = (recipientInfo.recipientPublicKey.algorithm as EcKeyAlgorithm).namedCurve;
    850        recipientPublicKey = recipientInfo.recipientPublicKey;
    851      } else if (recipientInfo.recipientCertificate) {
    852        const curveObject = recipientInfo.recipientCertificate.subjectPublicKeyInfo.algorithm.algorithmParams;
    853 
    854        if (curveObject.constructor.blockName() !== asn1js.ObjectIdentifier.blockName())
    855          throw new Error(`Incorrect "recipientCertificate" for index ${index}`);
    856 
    857        const curveOID = curveObject.valueBlock.toString();
    858 
    859        switch (curveOID) {
    860          case "1.2.840.10045.3.1.7":
    861            recipientCurve = "P-256";
    862            break;
    863          case "1.3.132.0.34":
    864            recipientCurve = "P-384";
    865            break;
    866          case "1.3.132.0.35":
    867            recipientCurve = "P-521";
    868            break;
    869          default:
    870            throw new Error(`Incorrect curve OID for index ${index}`);
    871        }
    872 
    873        recipientPublicKey = await recipientInfo.recipientCertificate.getPublicKey({
    874          algorithm: {
    875            algorithm: {
    876              name: "ECDH",
    877              namedCurve: recipientCurve
    878            } as EcKeyAlgorithm,
    879            usages: []
    880          }
    881        }, crypto);
    882      } else {
    883        throw new Error("Unsupported RecipientInfo");
    884      }
    885      //#endregion
    886 
    887      //#region Generate ephemeral ECDH key
    888      const recipientCurveLength = curveLengthByName[recipientCurve];
    889 
    890      const ecdhKeys = await crypto.generateKey(
    891        { name: "ECDH", namedCurve: recipientCurve } as EcKeyGenParams,
    892        true,
    893        ["deriveBits"]
    894      );
    895      //#endregion
    896      //#region Export public key of ephemeral ECDH key pair
    897 
    898      const exportedECDHPublicKey = await crypto.exportKey("spki", ecdhKeys.publicKey);
    899      //#endregion
    900 
    901      //#region Create shared secret
    902      const derivedBits = await crypto.deriveBits({
    903        name: "ECDH",
    904        public: recipientPublicKey
    905      },
    906        ecdhKeys.privateKey,
    907        recipientCurveLength);
    908      //#endregion
    909 
    910      //#region Apply KDF function to shared secret
    911 
    912      //#region Get length of used AES-KW algorithm
    913      const aesKWAlgorithm = new AlgorithmIdentifier({ schema: recipientInfo.keyEncryptionAlgorithm.algorithmParams });
    914 
    915      const kwAlgorithm = crypto.getAlgorithmByOID<AesKeyAlgorithm>(aesKWAlgorithm.algorithmId, true, "aesKWAlgorithm");
    916      //#endregion
    917 
    918      //#region Translate AES-KW length to ArrayBuffer
    919      let kwLength = kwAlgorithm.length;
    920 
    921      const kwLengthBuffer = new ArrayBuffer(4);
    922      const kwLengthView = new Uint8Array(kwLengthBuffer);
    923 
    924      for (let j = 3; j >= 0; j--) {
    925        kwLengthView[j] = kwLength;
    926        kwLength >>= 8;
    927      }
    928      //#endregion
    929 
    930      //#region Create and encode "ECC-CMS-SharedInfo" structure
    931      const eccInfo = new ECCCMSSharedInfo({
    932        keyInfo: new AlgorithmIdentifier({
    933          algorithmId: aesKWAlgorithm.algorithmId
    934        }),
    935        entityUInfo: (recipientInfo as KeyAgreeRecipientInfo).ukm, // TODO remove `as KeyAgreeRecipientInfo`
    936        suppPubInfo: new asn1js.OctetString({ valueHex: kwLengthBuffer })
    937      });
    938 
    939      const encodedInfo = eccInfo.toSchema().toBER(false);
    940      //#endregion
    941 
    942      //#region Get SHA algorithm used together with ECDH
    943      const ecdhAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "ecdhAlgorithm");
    944      //#endregion
    945 
    946      const derivedKeyRaw = await common.kdf(ecdhAlgorithm.kdf, derivedBits, kwAlgorithm.length, encodedInfo, crypto);
    947      //#endregion
    948      //#region Import AES-KW key from result of KDF function
    949      const awsKW = await crypto.importKey("raw", derivedKeyRaw, { name: "AES-KW" }, true, ["wrapKey"]);
    950      //#endregion
    951      //#region Finally wrap session key by using AES-KW algorithm
    952      const wrappedKey = await crypto.wrapKey("raw", sessionKey, awsKW, { name: "AES-KW" });
    953      //#endregion
    954      //#region Append all necessary data to current CMS_RECIPIENT_INFO object
    955      //#region OriginatorIdentifierOrKey
    956      const originator = new OriginatorIdentifierOrKey();
    957      originator.variant = 3;
    958      originator.value = OriginatorPublicKey.fromBER(exportedECDHPublicKey);
    959 
    960      recipientInfo.originator = originator;
    961      //#endregion
    962 
    963      //#region RecipientEncryptedKey
    964      /*
    965       We will not support using of same ephemeral key for many recipients
    966       */
    967      recipientInfo.recipientEncryptedKeys.encryptedKeys[0].encryptedKey = new asn1js.OctetString({ valueHex: wrappedKey });
    968      //#endregion
    969 
    970      return { ecdhPrivateKey: ecdhKeys.privateKey };
    971      //#endregion
    972    };
    973 
    974    const SubKeyTransRecipientInfo = async (index: number) => {
    975      const recipientInfo = this.recipientInfos[index].value as KeyTransRecipientInfo; // TODO Remove `as KeyTransRecipientInfo`
    976      const algorithmParameters = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "keyEncryptionAlgorithm");
    977 
    978      //#region RSA-OAEP case
    979      if (algorithmParameters.name === "RSA-OAEP") {
    980        const schema = recipientInfo.keyEncryptionAlgorithm.algorithmParams;
    981        const rsaOAEPParams = new RSAESOAEPParams({ schema });
    982 
    983        algorithmParameters.hash = crypto.getAlgorithmByOID(rsaOAEPParams.hashAlgorithm.algorithmId);
    984        if (("name" in algorithmParameters.hash) === false)
    985          throw new Error(`Incorrect OID for hash algorithm: ${rsaOAEPParams.hashAlgorithm.algorithmId}`);
    986      }
    987      //#endregion
    988 
    989      try {
    990        const publicKey = await recipientInfo.recipientCertificate.getPublicKey({
    991          algorithm: {
    992            algorithm: algorithmParameters,
    993            usages: ["encrypt", "wrapKey"]
    994          }
    995        }, crypto);
    996 
    997        const encryptedKey = await crypto.encrypt(publicKey.algorithm, publicKey, exportedSessionKey);
    998 
    999        //#region RecipientEncryptedKey
   1000        recipientInfo.encryptedKey = new asn1js.OctetString({ valueHex: encryptedKey });
   1001        //#endregion
   1002      }
   1003      catch {
   1004        // nothing
   1005      }
   1006    };
   1007 
   1008    const SubKEKRecipientInfo = async (index: number) => {
   1009      //#region Initial variables
   1010      const recipientInfo = this.recipientInfos[index].value as KEKRecipientInfo; // TODO Remove `as KEKRecipientInfo`
   1011      //#endregion
   1012 
   1013      //#region Import KEK from pre-defined data
   1014 
   1015      //#region Get WebCrypto form of "keyEncryptionAlgorithm"
   1016      const kekAlgorithm = crypto.getAlgorithmByOID(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "kekAlgorithm");
   1017      //#endregion
   1018 
   1019      const kekKey = await crypto.importKey("raw",
   1020        new Uint8Array(recipientInfo.preDefinedKEK),
   1021        kekAlgorithm,
   1022        true,
   1023        ["wrapKey"]); // Too specific for AES-KW
   1024      //#endregion
   1025 
   1026      //#region Wrap previously exported session key
   1027 
   1028      const wrappedKey = await crypto.wrapKey("raw", sessionKey, kekKey, kekAlgorithm);
   1029      //#endregion
   1030      //#region Append all necessary data to current CMS_RECIPIENT_INFO object
   1031      //#region RecipientEncryptedKey
   1032      recipientInfo.encryptedKey = new asn1js.OctetString({ valueHex: wrappedKey });
   1033      //#endregion
   1034      //#endregion
   1035    };
   1036 
   1037    const SubPasswordRecipientinfo = async (index: number) => {
   1038      //#region Initial variables
   1039      const recipientInfo = this.recipientInfos[index].value as PasswordRecipientinfo; // TODO Remove `as PasswordRecipientinfo`
   1040      let pbkdf2Params: PBKDF2Params;
   1041      //#endregion
   1042 
   1043      //#region Check that we have encoded "keyDerivationAlgorithm" plus "PBKDF2_params" in there
   1044 
   1045      if (!recipientInfo.keyDerivationAlgorithm)
   1046        throw new Error("Please append encoded \"keyDerivationAlgorithm\"");
   1047 
   1048      if (!recipientInfo.keyDerivationAlgorithm.algorithmParams)
   1049        throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\"");
   1050 
   1051      try {
   1052        pbkdf2Params = new PBKDF2Params({ schema: recipientInfo.keyDerivationAlgorithm.algorithmParams });
   1053      }
   1054      catch {
   1055        throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\"");
   1056      }
   1057 
   1058      //#endregion
   1059      //#region Derive PBKDF2 key from "password" buffer
   1060      const passwordView = new Uint8Array(recipientInfo.password);
   1061 
   1062      const derivationKey = await crypto.importKey("raw",
   1063        passwordView,
   1064        "PBKDF2",
   1065        false,
   1066        ["deriveKey"]);
   1067      //#endregion
   1068      //#region Derive key for "keyEncryptionAlgorithm"
   1069      //#region Get WebCrypto form of "keyEncryptionAlgorithm"
   1070      const kekAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "kekAlgorithm");
   1071 
   1072      //#endregion
   1073 
   1074      //#region Get HMAC hash algorithm
   1075      let hmacHashAlgorithm = "SHA-1";
   1076 
   1077      if (pbkdf2Params.prf) {
   1078        const prfAlgorithm = crypto.getAlgorithmByOID<any>(pbkdf2Params.prf.algorithmId, true, "prfAlgorithm");
   1079        hmacHashAlgorithm = prfAlgorithm.hash.name;
   1080      }
   1081      //#endregion
   1082 
   1083      //#region Get PBKDF2 "salt" value
   1084      const saltView = new Uint8Array(pbkdf2Params.salt.valueBlock.valueHex);
   1085      //#endregion
   1086 
   1087      //#region Get PBKDF2 iterations count
   1088      const iterations = pbkdf2Params.iterationCount;
   1089      //#endregion
   1090 
   1091      const derivedKey = await crypto.deriveKey({
   1092        name: "PBKDF2",
   1093        hash: {
   1094          name: hmacHashAlgorithm
   1095        },
   1096        salt: saltView,
   1097        iterations
   1098      },
   1099        derivationKey,
   1100        kekAlgorithm,
   1101        true,
   1102        ["wrapKey"]); // Usages are too specific for KEK algorithm
   1103 
   1104      //#endregion
   1105      //#region Wrap previously exported session key (Also too specific for KEK algorithm)
   1106      const wrappedKey = await crypto.wrapKey("raw", sessionKey, derivedKey, kekAlgorithm);
   1107      //#endregion
   1108      //#region Append all necessary data to current CMS_RECIPIENT_INFO object
   1109      //#region RecipientEncryptedKey
   1110      recipientInfo.encryptedKey = new asn1js.OctetString({ valueHex: wrappedKey });
   1111      //#endregion
   1112      //#endregion
   1113    };
   1114 
   1115    //#endregion
   1116 
   1117    const res = [];
   1118    //#region Create special routines for each "recipient"
   1119    for (let i = 0; i < this.recipientInfos.length; i++) {
   1120      switch (this.recipientInfos[i].variant) {
   1121        case 1: // KeyTransRecipientInfo
   1122          res.push(await SubKeyTransRecipientInfo(i));
   1123          break;
   1124        case 2: // KeyAgreeRecipientInfo
   1125          res.push(await SubKeyAgreeRecipientInfo(i));
   1126          break;
   1127        case 3: // KEKRecipientInfo
   1128          res.push(await SubKEKRecipientInfo(i));
   1129          break;
   1130        case 4: // PasswordRecipientinfo
   1131          res.push(await SubPasswordRecipientinfo(i));
   1132          break;
   1133        default:
   1134          throw new Error(`Unknown recipient type in array with index ${i}`);
   1135      }
   1136    }
   1137    //#endregion
   1138    return res;
   1139  }
   1140 
   1141  /**
   1142   * Decrypts existing CMS Enveloped Data content
   1143   * @param recipientIndex Index of recipient
   1144   * @param parameters Additional parameters
   1145   * @param crypto Crypto engine
   1146   */
   1147  async decrypt(recipientIndex: number, parameters: EnvelopedDataDecryptParams, crypto = common.getCrypto(true)) {
   1148    //#region Initial variables
   1149    const decryptionParameters = parameters || {};
   1150    //#endregion
   1151 
   1152    //#region Check for input parameters
   1153    if ((recipientIndex + 1) > this.recipientInfos.length) {
   1154      throw new Error(`Maximum value for "index" is: ${this.recipientInfos.length - 1}`);
   1155    }
   1156    //#endregion
   1157 
   1158    //#region Special sub-functions to work with each recipient's type
   1159    const SubKeyAgreeRecipientInfo = async (index: number) => {
   1160      //#region Initial variables
   1161      const recipientInfo = this.recipientInfos[index].value as KeyAgreeRecipientInfo; // TODO Remove `as KeyAgreeRecipientInfo`
   1162      //#endregion
   1163 
   1164      let curveOID: string;
   1165      let recipientCurve: string;
   1166      let recipientCurveLength: number;
   1167      const originator = recipientInfo.originator;
   1168 
   1169      //#region Get "namedCurve" parameter from recipient's certificate
   1170 
   1171      if (decryptionParameters.recipientCertificate) {
   1172        const curveObject = decryptionParameters.recipientCertificate.subjectPublicKeyInfo.algorithm.algorithmParams;
   1173        if (curveObject.constructor.blockName() !== asn1js.ObjectIdentifier.blockName()) {
   1174          throw new Error(`Incorrect "recipientCertificate" for index ${index}`);
   1175        }
   1176        curveOID = curveObject.valueBlock.toString();
   1177      } else if (originator.value.algorithm.algorithmParams) {
   1178        const curveObject = originator.value.algorithm.algorithmParams;
   1179        if (curveObject.constructor.blockName() !== asn1js.ObjectIdentifier.blockName()) {
   1180          throw new Error(`Incorrect originator for index ${index}`);
   1181        }
   1182        curveOID = curveObject.valueBlock.toString();
   1183      } else {
   1184        throw new Error("Parameter \"recipientCertificate\" is mandatory for \"KeyAgreeRecipientInfo\" if algorithm params are missing from originator");
   1185      }
   1186 
   1187      if (!decryptionParameters.recipientPrivateKey)
   1188        throw new Error("Parameter \"recipientPrivateKey\" is mandatory for \"KeyAgreeRecipientInfo\"");
   1189 
   1190      switch (curveOID) {
   1191        case "1.2.840.10045.3.1.7":
   1192          recipientCurve = "P-256";
   1193          recipientCurveLength = 256;
   1194          break;
   1195        case "1.3.132.0.34":
   1196          recipientCurve = "P-384";
   1197          recipientCurveLength = 384;
   1198          break;
   1199        case "1.3.132.0.35":
   1200          recipientCurve = "P-521";
   1201          recipientCurveLength = 528;
   1202          break;
   1203        default:
   1204          throw new Error(`Incorrect curve OID for index ${index}`);
   1205      }
   1206 
   1207      let ecdhPrivateKey: CryptoKey;
   1208      let keyCrypto: SubtleCrypto = crypto;
   1209      if (BufferSourceConverter.isBufferSource(decryptionParameters.recipientPrivateKey)) {
   1210        ecdhPrivateKey = await crypto.importKey("pkcs8",
   1211          decryptionParameters.recipientPrivateKey,
   1212          {
   1213            name: "ECDH",
   1214            namedCurve: recipientCurve
   1215          } as EcKeyImportParams,
   1216          true,
   1217          ["deriveBits"]
   1218        );
   1219      } else {
   1220        ecdhPrivateKey = decryptionParameters.recipientPrivateKey;
   1221        if ("crypto" in decryptionParameters && decryptionParameters.crypto) {
   1222          keyCrypto = decryptionParameters.crypto.subtle;
   1223        }
   1224      }
   1225      //#endregion
   1226      //#region Import sender's ephemeral public key
   1227      //#region Change "OriginatorPublicKey" if "curve" parameter absent
   1228      if (("algorithmParams" in originator.value.algorithm) === false)
   1229        originator.value.algorithm.algorithmParams = new asn1js.ObjectIdentifier({ value: curveOID });
   1230      //#endregion
   1231 
   1232      //#region Create ArrayBuffer with sender's public key
   1233      const buffer = originator.value.toSchema().toBER(false);
   1234      //#endregion
   1235 
   1236      const ecdhPublicKey = await crypto.importKey("spki",
   1237        buffer,
   1238        {
   1239          name: "ECDH",
   1240          namedCurve: recipientCurve
   1241        } as EcKeyImportParams,
   1242        true,
   1243        []);
   1244 
   1245      //#endregion
   1246      //#region Create shared secret
   1247      const sharedSecret = await keyCrypto.deriveBits({
   1248        name: "ECDH",
   1249        public: ecdhPublicKey
   1250      },
   1251        ecdhPrivateKey,
   1252        recipientCurveLength);
   1253      //#endregion
   1254      //#region Apply KDF function to shared secret
   1255      async function applyKDF(includeAlgorithmParams?: boolean) {
   1256        includeAlgorithmParams = includeAlgorithmParams || false;
   1257 
   1258        //#region Get length of used AES-KW algorithm
   1259        const aesKWAlgorithm = new AlgorithmIdentifier({ schema: recipientInfo.keyEncryptionAlgorithm.algorithmParams });
   1260 
   1261        const kwAlgorithm = crypto.getAlgorithmByOID<any>(aesKWAlgorithm.algorithmId, true, "kwAlgorithm");
   1262        //#endregion
   1263 
   1264        //#region Translate AES-KW length to ArrayBuffer
   1265        let kwLength = kwAlgorithm.length;
   1266 
   1267        const kwLengthBuffer = new ArrayBuffer(4);
   1268        const kwLengthView = new Uint8Array(kwLengthBuffer);
   1269 
   1270        for (let j = 3; j >= 0; j--) {
   1271          kwLengthView[j] = kwLength;
   1272          kwLength >>= 8;
   1273        }
   1274        //#endregion
   1275 
   1276        //#region Create and encode "ECC-CMS-SharedInfo" structure
   1277        const keyInfoAlgorithm: AlgorithmIdentifierParameters = {
   1278          algorithmId: aesKWAlgorithm.algorithmId
   1279        };
   1280        if (includeAlgorithmParams) {
   1281          keyInfoAlgorithm.algorithmParams = new asn1js.Null();
   1282        }
   1283        const eccInfo = new ECCCMSSharedInfo({
   1284          keyInfo: new AlgorithmIdentifier(keyInfoAlgorithm),
   1285          entityUInfo: recipientInfo.ukm,
   1286          suppPubInfo: new asn1js.OctetString({ valueHex: kwLengthBuffer })
   1287        });
   1288 
   1289        const encodedInfo = eccInfo.toSchema().toBER(false);
   1290        //#endregion
   1291 
   1292        //#region Get SHA algorithm used together with ECDH
   1293        const ecdhAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "ecdhAlgorithm");
   1294        if (!ecdhAlgorithm.name) {
   1295          throw new Error(`Incorrect OID for key encryption algorithm: ${recipientInfo.keyEncryptionAlgorithm.algorithmId}`);
   1296        }
   1297        //#endregion
   1298 
   1299        return common.kdf(ecdhAlgorithm.kdf, sharedSecret, kwAlgorithm.length, encodedInfo, crypto);
   1300      }
   1301 
   1302      const kdfResult = await applyKDF();
   1303      //#endregion
   1304      //#region Import AES-KW key from result of KDF function
   1305      const importAesKwKey = async (kdfResult: ArrayBuffer) => {
   1306        return crypto.importKey("raw",
   1307          kdfResult,
   1308          { name: "AES-KW" },
   1309          true,
   1310          ["unwrapKey"]
   1311        );
   1312      };
   1313 
   1314      const aesKwKey = await importAesKwKey(kdfResult);
   1315 
   1316      //#endregion
   1317      //#region Finally unwrap session key
   1318      const unwrapSessionKey = async (aesKwKey: CryptoKey) => {
   1319        //#region Get WebCrypto form of content encryption algorithm
   1320        const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
   1321        const contentEncryptionAlgorithm = crypto.getAlgorithmByOID<any>(algorithmId, true, "contentEncryptionAlgorithm");
   1322        //#endregion
   1323 
   1324        return crypto.unwrapKey("raw",
   1325          recipientInfo.recipientEncryptedKeys.encryptedKeys[0].encryptedKey.valueBlock.valueHexView as BufferSource,
   1326          aesKwKey,
   1327          { name: "AES-KW" },
   1328          contentEncryptionAlgorithm,
   1329          true,
   1330          ["decrypt"]);
   1331      };
   1332 
   1333      try {
   1334        return await unwrapSessionKey(aesKwKey);
   1335      } catch {
   1336        const kdfResult = await applyKDF(true);
   1337        const aesKwKey = await importAesKwKey(kdfResult);
   1338        return unwrapSessionKey(aesKwKey);
   1339      }
   1340    };
   1341    //#endregion
   1342 
   1343    const SubKeyTransRecipientInfo = async (index: number) => {
   1344      const recipientInfo = this.recipientInfos[index].value as KeyTransRecipientInfo; // TODO Remove `as KeyTransRecipientInfo`
   1345      if (!decryptionParameters.recipientPrivateKey) {
   1346        throw new Error("Parameter \"recipientPrivateKey\" is mandatory for \"KeyTransRecipientInfo\"");
   1347      }
   1348 
   1349      const algorithmParameters = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "keyEncryptionAlgorithm");
   1350 
   1351      //#region RSA-OAEP case
   1352      if (algorithmParameters.name === "RSA-OAEP") {
   1353        const schema = recipientInfo.keyEncryptionAlgorithm.algorithmParams;
   1354        const rsaOAEPParams = new RSAESOAEPParams({ schema });
   1355 
   1356        algorithmParameters.hash = crypto.getAlgorithmByOID(rsaOAEPParams.hashAlgorithm.algorithmId);
   1357        if (("name" in algorithmParameters.hash) === false)
   1358          throw new Error(`Incorrect OID for hash algorithm: ${rsaOAEPParams.hashAlgorithm.algorithmId}`);
   1359      }
   1360      //#endregion
   1361 
   1362      let privateKey: CryptoKey;
   1363      let keyCrypto: SubtleCrypto = crypto;
   1364      if (BufferSourceConverter.isBufferSource(decryptionParameters.recipientPrivateKey)) {
   1365        privateKey = await crypto.importKey(
   1366          "pkcs8",
   1367          decryptionParameters.recipientPrivateKey,
   1368          algorithmParameters,
   1369          true,
   1370          ["decrypt"]
   1371        );
   1372      } else {
   1373        privateKey = decryptionParameters.recipientPrivateKey;
   1374        if ("crypto" in decryptionParameters && decryptionParameters.crypto) {
   1375          keyCrypto = decryptionParameters.crypto.subtle;
   1376        }
   1377      }
   1378 
   1379      const sessionKey = await keyCrypto.decrypt(
   1380        privateKey.algorithm,
   1381        privateKey,
   1382        recipientInfo.encryptedKey.valueBlock.valueHexView as BufferSource
   1383      );
   1384 
   1385      //#region Get WebCrypto form of content encryption algorithm
   1386      const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
   1387      const contentEncryptionAlgorithm = crypto.getAlgorithmByOID(algorithmId, true, "contentEncryptionAlgorithm");
   1388      if (("name" in contentEncryptionAlgorithm) === false)
   1389        throw new Error(`Incorrect "contentEncryptionAlgorithm": ${algorithmId}`);
   1390      //#endregion
   1391 
   1392      return crypto.importKey("raw",
   1393        sessionKey,
   1394        contentEncryptionAlgorithm,
   1395        true,
   1396        ["decrypt"]
   1397      );
   1398    };
   1399 
   1400    const SubKEKRecipientInfo = async (index: number) => {
   1401      //#region Initial variables
   1402      const recipientInfo = this.recipientInfos[index].value as KEKRecipientInfo; // TODO Remove `as KEKRecipientInfo`
   1403      //#endregion
   1404 
   1405      //#region Import KEK from pre-defined data
   1406      if (!decryptionParameters.preDefinedData)
   1407        throw new Error("Parameter \"preDefinedData\" is mandatory for \"KEKRecipientInfo\"");
   1408 
   1409      //#region Get WebCrypto form of "keyEncryptionAlgorithm"
   1410      const kekAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "kekAlgorithm");
   1411      //#endregion
   1412 
   1413      const importedKey = await crypto.importKey("raw",
   1414        decryptionParameters.preDefinedData,
   1415        kekAlgorithm,
   1416        true,
   1417        ["unwrapKey"]); // Too specific for AES-KW
   1418 
   1419      //#endregion
   1420      //#region Unwrap previously exported session key
   1421      //#region Get WebCrypto form of content encryption algorithm
   1422      const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
   1423      const contentEncryptionAlgorithm = crypto.getAlgorithmByOID<any>(algorithmId, true, "contentEncryptionAlgorithm");
   1424      if (!contentEncryptionAlgorithm.name) {
   1425        throw new Error(`Incorrect "contentEncryptionAlgorithm": ${algorithmId}`);
   1426      }
   1427      //#endregion
   1428 
   1429      return crypto.unwrapKey("raw",
   1430        recipientInfo.encryptedKey.valueBlock.valueHexView as BufferSource,
   1431        importedKey,
   1432        kekAlgorithm,
   1433        contentEncryptionAlgorithm,
   1434        true,
   1435        ["decrypt"]);
   1436      //#endregion
   1437    };
   1438 
   1439    const SubPasswordRecipientinfo = async (index: number) => {
   1440      //#region Initial variables
   1441      const recipientInfo = this.recipientInfos[index].value as PasswordRecipientinfo; // TODO Remove `as PasswordRecipientinfo`
   1442      let pbkdf2Params: PBKDF2Params;
   1443      //#endregion
   1444 
   1445      //#region Derive PBKDF2 key from "password" buffer
   1446 
   1447      if (!decryptionParameters.preDefinedData) {
   1448        throw new Error("Parameter \"preDefinedData\" is mandatory for \"KEKRecipientInfo\"");
   1449      }
   1450 
   1451      if (!recipientInfo.keyDerivationAlgorithm) {
   1452        throw new Error("Please append encoded \"keyDerivationAlgorithm\"");
   1453      }
   1454 
   1455      if (!recipientInfo.keyDerivationAlgorithm.algorithmParams) {
   1456        throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\"");
   1457      }
   1458 
   1459      try {
   1460        pbkdf2Params = new PBKDF2Params({ schema: recipientInfo.keyDerivationAlgorithm.algorithmParams });
   1461      }
   1462      catch {
   1463        throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\"");
   1464      }
   1465 
   1466      const pbkdf2Key = await crypto.importKey("raw",
   1467        decryptionParameters.preDefinedData,
   1468        "PBKDF2",
   1469        false,
   1470        ["deriveKey"]);
   1471      //#endregion
   1472      //#region Derive key for "keyEncryptionAlgorithm"
   1473      //#region Get WebCrypto form of "keyEncryptionAlgorithm"
   1474      const kekAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "keyEncryptionAlgorithm");
   1475      //#endregion
   1476 
   1477      // Get HMAC hash algorithm
   1478      const hmacHashAlgorithm = pbkdf2Params.prf
   1479        ? crypto.getAlgorithmByOID<any>(pbkdf2Params.prf.algorithmId, true, "prfAlgorithm").hash.name
   1480        : "SHA-1";
   1481 
   1482      //#region Get PBKDF2 "salt" value
   1483      const saltView = new Uint8Array(pbkdf2Params.salt.valueBlock.valueHex);
   1484      //#endregion
   1485 
   1486      //#region Get PBKDF2 iterations count
   1487      const iterations = pbkdf2Params.iterationCount;
   1488      //#endregion
   1489 
   1490      const kekKey = await crypto.deriveKey({
   1491        name: "PBKDF2",
   1492        hash: {
   1493          name: hmacHashAlgorithm
   1494        },
   1495        salt: saltView,
   1496        iterations
   1497      },
   1498        pbkdf2Key,
   1499        kekAlgorithm,
   1500        true,
   1501        ["unwrapKey"]); // Usages are too specific for KEK algorithm
   1502      //#endregion
   1503      //#region Unwrap previously exported session key
   1504      //#region Get WebCrypto form of content encryption algorithm
   1505      const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
   1506      const contentEncryptionAlgorithm = crypto.getAlgorithmByOID<any>(algorithmId, true, "contentEncryptionAlgorithm");
   1507      //#endregion
   1508 
   1509      return crypto.unwrapKey("raw",
   1510        recipientInfo.encryptedKey.valueBlock.valueHexView as BufferSource,
   1511        kekKey,
   1512        kekAlgorithm,
   1513        contentEncryptionAlgorithm,
   1514        true,
   1515        ["decrypt"]);
   1516      //#endregion
   1517    };
   1518 
   1519    //#endregion
   1520 
   1521    //#region Perform steps, specific to each type of session key encryption
   1522    let unwrappedKey: CryptoKey;
   1523    switch (this.recipientInfos[recipientIndex].variant) {
   1524      case 1: // KeyTransRecipientInfo
   1525        unwrappedKey = await SubKeyTransRecipientInfo(recipientIndex);
   1526        break;
   1527      case 2: // KeyAgreeRecipientInfo
   1528        unwrappedKey = await SubKeyAgreeRecipientInfo(recipientIndex);
   1529        break;
   1530      case 3: // KEKRecipientInfo
   1531        unwrappedKey = await SubKEKRecipientInfo(recipientIndex);
   1532        break;
   1533      case 4: // PasswordRecipientinfo
   1534        unwrappedKey = await SubPasswordRecipientinfo(recipientIndex);
   1535        break;
   1536      default:
   1537        throw new Error(`Unknown recipient type in array with index ${recipientIndex}`);
   1538    }
   1539    //#endregion
   1540 
   1541    //#region Finally decrypt data by session key
   1542    //#region Get WebCrypto form of content encryption algorithm
   1543    const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
   1544    const contentEncryptionAlgorithm = crypto.getAlgorithmByOID(algorithmId, true, "contentEncryptionAlgorithm");
   1545    //#endregion
   1546 
   1547    //#region Get "initialization vector" for content encryption algorithm
   1548    const ivBuffer = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams.valueBlock.valueHex;
   1549    const ivView = new Uint8Array(ivBuffer);
   1550    //#endregion
   1551 
   1552    //#region Create correct data block for decryption
   1553    if (!this.encryptedContentInfo.encryptedContent) {
   1554      throw new Error("Required property `encryptedContent` is empty");
   1555    }
   1556    const dataBuffer = this.encryptedContentInfo.getEncryptedContent();
   1557    //#endregion
   1558 
   1559    return crypto.decrypt(
   1560      {
   1561        name: (contentEncryptionAlgorithm as any).name,
   1562        iv: ivView
   1563      },
   1564      unwrappedKey,
   1565      dataBuffer);
   1566    //#endregion
   1567  }
   1568 
   1569 }