• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PeculiarVentures / PKI.js / 24843230473

23 Apr 2026 03:16PM UTC coverage: 74.673%. First build
24843230473

Pull #487

github

web-flow
Merge 17983afcb into 9af5b5961
Pull Request #487: Emit GCMParameters SEQUENCE for AES-GCM AlgorithmIdentifier (RFC 5084)

2468 of 4367 branches covered (56.51%)

63 of 69 new or added lines in 4 files covered. (91.3%)

6740 of 9026 relevant lines covered (74.67%)

1617.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.85
/src/EnvelopedData.ts
1
import * as asn1js from "asn1js";
2✔
2
import * as pvutils from "pvutils";
2✔
3
import { BufferSourceConverter } from "pvtsutils";
2✔
4
import * as common from "./common";
2✔
5
import { OriginatorInfo, OriginatorInfoJson } from "./OriginatorInfo";
2✔
6
import { RecipientInfo, RecipientInfoJson } from "./RecipientInfo";
2✔
7
import { EncryptedContentInfo, EncryptedContentInfoJson, EncryptedContentInfoSchema, EncryptedContentInfoSplit } from "./EncryptedContentInfo";
2✔
8
import { Attribute, AttributeJson } from "./Attribute";
2✔
9
import { AlgorithmIdentifier, AlgorithmIdentifierParameters } from "./AlgorithmIdentifier";
2✔
10
import { GCMParams } from "./GCMParams";
2✔
11
import { RSAESOAEPParams } from "./RSAESOAEPParams";
2✔
12
import { KeyTransRecipientInfo } from "./KeyTransRecipientInfo";
2✔
13
import { IssuerAndSerialNumber } from "./IssuerAndSerialNumber";
2✔
14
import { RecipientKeyIdentifier } from "./RecipientKeyIdentifier";
2✔
15
import { RecipientEncryptedKey } from "./RecipientEncryptedKey";
2✔
16
import { KeyAgreeRecipientIdentifier } from "./KeyAgreeRecipientIdentifier";
2✔
17
import { KeyAgreeRecipientInfo, KeyAgreeRecipientInfoParameters } from "./KeyAgreeRecipientInfo";
2✔
18
import { RecipientEncryptedKeys } from "./RecipientEncryptedKeys";
2✔
19
import { KEKRecipientInfo } from "./KEKRecipientInfo";
2✔
20
import { KEKIdentifier } from "./KEKIdentifier";
2✔
21
import { PBKDF2Params } from "./PBKDF2Params";
2✔
22
import { PasswordRecipientinfo } from "./PasswordRecipientinfo";
2✔
23
import { ECCCMSSharedInfo } from "./ECCCMSSharedInfo";
2✔
24
import { OriginatorIdentifierOrKey } from "./OriginatorIdentifierOrKey";
2✔
25
import { OriginatorPublicKey } from "./OriginatorPublicKey";
2✔
26
import * as Schema from "./Schema";
27
import { Certificate } from "./Certificate";
28
import { ArgumentError, AsnError } from "./errors";
2✔
29
import { PkiObject, PkiObjectParameters } from "./PkiObject";
2✔
30
import { EMPTY_STRING } from "./constants";
2✔
31

32
const VERSION = "version";
2✔
33
const ORIGINATOR_INFO = "originatorInfo";
2✔
34
const RECIPIENT_INFOS = "recipientInfos";
2✔
35
const ENCRYPTED_CONTENT_INFO = "encryptedContentInfo";
2✔
36
const UNPROTECTED_ATTRS = "unprotectedAttrs";
2✔
37
const CLEAR_PROPS = [
2✔
38
  VERSION,
39
  ORIGINATOR_INFO,
40
  RECIPIENT_INFOS,
41
  ENCRYPTED_CONTENT_INFO,
42
  UNPROTECTED_ATTRS
43
];
44

45
const defaultEncryptionParams = {
2✔
46
  kdfAlgorithm: "SHA-512",
47
  kekEncryptionLength: 256
48
};
49
const curveLengthByName: Record<string, number> = {
2✔
50
  "P-256": 256,
51
  "P-384": 384,
52
  "P-521": 528
53
};
54

55
export interface IEnvelopedData {
56
  /**
57
   * Version number.
58
   *
59
   * The appropriate value depends on `originatorInfo`, `RecipientInfo`, and `unprotectedAttrs`.
60
   *
61
   * The version MUST be assigned as follows:
62
   * ```
63
   * IF (originatorInfo is present) AND
64
   *    ((any certificates with a type of other are present) OR
65
   *    (any crls with a type of other are present))
66
   * THEN version is 4
67
   * ELSE
68
   *    IF ((originatorInfo is present) AND
69
   *       (any version 2 attribute certificates are present)) OR
70
   *       (any RecipientInfo structures include pwri) OR
71
   *       (any RecipientInfo structures include ori)
72
   *    THEN version is 3
73
   *    ELSE
74
   *       IF (originatorInfo is absent) AND
75
   *          (unprotectedAttrs is absent) AND
76
   *          (all RecipientInfo structures are version 0)
77
   *       THEN version is 0
78
   *       ELSE version is 2
79
   * ```
80
   */
81
  version: number;
82
  /**
83
   * Optionally provides information about the originator. It is present only if required by the key management algorithm.
84
   * It may contain certificates and CRLs.
85
   */
86
  originatorInfo?: OriginatorInfo;
87
  /**
88
   * Collection of per-recipient information. There MUST be at least one element in the collection.
89
   */
90
  recipientInfos: RecipientInfo[];
91
  /**
92
   * Encrypted content information
93
   */
94
  encryptedContentInfo: EncryptedContentInfo;
95
  /**
96
   * Collection of attributes that are not encrypted
97
   */
98
  unprotectedAttrs?: Attribute[];
99
}
100

101
/**
102
 * JSON representation of {@link EnvelopedData}
103
 */
104
export interface EnvelopedDataJson {
105
  version: number;
106
  originatorInfo?: OriginatorInfoJson;
107
  recipientInfos: RecipientInfoJson[];
108
  encryptedContentInfo: EncryptedContentInfoJson;
109
  unprotectedAttrs?: AttributeJson[];
110
}
111

112
export type EnvelopedDataParameters = PkiObjectParameters & Partial<IEnvelopedData> & EncryptedContentInfoSplit;
113

114
export interface EnvelopedDataEncryptionParams {
115
  kekEncryptionLength: number;
116
  kdfAlgorithm: string;
117
}
118

119
export interface EnvelopedDataDecryptBaseParams {
120
  preDefinedData?: BufferSource;
121
  recipientCertificate?: Certificate;
122
}
123

124
export interface EnvelopedDataDecryptKeyParams extends EnvelopedDataDecryptBaseParams {
125
  recipientPrivateKey: CryptoKey;
126
  /**
127
   * Crypto provider assigned to `recipientPrivateKey`. If the filed is empty uses default crypto provider.
128
   */
129
  crypto?: Crypto;
130
}
131

132
export interface EnvelopedDataDecryptBufferParams extends EnvelopedDataDecryptBaseParams {
133
  recipientPrivateKey?: BufferSource;
134
}
135

136
export type EnvelopedDataDecryptParams = EnvelopedDataDecryptBufferParams | EnvelopedDataDecryptKeyParams;
137

138
/**
139
 * Represents the EnvelopedData structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652)
140
 *
141
 * @example The following example demonstrates how to create and encrypt CMS Enveloped Data
142
 * ```js
143
 * const cmsEnveloped = new pkijs.EnvelopedData();
144
 *
145
 * // Add recipient
146
 * cmsEnveloped.addRecipientByCertificate(cert, { oaepHashAlgorithm: "SHA-256" });
147
 *
148
 * // Secret key algorithm
149
 * const alg = {
150
 *   name: "AES-GCM",
151
 *   length: 256,
152
 * }
153
 * await cmsEnveloped.encrypt(alg, dataToEncrypt);
154
 *
155
 * // Add Enveloped Data into CMS Content Info
156
 * const cmsContent = new pkijs.ContentInfo();
157
 * cmsContent.contentType = pkijs.ContentInfo.ENVELOPED_DATA;
158
 * cmsContent.content = cmsEnveloped.toSchema();
159
 *
160
 * const cmsContentRaw = cmsContent.toSchema().toBER();
161
 * ```
162
 *
163
 * @example The following example demonstrates how to decrypt CMS Enveloped Data
164
 * ```js
165
 * // Get a "crypto" extension
166
 * const crypto = pkijs.getCrypto();
167
 *
168
 * // Parse CMS Content Info
169
 * const cmsContent = pkijs.ContentInfo.fromBER(cmsContentRaw);
170
 * if (cmsContent.contentType !== pkijs.ContentInfo.ENVELOPED_DATA) {
171
 *   throw new Error("CMS is not Enveloped Data");
172
 * }
173
 * // Parse CMS Enveloped Data
174
 * const cmsEnveloped = new pkijs.EnvelopedData({ schema: cmsContent.content });
175
 *
176
 * // Export private key to PKCS#8
177
 * const pkcs8 = await crypto.exportKey("pkcs8", keys.privateKey);
178
 *
179
 * // Decrypt data
180
 * const decryptedData = await cmsEnveloped.decrypt(0, {
181
 *   recipientCertificate: cert,
182
 *   recipientPrivateKey: pkcs8,
183
 * });
184
 * ```
185
 */
