tor-browser

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

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 }