SignedData.ts (34557B)
1 import * as asn1js from "asn1js"; 2 import * as pvtsutils from "pvtsutils"; 3 import * as pvutils from "pvutils"; 4 import * as common from "./common"; 5 import { AlgorithmIdentifier, AlgorithmIdentifierJson } from "./AlgorithmIdentifier"; 6 import { EncapsulatedContentInfo, EncapsulatedContentInfoJson, EncapsulatedContentInfoSchema } from "./EncapsulatedContentInfo"; 7 import { Certificate, checkCA } from "./Certificate"; 8 import { CertificateRevocationList, CertificateRevocationListJson } from "./CertificateRevocationList"; 9 import { OtherRevocationInfoFormat, OtherRevocationInfoFormatJson } from "./OtherRevocationInfoFormat"; 10 import { SignerInfo, SignerInfoJson } from "./SignerInfo"; 11 import { CertificateSet, CertificateSetItem, CertificateSetItemJson } from "./CertificateSet"; 12 import { RevocationInfoChoices, RevocationInfoChoicesSchema } from "./RevocationInfoChoices"; 13 import { IssuerAndSerialNumber } from "./IssuerAndSerialNumber"; 14 import { TSTInfo } from "./TSTInfo"; 15 import { CertificateChainValidationEngine, CertificateChainValidationEngineParameters, FindIssuerCallback, FindOriginCallback } from "./CertificateChainValidationEngine"; 16 import { BasicOCSPResponse, BasicOCSPResponseJson } from "./BasicOCSPResponse"; 17 import { OtherCertificateFormat } from "./OtherCertificateFormat"; 18 import { AttributeCertificateV1 } from "./AttributeCertificateV1"; 19 import { AttributeCertificateV2 } from "./AttributeCertificateV2"; 20 import * as Schema from "./Schema"; 21 import { id_ContentType_Data, id_eContentType_TSTInfo, id_PKIX_OCSP_Basic } from "./ObjectIdentifiers"; 22 import { AsnError } from "./errors"; 23 import { PkiObject, PkiObjectParameters } from "./PkiObject"; 24 import { EMPTY_BUFFER, EMPTY_STRING } from "./constants"; 25 import { ICryptoEngine } from "./CryptoEngine/CryptoEngineInterface"; 26 27 export type SignedDataCRL = CertificateRevocationList | OtherRevocationInfoFormat; 28 export type SignedDataCRLJson = CertificateRevocationListJson | OtherRevocationInfoFormatJson; 29 30 const VERSION = "version"; 31 const DIGEST_ALGORITHMS = "digestAlgorithms"; 32 const ENCAP_CONTENT_INFO = "encapContentInfo"; 33 const CERTIFICATES = "certificates"; 34 const CRLS = "crls"; 35 const SIGNER_INFOS = "signerInfos"; 36 const OCSPS = "ocsps"; 37 const SIGNED_DATA = "SignedData"; 38 const SIGNED_DATA_VERSION = `${SIGNED_DATA}.${VERSION}`; 39 const SIGNED_DATA_DIGEST_ALGORITHMS = `${SIGNED_DATA}.${DIGEST_ALGORITHMS}`; 40 const SIGNED_DATA_ENCAP_CONTENT_INFO = `${SIGNED_DATA}.${ENCAP_CONTENT_INFO}`; 41 const SIGNED_DATA_CERTIFICATES = `${SIGNED_DATA}.${CERTIFICATES}`; 42 const SIGNED_DATA_CRLS = `${SIGNED_DATA}.${CRLS}`; 43 const SIGNED_DATA_SIGNER_INFOS = `${SIGNED_DATA}.${SIGNER_INFOS}`; 44 const CLEAR_PROPS = [ 45 SIGNED_DATA_VERSION, 46 SIGNED_DATA_DIGEST_ALGORITHMS, 47 SIGNED_DATA_ENCAP_CONTENT_INFO, 48 SIGNED_DATA_CERTIFICATES, 49 SIGNED_DATA_CRLS, 50 SIGNED_DATA_SIGNER_INFOS 51 ]; 52 53 export interface ISignedData { 54 version: number; 55 digestAlgorithms: AlgorithmIdentifier[]; 56 encapContentInfo: EncapsulatedContentInfo; 57 certificates?: CertificateSetItem[]; 58 crls?: SignedDataCRL[]; 59 ocsps?: BasicOCSPResponse[]; 60 signerInfos: SignerInfo[]; 61 } 62 63 export interface SignedDataJson { 64 version: number; 65 digestAlgorithms: AlgorithmIdentifierJson[]; 66 encapContentInfo: EncapsulatedContentInfoJson; 67 certificates?: CertificateSetItemJson[]; 68 crls?: SignedDataCRLJson[]; 69 ocsps?: BasicOCSPResponseJson[]; 70 signerInfos: SignerInfoJson[]; 71 } 72 73 export type SignedDataParameters = PkiObjectParameters & Partial<ISignedData>; 74 75 export interface SignedDataVerifyParams { 76 signer?: number; 77 data?: ArrayBuffer; 78 trustedCerts?: Certificate[]; 79 checkDate?: Date; 80 checkChain?: boolean; 81 passedWhenNotRevValues?: boolean; 82 extendedMode?: boolean; 83 findOrigin?: FindOriginCallback | null; 84 findIssuer?: FindIssuerCallback | null; 85 } 86 87 export interface SignedDataVerifyErrorParams { 88 message: string; 89 date?: Date; 90 code?: number; 91 timestampSerial?: ArrayBuffer | null; 92 signatureVerified?: boolean | null; 93 signerCertificate?: Certificate | null; 94 signerCertificateVerified?: boolean | null; 95 certificatePath?: Certificate[]; 96 } 97 98 export interface SignedDataVerifyResult { 99 message: string; 100 date?: Date; 101 code?: number; 102 timestampSerial?: ArrayBuffer | null; 103 signatureVerified?: boolean | null; 104 signerCertificate?: Certificate | null; 105 signerCertificateVerified?: boolean | null; 106 certificatePath: Certificate[]; 107 } 108 109 export class SignedDataVerifyError extends Error implements SignedDataVerifyResult { 110 111 public date: Date; 112 public code: number; 113 public signatureVerified: boolean | null; 114 public signerCertificate: Certificate | null; 115 public signerCertificateVerified: boolean | null; 116 public timestampSerial: ArrayBuffer | null; 117 public certificatePath: Certificate[]; 118 119 constructor({ 120 message, 121 code = 0, 122 date = new Date(), 123 signatureVerified = null, 124 signerCertificate = null, 125 signerCertificateVerified = null, 126 timestampSerial = null, 127 certificatePath = [], 128 }: SignedDataVerifyErrorParams) { 129 super(message); 130 this.name = "SignedDataVerifyError"; 131 132 this.date = date; 133 this.code = code; 134 this.timestampSerial = timestampSerial; 135 this.signatureVerified = signatureVerified; 136 this.signerCertificate = signerCertificate; 137 this.signerCertificateVerified = signerCertificateVerified; 138 this.certificatePath = certificatePath; 139 140 } 141 } 142 143 /** 144 * Represents the SignedData structure described in [RFC5652](https://datatracker.ietf.org/doc/html/rfc5652) 145 * 146 * @example The following example demonstrates how to create and sign CMS Signed Data 147 * ```js 148 * // Create a new CMS Signed Data 149 * const cmsSigned = new pkijs.SignedData({ 150 * encapContentInfo: new pkijs.EncapsulatedContentInfo({ 151 * eContentType: pkijs.ContentInfo.DATA,, // "data" content type 152 * eContent: new asn1js.OctetString({ valueHex: buffer }) 153 * }), 154 * signerInfos: [ 155 * new pkijs.SignerInfo({ 156 * sid: new pkijs.IssuerAndSerialNumber({ 157 * issuer: cert.issuer, 158 * serialNumber: cert.serialNumber 159 * }) 160 * }) 161 * ], 162 * // Signer certificate for chain validation 163 * certificates: [cert] 164 * }); 165 * 166 * await cmsSigned.sign(keys.privateKey, 0, "SHA-256"); 167 * 168 * // Add Signed Data to Content Info 169 * const cms = new pkijs.ContentInfo({ 170 * contentType: pkijs.ContentInfo.SIGNED_DATA,, 171 * content: cmsSigned.toSchema(true), 172 * }); 173 * 174 * // Encode CMS to ASN.1 175 * const cmsRaw = cms.toSchema().toBER(); 176 * ``` 177 * 178 * @example The following example demonstrates how to verify CMS Signed Data 179 * ```js 180 * // Parse CMS and detect it's Signed Data 181 * const cms = pkijs.ContentInfo.fromBER(cmsRaw); 182 * if (cms.contentType !== pkijs.ContentInfo.SIGNED_DATA) { 183 * throw new Error("CMS is not Signed Data"); 184 * } 185 * 186 * // Read Signed Data 187 * const signedData = new pkijs.SignedData({ schema: cms.content }); 188 * 189 * // Verify Signed Data signature 190 * const ok = await signedData.verify({ 191 * signer: 0, 192 * checkChain: true, 193 * trustedCerts: [trustedCert], 194 * }); 195 * 196 * if (!ok) { 197 * throw new Error("CMS signature is invalid") 198 * } 199 * ``` 200 */ 201 export class SignedData extends PkiObject implements ISignedData { 202 203 public static override CLASS_NAME = "SignedData"; 204 205 public static ID_DATA: typeof id_ContentType_Data = id_ContentType_Data; 206 207 public version!: number; 208 public digestAlgorithms!: AlgorithmIdentifier[]; 209 public encapContentInfo!: EncapsulatedContentInfo; 210 public certificates?: CertificateSetItem[]; 211 public crls?: SignedDataCRL[]; 212 public ocsps?: BasicOCSPResponse[]; 213 public signerInfos!: SignerInfo[]; 214 215 /** 216 * Initializes a new instance of the {@link SignedData} class 217 * @param parameters Initialization parameters 218 */ 219 constructor(parameters: SignedDataParameters = {}) { 220 super(); 221 222 this.version = pvutils.getParametersValue(parameters, VERSION, SignedData.defaultValues(VERSION)); 223 this.digestAlgorithms = pvutils.getParametersValue(parameters, DIGEST_ALGORITHMS, SignedData.defaultValues(DIGEST_ALGORITHMS)); 224 this.encapContentInfo = pvutils.getParametersValue(parameters, ENCAP_CONTENT_INFO, SignedData.defaultValues(ENCAP_CONTENT_INFO)); 225 if (CERTIFICATES in parameters) { 226 this.certificates = pvutils.getParametersValue(parameters, CERTIFICATES, SignedData.defaultValues(CERTIFICATES)); 227 } 228 if (CRLS in parameters) { 229 this.crls = pvutils.getParametersValue(parameters, CRLS, SignedData.defaultValues(CRLS)); 230 } 231 if (OCSPS in parameters) { 232 this.ocsps = pvutils.getParametersValue(parameters, OCSPS, SignedData.defaultValues(OCSPS)); 233 } 234 this.signerInfos = pvutils.getParametersValue(parameters, SIGNER_INFOS, SignedData.defaultValues(SIGNER_INFOS)); 235 236 if (parameters.schema) { 237 this.fromSchema(parameters.schema); 238 } 239 } 240 241 /** 242 * Returns default values for all class members 243 * @param memberName String name for a class member 244 * @returns Default value 245 */ 246 public static override defaultValues(memberName: typeof VERSION): number; 247 public static override defaultValues(memberName: typeof DIGEST_ALGORITHMS): AlgorithmIdentifier[]; 248 public static override defaultValues(memberName: typeof ENCAP_CONTENT_INFO): EncapsulatedContentInfo; 249 public static override defaultValues(memberName: typeof CERTIFICATES): CertificateSetItem[]; 250 public static override defaultValues(memberName: typeof CRLS): SignedDataCRL[]; 251 public static override defaultValues(memberName: typeof OCSPS): BasicOCSPResponse[]; 252 public static override defaultValues(memberName: typeof SIGNER_INFOS): SignerInfo[]; 253 public static override defaultValues(memberName: string): any { 254 switch (memberName) { 255 case VERSION: 256 return 0; 257 case DIGEST_ALGORITHMS: 258 return []; 259 case ENCAP_CONTENT_INFO: 260 return new EncapsulatedContentInfo(); 261 case CERTIFICATES: 262 return []; 263 case CRLS: 264 return []; 265 case OCSPS: 266 return []; 267 case SIGNER_INFOS: 268 return []; 269 default: 270 return super.defaultValues(memberName); 271 } 272 } 273 274 /** 275 * Compare values with default values for all class members 276 * @param memberName String name for a class member 277 * @param memberValue Value to compare with default value 278 */ 279 public static compareWithDefault(memberName: string, memberValue: any): boolean { 280 switch (memberName) { 281 case VERSION: 282 return (memberValue === SignedData.defaultValues(VERSION)); 283 case ENCAP_CONTENT_INFO: 284 return EncapsulatedContentInfo.compareWithDefault("eContentType", memberValue.eContentType) && 285 EncapsulatedContentInfo.compareWithDefault("eContent", memberValue.eContent); 286 case DIGEST_ALGORITHMS: 287 case CERTIFICATES: 288 case CRLS: 289 case OCSPS: 290 case SIGNER_INFOS: 291 return (memberValue.length === 0); 292 default: 293 return super.defaultValues(memberName); 294 } 295 } 296 297 /** 298 * @inheritdoc 299 * @asn ASN.1 schema 300 * ```asn 301 * SignedData ::= SEQUENCE { 302 * version CMSVersion, 303 * digestAlgorithms DigestAlgorithmIdentifiers, 304 * encapContentInfo EncapsulatedContentInfo, 305 * certificates [0] IMPLICIT CertificateSet OPTIONAL, 306 * crls [1] IMPLICIT RevocationInfoChoices OPTIONAL, 307 * signerInfos SignerInfos } 308 *``` 309 */ 310 public static override schema(parameters: Schema.SchemaParameters<{ 311 version?: string; 312 digestAlgorithms?: string; 313 encapContentInfo?: EncapsulatedContentInfoSchema; 314 certificates?: string; 315 crls?: RevocationInfoChoicesSchema; 316 signerInfos?: string; 317 }> = {}): Schema.SchemaType { 318 const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); 319 320 if (names.optional === undefined) { 321 names.optional = false; 322 } 323 324 return (new asn1js.Sequence({ 325 name: (names.blockName || SIGNED_DATA), 326 optional: names.optional, 327 value: [ 328 new asn1js.Integer({ name: (names.version || SIGNED_DATA_VERSION) }), 329 new asn1js.Set({ 330 value: [ 331 new asn1js.Repeated({ 332 name: (names.digestAlgorithms || SIGNED_DATA_DIGEST_ALGORITHMS), 333 value: AlgorithmIdentifier.schema() 334 }) 335 ] 336 }), 337 EncapsulatedContentInfo.schema(names.encapContentInfo || { 338 names: { 339 blockName: SIGNED_DATA_ENCAP_CONTENT_INFO 340 } 341 }), 342 new asn1js.Constructed({ 343 name: (names.certificates || SIGNED_DATA_CERTIFICATES), 344 optional: true, 345 idBlock: { 346 tagClass: 3, // CONTEXT-SPECIFIC 347 tagNumber: 0 // [0] 348 }, 349 value: CertificateSet.schema().valueBlock.value 350 }), // IMPLICIT CertificateSet 351 new asn1js.Constructed({ 352 optional: true, 353 idBlock: { 354 tagClass: 3, // CONTEXT-SPECIFIC 355 tagNumber: 1 // [1] 356 }, 357 value: RevocationInfoChoices.schema(names.crls || { 358 names: { 359 crls: SIGNED_DATA_CRLS 360 } 361 }).valueBlock.value 362 }), // IMPLICIT RevocationInfoChoices 363 new asn1js.Set({ 364 value: [ 365 new asn1js.Repeated({ 366 name: (names.signerInfos || SIGNED_DATA_SIGNER_INFOS), 367 value: SignerInfo.schema() 368 }) 369 ] 370 }) 371 ] 372 })); 373 } 374 375 public fromSchema(schema: Schema.SchemaType): void { 376 // Clear input data first 377 pvutils.clearProps(schema, CLEAR_PROPS); 378 379 //#region Check the schema is valid 380 const asn1 = asn1js.compareSchema(schema, 381 schema, 382 SignedData.schema() 383 ); 384 AsnError.assertSchema(asn1, this.className); 385 //#endregion 386 387 //#region Get internal properties from parsed schema 388 this.version = asn1.result[SIGNED_DATA_VERSION].valueBlock.valueDec; 389 390 if (SIGNED_DATA_DIGEST_ALGORITHMS in asn1.result) // Could be empty SET of digest algorithms 391 this.digestAlgorithms = Array.from(asn1.result[SIGNED_DATA_DIGEST_ALGORITHMS], algorithm => new AlgorithmIdentifier({ schema: algorithm })); 392 393 this.encapContentInfo = new EncapsulatedContentInfo({ schema: asn1.result[SIGNED_DATA_ENCAP_CONTENT_INFO] }); 394 395 if (SIGNED_DATA_CERTIFICATES in asn1.result) { 396 const certificateSet = new CertificateSet({ 397 schema: new asn1js.Set({ 398 value: asn1.result[SIGNED_DATA_CERTIFICATES].valueBlock.value 399 }) 400 }); 401 this.certificates = certificateSet.certificates.slice(0); // Copy all just for making comfortable access 402 } 403 404 if (SIGNED_DATA_CRLS in asn1.result) { 405 this.crls = Array.from(asn1.result[SIGNED_DATA_CRLS], (crl: Schema.SchemaType) => { 406 if (crl.idBlock.tagClass === 1) 407 return new CertificateRevocationList({ schema: crl }); 408 409 //#region Create SEQUENCE from [1] 410 crl.idBlock.tagClass = 1; // UNIVERSAL 411 crl.idBlock.tagNumber = 16; // SEQUENCE 412 //#endregion 413 414 return new OtherRevocationInfoFormat({ schema: crl }); 415 }); 416 } 417 418 if (SIGNED_DATA_SIGNER_INFOS in asn1.result) // Could be empty SET SignerInfos 419 this.signerInfos = Array.from(asn1.result[SIGNED_DATA_SIGNER_INFOS], signerInfoSchema => new SignerInfo({ schema: signerInfoSchema })); 420 //#endregion 421 } 422 423 public toSchema(encodeFlag = false): Schema.SchemaType { 424 //#region Create array for output sequence 425 const outputArray = []; 426 427 // IF ((certificates is present) AND 428 // (any certificates with a type of other are present)) OR 429 // ((crls is present) AND 430 // (any crls with a type of other are present)) 431 // THEN version MUST be 5 432 // ELSE 433 // IF (certificates is present) AND 434 // (any version 2 attribute certificates are present) 435 // THEN version MUST be 4 436 // ELSE 437 // IF ((certificates is present) AND 438 // (any version 1 attribute certificates are present)) OR 439 // (any SignerInfo structures are version 3) OR 440 // (encapContentInfo eContentType is other than id-data) 441 // THEN version MUST be 3 442 // ELSE version MUST be 1 443 if ((this.certificates && this.certificates.length && this.certificates.some(o => o instanceof OtherCertificateFormat)) 444 || (this.crls && this.crls.length && this.crls.some(o => o instanceof OtherRevocationInfoFormat))) { 445 this.version = 5; 446 } else if (this.certificates && this.certificates.length && this.certificates.some(o => o instanceof AttributeCertificateV2)) { 447 this.version = 4; 448 } else if ((this.certificates && this.certificates.length && this.certificates.some(o => o instanceof AttributeCertificateV1)) 449 || this.signerInfos.some(o => o.version === 3) 450 || this.encapContentInfo.eContentType !== SignedData.ID_DATA) { 451 this.version = 3; 452 } else { 453 this.version = 1; 454 } 455 456 outputArray.push(new asn1js.Integer({ value: this.version })); 457 458 //#region Create array of digest algorithms 459 outputArray.push(new asn1js.Set({ 460 value: Array.from(this.digestAlgorithms, algorithm => algorithm.toSchema()) 461 })); 462 //#endregion 463 464 outputArray.push(this.encapContentInfo.toSchema()); 465 466 if (this.certificates) { 467 const certificateSet = new CertificateSet({ certificates: this.certificates }); 468 const certificateSetSchema = certificateSet.toSchema(); 469 470 outputArray.push(new asn1js.Constructed({ 471 idBlock: { 472 tagClass: 3, 473 tagNumber: 0 474 }, 475 value: certificateSetSchema.valueBlock.value 476 })); 477 } 478 479 if (this.crls) { 480 outputArray.push(new asn1js.Constructed({ 481 idBlock: { 482 tagClass: 3, // CONTEXT-SPECIFIC 483 tagNumber: 1 // [1] 484 }, 485 value: Array.from(this.crls, crl => { 486 if (crl instanceof OtherRevocationInfoFormat) { 487 const crlSchema = crl.toSchema(); 488 489 crlSchema.idBlock.tagClass = 3; 490 crlSchema.idBlock.tagNumber = 1; 491 492 return crlSchema; 493 } 494 495 return crl.toSchema(encodeFlag); 496 }) 497 })); 498 } 499 500 //#region Create array of signer infos 501 outputArray.push(new asn1js.Set({ 502 value: Array.from(this.signerInfos, signerInfo => signerInfo.toSchema()) 503 })); 504 //#endregion 505 //#endregion 506 507 //#region Construct and return new ASN.1 schema for this object 508 return (new asn1js.Sequence({ 509 value: outputArray 510 })); 511 //#endregion 512 } 513 514 public toJSON(): SignedDataJson { 515 const res: SignedDataJson = { 516 version: this.version, 517 digestAlgorithms: Array.from(this.digestAlgorithms, algorithm => algorithm.toJSON()), 518 encapContentInfo: this.encapContentInfo.toJSON(), 519 signerInfos: Array.from(this.signerInfos, signerInfo => signerInfo.toJSON()), 520 }; 521 522 if (this.certificates) { 523 res.certificates = Array.from(this.certificates, certificate => certificate.toJSON()); 524 } 525 526 if (this.crls) { 527 res.crls = Array.from(this.crls, crl => crl.toJSON()); 528 } 529 530 531 return res; 532 } 533 534 public verify(params?: SignedDataVerifyParams & { extendedMode?: false; }, crypto?: ICryptoEngine): Promise<boolean>; 535 public verify(params: SignedDataVerifyParams & { extendedMode: true; }, crypto?: ICryptoEngine): Promise<SignedDataVerifyResult>; 536 public async verify({ 537 signer = (-1), 538 data = (EMPTY_BUFFER), 539 trustedCerts = [], 540 checkDate = (new Date()), 541 checkChain = false, 542 passedWhenNotRevValues = false, 543 extendedMode = false, 544 findOrigin = null, 545 findIssuer = null 546 }: SignedDataVerifyParams = {}, crypto = common.getCrypto(true)): Promise<boolean | SignedDataVerifyResult> { 547 let signerCert: Certificate | null = null; 548 let timestampSerial: ArrayBuffer | null = null; 549 try { 550 //#region Global variables 551 let messageDigestValue = EMPTY_BUFFER; 552 let shaAlgorithm = EMPTY_STRING; 553 let certificatePath: Certificate[] = []; 554 //#endregion 555 556 //#region Get a signer number 557 const signerInfo = this.signerInfos[signer]; 558 if (!signerInfo) { 559 throw new SignedDataVerifyError({ 560 date: checkDate, 561 code: 1, 562 message: "Unable to get signer by supplied index", 563 }); 564 } 565 //#endregion 566 567 //#region Check that certificates field was included in signed data 568 if (!this.certificates) { 569 throw new SignedDataVerifyError({ 570 date: checkDate, 571 code: 2, 572 message: "No certificates attached to this signed data", 573 }); 574 } 575 //#endregion 576 577 //#region Find a certificate for specified signer 578 579 if (signerInfo.sid instanceof IssuerAndSerialNumber) { 580 for (const certificate of this.certificates) { 581 if (!(certificate instanceof Certificate)) 582 continue; 583 584 if ((certificate.issuer.isEqual(signerInfo.sid.issuer)) && 585 (certificate.serialNumber.isEqual(signerInfo.sid.serialNumber))) { 586 signerCert = certificate; 587 break; 588 } 589 } 590 } else { // Find by SubjectKeyIdentifier 591 const sid = signerInfo.sid; 592 const keyId = sid.idBlock.isConstructed 593 ? sid.valueBlock.value[0].valueBlock.valueHex // EXPLICIT OCTET STRING 594 : sid.valueBlock.valueHex; // IMPLICIT OCTET STRING 595 596 for (const certificate of this.certificates) { 597 if (!(certificate instanceof Certificate)) { 598 continue; 599 } 600 601 const digest = await crypto.digest({ name: "sha-1" }, certificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHexView as BufferSource); 602 if (pvutils.isEqualBuffer(digest, keyId)) { 603 signerCert = certificate; 604 break; 605 } 606 } 607 } 608 609 if (!signerCert) { 610 throw new SignedDataVerifyError({ 611 date: checkDate, 612 code: 3, 613 message: "Unable to find signer certificate", 614 }); 615 } 616 //#endregion 617 618 //#region Verify internal digest in case of "tSTInfo" content type 619 if (this.encapContentInfo.eContentType === id_eContentType_TSTInfo) { 620 //#region Check "eContent" presence 621 if (!this.encapContentInfo.eContent) { 622 throw new SignedDataVerifyError({ 623 date: checkDate, 624 code: 15, 625 message: "Error during verification: TSTInfo eContent is empty", 626 signatureVerified: null, 627 signerCertificate: signerCert, 628 timestampSerial, 629 signerCertificateVerified: true 630 }); 631 } 632 //#endregion 633 634 //#region Initialize TST_INFO value 635 let tstInfo: TSTInfo; 636 637 try { 638 tstInfo = TSTInfo.fromBER(this.encapContentInfo.eContent.valueBlock.valueHexView as BufferSource); 639 } 640 catch { 641 throw new SignedDataVerifyError({ 642 date: checkDate, 643 code: 15, 644 message: "Error during verification: TSTInfo wrong ASN.1 schema ", 645 signatureVerified: null, 646 signerCertificate: signerCert, 647 timestampSerial, 648 signerCertificateVerified: true 649 }); 650 } 651 //#endregion 652 653 //#region Change "checkDate" and append "timestampSerial" 654 checkDate = tstInfo.genTime; 655 timestampSerial = tstInfo.serialNumber.valueBlock.valueHexView.slice().buffer; 656 //#endregion 657 658 //#region Check that we do have detached data content 659 if (data.byteLength === 0) { 660 throw new SignedDataVerifyError({ 661 date: checkDate, 662 code: 4, 663 message: "Missed detached data input array", 664 }); 665 } 666 //#endregion 667 668 if (!(await tstInfo.verify({ data }, crypto))) { 669 throw new SignedDataVerifyError({ 670 date: checkDate, 671 code: 15, 672 message: "Error during verification: TSTInfo verification is failed", 673 signatureVerified: false, 674 signerCertificate: signerCert, 675 timestampSerial, 676 signerCertificateVerified: true 677 }); 678 } 679 } 680 681 //#endregion 682 683 if (checkChain) { 684 const certs = this.certificates.filter(certificate => (certificate instanceof Certificate && !!checkCA(certificate, signerCert))) as Certificate[]; 685 const chainParams: CertificateChainValidationEngineParameters = { 686 checkDate, 687 certs, 688 trustedCerts, 689 }; 690 691 if (findIssuer) { 692 chainParams.findIssuer = findIssuer; 693 } 694 if (findOrigin) { 695 chainParams.findOrigin = findOrigin; 696 } 697 698 const chainEngine = new CertificateChainValidationEngine(chainParams); 699 chainEngine.certs.push(signerCert); 700 701 if (this.crls) { 702 for (const crl of this.crls) { 703 if ("thisUpdate" in crl) 704 chainEngine.crls.push(crl); 705 else // Assumed "revocation value" has "OtherRevocationInfoFormat" 706 { 707 if (crl.otherRevInfoFormat === id_PKIX_OCSP_Basic) // Basic OCSP response 708 chainEngine.ocsps.push(new BasicOCSPResponse({ schema: crl.otherRevInfo })); 709 } 710 } 711 } 712 713 if (this.ocsps) { 714 chainEngine.ocsps.push(...(this.ocsps)); 715 } 716 717 const verificationResult = await chainEngine.verify({ passedWhenNotRevValues }, crypto) 718 .catch(e => { 719 throw new SignedDataVerifyError({ 720 date: checkDate, 721 code: 5, 722 message: `Validation of signer's certificate failed with error: ${((e instanceof Object) ? e.resultMessage : e)}`, 723 signerCertificate: signerCert, 724 signerCertificateVerified: false 725 }); 726 }); 727 if (verificationResult.certificatePath) { 728 certificatePath = verificationResult.certificatePath; 729 } 730 731 if (!verificationResult.result) 732 throw new SignedDataVerifyError({ 733 date: checkDate, 734 code: 5, 735 message: `Validation of signer's certificate failed: ${verificationResult.resultMessage}`, 736 signerCertificate: signerCert, 737 signerCertificateVerified: false 738 }); 739 } 740 //#endregion 741 742 //#region Find signer's hashing algorithm 743 744 const signerInfoHashAlgorithm = crypto.getAlgorithmByOID(signerInfo.digestAlgorithm.algorithmId); 745 if (!("name" in signerInfoHashAlgorithm)) { 746 throw new SignedDataVerifyError({ 747 date: checkDate, 748 code: 7, 749 message: `Unsupported signature algorithm: ${signerInfo.digestAlgorithm.algorithmId}`, 750 signerCertificate: signerCert, 751 signerCertificateVerified: true 752 }); 753 } 754 755 shaAlgorithm = signerInfoHashAlgorithm.name; 756 //#endregion 757 758 //#region Create correct data block for verification 759 760 const eContent = this.encapContentInfo.eContent; 761 if (eContent) // Attached data 762 { 763 if ((eContent.idBlock.tagClass === 1) && 764 (eContent.idBlock.tagNumber === 4)) { 765 data = eContent.getValue(); 766 } 767 else 768 data = eContent.valueBlock.valueBeforeDecodeView.slice().buffer; 769 } 770 else // Detached data 771 { 772 if (data.byteLength === 0) // Check that "data" already provided by function parameter 773 { 774 throw new SignedDataVerifyError({ 775 date: checkDate, 776 code: 8, 777 message: "Missed detached data input array", 778 signerCertificate: signerCert, 779 signerCertificateVerified: true 780 }); 781 } 782 } 783 784 if (signerInfo.signedAttrs) { 785 //#region Check mandatory attributes 786 let foundContentType = false; 787 let foundMessageDigest = false; 788 789 for (const attribute of signerInfo.signedAttrs.attributes) { 790 //#region Check that "content-type" attribute exists 791 if (attribute.type === "1.2.840.113549.1.9.3") 792 foundContentType = true; 793 //#endregion 794 795 //#region Check that "message-digest" attribute exists 796 if (attribute.type === "1.2.840.113549.1.9.4") { 797 foundMessageDigest = true; 798 messageDigestValue = attribute.values[0].valueBlock.valueHex; 799 } 800 //#endregion 801 802 //#region Speed-up searching 803 if (foundContentType && foundMessageDigest) 804 break; 805 //#endregion 806 } 807 808 if (foundContentType === false) { 809 throw new SignedDataVerifyError({ 810 date: checkDate, 811 code: 9, 812 message: "Attribute \"content-type\" is a mandatory attribute for \"signed attributes\"", 813 signerCertificate: signerCert, 814 signerCertificateVerified: true 815 }); 816 } 817 818 if (foundMessageDigest === false) { 819 throw new SignedDataVerifyError({ 820 date: checkDate, 821 code: 10, 822 message: "Attribute \"message-digest\" is a mandatory attribute for \"signed attributes\"", 823 signatureVerified: null, 824 signerCertificate: signerCert, 825 signerCertificateVerified: true 826 }); 827 } 828 //#endregion 829 } 830 //#endregion 831 832 //#region Verify "message-digest" attribute in case of "signedAttrs" 833 if (signerInfo.signedAttrs) { 834 const messageDigest = await crypto.digest(shaAlgorithm, new Uint8Array(data)); 835 if (!pvutils.isEqualBuffer(messageDigest, messageDigestValue)) { 836 throw new SignedDataVerifyError({ 837 date: checkDate, 838 code: 15, 839 message: "Error during verification: Message digest doesn't match", 840 signatureVerified: null, 841 signerCertificate: signerCert, 842 timestampSerial, 843 signerCertificateVerified: true 844 }); 845 } 846 data = signerInfo.signedAttrs.encodedValue; 847 } 848 //#endregion 849 850 // This adjustment is specifically for cases where the signature algorithm is rsaEncryption. 851 // In such cases, we rely on the hash mechanism defined in signerInfo.digestAlgorithm for verification. 852 const verifyResult = signerInfo.signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.1" 853 ? await crypto.verifyWithPublicKey(data, signerInfo.signature, signerCert.subjectPublicKeyInfo, signerInfo.signatureAlgorithm, shaAlgorithm) 854 : await crypto.verifyWithPublicKey(data, signerInfo.signature, signerCert.subjectPublicKeyInfo, signerInfo.signatureAlgorithm); 855 856 //#region Make a final result 857 858 if (extendedMode) { 859 return { 860 date: checkDate, 861 code: 14, 862 message: EMPTY_STRING, 863 signatureVerified: verifyResult, 864 signerCertificate: signerCert, 865 timestampSerial, 866 signerCertificateVerified: true, 867 certificatePath 868 }; 869 } else { 870 return verifyResult; 871 } 872 } catch (e) { 873 if (e instanceof SignedDataVerifyError) { 874 throw e; 875 } 876 throw new SignedDataVerifyError({ 877 date: checkDate, 878 code: 15, 879 message: `Error during verification: ${e instanceof Error ? e.message : e}`, 880 signatureVerified: null, 881 signerCertificate: signerCert, 882 timestampSerial, 883 signerCertificateVerified: true 884 }); 885 } 886 } 887 888 /** 889 * Signing current SignedData 890 * @param privateKey Private key for "subjectPublicKeyInfo" structure 891 * @param signerIndex Index number (starting from 0) of signer index to make signature for 892 * @param hashAlgorithm Hashing algorithm. Default SHA-1 893 * @param data Detached data 894 * @param crypto Crypto engine 895 */ 896 public async sign(privateKey: CryptoKey, signerIndex: number, hashAlgorithm = "SHA-1", data: BufferSource = (EMPTY_BUFFER), crypto = common.getCrypto(true)): Promise<void> { 897 //#region Initial checking 898 if (!privateKey) 899 throw new Error("Need to provide a private key for signing"); 900 901 const signerInfo = this.signerInfos[signerIndex]; 902 if (!signerInfo) { 903 throw new RangeError("SignerInfo index is out of range"); 904 } 905 906 //#endregion 907 908 //#region Adjust hashAlgorithm based on privateKey if signedAttrs are missing 909 if (!signerInfo.signedAttrs?.attributes.length && "hash" in privateKey.algorithm && "hash" in privateKey.algorithm && privateKey.algorithm.hash) { 910 hashAlgorithm = (privateKey.algorithm.hash as Algorithm).name; 911 } 912 const hashAlgorithmOID = crypto.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm"); 913 //#endregion 914 915 //#region Append information about hash algorithm 916 if ((this.digestAlgorithms.filter(algorithm => algorithm.algorithmId === hashAlgorithmOID)).length === 0) { 917 this.digestAlgorithms.push(new AlgorithmIdentifier({ 918 algorithmId: hashAlgorithmOID, 919 algorithmParams: new asn1js.Null() 920 })); 921 } 922 923 signerInfo.digestAlgorithm = new AlgorithmIdentifier({ 924 algorithmId: hashAlgorithmOID, 925 algorithmParams: new asn1js.Null() 926 }); 927 //#endregion 928 929 //#region Get a "default parameters" for current algorithm and set correct signature algorithm 930 const signatureParams = await crypto.getSignatureParameters(privateKey, hashAlgorithm); 931 const parameters = signatureParams.parameters; 932 signerInfo.signatureAlgorithm = signatureParams.signatureAlgorithm; 933 //#endregion 934 935 //#region Create TBS data for signing 936 if (signerInfo.signedAttrs) { 937 if (signerInfo.signedAttrs.encodedValue.byteLength !== 0) 938 data = signerInfo.signedAttrs.encodedValue; 939 else { 940 data = signerInfo.signedAttrs.toSchema().toBER(); 941 942 //#region Change type from "[0]" to "SET" accordingly to standard 943 const view = pvtsutils.BufferSourceConverter.toUint8Array(data); 944 view[0] = 0x31; 945 //#endregion 946 } 947 } 948 else { 949 const eContent = this.encapContentInfo.eContent; 950 if (eContent) // Attached data 951 { 952 if ((eContent.idBlock.tagClass === 1) && 953 (eContent.idBlock.tagNumber === 4)) { 954 data = eContent.getValue(); 955 } 956 else 957 data = eContent.valueBlock.valueBeforeDecodeView.slice().buffer; 958 } 959 else // Detached data 960 { 961 if (data.byteLength === 0) // Check that "data" already provided by function parameter 962 throw new Error("Missed detached data input array"); 963 } 964 } 965 //#endregion 966 967 //#region Signing TBS data on provided private key 968 const signature = await crypto.signWithPrivateKey(data, privateKey, parameters as any); 969 signerInfo.signature = new asn1js.OctetString({ valueHex: signature }); 970 //#endregion 971 } 972 973 }