186
export class EnvelopedData extends PkiObject implements IEnvelopedData {
2✔
187

188
  public static override CLASS_NAME = "EnvelopedData";
2✔
189

190
  public version!: number;
191
  public originatorInfo?: OriginatorInfo;
192
  public recipientInfos!: RecipientInfo[];
193
  public encryptedContentInfo!: EncryptedContentInfo;
194
  public unprotectedAttrs?: Attribute[];
195

196
  public policy: Required<EncryptedContentInfoSplit>;
197

198
  /**
199
   * Initializes a new instance of the {@link EnvelopedData} class
200
   * @param parameters Initialization parameters
201
   */
202
  constructor(parameters: EnvelopedDataParameters = {}) {
170✔
203
    super();
1,510✔
204

205
    this.version = pvutils.getParametersValue(parameters, VERSION, EnvelopedData.defaultValues(VERSION));
1,510✔
206
    if (ORIGINATOR_INFO in parameters) {
1,510✔
207
      this.originatorInfo = pvutils.getParametersValue(parameters, ORIGINATOR_INFO, EnvelopedData.defaultValues(ORIGINATOR_INFO));
576✔
208
    }
209
    this.recipientInfos = pvutils.getParametersValue(parameters, RECIPIENT_INFOS, EnvelopedData.defaultValues(RECIPIENT_INFOS));
1,510✔
210
    this.encryptedContentInfo = pvutils.getParametersValue(parameters, ENCRYPTED_CONTENT_INFO, EnvelopedData.defaultValues(ENCRYPTED_CONTENT_INFO));
1,510✔
211
    if (UNPROTECTED_ATTRS in parameters) {
1,510!
212
      this.unprotectedAttrs = pvutils.getParametersValue(parameters, UNPROTECTED_ATTRS, EnvelopedData.defaultValues(UNPROTECTED_ATTRS));
×
213
    }
214
    this.policy = {
1,510✔
215
      disableSplit: !!parameters.disableSplit,
216
    };
217

218
    if (parameters.schema) {
1,510✔
219
      this.fromSchema(parameters.schema);
754✔
220
    }
221
  }
222

223
  /**
224
   * Returns default values for all class members
225
   * @param memberName String name for a class member
226
   * @returns Default value
227
   */
228
  public static override defaultValues(memberName: typeof VERSION): number;
229
  public static override defaultValues(memberName: typeof ORIGINATOR_INFO): OriginatorInfo;
230
  public static override defaultValues(memberName: typeof RECIPIENT_INFOS): RecipientInfo[];
231
  public static override defaultValues(memberName: typeof ENCRYPTED_CONTENT_INFO): EncryptedContentInfo;
232
  public static override defaultValues(memberName: typeof UNPROTECTED_ATTRS): Attribute[];
233
  public static override defaultValues(memberName: string): any {
234
    switch (memberName) {
5,106!
235
      case VERSION:
236
        return 0;
1,510✔
237
      case ORIGINATOR_INFO:
238
        return new OriginatorInfo();
576✔
239
      case RECIPIENT_INFOS:
240
        return [];
1,510✔
241
      case ENCRYPTED_CONTENT_INFO:
242
        return new EncryptedContentInfo();
1,510✔
243
      case UNPROTECTED_ATTRS:
244
        return [];
×
245
      default:
246
        return super.defaultValues(memberName);
×
247
    }
248
  }
249

250
  /**
251
   * Compare values with default values for all class members
252
   * @param memberName String name for a class member
253
   * @param memberValue Value to compare with default value
254
   */
255
  public static compareWithDefault(memberName: string, memberValue: any): boolean {
256
    switch (memberName) {
×
257
      case VERSION:
258
        return (memberValue === EnvelopedData.defaultValues(memberName));
×
259
      case ORIGINATOR_INFO:
260
        return ((memberValue.certs.certificates.length === 0) && (memberValue.crls.crls.length === 0));
×
261
      case RECIPIENT_INFOS:
262
      case UNPROTECTED_ATTRS:
263
        return (memberValue.length === 0);
×
264
      case ENCRYPTED_CONTENT_INFO:
265
        return ((EncryptedContentInfo.compareWithDefault("contentType", memberValue.contentType)) &&
×
266
          (EncryptedContentInfo.compareWithDefault("contentEncryptionAlgorithm", memberValue.contentEncryptionAlgorithm) &&
267
            (EncryptedContentInfo.compareWithDefault("encryptedContent", memberValue.encryptedContent))));
268
      default:
269
        return super.defaultValues(memberName);
×
270
    }
271
  }
272

273
  /**
274
   * @inheritdoc
275
   * @asn ASN.1 schema
276
   * ```asn
277
   * EnvelopedData ::= SEQUENCE {
278
   *    version CMSVersion,
279
   *    originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
280
   *    recipientInfos RecipientInfos,
281
   *    encryptedContentInfo EncryptedContentInfo,
282
   *    unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }
283
   *```
284
   */
285
  public static override schema(parameters: Schema.SchemaParameters<{
×
286
    version?: string;
287
    originatorInfo?: string;
288
    recipientInfos?: string;
289
    encryptedContentInfo?: EncryptedContentInfoSchema;
290
    unprotectedAttrs?: string;
291
  }> = {}): Schema.SchemaType {
292
    const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {});
754✔
293

294
    return (new asn1js.Sequence({
754✔
295
      name: (names.blockName || EMPTY_STRING),
1,508✔
296
      value: [
297
        new asn1js.Integer({ name: (names.version || EMPTY_STRING) }),
754!
298
        new asn1js.Constructed({
299
          name: (names.originatorInfo || EMPTY_STRING),
754!
300
          optional: true,
301
          idBlock: {
302
            tagClass: 3, // CONTEXT-SPECIFIC
303
            tagNumber: 0 // [0]
304
          },
305
          value: OriginatorInfo.schema().valueBlock.value
306
        }),
307
        new asn1js.Set({
308
          value: [
309
            new asn1js.Repeated({
310
              name: (names.recipientInfos || EMPTY_STRING),
754!
311
              value: RecipientInfo.schema()
312
            })
313
          ]
314
        }),
315
        EncryptedContentInfo.schema(names.encryptedContentInfo || {}),
754!
316
        new asn1js.Constructed({
317
          optional: true,
318
          idBlock: {
319
            tagClass: 3, // CONTEXT-SPECIFIC
320
            tagNumber: 1 // [1]
321
          },
322
          value: [
323
            new asn1js.Repeated({
324
              name: (names.unprotectedAttrs || EMPTY_STRING),
754!
325
              value: Attribute.schema()
326
            })
327
          ]
328
        })
329
      ]
330
    }));
331
  }
332

333
  public fromSchema(schema: Schema.SchemaType): void {
334
    // Clear input data first
335
    pvutils.clearProps(schema, CLEAR_PROPS);
754✔
336

337
    // Check the schema is valid
338
    const asn1 = asn1js.compareSchema(schema,
754✔
339
      schema,
340
      EnvelopedData.schema({
341
        names: {
342
          version: VERSION,
343
          originatorInfo: ORIGINATOR_INFO,
344
          recipientInfos: RECIPIENT_INFOS,
345
          encryptedContentInfo: {
346
            names: {
347
              blockName: ENCRYPTED_CONTENT_INFO
348
            }
349
          },
350
          unprotectedAttrs: UNPROTECTED_ATTRS
351
        }
352
      })
353
    );
354
    AsnError.assertSchema(asn1, this.className);
754✔
355

356
    // Get internal properties from parsed schema
357
    this.version = asn1.result.version.valueBlock.valueDec;
754✔
358

359
    if (ORIGINATOR_INFO in asn1.result) {
754✔
360
      this.originatorInfo = new OriginatorInfo({
576✔
361
        schema: new asn1js.Sequence({
362
          value: asn1.result.originatorInfo.valueBlock.value
363
        })
364
      });
365
    }
366

367
    this.recipientInfos = Array.from(asn1.result.recipientInfos, o => new RecipientInfo({ schema: o }));
754✔
368
    this.encryptedContentInfo = new EncryptedContentInfo({ schema: asn1.result.encryptedContentInfo });
754✔
369

370
    if (UNPROTECTED_ATTRS in asn1.result)
754✔
371
      this.unprotectedAttrs = Array.from(asn1.result.unprotectedAttrs, o => new Attribute({ schema: o }));
2✔
372
  }
373

374
  public toSchema(): asn1js.Sequence {
375
    //#region Create array for output sequence
376
    const outputArray = [];
754✔
377

378
    outputArray.push(new asn1js.Integer({ value: this.version }));
754✔
379

380
    if (this.originatorInfo) {
754✔
381
      outputArray.push(new asn1js.Constructed({
576✔
382
        optional: true,
383
        idBlock: {
384
          tagClass: 3, // CONTEXT-SPECIFIC
385
          tagNumber: 0 // [0]
386
        },
387
        value: this.originatorInfo.toSchema().valueBlock.value
388
      }));
389
    }
390

391
    outputArray.push(new asn1js.Set({
754✔
392
      value: Array.from(this.recipientInfos, o => o.toSchema())
754✔
393
    }));
394

395
    outputArray.push(this.encryptedContentInfo.toSchema());
754✔
396

397
    if (this.unprotectedAttrs) {
754!
398
      outputArray.push(new asn1js.Constructed({
×
399
        optional: true,
400
        idBlock: {
401
          tagClass: 3, // CONTEXT-SPECIFIC
402
          tagNumber: 1 // [1]
403
        },
404
        value: Array.from(this.unprotectedAttrs, o => o.toSchema())
×
405
      }));
406
    }
407
    //#endregion
408

409
    //#region Construct and return new ASN.1 schema for this object
410
    return (new asn1js.Sequence({
754✔
411
      value: outputArray
412
    }));
413
    //#endregion
414
  }
