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 }