CertificateChainValidationEngine.ts (63941B)
1 import * as asn1js from "asn1js"; 2 import * as pvtsutils from "pvtsutils"; 3 import * as pvutils from "pvutils"; 4 import { AuthorityKeyIdentifier } from "./AuthorityKeyIdentifier"; 5 import { BasicOCSPResponse } from "./BasicOCSPResponse"; 6 import { Certificate } from "./Certificate"; 7 import { CertificateRevocationList } from "./CertificateRevocationList"; 8 import * as common from "./common"; 9 import * as Helpers from "./Helpers"; 10 import { GeneralName } from "./GeneralName"; 11 import { id_AnyPolicy, id_AuthorityInfoAccess, id_AuthorityKeyIdentifier, id_BasicConstraints, id_CertificatePolicies, id_CRLDistributionPoints, id_FreshestCRL, id_InhibitAnyPolicy, id_KeyUsage, id_NameConstraints, id_PolicyConstraints, id_PolicyMappings, id_SubjectAltName, id_SubjectKeyIdentifier } from "./ObjectIdentifiers"; 12 import { RelativeDistinguishedNames } from "./RelativeDistinguishedNames"; 13 import { GeneralSubtree } from "./GeneralSubtree"; 14 import { EMPTY_STRING } from "./constants"; 15 16 const TRUSTED_CERTS = "trustedCerts"; 17 const CERTS = "certs"; 18 const CRLS = "crls"; 19 const OCSPS = "ocsps"; 20 const CHECK_DATE = "checkDate"; 21 const FIND_ORIGIN = "findOrigin"; 22 const FIND_ISSUER = "findIssuer"; 23 24 25 export enum ChainValidationCode { 26 unknown = -1, 27 success = 0, 28 noRevocation = 11, 29 noPath = 60, 30 noValidPath = 97, 31 } 32 33 export class ChainValidationError extends Error { 34 35 public static readonly NAME = "ChainValidationError"; 36 37 public code: ChainValidationCode; 38 39 constructor(code: ChainValidationCode, message: string) { 40 super(message); 41 42 this.name = ChainValidationError.NAME; 43 this.code = code; 44 this.message = message; 45 } 46 47 } 48 49 export interface CertificateChainValidationEngineVerifyResult { 50 result: boolean; 51 resultCode: number; 52 resultMessage: string; 53 error?: Error | ChainValidationError; 54 authConstrPolicies?: string[]; 55 userConstrPolicies?: string[]; 56 explicitPolicyIndicator?: boolean; 57 policyMappings?: string[]; 58 certificatePath?: Certificate[]; 59 } 60 61 export type FindOriginCallback = (certificate: Certificate, validationEngine: CertificateChainValidationEngine) => string; 62 export type FindIssuerCallback = (certificate: Certificate, validationEngine: CertificateChainValidationEngine, crypto?: common.ICryptoEngine) => Promise<Certificate[]>; 63 64 export interface CertificateChainValidationEngineParameters { 65 trustedCerts?: Certificate[]; 66 certs?: Certificate[]; 67 crls?: CertificateRevocationList[]; 68 ocsps?: BasicOCSPResponse[]; 69 checkDate?: Date; 70 findOrigin?: FindOriginCallback; 71 findIssuer?: FindIssuerCallback; 72 } 73 interface CrlAndCertificate { 74 crl: CertificateRevocationList; 75 certificate: Certificate; 76 } 77 78 interface FindCrlResult { 79 status: number; 80 statusMessage: string; 81 result?: CrlAndCertificate[]; 82 } 83 84 export interface CertificateChainValidationEngineVerifyParams { 85 initialPolicySet?: string[]; 86 initialExplicitPolicy?: boolean; 87 initialPolicyMappingInhibit?: boolean; 88 initialInhibitPolicy?: boolean; 89 initialPermittedSubtreesSet?: GeneralSubtree[]; 90 initialExcludedSubtreesSet?: GeneralSubtree[]; 91 initialRequiredNameForms?: GeneralSubtree[]; 92 passedWhenNotRevValues?: boolean; 93 } 94 95 /** 96 * Returns `true` if the certificate is in the trusted list, otherwise `false` 97 * @param cert A certificate that is expected to be in the trusted list 98 * @param trustedList List of trusted certificates 99 * @returns 100 */ 101 function isTrusted(cert: Certificate, trustedList: Certificate[]): boolean { 102 for (let i = 0; i < trustedList.length; i++) { 103 if (pvtsutils.BufferSourceConverter.isEqual(cert.tbsView, trustedList[i].tbsView)) { 104 return true; 105 } 106 } 107 108 return false; 109 } 110 111 /** 112 * Represents a chain-building engine for {@link Certificate} certificates. 113 * 114 * @example 115 * ```js The following example demonstrates how to verify certificate chain 116 * const rootCa = pkijs.Certificate.fromBER(certRaw1); 117 * const intermediateCa = pkijs.Certificate.fromBER(certRaw2); 118 * const leafCert = pkijs.Certificate.fromBER(certRaw3); 119 * const crl1 = pkijs.CertificateRevocationList.fromBER(crlRaw1); 120 * const ocsp1 = pkijs.BasicOCSPResponse.fromBER(ocspRaw1); 121 * 122 * const chainEngine = new pkijs.CertificateChainValidationEngine({ 123 * certs: [rootCa, intermediateCa, leafCert], 124 * crls: [crl1], 125 * ocsps: [ocsp1], 126 * checkDate: new Date("2015-07-13"), // optional 127 * trustedCerts: [rootCa], 128 * }); 129 * 130 * const chain = await chainEngine.verify(); 131 * ``` 132 */ 133 export class CertificateChainValidationEngine { 134 135 /** 136 * Array of pre-defined trusted (by user) certificates 137 */ 138 public trustedCerts: Certificate[]; 139 /** 140 * Array with certificate chain. Could be only one end-user certificate in there! 141 */ 142 public certs: Certificate[]; 143 /** 144 * Array of all CRLs for all certificates from certificate chain 145 */ 146 public crls: CertificateRevocationList[]; 147 /** 148 * Array of all OCSP responses 149 */ 150 public ocsps: BasicOCSPResponse[]; 151 /** 152 * The date at which the check would be 153 */ 154 public checkDate: Date; 155 /** 156 * The date at which the check would be 157 */ 158 public findOrigin: FindOriginCallback; 159 /** 160 * The date at which the check would be 161 */ 162 public findIssuer: FindIssuerCallback; 163 164 /** 165 * Constructor for CertificateChainValidationEngine class 166 * @param parameters 167 */ 168 constructor(parameters: CertificateChainValidationEngineParameters = {}) { 169 //#region Internal properties of the object 170 this.trustedCerts = pvutils.getParametersValue(parameters, TRUSTED_CERTS, this.defaultValues(TRUSTED_CERTS)); 171 this.certs = pvutils.getParametersValue(parameters, CERTS, this.defaultValues(CERTS)); 172 this.crls = pvutils.getParametersValue(parameters, CRLS, this.defaultValues(CRLS)); 173 this.ocsps = pvutils.getParametersValue(parameters, OCSPS, this.defaultValues(OCSPS)); 174 this.checkDate = pvutils.getParametersValue(parameters, CHECK_DATE, this.defaultValues(CHECK_DATE)); 175 this.findOrigin = pvutils.getParametersValue(parameters, FIND_ORIGIN, this.defaultValues(FIND_ORIGIN)); 176 this.findIssuer = pvutils.getParametersValue(parameters, FIND_ISSUER, this.defaultValues(FIND_ISSUER)); 177 //#endregion 178 } 179 180 public static defaultFindOrigin(certificate: Certificate, validationEngine: CertificateChainValidationEngine): string { 181 //#region Firstly encode TBS for certificate 182 if (certificate.tbsView.byteLength === 0) { 183 certificate.tbsView = new Uint8Array(certificate.encodeTBS().toBER()); 184 } 185 //#endregion 186 187 //#region Search in Intermediate Certificates 188 for (const localCert of validationEngine.certs) { 189 //#region Firstly encode TBS for certificate 190 if (localCert.tbsView.byteLength === 0) { 191 localCert.tbsView = new Uint8Array(localCert.encodeTBS().toBER()); 192 } 193 //#endregion 194 195 if (pvtsutils.BufferSourceConverter.isEqual(certificate.tbsView, localCert.tbsView)) 196 return "Intermediate Certificates"; 197 } 198 //#endregion 199 200 //#region Search in Trusted Certificates 201 for (const trustedCert of validationEngine.trustedCerts) { 202 //#region Firstly encode TBS for certificate 203 if (trustedCert.tbsView.byteLength === 0) 204 trustedCert.tbsView = new Uint8Array(trustedCert.encodeTBS().toBER()); 205 //#endregion 206 207 if (pvtsutils.BufferSourceConverter.isEqual(certificate.tbsView, trustedCert.tbsView)) 208 return "Trusted Certificates"; 209 } 210 //#endregion 211 212 return "Unknown"; 213 } 214 215 public async defaultFindIssuer(certificate: Certificate, validationEngine: CertificateChainValidationEngine, crypto = common.getCrypto(true)): Promise<Certificate[]> { 216 //#region Initial variables 217 const result: Certificate[] = []; 218 219 let keyIdentifier: asn1js.OctetString | null = null; 220 let authorityCertIssuer: GeneralName[] | null = null; 221 let authorityCertSerialNumber: asn1js.Integer | null = null; 222 //#endregion 223 224 //#region Speed-up searching in case of self-signed certificates 225 if (certificate.subject.isEqual(certificate.issuer)) { 226 try { 227 const verificationResult = await certificate.verify(undefined, crypto); 228 if (verificationResult) { 229 return [certificate]; 230 } 231 } 232 catch { 233 // nothing 234 } 235 } 236 //#endregion 237 238 //#region Find values to speed-up search 239 if (certificate.extensions) { 240 for (const extension of certificate.extensions) { 241 if (extension.extnID === id_AuthorityKeyIdentifier && extension.parsedValue instanceof AuthorityKeyIdentifier) { 242 if (extension.parsedValue.keyIdentifier) { 243 keyIdentifier = extension.parsedValue.keyIdentifier; 244 } else { 245 if (extension.parsedValue.authorityCertIssuer) { 246 authorityCertIssuer = extension.parsedValue.authorityCertIssuer; 247 } 248 if (extension.parsedValue.authorityCertSerialNumber) { 249 authorityCertSerialNumber = extension.parsedValue.authorityCertSerialNumber; 250 } 251 } 252 253 break; 254 } 255 } 256 } 257 //#endregion 258 259 // Aux function 260 function checkCertificate(possibleIssuer: Certificate): void { 261 //#region Firstly search for appropriate extensions 262 if (keyIdentifier !== null) { 263 if (possibleIssuer.extensions) { 264 let extensionFound = false; 265 266 for (const extension of possibleIssuer.extensions) { 267 if (extension.extnID === id_SubjectKeyIdentifier && extension.parsedValue) { 268 extensionFound = true; 269 270 if (pvtsutils.BufferSourceConverter.isEqual(extension.parsedValue.valueBlock.valueHex, keyIdentifier.valueBlock.valueHexView)) { 271 result.push(possibleIssuer); 272 } 273 274 break; 275 } 276 } 277 278 if (extensionFound) { 279 return; 280 } 281 } 282 } 283 //#endregion 284 285 //#region Now search for authorityCertSerialNumber 286 let authorityCertSerialNumberEqual = false; 287 288 if (authorityCertSerialNumber !== null) 289 authorityCertSerialNumberEqual = possibleIssuer.serialNumber.isEqual(authorityCertSerialNumber); 290 //#endregion 291 292 //#region And at least search for Issuer data 293 if (authorityCertIssuer !== null) { 294 if (possibleIssuer.subject.isEqual(authorityCertIssuer)) { 295 if (authorityCertSerialNumberEqual) 296 result.push(possibleIssuer); 297 } 298 } 299 else { 300 if (certificate.issuer.isEqual(possibleIssuer.subject)) 301 result.push(possibleIssuer); 302 } 303 //#endregion 304 } 305 306 // Search in Trusted Certificates 307 for (const trustedCert of validationEngine.trustedCerts) { 308 checkCertificate(trustedCert); 309 } 310 311 // Search in Intermediate Certificates 312 for (const intermediateCert of validationEngine.certs) { 313 checkCertificate(intermediateCert); 314 } 315 316 // Now perform certificate verification checking 317 for (let i = result.length - 1; i >= 0; i--) { 318 try { 319 const verificationResult = await certificate.verify(result[i], crypto); 320 if (verificationResult === false) 321 result.splice(i, 1); 322 } 323 catch { 324 result.splice(i, 1); // Something wrong, remove the certificate 325 } 326 } 327 328 return result; 329 } 330 331 /** 332 * Returns default values for all class members 333 * @param memberName String name for a class member 334 * @returns Default value 335 */ 336 public defaultValues(memberName: typeof TRUSTED_CERTS): Certificate[]; 337 public defaultValues(memberName: typeof CERTS): Certificate[]; 338 public defaultValues(memberName: typeof CRLS): CertificateRevocationList[]; 339 public defaultValues(memberName: typeof OCSPS): BasicOCSPResponse[]; 340 public defaultValues(memberName: typeof CHECK_DATE): Date; 341 public defaultValues(memberName: typeof FIND_ORIGIN): FindOriginCallback; 342 public defaultValues(memberName: typeof FIND_ISSUER): FindIssuerCallback; 343 public defaultValues(memberName: string): any { 344 switch (memberName) { 345 case TRUSTED_CERTS: 346 return []; 347 case CERTS: 348 return []; 349 case CRLS: 350 return []; 351 case OCSPS: 352 return []; 353 case CHECK_DATE: 354 return new Date(); 355 case FIND_ORIGIN: 356 return CertificateChainValidationEngine.defaultFindOrigin; 357 case FIND_ISSUER: 358 return this.defaultFindIssuer; 359 default: 360 throw new Error(`Invalid member name for CertificateChainValidationEngine class: ${memberName}`); 361 } 362 } 363 364 public async sort(passedWhenNotRevValues = false, crypto = common.getCrypto(true)): Promise<Certificate[]> { 365 // Initial variables 366 const localCerts: Certificate[] = []; 367 368 //#region Building certificate path 369 const buildPath = async (certificate: Certificate, crypto: common.ICryptoEngine): Promise<Certificate[][]> => { 370 const result: Certificate[][] = []; 371 372 // Aux function checking array for unique elements 373 function checkUnique(array: Certificate[]): boolean { 374 let unique = true; 375 376 for (let i = 0; i < array.length; i++) { 377 for (let j = 0; j < array.length; j++) { 378 if (j === i) 379 continue; 380 381 if (array[i] === array[j]) { 382 unique = false; 383 break; 384 } 385 } 386 387 if (!unique) 388 break; 389 } 390 391 return unique; 392 } 393 394 if (isTrusted(certificate, this.trustedCerts)) { 395 return [[certificate]]; 396 } 397 398 const findIssuerResult = await this.findIssuer(certificate, this, crypto); 399 if (findIssuerResult.length === 0) { 400 throw new Error("No valid certificate paths found"); 401 } 402 403 for (let i = 0; i < findIssuerResult.length; i++) { 404 if (pvtsutils.BufferSourceConverter.isEqual(findIssuerResult[i].tbsView, certificate.tbsView)) { 405 result.push([findIssuerResult[i]]); 406 continue; 407 } 408 409 const buildPathResult = await buildPath(findIssuerResult[i], crypto); 410 411 for (let j = 0; j < buildPathResult.length; j++) { 412 const copy = buildPathResult[j].slice(); 413 copy.splice(0, 0, findIssuerResult[i]); 414 415 if (checkUnique(copy)) 416 result.push(copy); 417 else 418 result.push(buildPathResult[j]); 419 } 420 } 421 422 return result; 423 }; 424 //#endregion 425 426 //#region Find CRL for specific certificate 427 const findCRL = async (certificate: Certificate): Promise<FindCrlResult> => { 428 //#region Initial variables 429 const issuerCertificates: Certificate[] = []; 430 const crls: CertificateRevocationList[] = []; 431 const crlsAndCertificates: CrlAndCertificate[] = []; 432 //#endregion 433 434 //#region Find all possible CRL issuers 435 issuerCertificates.push(...localCerts.filter(element => certificate.issuer.isEqual(element.subject))); 436 if (issuerCertificates.length === 0) { 437 return { 438 status: 1, 439 statusMessage: "No certificate's issuers" 440 }; 441 } 442 //#endregion 443 444 //#region Find all CRLs for certificate's issuer 445 crls.push(...this.crls.filter(o => o.issuer.isEqual(certificate.issuer))); 446 if (crls.length === 0) { 447 return { 448 status: 2, 449 statusMessage: "No CRLs for specific certificate issuer" 450 }; 451 } 452 //#endregion 453 454 //#region Find specific certificate of issuer for each CRL 455 for (let i = 0; i < crls.length; i++) { 456 const crl = crls[i]; 457 //#region Check "nextUpdate" for the CRL 458 // The "nextUpdate" is older than CHECK_DATE. 459 // Thus we should do have another, updated CRL. 460 // Thus the CRL assumed to be invalid. 461 if (crl.nextUpdate && crl.nextUpdate.value < this.checkDate) { 462 continue; 463 } 464 //#endregion 465 466 for (let j = 0; j < issuerCertificates.length; j++) { 467 try { 468 const result = await crls[i].verify({ issuerCertificate: issuerCertificates[j] }, crypto); 469 if (result) { 470 crlsAndCertificates.push({ 471 crl: crls[i], 472 certificate: issuerCertificates[j] 473 }); 474 475 break; 476 } 477 } 478 catch { 479 // nothing 480 } 481 } 482 } 483 //#endregion 484 485 if (crlsAndCertificates.length) { 486 return { 487 status: 0, 488 statusMessage: EMPTY_STRING, 489 result: crlsAndCertificates 490 }; 491 } 492 493 return { 494 status: 3, 495 statusMessage: "No valid CRLs found" 496 }; 497 }; 498 //#endregion 499 500 //#region Find OCSP for specific certificate 501 const findOCSP = async (certificate: Certificate, issuerCertificate: Certificate): Promise<number> => { 502 //#region Get hash algorithm from certificate 503 const hashAlgorithm = crypto.getAlgorithmByOID<any>(certificate.signatureAlgorithm.algorithmId); 504 if (!hashAlgorithm.name) { 505 return 1; 506 } 507 if (!hashAlgorithm.hash) { 508 return 1; 509 } 510 //#endregion 511 512 //#region Search for OCSP response for the certificate 513 for (let i = 0; i < this.ocsps.length; i++) { 514 const ocsp = this.ocsps[i]; 515 const result = await ocsp.getCertificateStatus(certificate, issuerCertificate, crypto); 516 if (result.isForCertificate) { 517 if (result.status === 0) 518 return 0; 519 520 return 1; 521 } 522 } 523 //#endregion 524 525 return 2; 526 }; 527 //#endregion 528 529 //#region Check for certificate to be CA 530 async function checkForCA(certificate: Certificate, needToCheckCRL = false) { 531 //#region Initial variables 532 let isCA = false; 533 let mustBeCA = false; 534 let keyUsagePresent = false; 535 let cRLSign = false; 536 //#endregion 537 538 if (certificate.extensions) { 539 for (let j = 0; j < certificate.extensions.length; j++) { 540 const extension = certificate.extensions[j]; 541 if (extension.critical && !extension.parsedValue) { 542 return { 543 result: false, 544 resultCode: 6, 545 resultMessage: `Unable to parse critical certificate extension: ${extension.extnID}` 546 }; 547 } 548 549 if (extension.extnID === id_KeyUsage) // KeyUsage 550 { 551 keyUsagePresent = true; 552 553 const view = new Uint8Array(extension.parsedValue.valueBlock.valueHex); 554 555 if ((view[0] & 0x04) === 0x04) // Set flag "keyCertSign" 556 mustBeCA = true; 557 558 if ((view[0] & 0x02) === 0x02) // Set flag "cRLSign" 559 cRLSign = true; 560 } 561 562 if (extension.extnID === id_BasicConstraints) // BasicConstraints 563 { 564 if ("cA" in extension.parsedValue) { 565 if (extension.parsedValue.cA === true) 566 isCA = true; 567 } 568 } 569 } 570 571 if ((mustBeCA === true) && (isCA === false)) { 572 return { 573 result: false, 574 resultCode: 3, 575 resultMessage: "Unable to build certificate chain - using \"keyCertSign\" flag set without BasicConstraints" 576 }; 577 } 578 579 if ((keyUsagePresent === true) && (isCA === true) && (mustBeCA === false)) { 580 return { 581 result: false, 582 resultCode: 4, 583 resultMessage: "Unable to build certificate chain - \"keyCertSign\" flag was not set" 584 }; 585 } 586 587 if ((isCA === true) && (keyUsagePresent === true) && ((needToCheckCRL) && (cRLSign === false))) { 588 return { 589 result: false, 590 resultCode: 5, 591 resultMessage: "Unable to build certificate chain - intermediate certificate must have \"cRLSign\" key usage flag" 592 }; 593 } 594 } 595 596 if (isCA === false) { 597 return { 598 result: false, 599 resultCode: 7, 600 resultMessage: "Unable to build certificate chain - more than one possible end-user certificate" 601 }; 602 } 603 604 return { 605 result: true, 606 resultCode: 0, 607 resultMessage: EMPTY_STRING 608 }; 609 } 610 //#endregion 611 612 //#region Basic check for certificate path 613 const basicCheck = async (path: Certificate[], checkDate: Date): Promise<{ result: boolean; resultCode?: number; resultMessage?: string; }> => { 614 //#region Check that all dates are valid 615 for (let i = 0; i < path.length; i++) { 616 if ((path[i].notBefore.value > checkDate) || 617 (path[i].notAfter.value < checkDate)) { 618 return { 619 result: false, 620 resultCode: 8, 621 resultMessage: "The certificate is either not yet valid or expired" 622 }; 623 } 624 } 625 //#endregion 626 627 //#region Check certificate name chain 628 629 // We should have at least two certificates: end entity and trusted root 630 if (path.length < 2) { 631 return { 632 result: false, 633 resultCode: 9, 634 resultMessage: "Too short certificate path" 635 }; 636 } 637 638 for (let i = (path.length - 2); i >= 0; i--) { 639 //#region Check that we do not have a "self-signed" certificate 640 if (path[i].issuer.isEqual(path[i].subject) === false) { 641 if (path[i].issuer.isEqual(path[i + 1].subject) === false) { 642 return { 643 result: false, 644 resultCode: 10, 645 resultMessage: "Incorrect name chaining" 646 }; 647 } 648 } 649 //#endregion 650 } 651 //#endregion 652 653 //#region Check each certificate (except "trusted root") to be non-revoked 654 if ((this.crls.length !== 0) || (this.ocsps.length !== 0)) // If CRLs and OCSPs are empty then we consider all certificates to be valid 655 { 656 for (let i = 0; i < (path.length - 1); i++) { 657 //#region Initial variables 658 let ocspResult = 2; 659 let crlResult: FindCrlResult = { 660 status: 0, 661 statusMessage: EMPTY_STRING 662 }; 663 //#endregion 664 665 //#region Check OCSPs first 666 if (this.ocsps.length !== 0) { 667 ocspResult = await findOCSP(path[i], path[i + 1]); 668 669 switch (ocspResult) { 670 case 0: 671 continue; 672 case 1: 673 return { 674 result: false, 675 resultCode: 12, 676 resultMessage: "One of certificates was revoked via OCSP response" 677 }; 678 case 2: // continue to check the certificate with CRL 679 break; 680 default: 681 } 682 } 683 //#endregion 684 685 //#region Check CRLs 686 if (this.crls.length !== 0) { 687 crlResult = await findCRL(path[i]); 688 689 if (crlResult.status === 0 && crlResult.result) { 690 for (let j = 0; j < crlResult.result.length; j++) { 691 //#region Check that the CRL issuer certificate have not been revoked 692 const isCertificateRevoked = crlResult.result[j].crl.isCertificateRevoked(path[i]); 693 if (isCertificateRevoked) { 694 return { 695 result: false, 696 resultCode: 12, 697 resultMessage: "One of certificates had been revoked" 698 }; 699 } 700 //#endregion 701 702 //#region Check that the CRL issuer certificate is a CA certificate 703 const isCertificateCA = await checkForCA(crlResult.result[j].certificate, true); 704 if (isCertificateCA.result === false) { 705 return { 706 result: false, 707 resultCode: 13, 708 resultMessage: "CRL issuer certificate is not a CA certificate or does not have crlSign flag" 709 }; 710 } 711 //#endregion 712 } 713 } else { 714 if (passedWhenNotRevValues === false) { 715 throw new ChainValidationError(ChainValidationCode.noRevocation, `No revocation values found for one of certificates: ${crlResult.statusMessage}`); 716 } 717 } 718 } else { 719 if (ocspResult === 2) { 720 return { 721 result: false, 722 resultCode: 11, 723 resultMessage: "No revocation values found for one of certificates" 724 }; 725 } 726 } 727 //#endregion 728 729 //#region Check we do have links to revocation values inside issuer's certificate 730 if ((ocspResult === 2) && (crlResult.status === 2) && passedWhenNotRevValues) { 731 const issuerCertificate = path[i + 1]; 732 let extensionFound = false; 733 734 if (issuerCertificate.extensions) { 735 for (const extension of issuerCertificate.extensions) { 736 switch (extension.extnID) { 737 case id_CRLDistributionPoints: 738 case id_FreshestCRL: 739 case id_AuthorityInfoAccess: 740 extensionFound = true; 741 break; 742 default: 743 } 744 } 745 } 746 747 if (extensionFound) { 748 throw new ChainValidationError(ChainValidationCode.noRevocation, `No revocation values found for one of certificates: ${crlResult.statusMessage}`); 749 } 750 } 751 //#endregion 752 } 753 } 754 //#endregion 755 756 //#region Check each certificate (except "end entity") in the path to be a CA certificate 757 for (const [i, cert] of path.entries()) { 758 if (!i) { 759 // Skip entity certificate 760 continue; 761 } 762 763 const result = await checkForCA(cert); 764 if (!result.result) { 765 return { 766 result: false, 767 resultCode: 14, 768 resultMessage: "One of intermediate certificates is not a CA certificate" 769 }; 770 } 771 } 772 //#endregion 773 774 return { 775 result: true 776 }; 777 }; 778 //#endregion 779 780 //#region Do main work 781 //#region Initialize "localCerts" by value of "this.certs" + "this.trustedCerts" arrays 782 localCerts.push(...this.trustedCerts); 783 localCerts.push(...this.certs); 784 //#endregion 785 786 //#region Check all certificates for been unique 787 for (let i = 0; i < localCerts.length; i++) { 788 for (let j = 0; j < localCerts.length; j++) { 789 if (i === j) 790 continue; 791 792 if (pvtsutils.BufferSourceConverter.isEqual(localCerts[i].tbsView, localCerts[j].tbsView)) { 793 localCerts.splice(j, 1); 794 i = 0; 795 break; 796 } 797 } 798 } 799 //#endregion 800 801 const leafCert = localCerts[localCerts.length - 1]; 802 803 //#region Initial variables 804 let result; 805 const certificatePath = [leafCert]; // The "end entity" certificate must be the least in CERTS array 806 //#endregion 807 808 //#region Build path for "end entity" certificate 809 result = await buildPath(leafCert, crypto); 810 if (result.length === 0) { 811 throw new ChainValidationError(ChainValidationCode.noPath, "Unable to find certificate path"); 812 } 813 //#endregion 814 815 //#region Exclude certificate paths not ended with "trusted roots" 816 for (let i = result.length - 1; i >= 0; i--) { 817 let found = false; 818 819 for (let j = 0; j < (result[i]).length; j++) { 820 const certificate = (result[i])[j]; 821 822 for (let k = 0; k < this.trustedCerts.length; k++) { 823 if (pvtsutils.BufferSourceConverter.isEqual(certificate.tbsView, this.trustedCerts[k].tbsView)) { 824 found = true; 825 break; 826 } 827 } 828 829 if (found) 830 break; 831 } 832 833 if (!found) { 834 result.splice(i, 1); 835 } 836 } 837 838 if (result.length === 0) { 839 throw new ChainValidationError(ChainValidationCode.noValidPath, "No valid certificate paths found"); 840 } 841 //#endregion 842 843 //#region Find shortest certificate path (for the moment it is the only criteria) 844 let shortestLength = result[0].length; 845 let shortestIndex = 0; 846 847 for (let i = 0; i < result.length; i++) { 848 if (result[i].length < shortestLength) { 849 shortestLength = result[i].length; 850 shortestIndex = i; 851 } 852 } 853 //#endregion 854 855 //#region Create certificate path for basic check 856 for (let i = 0; i < result[shortestIndex].length; i++) 857 certificatePath.push((result[shortestIndex])[i]); 858 //#endregion 859 860 //#region Perform basic checking for all certificates in the path 861 result = await basicCheck(certificatePath, this.checkDate); 862 if (result.result === false) 863 throw result; 864 //#endregion 865 866 return certificatePath; 867 //#endregion 868 } 869 870 /** 871 * Major verification function for certificate chain. 872 * @param parameters 873 * @param crypto Crypto engine 874 * @returns 875 */ 876 async verify(parameters: CertificateChainValidationEngineVerifyParams = {}, crypto = common.getCrypto(true)): Promise<CertificateChainValidationEngineVerifyResult> { 877 //#region Auxiliary functions for name constraints checking 878 /** 879 * Compare two dNSName values 880 * @param name DNS from name 881 * @param constraint Constraint for DNS from name 882 * @returns Boolean result - valid or invalid the "name" against the "constraint" 883 */ 884 function compareDNSName(name: string, constraint: string): boolean { 885 //#region Make a "string preparation" for both name and constrain 886 const namePrepared = Helpers.stringPrep(name); 887 const constraintPrepared = Helpers.stringPrep(constraint); 888 //#endregion 889 890 //#region Make a "splitted" versions of "constraint" and "name" 891 const nameSplitted = namePrepared.split("."); 892 const constraintSplitted = constraintPrepared.split("."); 893 //#endregion 894 895 //#region Length calculation and additional check 896 const nameLen = nameSplitted.length; 897 const constrLen = constraintSplitted.length; 898 899 if ((nameLen === 0) || (constrLen === 0) || (nameLen < constrLen)) { 900 return false; 901 } 902 //#endregion 903 904 //#region Check that no part of "name" has zero length 905 for (let i = 0; i < nameLen; i++) { 906 if (nameSplitted[i].length === 0) { 907 return false; 908 } 909 } 910 //#endregion 911 912 //#region Check that no part of "constraint" has zero length 913 for (let i = 0; i < constrLen; i++) { 914 if (constraintSplitted[i].length === 0) { 915 if (i === 0) { 916 if (constrLen === 1) { 917 return false; 918 } 919 920 continue; 921 } 922 923 return false; 924 } 925 } 926 //#endregion 927 928 //#region Check that "name" has a tail as "constraint" 929 930 for (let i = 0; i < constrLen; i++) { 931 if (constraintSplitted[constrLen - 1 - i].length === 0) { 932 continue; 933 } 934 935 if (nameSplitted[nameLen - 1 - i].localeCompare(constraintSplitted[constrLen - 1 - i]) !== 0) { 936 return false; 937 } 938 } 939 //#endregion 940 941 return true; 942 } 943 944 /** 945 * Compare two rfc822Name values 946 * @param name E-mail address from name 947 * @param constraint Constraint for e-mail address from name 948 * @returns Boolean result - valid or invalid the "name" against the "constraint" 949 */ 950 function compareRFC822Name(name: string, constraint: string): boolean { 951 //#region Make a "string preparation" for both name and constrain 952 const namePrepared = Helpers.stringPrep(name); 953 const constraintPrepared = Helpers.stringPrep(constraint); 954 //#endregion 955 956 //#region Make a "splitted" versions of "constraint" and "name" 957 const nameSplitted = namePrepared.split("@"); 958 const constraintSplitted = constraintPrepared.split("@"); 959 //#endregion 960 961 //#region Splitted array length checking 962 if ((nameSplitted.length === 0) || (constraintSplitted.length === 0) || (nameSplitted.length < constraintSplitted.length)) 963 return false; 964 //#endregion 965 966 if (constraintSplitted.length === 1) { 967 const result = compareDNSName(nameSplitted[1], constraintSplitted[0]); 968 969 if (result) { 970 //#region Make a "splitted" versions of domain name from "constraint" and "name" 971 const ns = nameSplitted[1].split("."); 972 const cs = constraintSplitted[0].split("."); 973 //#endregion 974 975 if (cs[0].length === 0) 976 return true; 977 978 return ns.length === cs.length; 979 } 980 981 return false; 982 } 983 984 return (namePrepared.localeCompare(constraintPrepared) === 0); 985 } 986 987 /** 988 * Compare two uniformResourceIdentifier values 989 * @param name uniformResourceIdentifier from name 990 * @param constraint Constraint for uniformResourceIdentifier from name 991 * @returns Boolean result - valid or invalid the "name" against the "constraint" 992 */ 993 function compareUniformResourceIdentifier(name: string, constraint: string): boolean { 994 //#region Make a "string preparation" for both name and constrain 995 let namePrepared = Helpers.stringPrep(name); 996 const constraintPrepared = Helpers.stringPrep(constraint); 997 //#endregion 998 999 //#region Find out a major URI part to compare with 1000 const ns = namePrepared.split("/"); 1001 const cs = constraintPrepared.split("/"); 1002 1003 if (cs.length > 1) // Malformed constraint 1004 return false; 1005 1006 if (ns.length > 1) // Full URI string 1007 { 1008 for (let i = 0; i < ns.length; i++) { 1009 if ((ns[i].length > 0) && (ns[i].charAt(ns[i].length - 1) !== ":")) { 1010 const nsPort = ns[i].split(":"); 1011 namePrepared = nsPort[0]; 1012 break; 1013 } 1014 } 1015 } 1016 //#endregion 1017 1018 const result = compareDNSName(namePrepared, constraintPrepared); 1019 1020 if (result) { 1021 //#region Make a "splitted" versions of "constraint" and "name" 1022 const nameSplitted = namePrepared.split("."); 1023 const constraintSplitted = constraintPrepared.split("."); 1024 //#endregion 1025 1026 if (constraintSplitted[0].length === 0) 1027 return true; 1028 1029 return nameSplitted.length === constraintSplitted.length; 1030 } 1031 1032 return false; 1033 } 1034 1035 /** 1036 * Compare two iPAddress values 1037 * @param name iPAddress from name 1038 * @param constraint Constraint for iPAddress from name 1039 * @returns Boolean result - valid or invalid the "name" against the "constraint" 1040 */ 1041 function compareIPAddress(name: asn1js.OctetString, constraint: asn1js.OctetString): boolean { 1042 //#region Common variables 1043 const nameView = name.valueBlock.valueHexView; 1044 const constraintView = constraint.valueBlock.valueHexView; 1045 //#endregion 1046 1047 //#region Work with IPv4 addresses 1048 if ((nameView.length === 4) && (constraintView.length === 8)) { 1049 for (let i = 0; i < 4; i++) { 1050 if ((nameView[i] ^ constraintView[i]) & constraintView[i + 4]) 1051 return false; 1052 } 1053 1054 return true; 1055 } 1056 //#endregion 1057 1058 //#region Work with IPv6 addresses 1059 if ((nameView.length === 16) && (constraintView.length === 32)) { 1060 for (let i = 0; i < 16; i++) { 1061 if ((nameView[i] ^ constraintView[i]) & constraintView[i + 16]) 1062 return false; 1063 } 1064 1065 return true; 1066 } 1067 //#endregion 1068 1069 return false; 1070 } 1071 1072 /** 1073 * Compare two directoryName values 1074 * @param name directoryName from name 1075 * @param constraint Constraint for directoryName from name 1076 * @returns Boolean result - valid or invalid the "name" against the "constraint" 1077 */ 1078 function compareDirectoryName(name: RelativeDistinguishedNames, constraint: RelativeDistinguishedNames): boolean { 1079 //#region Initial check 1080 if ((name.typesAndValues.length === 0) || (constraint.typesAndValues.length === 0)) 1081 return true; 1082 1083 if (name.typesAndValues.length < constraint.typesAndValues.length) 1084 return false; 1085 //#endregion 1086 1087 //#region Initial variables 1088 let result = true; 1089 let nameStart = 0; 1090 //#endregion 1091 1092 for (let i = 0; i < constraint.typesAndValues.length; i++) { 1093 let localResult = false; 1094 1095 for (let j = nameStart; j < name.typesAndValues.length; j++) { 1096 localResult = name.typesAndValues[j].isEqual(constraint.typesAndValues[i]); 1097 1098 if (name.typesAndValues[j].type === constraint.typesAndValues[i].type) 1099 result = result && localResult; 1100 1101 if (localResult === true) { 1102 if ((nameStart === 0) || (nameStart === j)) { 1103 nameStart = j + 1; 1104 break; 1105 } 1106 else // Structure of "name" must be the same with "constraint" 1107 return false; 1108 } 1109 } 1110 1111 if (localResult === false) 1112 return false; 1113 } 1114 1115 return (nameStart === 0) ? false : result; 1116 } 1117 //#endregion 1118 1119 try { 1120 //#region Initial checks 1121 if (this.certs.length === 0) 1122 throw new Error("Empty certificate array"); 1123 //#endregion 1124 1125 //#region Get input variables 1126 const passedWhenNotRevValues = parameters.passedWhenNotRevValues || false; 1127 1128 const initialPolicySet = parameters.initialPolicySet || [id_AnyPolicy]; 1129 1130 const initialExplicitPolicy = parameters.initialExplicitPolicy || false; 1131 const initialPolicyMappingInhibit = parameters.initialPolicyMappingInhibit || false; 1132 const initialInhibitPolicy = parameters.initialInhibitPolicy || false; 1133 1134 const initialPermittedSubtreesSet = parameters.initialPermittedSubtreesSet || []; 1135 const initialExcludedSubtreesSet = parameters.initialExcludedSubtreesSet || []; 1136 const initialRequiredNameForms = parameters.initialRequiredNameForms || []; 1137 1138 let explicitPolicyIndicator = initialExplicitPolicy; 1139 let policyMappingInhibitIndicator = initialPolicyMappingInhibit; 1140 let inhibitAnyPolicyIndicator = initialInhibitPolicy; 1141 1142 const pendingConstraints = [ 1143 false, // For "explicitPolicyPending" 1144 false, // For "policyMappingInhibitPending" 1145 false, // For "inhibitAnyPolicyPending" 1146 ]; 1147 1148 let explicitPolicyPending = 0; 1149 let policyMappingInhibitPending = 0; 1150 let inhibitAnyPolicyPending = 0; 1151 1152 let permittedSubtrees = initialPermittedSubtreesSet; 1153 let excludedSubtrees = initialExcludedSubtreesSet; 1154 const requiredNameForms = initialRequiredNameForms; 1155 1156 let pathDepth = 1; 1157 //#endregion 1158 1159 //#region Sorting certificates in the chain array 1160 this.certs = await this.sort(passedWhenNotRevValues, crypto); 1161 //#endregion 1162 1163 //#region Work with policies 1164 //#region Support variables 1165 const allPolicies: string[] = []; // Array of all policies (string values) 1166 allPolicies.push(id_AnyPolicy); // Put "anyPolicy" at first place 1167 1168 const policiesAndCerts = []; // In fact "array of array" where rows are for each specific policy, column for each certificate and value is "true/false" 1169 1170 const anyPolicyArray = new Array(this.certs.length - 1); // Minus "trusted anchor" 1171 for (let ii = 0; ii < (this.certs.length - 1); ii++) 1172 anyPolicyArray[ii] = true; 1173 1174 policiesAndCerts.push(anyPolicyArray); 1175 1176 const policyMappings = new Array(this.certs.length - 1); // Array of "PolicyMappings" for each certificate 1177 const certPolicies = new Array(this.certs.length - 1); // Array of "CertificatePolicies" for each certificate 1178 1179 let explicitPolicyStart = (explicitPolicyIndicator) ? (this.certs.length - 1) : (-1); 1180 //#endregion 1181 1182 //#region Gather all necessary information from certificate chain 1183 for (let i = (this.certs.length - 2); i >= 0; i--, pathDepth++) { 1184 const cert = this.certs[i]; 1185 if (cert.extensions) { 1186 //#region Get information about certificate extensions 1187 for (let j = 0; j < cert.extensions.length; j++) { 1188 const extension = cert.extensions[j]; 1189 //#region CertificatePolicies 1190 if (extension.extnID === id_CertificatePolicies) { 1191 certPolicies[i] = extension.parsedValue; 1192 1193 //#region Remove entry from "anyPolicies" for the certificate 1194 for (let s = 0; s < allPolicies.length; s++) { 1195 if (allPolicies[s] === id_AnyPolicy) { 1196 delete (policiesAndCerts[s])[i]; 1197 break; 1198 } 1199 } 1200 //#endregion 1201 1202 for (let k = 0; k < extension.parsedValue.certificatePolicies.length; k++) { 1203 let policyIndex = (-1); 1204 const policyId = extension.parsedValue.certificatePolicies[k].policyIdentifier; 1205 1206 //#region Try to find extension in "allPolicies" array 1207 for (let s = 0; s < allPolicies.length; s++) { 1208 if (policyId === allPolicies[s]) { 1209 policyIndex = s; 1210 break; 1211 } 1212 } 1213 //#endregion 1214 1215 if (policyIndex === (-1)) { 1216 allPolicies.push(policyId); 1217 1218 const certArray = new Array(this.certs.length - 1); 1219 certArray[i] = true; 1220 1221 policiesAndCerts.push(certArray); 1222 } 1223 else 1224 (policiesAndCerts[policyIndex])[i] = true; 1225 } 1226 } 1227 //#endregion 1228 1229 //#region PolicyMappings 1230 if (extension.extnID === id_PolicyMappings) { 1231 if (policyMappingInhibitIndicator) { 1232 return { 1233 result: false, 1234 resultCode: 98, 1235 resultMessage: "Policy mapping prohibited" 1236 }; 1237 } 1238 1239 policyMappings[i] = extension.parsedValue; 1240 } 1241 //#endregion 1242 1243 //#region PolicyConstraints 1244 if (extension.extnID === id_PolicyConstraints) { 1245 if (explicitPolicyIndicator === false) { 1246 //#region requireExplicitPolicy 1247 if (extension.parsedValue.requireExplicitPolicy === 0) { 1248 explicitPolicyIndicator = true; 1249 explicitPolicyStart = i; 1250 } 1251 else { 1252 if (pendingConstraints[0] === false) { 1253 pendingConstraints[0] = true; 1254 explicitPolicyPending = extension.parsedValue.requireExplicitPolicy; 1255 } 1256 else 1257 explicitPolicyPending = (explicitPolicyPending > extension.parsedValue.requireExplicitPolicy) ? extension.parsedValue.requireExplicitPolicy : explicitPolicyPending; 1258 } 1259 //#endregion 1260 1261 //#region inhibitPolicyMapping 1262 if (extension.parsedValue.inhibitPolicyMapping === 0) 1263 policyMappingInhibitIndicator = true; 1264 else { 1265 if (pendingConstraints[1] === false) { 1266 pendingConstraints[1] = true; 1267 policyMappingInhibitPending = extension.parsedValue.inhibitPolicyMapping + 1; 1268 } 1269 else 1270 policyMappingInhibitPending = (policyMappingInhibitPending > (extension.parsedValue.inhibitPolicyMapping + 1)) ? (extension.parsedValue.inhibitPolicyMapping + 1) : policyMappingInhibitPending; 1271 } 1272 //#endregion 1273 } 1274 } 1275 //#endregion 1276 1277 //#region InhibitAnyPolicy 1278 if (extension.extnID === id_InhibitAnyPolicy) { 1279 if (inhibitAnyPolicyIndicator === false) { 1280 if (extension.parsedValue.valueBlock.valueDec === 0) 1281 inhibitAnyPolicyIndicator = true; 1282 else { 1283 if (pendingConstraints[2] === false) { 1284 pendingConstraints[2] = true; 1285 inhibitAnyPolicyPending = extension.parsedValue.valueBlock.valueDec; 1286 } 1287 else 1288 inhibitAnyPolicyPending = (inhibitAnyPolicyPending > extension.parsedValue.valueBlock.valueDec) ? extension.parsedValue.valueBlock.valueDec : inhibitAnyPolicyPending; 1289 } 1290 } 1291 } 1292 //#endregion 1293 } 1294 //#endregion 1295 1296 //#region Check "inhibitAnyPolicyIndicator" 1297 if (inhibitAnyPolicyIndicator === true) { 1298 let policyIndex = (-1); 1299 1300 //#region Find "anyPolicy" index 1301 for (let searchAnyPolicy = 0; searchAnyPolicy < allPolicies.length; searchAnyPolicy++) { 1302 if (allPolicies[searchAnyPolicy] === id_AnyPolicy) { 1303 policyIndex = searchAnyPolicy; 1304 break; 1305 } 1306 } 1307 //#endregion 1308 1309 if (policyIndex !== (-1)) 1310 delete (policiesAndCerts[0])[i]; // Unset value to "undefined" for "anyPolicies" value for current certificate 1311 } 1312 //#endregion 1313 1314 //#region Process with "pending constraints" 1315 if (explicitPolicyIndicator === false) { 1316 if (pendingConstraints[0] === true) { 1317 explicitPolicyPending--; 1318 if (explicitPolicyPending === 0) { 1319 explicitPolicyIndicator = true; 1320 explicitPolicyStart = i; 1321 1322 pendingConstraints[0] = false; 1323 } 1324 } 1325 } 1326 1327 if (policyMappingInhibitIndicator === false) { 1328 if (pendingConstraints[1] === true) { 1329 policyMappingInhibitPending--; 1330 if (policyMappingInhibitPending === 0) { 1331 policyMappingInhibitIndicator = true; 1332 pendingConstraints[1] = false; 1333 } 1334 } 1335 } 1336 1337 if (inhibitAnyPolicyIndicator === false) { 1338 if (pendingConstraints[2] === true) { 1339 inhibitAnyPolicyPending--; 1340 if (inhibitAnyPolicyPending === 0) { 1341 inhibitAnyPolicyIndicator = true; 1342 pendingConstraints[2] = false; 1343 } 1344 } 1345 } 1346 //#endregion 1347 } 1348 } 1349 //#endregion 1350 1351 //#region Working with policy mappings 1352 for (let i = 0; i < (this.certs.length - 1); i++) { 1353 //#region Check that there is "policy mapping" for level "i + 1" 1354 if ((i < (this.certs.length - 2)) && (typeof policyMappings[i + 1] !== "undefined")) { 1355 for (let k = 0; k < policyMappings[i + 1].mappings.length; k++) { 1356 //#region Check that we do not have "anyPolicy" in current mapping 1357 if ((policyMappings[i + 1].mappings[k].issuerDomainPolicy === id_AnyPolicy) || (policyMappings[i + 1].mappings[k].subjectDomainPolicy === id_AnyPolicy)) { 1358 return { 1359 result: false, 1360 resultCode: 99, 1361 resultMessage: "The \"anyPolicy\" should not be a part of policy mapping scheme" 1362 }; 1363 } 1364 //#endregion 1365 1366 //#region Initial variables 1367 let issuerDomainPolicyIndex = (-1); 1368 let subjectDomainPolicyIndex = (-1); 1369 //#endregion 1370 1371 //#region Search for index of policies indexes 1372 for (let n = 0; n < allPolicies.length; n++) { 1373 if (allPolicies[n] === policyMappings[i + 1].mappings[k].issuerDomainPolicy) 1374 issuerDomainPolicyIndex = n; 1375 1376 if (allPolicies[n] === policyMappings[i + 1].mappings[k].subjectDomainPolicy) 1377 subjectDomainPolicyIndex = n; 1378 } 1379 //#endregion 1380 1381 //#region Delete existing "issuerDomainPolicy" because on the level we mapped the policy to another one 1382 if (typeof (policiesAndCerts[issuerDomainPolicyIndex])[i] !== "undefined") 1383 delete (policiesAndCerts[issuerDomainPolicyIndex])[i]; 1384 //#endregion 1385 1386 //#region Check all policies for the certificate 1387 for (let j = 0; j < certPolicies[i].certificatePolicies.length; j++) { 1388 if (policyMappings[i + 1].mappings[k].subjectDomainPolicy === certPolicies[i].certificatePolicies[j].policyIdentifier) { 1389 //#region Set mapped policy for current certificate 1390 if ((issuerDomainPolicyIndex !== (-1)) && (subjectDomainPolicyIndex !== (-1))) { 1391 for (let m = 0; m <= i; m++) { 1392 if (typeof (policiesAndCerts[subjectDomainPolicyIndex])[m] !== "undefined") { 1393 (policiesAndCerts[issuerDomainPolicyIndex])[m] = true; 1394 delete (policiesAndCerts[subjectDomainPolicyIndex])[m]; 1395 } 1396 } 1397 } 1398 //#endregion 1399 } 1400 } 1401 //#endregion 1402 } 1403 } 1404 //#endregion 1405 } 1406 //#endregion 1407 1408 //#region Working with "explicitPolicyIndicator" and "anyPolicy" 1409 for (let i = 0; i < allPolicies.length; i++) { 1410 if (allPolicies[i] === id_AnyPolicy) { 1411 for (let j = 0; j < explicitPolicyStart; j++) 1412 delete (policiesAndCerts[i])[j]; 1413 } 1414 } 1415 //#endregion 1416 1417 //#region Create "set of authorities-constrained policies" 1418 const authConstrPolicies = []; 1419 1420 for (let i = 0; i < policiesAndCerts.length; i++) { 1421 let found = true; 1422 1423 for (let j = 0; j < (this.certs.length - 1); j++) { 1424 let anyPolicyFound = false; 1425 1426 if ((j < explicitPolicyStart) && (allPolicies[i] === id_AnyPolicy) && (allPolicies.length > 1)) { 1427 found = false; 1428 break; 1429 } 1430 1431 if (typeof (policiesAndCerts[i])[j] === "undefined") { 1432 if (j >= explicitPolicyStart) { 1433 //#region Search for "anyPolicy" in the policy set 1434 for (let k = 0; k < allPolicies.length; k++) { 1435 if (allPolicies[k] === id_AnyPolicy) { 1436 if ((policiesAndCerts[k])[j] === true) 1437 anyPolicyFound = true; 1438 1439 break; 1440 } 1441 } 1442 //#endregion 1443 } 1444 1445 if (!anyPolicyFound) { 1446 found = false; 1447 break; 1448 } 1449 } 1450 } 1451 1452 if (found === true) 1453 authConstrPolicies.push(allPolicies[i]); 1454 } 1455 //#endregion 1456 1457 //#region Create "set of user-constrained policies" 1458 let userConstrPolicies: string[] = []; 1459 1460 if ((initialPolicySet.length === 1) && (initialPolicySet[0] === id_AnyPolicy) && (explicitPolicyIndicator === false)) 1461 userConstrPolicies = initialPolicySet; 1462 else { 1463 if ((authConstrPolicies.length === 1) && (authConstrPolicies[0] === id_AnyPolicy)) 1464 userConstrPolicies = initialPolicySet; 1465 else { 1466 for (let i = 0; i < authConstrPolicies.length; i++) { 1467 for (let j = 0; j < initialPolicySet.length; j++) { 1468 if ((initialPolicySet[j] === authConstrPolicies[i]) || (initialPolicySet[j] === id_AnyPolicy)) { 1469 userConstrPolicies.push(authConstrPolicies[i]); 1470 break; 1471 } 1472 } 1473 } 1474 } 1475 } 1476 1477 //#endregion 1478 1479 //#region Combine output object 1480 const policyResult: CertificateChainValidationEngineVerifyResult = { 1481 result: (userConstrPolicies.length > 0), 1482 resultCode: 0, 1483 resultMessage: (userConstrPolicies.length > 0) ? EMPTY_STRING : "Zero \"userConstrPolicies\" array, no intersections with \"authConstrPolicies\"", 1484 authConstrPolicies, 1485 userConstrPolicies, 1486 explicitPolicyIndicator, 1487 policyMappings, 1488 certificatePath: this.certs 1489 }; 1490 1491 if (userConstrPolicies.length === 0) 1492 return policyResult; 1493 //#endregion 1494 //#endregion 1495 1496 //#region Work with name constraints 1497 //#region Check a result from "policy checking" part 1498 if (policyResult.result === false) 1499 return policyResult; 1500 //#endregion 1501 1502 //#region Check all certificates, excluding "trust anchor" 1503 pathDepth = 1; 1504 1505 for (let i = (this.certs.length - 2); i >= 0; i--, pathDepth++) { 1506 const cert = this.certs[i]; 1507 //#region Support variables 1508 let subjectAltNames: GeneralName[] = []; 1509 1510 let certPermittedSubtrees: GeneralSubtree[] = []; 1511 let certExcludedSubtrees: GeneralSubtree[] = []; 1512 //#endregion 1513 1514 if (cert.extensions) { 1515 for (let j = 0; j < cert.extensions.length; j++) { 1516 const extension = cert.extensions[j]; 1517 //#region NameConstraints 1518 if (extension.extnID === id_NameConstraints) { 1519 if ("permittedSubtrees" in extension.parsedValue) 1520 certPermittedSubtrees = certPermittedSubtrees.concat(extension.parsedValue.permittedSubtrees); 1521 1522 if ("excludedSubtrees" in extension.parsedValue) 1523 certExcludedSubtrees = certExcludedSubtrees.concat(extension.parsedValue.excludedSubtrees); 1524 } 1525 //#endregion 1526 1527 //#region SubjectAltName 1528 if (extension.extnID === id_SubjectAltName) 1529 subjectAltNames = subjectAltNames.concat(extension.parsedValue.altNames); 1530 //#endregion 1531 } 1532 } 1533 1534 //#region Checking for "required name forms" 1535 let formFound = (requiredNameForms.length <= 0); 1536 1537 for (let j = 0; j < requiredNameForms.length; j++) { 1538 switch (requiredNameForms[j].base.type) { 1539 case 4: // directoryName 1540 { 1541 if (requiredNameForms[j].base.value.typesAndValues.length !== cert.subject.typesAndValues.length) 1542 continue; 1543 1544 formFound = true; 1545 1546 for (let k = 0; k < cert.subject.typesAndValues.length; k++) { 1547 if (cert.subject.typesAndValues[k].type !== requiredNameForms[j].base.value.typesAndValues[k].type) { 1548 formFound = false; 1549 break; 1550 } 1551 } 1552 1553 if (formFound === true) 1554 break; 1555 } 1556 break; 1557 default: // ??? Probably here we should reject the certificate ??? 1558 } 1559 } 1560 1561 if (formFound === false) { 1562 policyResult.result = false; 1563 policyResult.resultCode = 21; 1564 policyResult.resultMessage = "No necessary name form found"; 1565 1566 throw policyResult; 1567 } 1568 //#endregion 1569 1570 //#region Checking for "permited sub-trees" 1571 //#region Make groups for all types of constraints 1572 const constrGroups: GeneralSubtree[][] = [ // Array of array for groupped constraints 1573 [], // rfc822Name 1574 [], // dNSName 1575 [], // directoryName 1576 [], // uniformResourceIdentifier 1577 [], // iPAddress 1578 ]; 1579 1580 for (let j = 0; j < permittedSubtrees.length; j++) { 1581 switch (permittedSubtrees[j].base.type) { 1582 //#region rfc822Name 1583 case 1: 1584 constrGroups[0].push(permittedSubtrees[j]); 1585 break; 1586 //#endregion 1587 //#region dNSName 1588 case 2: 1589 constrGroups[1].push(permittedSubtrees[j]); 1590 break; 1591 //#endregion 1592 //#region directoryName 1593 case 4: 1594 constrGroups[2].push(permittedSubtrees[j]); 1595 break; 1596 //#endregion 1597 //#region uniformResourceIdentifier 1598 case 6: 1599 constrGroups[3].push(permittedSubtrees[j]); 1600 break; 1601 //#endregion 1602 //#region iPAddress 1603 case 7: 1604 constrGroups[4].push(permittedSubtrees[j]); 1605 break; 1606 //#endregion 1607 //#region default 1608 default: 1609 //#endregion 1610 } 1611 } 1612 //#endregion 1613 1614 //#region Check name constraints groupped by type, one-by-one 1615 for (let p = 0; p < 5; p++) { 1616 let groupPermitted = false; 1617 let valueExists = false; 1618 const group = constrGroups[p]; 1619 1620 for (let j = 0; j < group.length; j++) { 1621 switch (p) { 1622 //#region rfc822Name 1623 case 0: 1624 if (subjectAltNames.length > 0) { 1625 for (let k = 0; k < subjectAltNames.length; k++) { 1626 if (subjectAltNames[k].type === 1) // rfc822Name 1627 { 1628 valueExists = true; 1629 groupPermitted = groupPermitted || compareRFC822Name(subjectAltNames[k].value, group[j].base.value); 1630 } 1631 } 1632 } 1633 else // Try to find out "emailAddress" inside "subject" 1634 { 1635 for (let k = 0; k < cert.subject.typesAndValues.length; k++) { 1636 if ((cert.subject.typesAndValues[k].type === "1.2.840.113549.1.9.1") || // PKCS#9 e-mail address 1637 (cert.subject.typesAndValues[k].type === "0.9.2342.19200300.100.1.3")) // RFC1274 "rfc822Mailbox" e-mail address 1638 { 1639 valueExists = true; 1640 groupPermitted = groupPermitted || compareRFC822Name(cert.subject.typesAndValues[k].value.valueBlock.value, group[j].base.value); 1641 } 1642 } 1643 } 1644 break; 1645 //#endregion 1646 //#region dNSName 1647 case 1: 1648 if (subjectAltNames.length > 0) { 1649 for (let k = 0; k < subjectAltNames.length; k++) { 1650 if (subjectAltNames[k].type === 2) // dNSName 1651 { 1652 valueExists = true; 1653 groupPermitted = groupPermitted || compareDNSName(subjectAltNames[k].value, group[j].base.value); 1654 } 1655 } 1656 } 1657 break; 1658 //#endregion 1659 //#region directoryName 1660 case 2: 1661 valueExists = true; 1662 groupPermitted = compareDirectoryName(cert.subject, group[j].base.value); 1663 break; 1664 //#endregion 1665 //#region uniformResourceIdentifier 1666 case 3: 1667 if (subjectAltNames.length > 0) { 1668 for (let k = 0; k < subjectAltNames.length; k++) { 1669 if (subjectAltNames[k].type === 6) // uniformResourceIdentifier 1670 { 1671 valueExists = true; 1672 groupPermitted = groupPermitted || compareUniformResourceIdentifier(subjectAltNames[k].value, group[j].base.value); 1673 } 1674 } 1675 } 1676 break; 1677 //#endregion 1678 //#region iPAddress 1679 case 4: 1680 if (subjectAltNames.length > 0) { 1681 for (let k = 0; k < subjectAltNames.length; k++) { 1682 if (subjectAltNames[k].type === 7) // iPAddress 1683 { 1684 valueExists = true; 1685 groupPermitted = groupPermitted || compareIPAddress(subjectAltNames[k].value, group[j].base.value); 1686 } 1687 } 1688 } 1689 break; 1690 //#endregion 1691 //#region default 1692 default: 1693 //#endregion 1694 } 1695 1696 if (groupPermitted) 1697 break; 1698 } 1699 1700 if ((groupPermitted === false) && (group.length > 0) && valueExists) { 1701 policyResult.result = false; 1702 policyResult.resultCode = 41; 1703 policyResult.resultMessage = "Failed to meet \"permitted sub-trees\" name constraint"; 1704 1705 throw policyResult; 1706 } 1707 } 1708 //#endregion 1709 //#endregion 1710 1711 //#region Checking for "excluded sub-trees" 1712 let excluded = false; 1713 1714 for (let j = 0; j < excludedSubtrees.length; j++) { 1715 switch (excludedSubtrees[j].base.type) { 1716 //#region rfc822Name 1717 case 1: 1718 if (subjectAltNames.length >= 0) { 1719 for (let k = 0; k < subjectAltNames.length; k++) { 1720 if (subjectAltNames[k].type === 1) // rfc822Name 1721 excluded = excluded || compareRFC822Name(subjectAltNames[k].value, excludedSubtrees[j].base.value); 1722 } 1723 } 1724 else // Try to find out "emailAddress" inside "subject" 1725 { 1726 for (let k = 0; k < cert.subject.typesAndValues.length; k++) { 1727 if ((cert.subject.typesAndValues[k].type === "1.2.840.113549.1.9.1") || // PKCS#9 e-mail address 1728 (cert.subject.typesAndValues[k].type === "0.9.2342.19200300.100.1.3")) // RFC1274 "rfc822Mailbox" e-mail address 1729 excluded = excluded || compareRFC822Name(cert.subject.typesAndValues[k].value.valueBlock.value, excludedSubtrees[j].base.value); 1730 } 1731 } 1732 break; 1733 //#endregion 1734 //#region dNSName 1735 case 2: 1736 if (subjectAltNames.length > 0) { 1737 for (let k = 0; k < subjectAltNames.length; k++) { 1738 if (subjectAltNames[k].type === 2) // dNSName 1739 excluded = excluded || compareDNSName(subjectAltNames[k].value, excludedSubtrees[j].base.value); 1740 } 1741 } 1742 break; 1743 //#endregion 1744 //#region directoryName 1745 case 4: 1746 excluded = excluded || compareDirectoryName(cert.subject, excludedSubtrees[j].base.value); 1747 break; 1748 //#endregion 1749 //#region uniformResourceIdentifier 1750 case 6: 1751 if (subjectAltNames.length > 0) { 1752 for (let k = 0; k < subjectAltNames.length; k++) { 1753 if (subjectAltNames[k].type === 6) // uniformResourceIdentifier 1754 excluded = excluded || compareUniformResourceIdentifier(subjectAltNames[k].value, excludedSubtrees[j].base.value); 1755 } 1756 } 1757 break; 1758 //#endregion 1759 //#region iPAddress 1760 case 7: 1761 if (subjectAltNames.length > 0) { 1762 for (let k = 0; k < subjectAltNames.length; k++) { 1763 if (subjectAltNames[k].type === 7) // iPAddress 1764 excluded = excluded || compareIPAddress(subjectAltNames[k].value, excludedSubtrees[j].base.value); 1765 } 1766 } 1767 break; 1768 //#endregion 1769 //#region default 1770 default: // No action, but probably here we need to create a warning for "malformed constraint" 1771 //#endregion 1772 } 1773 1774 if (excluded) 1775 break; 1776 } 1777 1778 if (excluded === true) { 1779 policyResult.result = false; 1780 policyResult.resultCode = 42; 1781 policyResult.resultMessage = "Failed to meet \"excluded sub-trees\" name constraint"; 1782 1783 throw policyResult; 1784 } 1785 //#endregion 1786 1787 //#region Append "cert_..._subtrees" to "..._subtrees" 1788 permittedSubtrees = permittedSubtrees.concat(certPermittedSubtrees); 1789 excludedSubtrees = excludedSubtrees.concat(certExcludedSubtrees); 1790 //#endregion 1791 } 1792 //#endregion 1793 1794 return policyResult; 1795 //#endregion 1796 } catch (error) { 1797 if (error instanceof Error) { 1798 if (error instanceof ChainValidationError) { 1799 return { 1800 result: false, 1801 resultCode: error.code, 1802 resultMessage: error.message, 1803 error: error, 1804 }; 1805 } 1806 1807 return { 1808 result: false, 1809 resultCode: ChainValidationCode.unknown, 1810 resultMessage: error.message, 1811 error: error, 1812 }; 1813 } 1814 1815 if (error && typeof error === "object" && "resultMessage" in error) { 1816 return error as CertificateChainValidationEngineVerifyResult; 1817 } 1818 1819 return { 1820 result: false, 1821 resultCode: -1, 1822 resultMessage: `${error}`, 1823 }; 1824 } 1825 } 1826 1827 }