415

416
  public toJSON(): EnvelopedDataJson {
417
    const res: EnvelopedDataJson = {
×
418
      version: this.version,
419
      recipientInfos: Array.from(this.recipientInfos, o => o.toJSON()),
×
420
      encryptedContentInfo: this.encryptedContentInfo.toJSON(),
421
    };
422

423
    if (this.originatorInfo)
×
424
      res.originatorInfo = this.originatorInfo.toJSON();
×
425

426
    if (this.unprotectedAttrs)
×
427
      res.unprotectedAttrs = Array.from(this.unprotectedAttrs, o => o.toJSON());
×
428

429
    return res;
×
430
  }
431

432
  /**
433
   * Helpers function for filling "RecipientInfo" based on recipient's certificate.
434
   * Problem with WebCrypto is that for RSA certificates we have only one option - "key transport" and
435
   * for ECC certificates we also have one option - "key agreement". As soon as Google will implement
436
   * DH algorithm it would be possible to use "key agreement" also for RSA certificates.
437
   * @param certificate Recipient's certificate
438
   * @param parameters Additional parameters necessary for "fine tunning" of encryption process
439
   * @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.
440
   * @param crypto Crypto engine
441
   */
442
  public addRecipientByCertificate(certificate: Certificate, parameters?: object, variant?: number, crypto = common.getCrypto(true)): boolean {
584✔
443
    //#region Initialize encryption parameters
444
    const encryptionParameters = Object.assign(
586✔
445
      { useOAEP: true, oaepHashAlgorithm: "SHA-512" },
446
      defaultEncryptionParams,
447
      parameters || {}
586!
448
    );
449
    //#endregion
450

451
    //#region Check type of certificate
452
    if (certificate.subjectPublicKeyInfo.algorithm.algorithmId.indexOf("1.2.840.113549") !== (-1))
586✔
453
      variant = 1; // For the moment it is the only variant for RSA-based certificates
394✔
454
    else {
455
      if (certificate.subjectPublicKeyInfo.algorithm.algorithmId.indexOf("1.2.840.10045") !== (-1))
192!
456
        variant = 2; // For the moment it is the only variant for ECC-based certificates
192✔
457
      else
458
        throw new Error(`Unknown type of certificate's public key: ${certificate.subjectPublicKeyInfo.algorithm.algorithmId}`);
×
459
    }
460
    //#endregion
461

462
    //#region Add new "recipient" depends on "variant" and certificate type
463
    switch (variant) {
586!
464
      case 1: // Key transport scheme
465
        {
466
          let algorithmId;
467
          let algorithmParams;
468

469
          if (encryptionParameters.useOAEP === true) {
394!
470
            // keyEncryptionAlgorithm
471
            algorithmId = crypto.getOIDByAlgorithm({
394✔
472
              name: "RSA-OAEP"
473
            }, true, "keyEncryptionAlgorithm");
474

475
            //#region RSAES-OAEP-params
476
            const hashOID = crypto.getOIDByAlgorithm({
394✔
477
              name: encryptionParameters.oaepHashAlgorithm
478
            }, true, "RSAES-OAEP-params");
479

480
            const hashAlgorithm = new AlgorithmIdentifier({
394✔
481
              algorithmId: hashOID,
482
              algorithmParams: new asn1js.Null()
483
            });
484

485
            const rsaOAEPParams = new RSAESOAEPParams({
394✔
486
              hashAlgorithm,
487
              maskGenAlgorithm: new AlgorithmIdentifier({
488
                algorithmId: "1.2.840.113549.1.1.8", // id-mgf1
489
                algorithmParams: hashAlgorithm.toSchema()
490
              })
491
            });
492

493
            algorithmParams = rsaOAEPParams.toSchema();
394✔
494
            //#endregion
495
          }
496
          else // Use old RSAES-PKCS1-v1_5 schema instead
497
          {
498
            //#region keyEncryptionAlgorithm
499
            algorithmId = crypto.getOIDByAlgorithm({
×
500
              name: "RSAES-PKCS1-v1_5"
501
            });
502
            if (algorithmId === EMPTY_STRING)
×
503
              throw new Error("Can not find OID for RSAES-PKCS1-v1_5");
×
504
            //#endregion
505

506
            algorithmParams = new asn1js.Null();
×
507
          }
508

509
          //#region KeyTransRecipientInfo
510
          const keyInfo = new KeyTransRecipientInfo({
394✔
511
            version: 0,
512
            rid: new IssuerAndSerialNumber({
513
              issuer: certificate.issuer,
514
              serialNumber: certificate.serialNumber
515
            }),
516
            keyEncryptionAlgorithm: new AlgorithmIdentifier({
517
              algorithmId,
518
              algorithmParams
519
            }),
520
            recipientCertificate: certificate,
521
            // "encryptedKey" will be calculated in "encrypt" function
522
          });
523
          //#endregion
524

525
          //#region Final values for "CMS_ENVELOPED_DATA"
526
          this.recipientInfos.push(new RecipientInfo({
394✔
527
            variant: 1,
528
            value: keyInfo
529
          }));
530
          //#endregion
531
        }
532
        break;
394✔
533
      case 2: // Key agreement scheme
534
        {
535
          const recipientIdentifier = new KeyAgreeRecipientIdentifier({
192✔
536
            variant: 1,
537
            value: new IssuerAndSerialNumber({
538
              issuer: certificate.issuer,
539
              serialNumber: certificate.serialNumber
540
            })
541
          });
542
          this._addKeyAgreeRecipientInfo(
192✔
543
            recipientIdentifier,
544
            encryptionParameters,
545
            { recipientCertificate: certificate },
546
            crypto,
547
          );
548
        }
549
        break;
192✔
550
      default:
551
        throw new Error(`Unknown "variant" value: ${variant}`);
×
552
    }
553
    //#endregion
554

555
    return true;
586✔
556
  }
557

558
  /**
559
   * Add recipient based on pre-defined data like password or KEK
560
   * @param preDefinedData ArrayBuffer with pre-defined data
561
   * @param parameters Additional parameters necessary for "fine tunning" of encryption process
562
   * @param variant Variant = 1 for pre-defined "key encryption key" (KEK). Variant = 2 for password-based encryption.
563
   * @param crypto Crypto engine
564
   */
565
  public addRecipientByPreDefinedData(preDefinedData: ArrayBuffer, parameters: {
×
566
    keyIdentifier?: ArrayBuffer;
567
    hmacHashAlgorithm?: string;
568
    iterationCount?: number;
569
    keyEncryptionAlgorithm?: AesKeyGenParams;
570
    keyEncryptionAlgorithmParams?: any;
571
  } = {}, variant: number, crypto = common.getCrypto(true)) {
24✔
572
    //#region Check initial parameters
573
    ArgumentError.assert(preDefinedData, "preDefinedData", "ArrayBuffer");
24✔
574
    if (!preDefinedData.byteLength) {
24!
575
      throw new Error("Pre-defined data could have zero length");
×
576
    }
577
    //#endregion
578

579
    //#region Initialize encryption parameters
580
    if (!parameters.keyIdentifier) {
24✔
581
      const keyIdentifierBuffer = new ArrayBuffer(16);
24✔
582
      const keyIdentifierView = new Uint8Array(keyIdentifierBuffer);
24✔
583
      crypto.getRandomValues(keyIdentifierView);
24✔
584

585
      parameters.keyIdentifier = keyIdentifierBuffer;
24✔
586
    }
587

588
    if (!parameters.hmacHashAlgorithm)
24✔
589
      parameters.hmacHashAlgorithm = "SHA-512";
24✔
590

591
    if (parameters.iterationCount === undefined) {
24✔
592
      parameters.iterationCount = 2048;
24✔
593
    }
594

595
    if (!parameters.keyEncryptionAlgorithm) {
24✔
596
      parameters.keyEncryptionAlgorithm = {
24✔
597
        name: "AES-KW",
598
        length: 256
599
      };
600
    }
601

602
    if (!parameters.keyEncryptionAlgorithmParams)
24✔
603
      parameters.keyEncryptionAlgorithmParams = new asn1js.Null();
24✔
604
    //#endregion
605

606
    //#region Add new recipient based on passed variant
607
    switch (variant) {
24!
608
      case 1: // KEKRecipientInfo
609
        {
610
          // keyEncryptionAlgorithm
611
          const kekOID = crypto.getOIDByAlgorithm(parameters.keyEncryptionAlgorithm, true, "keyEncryptionAlgorithm");
12✔
612

613
          //#region KEKRecipientInfo
614
          const keyInfo = new KEKRecipientInfo({
12✔
615
            version: 4,
616
            kekid: new KEKIdentifier({
617
              keyIdentifier: new asn1js.OctetString({ valueHex: parameters.keyIdentifier })
618
            }),
619
            keyEncryptionAlgorithm: new AlgorithmIdentifier({
620
              algorithmId: kekOID,
621
              /*
622
               For AES-KW params are NULL, but for other algorithm could another situation.
623
               */
624
              algorithmParams: parameters.keyEncryptionAlgorithmParams
625
            }),
626
            preDefinedKEK: preDefinedData
627
            // "encryptedKey" would be set in "ecrypt" function
628
          });
629
          //#endregion
630

631
          //#region Final values for "CMS_ENVELOPED_DATA"
632
          this.recipientInfos.push(new RecipientInfo({
12✔
633
            variant: 3,
634
            value: keyInfo
635
          }));
636
          //#endregion
637
        }
638
        break;
12✔
639
      case 2: // PasswordRecipientinfo
640
        {
641
          // keyDerivationAlgorithm
642
          const pbkdf2OID = crypto.getOIDByAlgorithm({ name: "PBKDF2" }, true, "keyDerivationAlgorithm");
12✔
643

644
          //#region Salt
645
          const saltBuffer = new ArrayBuffer(64);
12✔
646
          const saltView = new Uint8Array(saltBuffer);
12✔
647
          crypto.getRandomValues(saltView);
12✔
648
          //#endregion
649

650
          //#region HMAC-based algorithm
651
          const hmacOID = crypto.getOIDByAlgorithm({
12✔
652
            name: "HMAC",
653
            hash: {
654
              name: parameters.hmacHashAlgorithm
655
            }
656
          } as Algorithm, true, "hmacHashAlgorithm");
657
          //#endregion
658

659
          //#region PBKDF2-params
660
          const pbkdf2Params = new PBKDF2Params({
12✔
661
            salt: new asn1js.OctetString({ valueHex: saltBuffer }),
662
            iterationCount: parameters.iterationCount,
663
            prf: new AlgorithmIdentifier({
664
              algorithmId: hmacOID,
665
              algorithmParams: new asn1js.Null()
666
            })
667
          });
668
          //#endregion
669

670
          // keyEncryptionAlgorithm
671
          const kekOID = crypto.getOIDByAlgorithm(parameters.keyEncryptionAlgorithm, true, "keyEncryptionAlgorithm");
12✔
672

673
          //#region PasswordRecipientinfo
674
          const keyInfo = new PasswordRecipientinfo({
12✔
675
            version: 0,
676
            keyDerivationAlgorithm: new AlgorithmIdentifier({
677
              algorithmId: pbkdf2OID,
678
              algorithmParams: pbkdf2Params.toSchema()
679
            }),
680
            keyEncryptionAlgorithm: new AlgorithmIdentifier({
681
              algorithmId: kekOID,
682
              /*
683
               For AES-KW params are NULL, but for other algorithm could be another situation.
684
               */
685
              algorithmParams: parameters.keyEncryptionAlgorithmParams
686
            }),
687
            password: preDefinedData
688
            // "encryptedKey" would be set in "encrypt" function
689
          });
690
          //#endregion
691

692
          //#region Final values for "CMS_ENVELOPED_DATA"
693
          this.recipientInfos.push(new RecipientInfo({
12✔
694
            variant: 4,
695
            value: keyInfo
696
          }));
697
          //#endregion
698
        }
699
        break;
12✔
700
      default:
701
        throw new Error(`Unknown value for "variant": ${variant}`);
×
702
    }
703
    //#endregion
704
  }
705

706
  /**
707
   * Add a "RecipientInfo" using a KeyAgreeRecipientInfo of type RecipientKeyIdentifier.
708
   * @param key Recipient's public key
709
   * @param keyId The id for the recipient's public key
710
   * @param parameters Additional parameters for "fine tuning" the encryption process
711
   * @param crypto Crypto engine
712
   */
713
  addRecipientByKeyIdentifier(key?: CryptoKey, keyId?: ArrayBuffer, parameters?: any, crypto = common.getCrypto(true)) {
144✔
714
    //#region Initialize encryption parameters
715
    const encryptionParameters = Object.assign({}, defaultEncryptionParams, parameters || {});
144!
716
    //#endregion
717

718
    const recipientIdentifier = new KeyAgreeRecipientIdentifier({
144✔
719
      variant: 2,
720
      value: new RecipientKeyIdentifier({
721
        subjectKeyIdentifier: new asn1js.OctetString({ valueHex: keyId }),
722
      })
723
    });
724
    this._addKeyAgreeRecipientInfo(
144✔
725
      recipientIdentifier,
726
      encryptionParameters,
727
      { recipientPublicKey: key },
728
      crypto,
729
    );
730
  }
731

732
  /**
733
   * Add a "RecipientInfo" using a KeyAgreeRecipientInfo of type RecipientKeyIdentifier.
734
   * @param recipientIdentifier Recipient identifier
735
   * @param encryptionParameters Additional parameters for "fine tuning" the encryption process
736
   * @param extraRecipientInfoParams Additional params for KeyAgreeRecipientInfo
737
   * @param crypto Crypto engine
738
   */
739
  private _addKeyAgreeRecipientInfo(recipientIdentifier: KeyAgreeRecipientIdentifier, encryptionParameters: EnvelopedDataEncryptionParams, extraRecipientInfoParams: KeyAgreeRecipientInfoParameters, crypto = common.getCrypto(true)) {
×
740
    //#region RecipientEncryptedKey
741
    const encryptedKey = new RecipientEncryptedKey({
336✔
742
      rid: recipientIdentifier
743
      // "encryptedKey" will be calculated in "encrypt" function
744
    });
745
    //#endregion
746

747
    //#region keyEncryptionAlgorithm
748
    const aesKWoid = crypto.getOIDByAlgorithm({
336✔
749
      name: "AES-KW",
750
      length: encryptionParameters.kekEncryptionLength
751
    } as Algorithm, true, "keyEncryptionAlgorithm");
752

753
    const aesKW = new AlgorithmIdentifier({
336✔
754
      algorithmId: aesKWoid,
755
    });
756
    //#endregion
757

758
    //#region KeyAgreeRecipientInfo
759
    const ecdhOID = crypto.getOIDByAlgorithm({
336✔
760
      name: "ECDH",
761
      kdf: encryptionParameters.kdfAlgorithm
762
    } as Algorithm, true, "KeyAgreeRecipientInfo");
763

764
    // In fact there is no need in so long UKM, but RFC2631
765
    // has requirement that "UserKeyMaterial" must be 512 bits long
766
    const ukmBuffer = new ArrayBuffer(64);
336✔
767
    const ukmView = new Uint8Array(ukmBuffer);
336✔
768
    crypto.getRandomValues(ukmView); // Generate random values in 64 bytes long buffer
336✔
769

770
    const recipientInfoParams = {
336✔
771
      version: 3,
772
      // "originator" will be calculated in "encrypt" function because ephemeral key would be generated there
773
      ukm: new asn1js.OctetString({ valueHex: ukmBuffer }),
774
      keyEncryptionAlgorithm: new AlgorithmIdentifier({
775
        algorithmId: ecdhOID,
776
        algorithmParams: aesKW.toSchema()
777
      }),
778
      recipientEncryptedKeys: new RecipientEncryptedKeys({
779
        encryptedKeys: [encryptedKey]
780
      })
781
    };
782
    const keyInfo = new KeyAgreeRecipientInfo(Object.assign(recipientInfoParams, extraRecipientInfoParams));
336✔
783
    //#endregion
784

785
    //#region Final values for "CMS_ENVELOPED_DATA"
786
    this.recipientInfos.push(new RecipientInfo({
336✔
787
      variant: 2,
788
      value: keyInfo
789
    }));
790
    //#endregion
791
  }
792

793
  /**
794
   * Creates a new CMS Enveloped Data content with encrypted data
795
   * @param contentEncryptionAlgorithm WebCrypto algorithm. For the moment here could be only "AES-CBC" or "AES-GCM" algorithms.
796
   * @param contentToEncrypt Content to encrypt
797
   * @param crypto Crypto engine
798
   */
799
  public async encrypt(contentEncryptionAlgorithm: Algorithm, contentToEncrypt: ArrayBuffer, crypto = common.getCrypto(true)): Promise<(void | { ecdhPrivateKey: CryptoKey; })[]> {
754✔
800
    //#region Initial variables
801
    // AES-GCM uses a 12-byte nonce per NIST SP 800-38D ยง8.2.1 and RFC 5084
802
    // recommendation. Other AES modes use a 16-byte IV.
803
    const isAesGcm = contentEncryptionAlgorithm.name === "AES-GCM";
756✔
804
    const ivBuffer = new ArrayBuffer(isAesGcm ? 12 : 16);
756✔
805
    const ivView = new Uint8Array(ivBuffer);
756✔
806
    crypto.getRandomValues(ivView);
756✔
807

808
    const contentView = new Uint8Array(contentToEncrypt);
756✔
809
    //#endregion
810

811
    // Check for input parameters
812
    const contentEncryptionOID = crypto.getOIDByAlgorithm(contentEncryptionAlgorithm, true, "contentEncryptionAlgorithm");
756✔
813

814
    //#region Generate new content encryption key
815
    const sessionKey = await crypto.generateKey(contentEncryptionAlgorithm as AesKeyAlgorithm, true, ["encrypt"]);
756✔
816
    //#endregion
817
    //#region Encrypt content
818

819
    const encryptedContent = await crypto.encrypt({
756✔
820
      name: contentEncryptionAlgorithm.name,
821
      iv: ivView
822
    },
823
      sessionKey,
824
      contentView);
825
    //#endregion
826
    //#region Export raw content of content encryption key
827
    const exportedSessionKey = await crypto.exportKey("raw", sessionKey);
756✔
828

829
    //#endregion
830
    //#region Append common information to CMS_ENVELOPED_DATA
831
    this.version = 2;
756✔
832
    this.encryptedContentInfo = new EncryptedContentInfo({
756✔
833
      disableSplit: this.policy.disableSplit,
834
      contentType: "1.2.840.113549.1.7.1", // "data"
835
      contentEncryptionAlgorithm: new AlgorithmIdentifier({
836
        algorithmId: contentEncryptionOID,
837
        // RFC 5084 ยง3.2 requires AES-GCM AlgorithmIdentifier parameters to be
838
        // a GCMParameters SEQUENCE (nonce + optional ICV length), not a bare
839
        // OCTET STRING. WebCrypto AES-GCM emits a 128-bit / 16-byte ICV by
840
        // default; advertise it explicitly rather than relying on the schema
841
        // default of 12. Other ciphers keep the legacy OCTET-STRING IV.
842
        algorithmParams: isAesGcm
756✔
843
          ? new GCMParams({ nonce: ivBuffer, icvLen: 16 }).toSchema()
844
          : new asn1js.OctetString({ valueHex: ivBuffer })
845
      }),
846
      encryptedContent: new asn1js.OctetString({ valueHex: encryptedContent })
847
    });
848
    //#endregion
849

850
    //#region Special sub-functions to work with each recipient's type
851
    const SubKeyAgreeRecipientInfo = async (index: number) => {
756✔
852
      //#region Initial variables
853
      const recipientInfo = this.recipientInfos[index].value as KeyAgreeRecipientInfo;
336✔
854
      let recipientCurve: string;
855
      //#endregion
856

857
      //#region Get public key and named curve from recipient's certificate or public key
858
      let recipientPublicKey: CryptoKey;
859
      if (recipientInfo.recipientPublicKey) {
336✔
860
        recipientCurve = (recipientInfo.recipientPublicKey.algorithm as EcKeyAlgorithm).namedCurve;
144✔
861
        recipientPublicKey = recipientInfo.recipientPublicKey;
144✔
862
      } else if (recipientInfo.recipientCertificate) {
192!
863
        const curveObject = recipientInfo.recipientCertificate.subjectPublicKeyInfo.algorithm.algorithmParams;
192✔
864

865
        if (curveObject.constructor.blockName() !== asn1js.ObjectIdentifier.blockName())
192!
866
          throw new Error(`Incorrect "recipientCertificate" for index ${index}`);
×
867

868
        const curveOID = curveObject.valueBlock.toString();
192✔
869

870
        switch (curveOID) {
192!
871
          case "1.2.840.10045.3.1.7":
872
            recipientCurve = "P-256";
192✔
873
            break;
192✔
874
          case "1.3.132.0.34":
875
            recipientCurve = "P-384";
×
876
            break;
×
877
          case "1.3.132.0.35":
878
            recipientCurve = "P-521";
×
879
            break;
×
880
          default:
881
            throw new Error(`Incorrect curve OID for index ${index}`);
×
882
        }
883

884
        recipientPublicKey = await recipientInfo.recipientCertificate.getPublicKey({
192✔
885
          algorithm: {
886
            algorithm: {
887
              name: "ECDH",
888
              namedCurve: recipientCurve
889
            } as EcKeyAlgorithm,
890
            usages: []
891
          }
892
        }, crypto);
893
      } else {
894
        throw new Error("Unsupported RecipientInfo");
×
895
      }
896
      //#endregion
897

898
      //#region Generate ephemeral ECDH key
899
      const recipientCurveLength = curveLengthByName[recipientCurve];
336✔
900

901
      const ecdhKeys = await crypto.generateKey(
336✔
902
        { name: "ECDH", namedCurve: recipientCurve } as EcKeyGenParams,
903
        true,
904
        ["deriveBits"]
905
      );
906
      //#endregion
907
      //#region Export public key of ephemeral ECDH key pair
908

909
      const exportedECDHPublicKey = await crypto.exportKey("spki", ecdhKeys.publicKey);
336✔
910
      //#endregion
911

912
      //#region Create shared secret
913
      const derivedBits = await crypto.deriveBits({
336✔
914
        name: "ECDH",
915
        public: recipientPublicKey
916
      },
917
        ecdhKeys.privateKey,
918
        recipientCurveLength);
919
      //#endregion
920

921
      //#region Apply KDF function to shared secret
922

923
      //#region Get length of used AES-KW algorithm
924
      const aesKWAlgorithm = new AlgorithmIdentifier({ schema: recipientInfo.keyEncryptionAlgorithm.algorithmParams });
336✔
925

926
      const kwAlgorithm = crypto.getAlgorithmByOID<AesKeyAlgorithm>(aesKWAlgorithm.algorithmId, true, "aesKWAlgorithm");
336✔
927
      //#endregion
928

929
      //#region Translate AES-KW length to ArrayBuffer
930
      let kwLength = kwAlgorithm.length;
336✔
931

932
      const kwLengthBuffer = new ArrayBuffer(4);
336✔
933
      const kwLengthView = new Uint8Array(kwLengthBuffer);
336✔
934

935
      for (let j = 3; j >= 0; j--) {
336✔
936
        kwLengthView[j] = kwLength;
1,344✔
937
        kwLength >>= 8;
1,344✔
938
      }
939
      //#endregion
940

941
      //#region Create and encode "ECC-CMS-SharedInfo" structure
942
      const eccInfo = new ECCCMSSharedInfo({
336✔
943
        keyInfo: new AlgorithmIdentifier({
944
          algorithmId: aesKWAlgorithm.algorithmId
945
        }),
946
        entityUInfo: (recipientInfo as KeyAgreeRecipientInfo).ukm, // TODO remove `as KeyAgreeRecipientInfo`
947
        suppPubInfo: new asn1js.OctetString({ valueHex: kwLengthBuffer })
948
      });
949

950
      const encodedInfo = eccInfo.toSchema().toBER(false);
336✔
951
      //#endregion
952

953
      //#region Get SHA algorithm used together with ECDH
954
      const ecdhAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "ecdhAlgorithm");
336✔
955
      //#endregion
956

957
      const derivedKeyRaw = await common.kdf(ecdhAlgorithm.kdf, derivedBits, kwAlgorithm.length, encodedInfo, crypto);
336✔
958
      //#endregion
959
      //#region Import AES-KW key from result of KDF function
960
      const awsKW = await crypto.importKey("raw", derivedKeyRaw, { name: "AES-KW" }, true, ["wrapKey"]);
336✔
961
      //#endregion
962
      //#region Finally wrap session key by using AES-KW algorithm
963
      const wrappedKey = await crypto.wrapKey("raw", sessionKey, awsKW, { name: "AES-KW" });
336✔
964
      //#endregion
965
      //#region Append all necessary data to current CMS_RECIPIENT_INFO object
966
      //#region OriginatorIdentifierOrKey
967
      const originator = new OriginatorIdentifierOrKey();
336✔
968
      originator.variant = 3;
336✔
969
      originator.value = OriginatorPublicKey.fromBER(exportedECDHPublicKey);
336✔
970

971
      recipientInfo.originator = originator;
336✔
972
      //#endregion
973

974
      //#region RecipientEncryptedKey
975
      /*
976
       We will not support using of same ephemeral key for many recipients
977
       */
978
      recipientInfo.recipientEncryptedKeys.encryptedKeys[0].encryptedKey = new asn1js.OctetString({ valueHex: wrappedKey });
336✔
979
      //#endregion
980

981
      return { ecdhPrivateKey: ecdhKeys.privateKey };
336✔
982
      //#endregion
983
    };
984

985
    const SubKeyTransRecipientInfo = async (index: number) => {
756✔
986
      const recipientInfo = this.recipientInfos[index].value as KeyTransRecipientInfo; // TODO Remove `as KeyTransRecipientInfo`
394✔
987
      const algorithmParameters = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "keyEncryptionAlgorithm");
394✔
988

989
      //#region RSA-OAEP case
990
      if (algorithmParameters.name === "RSA-OAEP") {
394✔
991
        const schema = recipientInfo.keyEncryptionAlgorithm.algorithmParams;
394✔
992
        const rsaOAEPParams = new RSAESOAEPParams({ schema });
394✔
993

994
        algorithmParameters.hash = crypto.getAlgorithmByOID(rsaOAEPParams.hashAlgorithm.algorithmId);
394✔
995
        if (("name" in algorithmParameters.hash) === false)
394!
996
          throw new Error(`Incorrect OID for hash algorithm: ${rsaOAEPParams.hashAlgorithm.algorithmId}`);
×
997
      }
998
      //#endregion
999

1000
      try {
394✔
1001
        const publicKey = await recipientInfo.recipientCertificate.getPublicKey({
394✔
1002
          algorithm: {
1003
            algorithm: algorithmParameters,
1004
            usages: ["encrypt", "wrapKey"]
1005
          }
1006
        }, crypto);
1007

1008
        const encryptedKey = await crypto.encrypt(publicKey.algorithm, publicKey, exportedSessionKey);
394✔
1009

1010
        //#region RecipientEncryptedKey
1011
        recipientInfo.encryptedKey = new asn1js.OctetString({ valueHex: encryptedKey });
394✔
1012
        //#endregion
1013
      }
1014
      catch {
1015
        // nothing
1016
      }
1017
    };
1018

1019
    const SubKEKRecipientInfo = async (index: number) => {
756✔
1020
      //#region Initial variables
1021
      const recipientInfo = this.recipientInfos[index].value as KEKRecipientInfo; // TODO Remove `as KEKRecipientInfo`
12✔
1022
      //#endregion
1023

1024
      //#region Import KEK from pre-defined data
1025

1026
      //#region Get WebCrypto form of "keyEncryptionAlgorithm"
1027
      const kekAlgorithm = crypto.getAlgorithmByOID(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "kekAlgorithm");
12✔
1028
      //#endregion
1029

1030
      const kekKey = await crypto.importKey("raw",
12✔
1031
        new Uint8Array(recipientInfo.preDefinedKEK),
1032
        kekAlgorithm,
1033
        true,
1034
        ["wrapKey"]); // Too specific for AES-KW
1035
      //#endregion
1036

1037
      //#region Wrap previously exported session key
1038

1039
      const wrappedKey = await crypto.wrapKey("raw", sessionKey, kekKey, kekAlgorithm);
12✔
1040
      //#endregion
1041
      //#region Append all necessary data to current CMS_RECIPIENT_INFO object
1042
      //#region RecipientEncryptedKey
1043
      recipientInfo.encryptedKey = new asn1js.OctetString({ valueHex: wrappedKey });
12✔
1044
      //#endregion
1045
      //#endregion
1046
    };
1047

1048
    const SubPasswordRecipientinfo = async (index: number) => {
756✔
1049
      //#region Initial variables
1050
      const recipientInfo = this.recipientInfos[index].value as PasswordRecipientinfo; // TODO Remove `as PasswordRecipientinfo`
12✔
1051
      let pbkdf2Params: PBKDF2Params;
1052
      //#endregion
1053

1054
      //#region Check that we have encoded "keyDerivationAlgorithm" plus "PBKDF2_params" in there
1055

1056
      if (!recipientInfo.keyDerivationAlgorithm)
12!
1057
        throw new Error("Please append encoded \"keyDerivationAlgorithm\"");
×
1058

1059
      if (!recipientInfo.keyDerivationAlgorithm.algorithmParams)
12!
1060
        throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\"");
×
1061

1062
      try {
12✔
1063
        pbkdf2Params = new PBKDF2Params({ schema: recipientInfo.keyDerivationAlgorithm.algorithmParams });
12✔
1064
      }
1065
      catch {
1066
        throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\"");
×
1067
      }
1068

1069
      //#endregion
1070
      //#region Derive PBKDF2 key from "password" buffer
1071
      const passwordView = new Uint8Array(recipientInfo.password);
12✔
1072

1073
      const derivationKey = await crypto.importKey("raw",
12✔
1074
        passwordView,
1075
        "PBKDF2",
1076
        false,
1077
        ["deriveKey"]);
1078
      //#endregion
1079
      //#region Derive key for "keyEncryptionAlgorithm"
1080
      //#region Get WebCrypto form of "keyEncryptionAlgorithm"
1081
      const kekAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "kekAlgorithm");
12✔
1082

1083
      //#endregion
1084

1085
      //#region Get HMAC hash algorithm
1086
      let hmacHashAlgorithm = "SHA-1";
12✔
1087

1088
      if (pbkdf2Params.prf) {
12✔
1089
        const prfAlgorithm = crypto.getAlgorithmByOID<any>(pbkdf2Params.prf.algorithmId, true, "prfAlgorithm");
12✔
1090
        hmacHashAlgorithm = prfAlgorithm.hash.name;
12✔
1091
      }
1092
      //#endregion
1093

1094
      //#region Get PBKDF2 "salt" value
1095
      const saltView = new Uint8Array(pbkdf2Params.salt.valueBlock.valueHex);
12✔
1096
      //#endregion
1097

1098
      //#region Get PBKDF2 iterations count
1099
      const iterations = pbkdf2Params.iterationCount;
12✔
1100
      //#endregion
1101

1102
      const derivedKey = await crypto.deriveKey({
12✔
1103
        name: "PBKDF2",
1104
        hash: {
1105
          name: hmacHashAlgorithm
1106
        },
1107
        salt: saltView,
1108
        iterations
1109
      },
1110
        derivationKey,
1111
        kekAlgorithm,
1112
        true,
1113
        ["wrapKey"]); // Usages are too specific for KEK algorithm
1114

1115
      //#endregion
1116
      //#region Wrap previously exported session key (Also too specific for KEK algorithm)
1117
      const wrappedKey = await crypto.wrapKey("raw", sessionKey, derivedKey, kekAlgorithm);
12✔
1118
      //#endregion
1119
      //#region Append all necessary data to current CMS_RECIPIENT_INFO object
1120
      //#region RecipientEncryptedKey
1121
      recipientInfo.encryptedKey = new asn1js.OctetString({ valueHex: wrappedKey });
12✔
1122
      //#endregion
1123
      //#endregion
1124
    };
1125

1126
    //#endregion
1127

1128
    const res = [];
756✔
1129
    //#region Create special routines for each "recipient"
1130
    for (let i = 0; i < this.recipientInfos.length; i++) {
756✔
1131
      switch (this.recipientInfos[i].variant) {
754!
1132
        case 1: // KeyTransRecipientInfo
1133
          res.push(await SubKeyTransRecipientInfo(i));
394✔
1134
          break;
394✔
1135
        case 2: // KeyAgreeRecipientInfo
1136
          res.push(await SubKeyAgreeRecipientInfo(i));
336✔
1137
          break;
336✔
1138
        case 3: // KEKRecipientInfo
1139
          res.push(await SubKEKRecipientInfo(i));
12✔
1140
          break;
12✔
1141
        case 4: // PasswordRecipientinfo
1142
          res.push(await SubPasswordRecipientinfo(i));
12✔
1143
          break;
12✔
1144
        default:
1145
          throw new Error(`Unknown recipient type in array with index ${i}`);
×
1146
      }
1147
    }
1148
    //#endregion
1149
    return res;
756✔
1150
  }
1151

1152
  /**
1153
   * Decrypts existing CMS Enveloped Data content
1154
   * @param recipientIndex Index of recipient
1155
   * @param parameters Additional parameters
1156
   * @param crypto Crypto engine
1157
   */
1158
  async decrypt(recipientIndex: number, parameters: EnvelopedDataDecryptParams, crypto = common.getCrypto(true)) {
754✔
1159
    //#region Initial variables
1160
    const decryptionParameters = parameters || {};
754!
1161
    //#endregion
1162

1163
    //#region Check for input parameters
1164
    if ((recipientIndex + 1) > this.recipientInfos.length) {
754!
1165
      throw new Error(`Maximum value for "index" is: ${this.recipientInfos.length - 1}`);
×
1166
    }
1167
    //#endregion
1168

1169
    //#region Special sub-functions to work with each recipient's type
1170
    const SubKeyAgreeRecipientInfo = async (index: number) => {
754✔
1171
      //#region Initial variables
1172
      const recipientInfo = this.recipientInfos[index].value as KeyAgreeRecipientInfo; // TODO Remove `as KeyAgreeRecipientInfo`
338✔
1173
      //#endregion
1174

1175
      let curveOID: string;
1176
      let recipientCurve: string;
1177
      let recipientCurveLength: number;
1178
      const originator = recipientInfo.originator;
338✔
1179

1180
      //#region Get "namedCurve" parameter from recipient's certificate
1181

1182
      if (decryptionParameters.recipientCertificate) {
338✔
1183
        const curveObject = decryptionParameters.recipientCertificate.subjectPublicKeyInfo.algorithm.algorithmParams;
194✔
1184
        if (curveObject.constructor.blockName() !== asn1js.ObjectIdentifier.blockName()) {
194!
1185
          throw new Error(`Incorrect "recipientCertificate" for index ${index}`);
×
1186
        }
1187
        curveOID = curveObject.valueBlock.toString();
194✔
1188
      } else if (originator.value.algorithm.algorithmParams) {
144!
1189
        const curveObject = originator.value.algorithm.algorithmParams;
144✔
1190
        if (curveObject.constructor.blockName() !== asn1js.ObjectIdentifier.blockName()) {
144!
1191
          throw new Error(`Incorrect originator for index ${index}`);
×
1192
        }
1193
        curveOID = curveObject.valueBlock.toString();
144✔
1194
      } else {
1195
        throw new Error("Parameter \"recipientCertificate\" is mandatory for \"KeyAgreeRecipientInfo\" if algorithm params are missing from originator");
×
1196
      }
1197

1198
      if (!decryptionParameters.recipientPrivateKey)
338!
1199
        throw new Error("Parameter \"recipientPrivateKey\" is mandatory for \"KeyAgreeRecipientInfo\"");
×
1200

1201
      switch (curveOID) {
338!
1202
        case "1.2.840.10045.3.1.7":
1203
          recipientCurve = "P-256";
242✔
1204
          recipientCurveLength = 256;
242✔
1205
          break;
242✔
1206
        case "1.3.132.0.34":
1207
          recipientCurve = "P-384";
48✔
1208
          recipientCurveLength = 384;
48✔
1209
          break;
48✔
1210
        case "1.3.132.0.35":
1211
          recipientCurve = "P-521";
48✔
1212
          recipientCurveLength = 528;
48✔
1213
          break;
48✔
1214
        default:
1215
          throw new Error(`Incorrect curve OID for index ${index}`);
×
1216
      }
1217

1218
      let ecdhPrivateKey: CryptoKey;
1219
      let keyCrypto: SubtleCrypto = crypto;
338✔
1220
      if (BufferSourceConverter.isBufferSource(decryptionParameters.recipientPrivateKey)) {
338!
1221
        ecdhPrivateKey = await crypto.importKey("pkcs8",
338✔
1222
          decryptionParameters.recipientPrivateKey,
1223
          {
1224
            name: "ECDH",
1225
            namedCurve: recipientCurve
1226
          } as EcKeyImportParams,
1227
          true,
1228
          ["deriveBits"]
1229
        );
1230
      } else {
1231
        ecdhPrivateKey = decryptionParameters.recipientPrivateKey;
×
1232
        if ("crypto" in decryptionParameters && decryptionParameters.crypto) {
×
1233
          keyCrypto = decryptionParameters.crypto.subtle;
×
1234
        }
1235
      }
1236
      //#endregion
1237
      //#region Import sender's ephemeral public key
1238
      //#region Change "OriginatorPublicKey" if "curve" parameter absent
1239
      if (("algorithmParams" in originator.value.algorithm) === false)
338✔
1240
        originator.value.algorithm.algorithmParams = new asn1js.ObjectIdentifier({ value: curveOID });
2✔
1241
      //#endregion
1242

1243
      //#region Create ArrayBuffer with sender's public key
1244
      const buffer = originator.value.toSchema().toBER(false);
338✔
1245
      //#endregion
1246

1247
      const ecdhPublicKey = await crypto.importKey("spki",
338✔
1248
        buffer,
1249
        {
1250
          name: "ECDH",
1251
          namedCurve: recipientCurve
1252
        } as EcKeyImportParams,
1253
        true,
1254
        []);
1255

1256
      //#endregion
1257
      //#region Create shared secret
1258
      const sharedSecret = await keyCrypto.deriveBits({
338✔
1259
        name: "ECDH",
1260
        public: ecdhPublicKey
1261
      },
1262
        ecdhPrivateKey,
1263
        recipientCurveLength);
1264
      //#endregion
1265
      //#region Apply KDF function to shared secret
1266
      async function applyKDF(includeAlgorithmParams?: boolean) {
1267
        includeAlgorithmParams = includeAlgorithmParams || false;
340✔
1268

1269
        //#region Get length of used AES-KW algorithm
1270
        const aesKWAlgorithm = new AlgorithmIdentifier({ schema: recipientInfo.keyEncryptionAlgorithm.algorithmParams });
340✔
1271

1272
        const kwAlgorithm = crypto.getAlgorithmByOID<any>(aesKWAlgorithm.algorithmId, true, "kwAlgorithm");
340✔
1273
        //#endregion
1274

1275
        //#region Translate AES-KW length to ArrayBuffer
1276
        let kwLength = kwAlgorithm.length;
340✔
1277

1278
        const kwLengthBuffer = new ArrayBuffer(4);
340✔
1279
        const kwLengthView = new Uint8Array(kwLengthBuffer);
340✔
1280

1281
        for (let j = 3; j >= 0; j--) {
340✔
1282
          kwLengthView[j] = kwLength;
1,360✔
1283
          kwLength >>= 8;
1,360✔
1284
        }
1285
        //#endregion
1286

1287
        //#region Create and encode "ECC-CMS-SharedInfo" structure
1288
        const keyInfoAlgorithm: AlgorithmIdentifierParameters = {
340✔
1289
          algorithmId: aesKWAlgorithm.algorithmId
1290
        };
1291
        if (includeAlgorithmParams) {
340✔
1292
          keyInfoAlgorithm.algorithmParams = new asn1js.Null();
2✔
1293
        }
1294
        const eccInfo = new ECCCMSSharedInfo({
340✔
1295
          keyInfo: new AlgorithmIdentifier(keyInfoAlgorithm),
1296
          entityUInfo: recipientInfo.ukm,
1297
          suppPubInfo: new asn1js.OctetString({ valueHex: kwLengthBuffer })
1298
        });
1299

1300
        const encodedInfo = eccInfo.toSchema().toBER(false);
340✔
1301
        //#endregion
1302

1303
        //#region Get SHA algorithm used together with ECDH
1304
        const ecdhAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "ecdhAlgorithm");
340✔
1305
        if (!ecdhAlgorithm.name) {
340!
1306
          throw new Error(`Incorrect OID for key encryption algorithm: ${recipientInfo.keyEncryptionAlgorithm.algorithmId}`);
×
1307
        }
1308
        //#endregion
1309

1310
        return common.kdf(ecdhAlgorithm.kdf, sharedSecret, kwAlgorithm.length, encodedInfo, crypto);
340✔
1311
      }
1312

1313
      const kdfResult = await applyKDF();
338✔
1314
      //#endregion
1315
      //#region Import AES-KW key from result of KDF function
1316
      const importAesKwKey = async (kdfResult: ArrayBuffer) => {
338✔
1317
        return crypto.importKey("raw",
340✔
1318
          kdfResult,
1319
          { name: "AES-KW" },
1320
          true,
1321
          ["unwrapKey"]
1322
        );
1323
      };
1324

1325
      const aesKwKey = await importAesKwKey(kdfResult);
338✔
1326

1327
      //#endregion
1328
      //#region Finally unwrap session key
1329
      const unwrapSessionKey = async (aesKwKey: CryptoKey) => {
338✔
1330
        //#region Get WebCrypto form of content encryption algorithm
1331
        const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
340✔
1332
        const contentEncryptionAlgorithm = crypto.getAlgorithmByOID<any>(algorithmId, true, "contentEncryptionAlgorithm");
340✔
1333
        //#endregion
1334

1335
        return crypto.unwrapKey("raw",
340✔
1336
          recipientInfo.recipientEncryptedKeys.encryptedKeys[0].encryptedKey.valueBlock.valueHexView as BufferSource,
1337
          aesKwKey,
1338
          { name: "AES-KW" },
1339
          contentEncryptionAlgorithm,
1340
          true,
1341
          ["decrypt"]);
1342
      };
1343

1344
      try {
338✔
1345
        return await unwrapSessionKey(aesKwKey);
338✔
1346
      } catch {
1347
        const kdfResult = await applyKDF(true);
2✔
1348
        const aesKwKey = await importAesKwKey(kdfResult);
2✔
1349
        return unwrapSessionKey(aesKwKey);
2✔
1350
      }
1351
    };
1352
    //#endregion
1353

1354
    const SubKeyTransRecipientInfo = async (index: number) => {
754✔
1355
      const recipientInfo = this.recipientInfos[index].value as KeyTransRecipientInfo; // TODO Remove `as KeyTransRecipientInfo`
392✔
1356
      if (!decryptionParameters.recipientPrivateKey) {
392!
1357
        throw new Error("Parameter \"recipientPrivateKey\" is mandatory for \"KeyTransRecipientInfo\"");
×
1358
      }
1359

1360
      const algorithmParameters = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "keyEncryptionAlgorithm");
392✔
1361

1362
      //#region RSA-OAEP case
1363
      if (algorithmParameters.name === "RSA-OAEP") {
392✔
1364
        const schema = recipientInfo.keyEncryptionAlgorithm.algorithmParams;
392✔
1365
        const rsaOAEPParams = new RSAESOAEPParams({ schema });
392✔
1366

1367
        algorithmParameters.hash = crypto.getAlgorithmByOID(rsaOAEPParams.hashAlgorithm.algorithmId);
392✔
1368
        if (("name" in algorithmParameters.hash) === false)
392!
1369
          throw new Error(`Incorrect OID for hash algorithm: ${rsaOAEPParams.hashAlgorithm.algorithmId}`);
×
1370
      }
1371
      //#endregion
1372

1373
      let privateKey: CryptoKey;
1374
      let keyCrypto: SubtleCrypto = crypto;
392✔
1375
      if (BufferSourceConverter.isBufferSource(decryptionParameters.recipientPrivateKey)) {
392✔
1376
        privateKey = await crypto.importKey(
384✔
1377
          "pkcs8",
1378
          decryptionParameters.recipientPrivateKey,
1379
          algorithmParameters,
1380
          true,
1381
          ["decrypt"]
1382
        );
1383
      } else {
1384
        privateKey = decryptionParameters.recipientPrivateKey;
8✔
1385
        if ("crypto" in decryptionParameters && decryptionParameters.crypto) {
8✔
1386
          keyCrypto = decryptionParameters.crypto.subtle;
6✔
1387
        }
1388
      }
1389

1390
      const sessionKey = await keyCrypto.decrypt(
392✔
1391
        privateKey.algorithm,
1392
        privateKey,
1393
        recipientInfo.encryptedKey.valueBlock.valueHexView as BufferSource
1394
      );
1395

1396
      //#region Get WebCrypto form of content encryption algorithm
1397
      const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
392✔
1398
      const contentEncryptionAlgorithm = crypto.getAlgorithmByOID(algorithmId, true, "contentEncryptionAlgorithm");
392✔
1399
      if (("name" in contentEncryptionAlgorithm) === false)
392!
1400
        throw new Error(`Incorrect "contentEncryptionAlgorithm": ${algorithmId}`);
×
1401
      //#endregion
1402

1403
      return crypto.importKey("raw",
392✔
1404
        sessionKey,
1405
        contentEncryptionAlgorithm,
1406
        true,
1407
        ["decrypt"]
1408
      );
1409
    };
1410

1411
    const SubKEKRecipientInfo = async (index: number) => {
754✔
1412
      //#region Initial variables
1413
      const recipientInfo = this.recipientInfos[index].value as KEKRecipientInfo; // TODO Remove `as KEKRecipientInfo`
12✔
1414
      //#endregion
1415

1416
      //#region Import KEK from pre-defined data
1417
      if (!decryptionParameters.preDefinedData)
12!
1418
        throw new Error("Parameter \"preDefinedData\" is mandatory for \"KEKRecipientInfo\"");
×
1419

1420
      //#region Get WebCrypto form of "keyEncryptionAlgorithm"
1421
      const kekAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "kekAlgorithm");
12✔
1422
      //#endregion
1423

1424
      const importedKey = await crypto.importKey("raw",
12✔
1425
        decryptionParameters.preDefinedData,
1426
        kekAlgorithm,
1427
        true,
1428
        ["unwrapKey"]); // Too specific for AES-KW
1429

1430
      //#endregion
1431
      //#region Unwrap previously exported session key
1432
      //#region Get WebCrypto form of content encryption algorithm
1433
      const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
12✔
1434
      const contentEncryptionAlgorithm = crypto.getAlgorithmByOID<any>(algorithmId, true, "contentEncryptionAlgorithm");
12✔
1435
      if (!contentEncryptionAlgorithm.name) {
12!
1436
        throw new Error(`Incorrect "contentEncryptionAlgorithm": ${algorithmId}`);
×
1437
      }
1438
      //#endregion
1439

1440
      return crypto.unwrapKey("raw",
12✔
1441
        recipientInfo.encryptedKey.valueBlock.valueHexView as BufferSource,
1442
        importedKey,
1443
        kekAlgorithm,
1444
        contentEncryptionAlgorithm,
1445
        true,
1446
        ["decrypt"]);
1447
      //#endregion
1448
    };
1449

1450
    const SubPasswordRecipientinfo = async (index: number) => {
754✔
1451
      //#region Initial variables
1452
      const recipientInfo = this.recipientInfos[index].value as PasswordRecipientinfo; // TODO Remove `as PasswordRecipientinfo`
12✔
1453
      let pbkdf2Params: PBKDF2Params;
1454
      //#endregion
1455

1456
      //#region Derive PBKDF2 key from "password" buffer
1457

1458
      if (!decryptionParameters.preDefinedData) {
12!
1459
        throw new Error("Parameter \"preDefinedData\" is mandatory for \"KEKRecipientInfo\"");
×
1460
      }
1461

1462
      if (!recipientInfo.keyDerivationAlgorithm) {
12!
1463
        throw new Error("Please append encoded \"keyDerivationAlgorithm\"");
×
1464
      }
1465

1466
      if (!recipientInfo.keyDerivationAlgorithm.algorithmParams) {
12!
1467
        throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\"");
×
1468
      }
1469

1470
      try {
12✔
1471
        pbkdf2Params = new PBKDF2Params({ schema: recipientInfo.keyDerivationAlgorithm.algorithmParams });
12✔
1472
      }
1473
      catch {
1474
        throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\"");
×
1475
      }
1476

1477
      const pbkdf2Key = await crypto.importKey("raw",
12✔
1478
        decryptionParameters.preDefinedData,
1479
        "PBKDF2",
1480
        false,
1481
        ["deriveKey"]);
1482
      //#endregion
1483
      //#region Derive key for "keyEncryptionAlgorithm"
1484
      //#region Get WebCrypto form of "keyEncryptionAlgorithm"
1485
      const kekAlgorithm = crypto.getAlgorithmByOID<any>(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "keyEncryptionAlgorithm");
12✔
1486
      //#endregion
1487

1488
      // Get HMAC hash algorithm
1489
      const hmacHashAlgorithm = pbkdf2Params.prf
12!
1490
        ? crypto.getAlgorithmByOID<any>(pbkdf2Params.prf.algorithmId, true, "prfAlgorithm").hash.name
1491
        : "SHA-1";
1492

1493
      //#region Get PBKDF2 "salt" value
1494
      const saltView = new Uint8Array(pbkdf2Params.salt.valueBlock.valueHex);
12✔
1495
      //#endregion
1496

1497
      //#region Get PBKDF2 iterations count
1498
      const iterations = pbkdf2Params.iterationCount;
12✔
1499
      //#endregion
1500

1501
      const kekKey = await crypto.deriveKey({
12✔
1502
        name: "PBKDF2",
1503
        hash: {
1504
          name: hmacHashAlgorithm
1505
        },
1506
        salt: saltView,
1507
        iterations
1508
      },
1509
        pbkdf2Key,
1510
        kekAlgorithm,
1511
        true,
1512
        ["unwrapKey"]); // Usages are too specific for KEK algorithm
1513
      //#endregion
1514
      //#region Unwrap previously exported session key
1515
      //#region Get WebCrypto form of content encryption algorithm
1516
      const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
12✔
1517
      const contentEncryptionAlgorithm = crypto.getAlgorithmByOID<any>(algorithmId, true, "contentEncryptionAlgorithm");
12✔
1518
      //#endregion
1519

1520
      return crypto.unwrapKey("raw",
12✔
1521
        recipientInfo.encryptedKey.valueBlock.valueHexView as BufferSource,
1522
        kekKey,
1523
        kekAlgorithm,
1524
        contentEncryptionAlgorithm,
1525
        true,
1526
        ["decrypt"]);
1527
      //#endregion
1528
    };
1529

1530
    //#endregion
1531

1532
    //#region Perform steps, specific to each type of session key encryption
1533
    let unwrappedKey: CryptoKey;
1534
    switch (this.recipientInfos[recipientIndex].variant) {
754!
1535
      case 1: // KeyTransRecipientInfo
1536
        unwrappedKey = await SubKeyTransRecipientInfo(recipientIndex);
392✔
1537
        break;
392✔
1538
      case 2: // KeyAgreeRecipientInfo
1539
        unwrappedKey = await SubKeyAgreeRecipientInfo(recipientIndex);
338✔
1540
        break;
338✔
1541
      case 3: // KEKRecipientInfo
1542
        unwrappedKey = await SubKEKRecipientInfo(recipientIndex);
12✔
1543
        break;
12✔
1544
      case 4: // PasswordRecipientinfo
1545
        unwrappedKey = await SubPasswordRecipientinfo(recipientIndex);
12✔
1546
        break;
12✔
1547
      default:
1548
        throw new Error(`Unknown recipient type in array with index ${recipientIndex}`);
×
1549
    }
1550
    //#endregion
1551

1552
    //#region Finally decrypt data by session key
1553
    //#region Get WebCrypto form of content encryption algorithm
1554
    const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
754✔
1555
    const contentEncryptionAlgorithm = crypto.getAlgorithmByOID(algorithmId, true, "contentEncryptionAlgorithm");
754✔
1556
    //#endregion
1557

1558
    //#region Get "initialization vector" for content encryption algorithm
1559
    // AES-GCM parameters are a GCMParameters SEQUENCE per RFC 5084 ยง3.2.
1560
    // Older pkijs output emitted a bare OCTET STRING; fall back to that
1561
    // shape so previously-produced blobs remain decryptable.
1562
    const isAesGcm = (contentEncryptionAlgorithm as any).name === "AES-GCM";
754✔
1563
    let ivBuffer: ArrayBuffer;
1564
    let gcmTagLengthBits: number | undefined;
1565
    if (isAesGcm) {
754✔
1566
      try {
374✔
1567
        const gcmParams = new GCMParams({
374✔
1568
          schema: this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams
1569
        });
1570
        ivBuffer = gcmParams.nonce;
374✔
1571
        // RFC 5084 default is 12; WebCrypto default is 16.
1572
        gcmTagLengthBits = ((gcmParams.icvLen ?? 12) * 8);
374!
1573
      } catch {
1574
        // Legacy fallback: params encoded as bare OCTET STRING.
1575
        // No explicit ICV length available; let WebCrypto use its default (128 bits).
NEW
1576
        ivBuffer = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams.valueBlock.valueHex;
×
1577
      }
1578
    } else {
1579
      ivBuffer = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams.valueBlock.valueHex;
380✔
1580
    }
1581
    const ivView = new Uint8Array(ivBuffer);
754✔
1582
    //#endregion
1583

1584
    //#region Create correct data block for decryption
1585
    if (!this.encryptedContentInfo.encryptedContent) {
754!
1586
      throw new Error("Required property `encryptedContent` is empty");
×
1587
    }
1588
    const dataBuffer = this.encryptedContentInfo.getEncryptedContent();
754✔
1589
    //#endregion
1590

1591
    const decryptAlgorithm: Algorithm & { iv: Uint8Array; tagLength?: number } = {
754✔
1592
      name: (contentEncryptionAlgorithm as any).name,
1593
      iv: ivView
1594
    };
1595
    if (isAesGcm && gcmTagLengthBits !== undefined) {
754✔
1596
      decryptAlgorithm.tagLength = gcmTagLengthBits;
374✔
1597
    }
1598
    return crypto.decrypt(decryptAlgorithm, unwrappedKey, dataBuffer);
754✔
1599
    //#endregion
1600
  }
1601

1602
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc