Source code

Revision control

Copy as Markdown

Other Tools

import * as asn1js from "asn1js";
import * as pvtsutils from "pvtsutils";
import * as pvutils from "pvutils";
import { AuthorityKeyIdentifier } from "./AuthorityKeyIdentifier";
import { BasicOCSPResponse } from "./BasicOCSPResponse";
import { Certificate } from "./Certificate";
import { CertificateRevocationList } from "./CertificateRevocationList";
import * as common from "./common";
import * as Helpers from "./Helpers";
import { GeneralName } from "./GeneralName";
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";
import { RelativeDistinguishedNames } from "./RelativeDistinguishedNames";
import { GeneralSubtree } from "./GeneralSubtree";
import { EMPTY_STRING } from "./constants";
const TRUSTED_CERTS = "trustedCerts";
const CERTS = "certs";
const CRLS = "crls";
const OCSPS = "ocsps";
const CHECK_DATE = "checkDate";
const FIND_ORIGIN = "findOrigin";
const FIND_ISSUER = "findIssuer";
export enum ChainValidationCode {
unknown = -1,
success = 0,
noRevocation = 11,
noPath = 60,
noValidPath = 97,
}
export class ChainValidationError extends Error {
public static readonly NAME = "ChainValidationError";
public code: ChainValidationCode;
constructor(code: ChainValidationCode, message: string) {
super(message);
this.name = ChainValidationError.NAME;
this.code = code;
this.message = message;
}
}
export interface CertificateChainValidationEngineVerifyResult {
result: boolean;
resultCode: number;
resultMessage: string;
error?: Error | ChainValidationError;
authConstrPolicies?: string[];
userConstrPolicies?: string[];
explicitPolicyIndicator?: boolean;
policyMappings?: string[];
certificatePath?: Certificate[];
}
export type FindOriginCallback = (certificate: Certificate, validationEngine: CertificateChainValidationEngine) => string;
export type FindIssuerCallback = (certificate: Certificate, validationEngine: CertificateChainValidationEngine, crypto?: common.ICryptoEngine) => Promise<Certificate[]>;
export interface CertificateChainValidationEngineParameters {
trustedCerts?: Certificate[];
certs?: Certificate[];
crls?: CertificateRevocationList[];
ocsps?: BasicOCSPResponse[];
checkDate?: Date;
findOrigin?: FindOriginCallback;
findIssuer?: FindIssuerCallback;
}
interface CrlAndCertificate {
crl: CertificateRevocationList;
certificate: Certificate;
}
interface FindCrlResult {
status: number;
statusMessage: string;
result?: CrlAndCertificate[];
}
export interface CertificateChainValidationEngineVerifyParams {
initialPolicySet?: string[];
initialExplicitPolicy?: boolean;
initialPolicyMappingInhibit?: boolean;
initialInhibitPolicy?: boolean;
initialPermittedSubtreesSet?: GeneralSubtree[];
initialExcludedSubtreesSet?: GeneralSubtree[];
initialRequiredNameForms?: GeneralSubtree[];
passedWhenNotRevValues?: boolean;
}
/**
* Returns `true` if the certificate is in the trusted list, otherwise `false`
* @param cert A certificate that is expected to be in the trusted list
* @param trustedList List of trusted certificates
* @returns
*/
function isTrusted(cert: Certificate, trustedList: Certificate[]): boolean {
for (let i = 0; i < trustedList.length; i++) {
if (pvtsutils.BufferSourceConverter.isEqual(cert.tbsView, trustedList[i].tbsView)) {
return true;
}
}
return false;
}
/**
* Represents a chain-building engine for {@link Certificate} certificates.
*
* @example
* ```js The following example demonstrates how to verify certificate chain
* const rootCa = pkijs.Certificate.fromBER(certRaw1);
* const intermediateCa = pkijs.Certificate.fromBER(certRaw2);
* const leafCert = pkijs.Certificate.fromBER(certRaw3);
* const crl1 = pkijs.CertificateRevocationList.fromBER(crlRaw1);
* const ocsp1 = pkijs.BasicOCSPResponse.fromBER(ocspRaw1);
*
* const chainEngine = new pkijs.CertificateChainValidationEngine({
* certs: [rootCa, intermediateCa, leafCert],
* crls: [crl1],
* ocsps: [ocsp1],
* checkDate: new Date("2015-07-13"), // optional
* trustedCerts: [rootCa],
* });
*
* const chain = await chainEngine.verify();
* ```
*/
export class CertificateChainValidationEngine {
/**
* Array of pre-defined trusted (by user) certificates
*/
public trustedCerts: Certificate[];
/**
* Array with certificate chain. Could be only one end-user certificate in there!
*/
public certs: Certificate[];
/**
* Array of all CRLs for all certificates from certificate chain
*/
public crls: CertificateRevocationList[];
/**
* Array of all OCSP responses
*/
public ocsps: BasicOCSPResponse[];
/**
* The date at which the check would be
*/
public checkDate: Date;
/**
* The date at which the check would be
*/
public findOrigin: FindOriginCallback;
/**
* The date at which the check would be
*/
public findIssuer: FindIssuerCallback;
/**
* Constructor for CertificateChainValidationEngine class
* @param parameters
*/
constructor(parameters: CertificateChainValidationEngineParameters = {}) {
//#region Internal properties of the object
this.trustedCerts = pvutils.getParametersValue(parameters, TRUSTED_CERTS, this.defaultValues(TRUSTED_CERTS));
this.certs = pvutils.getParametersValue(parameters, CERTS, this.defaultValues(CERTS));
this.crls = pvutils.getParametersValue(parameters, CRLS, this.defaultValues(CRLS));
this.ocsps = pvutils.getParametersValue(parameters, OCSPS, this.defaultValues(OCSPS));
this.checkDate = pvutils.getParametersValue(parameters, CHECK_DATE, this.defaultValues(CHECK_DATE));
this.findOrigin = pvutils.getParametersValue(parameters, FIND_ORIGIN, this.defaultValues(FIND_ORIGIN));
this.findIssuer = pvutils.getParametersValue(parameters, FIND_ISSUER, this.defaultValues(FIND_ISSUER));
//#endregion
}
public static defaultFindOrigin(certificate: Certificate, validationEngine: CertificateChainValidationEngine): string {
//#region Firstly encode TBS for certificate
if (certificate.tbsView.byteLength === 0) {
certificate.tbsView = new Uint8Array(certificate.encodeTBS().toBER());
}
//#endregion
//#region Search in Intermediate Certificates
for (const localCert of validationEngine.certs) {
//#region Firstly encode TBS for certificate
if (localCert.tbsView.byteLength === 0) {
localCert.tbsView = new Uint8Array(localCert.encodeTBS().toBER());
}
//#endregion
if (pvtsutils.BufferSourceConverter.isEqual(certificate.tbsView, localCert.tbsView))
return "Intermediate Certificates";
}
//#endregion
//#region Search in Trusted Certificates
for (const trustedCert of validationEngine.trustedCerts) {
//#region Firstly encode TBS for certificate
if (trustedCert.tbsView.byteLength === 0)
trustedCert.tbsView = new Uint8Array(trustedCert.encodeTBS().toBER());
//#endregion
if (pvtsutils.BufferSourceConverter.isEqual(certificate.tbsView, trustedCert.tbsView))
return "Trusted Certificates";
}
//#endregion
return "Unknown";
}
public async defaultFindIssuer(certificate: Certificate, validationEngine: CertificateChainValidationEngine, crypto = common.getCrypto(true)): Promise<Certificate[]> {
//#region Initial variables
const result: Certificate[] = [];
let keyIdentifier: asn1js.OctetString | null = null;
let authorityCertIssuer: GeneralName[] | null = null;
let authorityCertSerialNumber: asn1js.Integer | null = null;
//#endregion
//#region Speed-up searching in case of self-signed certificates
if (certificate.subject.isEqual(certificate.issuer)) {
try {
const verificationResult = await certificate.verify(undefined, crypto);
if (verificationResult) {
return [certificate];
}
}
catch (ex) {
// nothing
}
}
//#endregion
//#region Find values to speed-up search
if (certificate.extensions) {
for (const extension of certificate.extensions) {
if (extension.extnID === id_AuthorityKeyIdentifier && extension.parsedValue instanceof AuthorityKeyIdentifier) {
if (extension.parsedValue.keyIdentifier) {
keyIdentifier = extension.parsedValue.keyIdentifier;
} else {
if (extension.parsedValue.authorityCertIssuer) {
authorityCertIssuer = extension.parsedValue.authorityCertIssuer;
}
if (extension.parsedValue.authorityCertSerialNumber) {
authorityCertSerialNumber = extension.parsedValue.authorityCertSerialNumber;
}
}
break;
}
}
}
//#endregion
// Aux function
function checkCertificate(possibleIssuer: Certificate): void {
//#region Firstly search for appropriate extensions
if (keyIdentifier !== null) {
if (possibleIssuer.extensions) {
let extensionFound = false;
for (const extension of possibleIssuer.extensions) {
if (extension.extnID === id_SubjectKeyIdentifier && extension.parsedValue) {
extensionFound = true;
if (pvtsutils.BufferSourceConverter.isEqual(extension.parsedValue.valueBlock.valueHex, keyIdentifier.valueBlock.valueHexView)) {
result.push(possibleIssuer);
}
break;
}
}
if (extensionFound) {
return;
}
}
}
//#endregion
//#region Now search for authorityCertSerialNumber
let authorityCertSerialNumberEqual = false;
if (authorityCertSerialNumber !== null)
authorityCertSerialNumberEqual = possibleIssuer.serialNumber.isEqual(authorityCertSerialNumber);
//#endregion
//#region And at least search for Issuer data
if (authorityCertIssuer !== null) {
if (possibleIssuer.subject.isEqual(authorityCertIssuer)) {
if (authorityCertSerialNumberEqual)
result.push(possibleIssuer);
}
}
else {
if (certificate.issuer.isEqual(possibleIssuer.subject))
result.push(possibleIssuer);
}
//#endregion
}
// Search in Trusted Certificates
for (const trustedCert of validationEngine.trustedCerts) {
checkCertificate(trustedCert);
}
// Search in Intermediate Certificates
for (const intermediateCert of validationEngine.certs) {
checkCertificate(intermediateCert);
}
// Now perform certificate verification checking
for (let i = 0; i < result.length; i++) {
try {
const verificationResult = await certificate.verify(result[i], crypto);
if (verificationResult === false)
result.splice(i, 1);
}
catch (ex) {
result.splice(i, 1); // Something wrong, remove the certificate
}
}
return result;
}
/**
* Returns default values for all class members
* @param memberName String name for a class member
* @returns Default value
*/
public defaultValues(memberName: typeof TRUSTED_CERTS): Certificate[];
public defaultValues(memberName: typeof CERTS): Certificate[];
public defaultValues(memberName: typeof CRLS): CertificateRevocationList[];
public defaultValues(memberName: typeof OCSPS): BasicOCSPResponse[];
public defaultValues(memberName: typeof CHECK_DATE): Date;
public defaultValues(memberName: typeof FIND_ORIGIN): FindOriginCallback;
public defaultValues(memberName: typeof FIND_ISSUER): FindIssuerCallback;
public defaultValues(memberName: string): any {
switch (memberName) {
case TRUSTED_CERTS:
return [];
case CERTS:
return [];
case CRLS:
return [];
case OCSPS:
return [];
case CHECK_DATE:
return new Date();
case FIND_ORIGIN:
return CertificateChainValidationEngine.defaultFindOrigin;
case FIND_ISSUER:
return this.defaultFindIssuer;
default:
throw new Error(`Invalid member name for CertificateChainValidationEngine class: ${memberName}`);
}
}
public async sort(passedWhenNotRevValues = false, crypto = common.getCrypto(true)): Promise<Certificate[]> {
// Initial variables
const localCerts: Certificate[] = [];
//#region Building certificate path
const buildPath = async (certificate: Certificate, crypto: common.ICryptoEngine): Promise<Certificate[][]> => {
const result: Certificate[][] = [];
// Aux function checking array for unique elements
function checkUnique(array: Certificate[]): boolean {
let unique = true;
for (let i = 0; i < array.length; i++) {
for (let j = 0; j < array.length; j++) {
if (j === i)
continue;
if (array[i] === array[j]) {
unique = false;
break;
}
}
if (!unique)
break;
}
return unique;
}
if (isTrusted(certificate, this.trustedCerts)) {
return [[certificate]];
}
const findIssuerResult = await this.findIssuer(certificate, this, crypto);
if (findIssuerResult.length === 0) {
throw new Error("No valid certificate paths found");
}
for (let i = 0; i < findIssuerResult.length; i++) {
if (pvtsutils.BufferSourceConverter.isEqual(findIssuerResult[i].tbsView, certificate.tbsView)) {
result.push([findIssuerResult[i]]);
continue;
}
const buildPathResult = await buildPath(findIssuerResult[i], crypto);
for (let j = 0; j < buildPathResult.length; j++) {
const copy = buildPathResult[j].slice();
copy.splice(0, 0, findIssuerResult[i]);
if (checkUnique(copy))
result.push(copy);
else
result.push(buildPathResult[j]);
}
}
return result;
};
//#endregion
//#region Find CRL for specific certificate
const findCRL = async (certificate: Certificate): Promise<FindCrlResult> => {
//#region Initial variables
const issuerCertificates: Certificate[] = [];
const crls: CertificateRevocationList[] = [];
const crlsAndCertificates: CrlAndCertificate[] = [];
//#endregion
//#region Find all possible CRL issuers
issuerCertificates.push(...localCerts.filter(element => certificate.issuer.isEqual(element.subject)));
if (issuerCertificates.length === 0) {
return {
status: 1,
statusMessage: "No certificate's issuers"
};
}
//#endregion
//#region Find all CRLs for certificate's issuer
crls.push(...this.crls.filter(o => o.issuer.isEqual(certificate.issuer)));
if (crls.length === 0) {
return {
status: 2,
statusMessage: "No CRLs for specific certificate issuer"
};
}
//#endregion
//#region Find specific certificate of issuer for each CRL
for (let i = 0; i < crls.length; i++) {
const crl = crls[i];
//#region Check "nextUpdate" for the CRL
// The "nextUpdate" is older than CHECK_DATE.
// Thus we should do have another, updated CRL.
// Thus the CRL assumed to be invalid.
if (crl.nextUpdate && crl.nextUpdate.value < this.checkDate) {
continue;
}
//#endregion
for (let j = 0; j < issuerCertificates.length; j++) {
try {
const result = await crls[i].verify({ issuerCertificate: issuerCertificates[j] }, crypto);
if (result) {
crlsAndCertificates.push({
crl: crls[i],
certificate: issuerCertificates[j]
});
break;
}
}
catch (ex) {
// nothing
}
}
}
//#endregion
if (crlsAndCertificates.length) {
return {
status: 0,
statusMessage: EMPTY_STRING,
result: crlsAndCertificates
};
}
return {
status: 3,
statusMessage: "No valid CRLs found"
};
};
//#endregion
//#region Find OCSP for specific certificate
const findOCSP = async (certificate: Certificate, issuerCertificate: Certificate): Promise<number> => {
//#region Get hash algorithm from certificate
const hashAlgorithm = crypto.getAlgorithmByOID<any>(certificate.signatureAlgorithm.algorithmId);
if (!hashAlgorithm.name) {
return 1;
}
if (!hashAlgorithm.hash) {
return 1;
}
//#endregion
//#region Search for OCSP response for the certificate
for (let i = 0; i < this.ocsps.length; i++) {
const ocsp = this.ocsps[i];
const result = await ocsp.getCertificateStatus(certificate, issuerCertificate, crypto);
if (result.isForCertificate) {
if (result.status === 0)
return 0;
return 1;
}
}
//#endregion
return 2;
};
//#endregion
//#region Check for certificate to be CA
async function checkForCA(certificate: Certificate, needToCheckCRL = false) {
//#region Initial variables
let isCA = false;
let mustBeCA = false;
let keyUsagePresent = false;
let cRLSign = false;
//#endregion
if (certificate.extensions) {
for (let j = 0; j < certificate.extensions.length; j++) {
const extension = certificate.extensions[j];
if (extension.critical && !extension.parsedValue) {
return {
result: false,
resultCode: 6,
resultMessage: `Unable to parse critical certificate extension: ${extension.extnID}`
};
}
if (extension.extnID === id_KeyUsage) // KeyUsage
{
keyUsagePresent = true;
const view = new Uint8Array(extension.parsedValue.valueBlock.valueHex);
if ((view[0] & 0x04) === 0x04) // Set flag "keyCertSign"
mustBeCA = true;
if ((view[0] & 0x02) === 0x02) // Set flag "cRLSign"
cRLSign = true;
}
if (extension.extnID === id_BasicConstraints) // BasicConstraints
{
if ("cA" in extension.parsedValue) {
if (extension.parsedValue.cA === true)
isCA = true;
}
}
}
if ((mustBeCA === true) && (isCA === false)) {
return {
result: false,
resultCode: 3,
resultMessage: "Unable to build certificate chain - using \"keyCertSign\" flag set without BasicConstraints"
};
}
if ((keyUsagePresent === true) && (isCA === true) && (mustBeCA === false)) {
return {
result: false,
resultCode: 4,
resultMessage: "Unable to build certificate chain - \"keyCertSign\" flag was not set"
};
}
if ((isCA === true) && (keyUsagePresent === true) && ((needToCheckCRL) && (cRLSign === false))) {
return {
result: false,
resultCode: 5,
resultMessage: "Unable to build certificate chain - intermediate certificate must have \"cRLSign\" key usage flag"
};
}
}
if (isCA === false) {
return {
result: false,
resultCode: 7,
resultMessage: "Unable to build certificate chain - more than one possible end-user certificate"
};
}
return {
result: true,
resultCode: 0,
resultMessage: EMPTY_STRING
};
}
//#endregion
//#region Basic check for certificate path
const basicCheck = async (path: Certificate[], checkDate: Date): Promise<{ result: boolean; resultCode?: number; resultMessage?: string; }> => {
//#region Check that all dates are valid
for (let i = 0; i < path.length; i++) {
if ((path[i].notBefore.value > checkDate) ||
(path[i].notAfter.value < checkDate)) {
return {
result: false,
resultCode: 8,
resultMessage: "The certificate is either not yet valid or expired"
};
}
}
//#endregion
//#region Check certificate name chain
// We should have at least two certificates: end entity and trusted root
if (path.length < 2) {
return {
result: false,
resultCode: 9,
resultMessage: "Too short certificate path"
};
}
for (let i = (path.length - 2); i >= 0; i--) {
//#region Check that we do not have a "self-signed" certificate
if (path[i].issuer.isEqual(path[i].subject) === false) {
if (path[i].issuer.isEqual(path[i + 1].subject) === false) {
return {
result: false,
resultCode: 10,
resultMessage: "Incorrect name chaining"
};
}
}
//#endregion
}
//#endregion
//#region Check each certificate (except "trusted root") to be non-revoked
if ((this.crls.length !== 0) || (this.ocsps.length !== 0)) // If CRLs and OCSPs are empty then we consider all certificates to be valid
{
for (let i = 0; i < (path.length - 1); i++) {
//#region Initial variables
let ocspResult = 2;
let crlResult: FindCrlResult = {
status: 0,
statusMessage: EMPTY_STRING
};
//#endregion
//#region Check OCSPs first
if (this.ocsps.length !== 0) {
ocspResult = await findOCSP(path[i], path[i + 1]);
switch (ocspResult) {
case 0:
continue;
case 1:
return {
result: false,
resultCode: 12,
resultMessage: "One of certificates was revoked via OCSP response"
};
case 2: // continue to check the certificate with CRL
break;
default:
}
}
//#endregion
//#region Check CRLs
if (this.crls.length !== 0) {
crlResult = await findCRL(path[i]);
if (crlResult.status === 0 && crlResult.result) {
for (let j = 0; j < crlResult.result.length; j++) {
//#region Check that the CRL issuer certificate have not been revoked
const isCertificateRevoked = crlResult.result[j].crl.isCertificateRevoked(path[i]);
if (isCertificateRevoked) {
return {
result: false,
resultCode: 12,
resultMessage: "One of certificates had been revoked"
};
}
//#endregion
//#region Check that the CRL issuer certificate is a CA certificate
const isCertificateCA = await checkForCA(crlResult.result[j].certificate, true);
if (isCertificateCA.result === false) {
return {
result: false,
resultCode: 13,
resultMessage: "CRL issuer certificate is not a CA certificate or does not have crlSign flag"
};
}
//#endregion
}
} else {
if (passedWhenNotRevValues === false) {
throw new ChainValidationError(ChainValidationCode.noRevocation, `No revocation values found for one of certificates: ${crlResult.statusMessage}`);
}
}
} else {
if (ocspResult === 2) {
return {
result: false,
resultCode: 11,
resultMessage: "No revocation values found for one of certificates"
};
}
}
//#endregion
//#region Check we do have links to revocation values inside issuer's certificate
if ((ocspResult === 2) && (crlResult.status === 2) && passedWhenNotRevValues) {
const issuerCertificate = path[i + 1];
let extensionFound = false;
if (issuerCertificate.extensions) {
for (const extension of issuerCertificate.extensions) {
switch (extension.extnID) {
case id_CRLDistributionPoints:
case id_FreshestCRL:
case id_AuthorityInfoAccess:
extensionFound = true;
break;
default:
}
}
}
if (extensionFound) {
throw new ChainValidationError(ChainValidationCode.noRevocation, `No revocation values found for one of certificates: ${crlResult.statusMessage}`);
}
}
//#endregion
}
}
//#endregion
//#region Check each certificate (except "end entity") in the path to be a CA certificate
for (const [i, cert] of path.entries()) {
if (!i) {
// Skip entity certificate
continue;
}
const result = await checkForCA(cert);
if (!result.result) {
return {
result: false,
resultCode: 14,
resultMessage: "One of intermediate certificates is not a CA certificate"
};
}
}
//#endregion
return {
result: true
};
};
//#endregion
//#region Do main work
//#region Initialize "localCerts" by value of "this.certs" + "this.trustedCerts" arrays
localCerts.push(...this.trustedCerts);
localCerts.push(...this.certs);
//#endregion
//#region Check all certificates for been unique
for (let i = 0; i < localCerts.length; i++) {
for (let j = 0; j < localCerts.length; j++) {
if (i === j)
continue;
if (pvtsutils.BufferSourceConverter.isEqual(localCerts[i].tbsView, localCerts[j].tbsView)) {
localCerts.splice(j, 1);
i = 0;
break;
}
}
}
//#endregion
const leafCert = localCerts[localCerts.length - 1];
//#region Initial variables
let result;
const certificatePath = [leafCert]; // The "end entity" certificate must be the least in CERTS array
//#endregion
//#region Build path for "end entity" certificate
result = await buildPath(leafCert, crypto);
if (result.length === 0) {
throw new ChainValidationError(ChainValidationCode.noPath, "Unable to find certificate path");
}
//#endregion
//#region Exclude certificate paths not ended with "trusted roots"
for (let i = 0; i < result.length; i++) {
let found = false;
for (let j = 0; j < (result[i]).length; j++) {
const certificate = (result[i])[j];
for (let k = 0; k < this.trustedCerts.length; k++) {
if (pvtsutils.BufferSourceConverter.isEqual(certificate.tbsView, this.trustedCerts[k].tbsView)) {
found = true;
break;
}
}
if (found)
break;
}
if (!found) {
result.splice(i, 1);
i = 0;
}
}
if (result.length === 0) {
throw new ChainValidationError(ChainValidationCode.noValidPath, "No valid certificate paths found");
}
//#endregion
//#region Find shortest certificate path (for the moment it is the only criteria)
let shortestLength = result[0].length;
let shortestIndex = 0;
for (let i = 0; i < result.length; i++) {
if (result[i].length < shortestLength) {
shortestLength = result[i].length;
shortestIndex = i;
}
}
//#endregion
//#region Create certificate path for basic check
for (let i = 0; i < result[shortestIndex].length; i++)
certificatePath.push((result[shortestIndex])[i]);
//#endregion
//#region Perform basic checking for all certificates in the path
result = await basicCheck(certificatePath, this.checkDate);
if (result.result === false)
throw result;
//#endregion
return certificatePath;
//#endregion
}
/**
* Major verification function for certificate chain.
* @param parameters
* @param crypto Crypto engine
* @returns
*/
async verify(parameters: CertificateChainValidationEngineVerifyParams = {}, crypto = common.getCrypto(true)): Promise<CertificateChainValidationEngineVerifyResult> {
//#region Auxiliary functions for name constraints checking
/**
* Compare two dNSName values
* @param name DNS from name
* @param constraint Constraint for DNS from name
* @returns Boolean result - valid or invalid the "name" against the "constraint"
*/
function compareDNSName(name: string, constraint: string): boolean {
//#region Make a "string preparation" for both name and constrain
const namePrepared = Helpers.stringPrep(name);
const constraintPrepared = Helpers.stringPrep(constraint);
//#endregion
//#region Make a "splitted" versions of "constraint" and "name"
const nameSplitted = namePrepared.split(".");
const constraintSplitted = constraintPrepared.split(".");
//#endregion
//#region Length calculation and additional check
const nameLen = nameSplitted.length;
const constrLen = constraintSplitted.length;
if ((nameLen === 0) || (constrLen === 0) || (nameLen < constrLen)) {
return false;
}
//#endregion
//#region Check that no part of "name" has zero length
for (let i = 0; i < nameLen; i++) {
if (nameSplitted[i].length === 0) {
return false;
}
}
//#endregion
//#region Check that no part of "constraint" has zero length
for (let i = 0; i < constrLen; i++) {
if (constraintSplitted[i].length === 0) {
if (i === 0) {
if (constrLen === 1) {
return false;
}
continue;
}
return false;
}
}
//#endregion
//#region Check that "name" has a tail as "constraint"
for (let i = 0; i < constrLen; i++) {
if (constraintSplitted[constrLen - 1 - i].length === 0) {
continue;
}
if (nameSplitted[nameLen - 1 - i].localeCompare(constraintSplitted[constrLen - 1 - i]) !== 0) {
return false;
}
}
//#endregion
return true;
}
/**
* Compare two rfc822Name values
* @param name E-mail address from name
* @param constraint Constraint for e-mail address from name
* @returns Boolean result - valid or invalid the "name" against the "constraint"
*/
function compareRFC822Name(name: string, constraint: string): boolean {
//#region Make a "string preparation" for both name and constrain
const namePrepared = Helpers.stringPrep(name);
const constraintPrepared = Helpers.stringPrep(constraint);
//#endregion
//#region Make a "splitted" versions of "constraint" and "name"
const nameSplitted = namePrepared.split("@");
const constraintSplitted = constraintPrepared.split("@");
//#endregion
//#region Splitted array length checking
if ((nameSplitted.length === 0) || (constraintSplitted.length === 0) || (nameSplitted.length < constraintSplitted.length))
return false;
//#endregion
if (constraintSplitted.length === 1) {
const result = compareDNSName(nameSplitted[1], constraintSplitted[0]);
if (result) {
//#region Make a "splitted" versions of domain name from "constraint" and "name"
const ns = nameSplitted[1].split(".");
const cs = constraintSplitted[0].split(".");
//#endregion
if (cs[0].length === 0)
return true;
return ns.length === cs.length;
}
return false;
}
return (namePrepared.localeCompare(constraintPrepared) === 0);
}
/**
* Compare two uniformResourceIdentifier values
* @param name uniformResourceIdentifier from name
* @param constraint Constraint for uniformResourceIdentifier from name
* @returns Boolean result - valid or invalid the "name" against the "constraint"
*/
function compareUniformResourceIdentifier(name: string, constraint: string): boolean {
//#region Make a "string preparation" for both name and constrain
let namePrepared = Helpers.stringPrep(name);
const constraintPrepared = Helpers.stringPrep(constraint);
//#endregion
//#region Find out a major URI part to compare with
const ns = namePrepared.split("/");
const cs = constraintPrepared.split("/");
if (cs.length > 1) // Malformed constraint
return false;
if (ns.length > 1) // Full URI string
{
for (let i = 0; i < ns.length; i++) {
if ((ns[i].length > 0) && (ns[i].charAt(ns[i].length - 1) !== ":")) {
const nsPort = ns[i].split(":");
namePrepared = nsPort[0];
break;
}
}
}
//#endregion
const result = compareDNSName(namePrepared, constraintPrepared);
if (result) {
//#region Make a "splitted" versions of "constraint" and "name"
const nameSplitted = namePrepared.split(".");
const constraintSplitted = constraintPrepared.split(".");
//#endregion
if (constraintSplitted[0].length === 0)
return true;
return nameSplitted.length === constraintSplitted.length;
}
return false;
}
/**
* Compare two iPAddress values
* @param name iPAddress from name
* @param constraint Constraint for iPAddress from name
* @returns Boolean result - valid or invalid the "name" against the "constraint"
*/
function compareIPAddress(name: asn1js.OctetString, constraint: asn1js.OctetString): boolean {
//#region Common variables
const nameView = name.valueBlock.valueHexView;
const constraintView = constraint.valueBlock.valueHexView;
//#endregion
//#region Work with IPv4 addresses
if ((nameView.length === 4) && (constraintView.length === 8)) {
for (let i = 0; i < 4; i++) {
if ((nameView[i] ^ constraintView[i]) & constraintView[i + 4])
return false;
}
return true;
}
//#endregion
//#region Work with IPv6 addresses
if ((nameView.length === 16) && (constraintView.length === 32)) {
for (let i = 0; i < 16; i++) {
if ((nameView[i] ^ constraintView[i]) & constraintView[i + 16])
return false;
}
return true;
}
//#endregion
return false;
}
/**
* Compare two directoryName values
* @param name directoryName from name
* @param constraint Constraint for directoryName from name
* @returns Boolean result - valid or invalid the "name" against the "constraint"
*/
function compareDirectoryName(name: RelativeDistinguishedNames, constraint: RelativeDistinguishedNames): boolean {
//#region Initial check
if ((name.typesAndValues.length === 0) || (constraint.typesAndValues.length === 0))
return true;
if (name.typesAndValues.length < constraint.typesAndValues.length)
return false;
//#endregion
//#region Initial variables
let result = true;
let nameStart = 0;
//#endregion
for (let i = 0; i < constraint.typesAndValues.length; i++) {
let localResult = false;
for (let j = nameStart; j < name.typesAndValues.length; j++) {
localResult = name.typesAndValues[j].isEqual(constraint.typesAndValues[i]);
if (name.typesAndValues[j].type === constraint.typesAndValues[i].type)
result = result && localResult;
if (localResult === true) {
if ((nameStart === 0) || (nameStart === j)) {
nameStart = j + 1;
break;
}
else // Structure of "name" must be the same with "constraint"
return false;
}
}
if (localResult === false)
return false;
}
return (nameStart === 0) ? false : result;
}
//#endregion
try {
//#region Initial checks
if (this.certs.length === 0)
throw new Error("Empty certificate array");
//#endregion
//#region Get input variables
const passedWhenNotRevValues = parameters.passedWhenNotRevValues || false;
const initialPolicySet = parameters.initialPolicySet || [id_AnyPolicy];
const initialExplicitPolicy = parameters.initialExplicitPolicy || false;
const initialPolicyMappingInhibit = parameters.initialPolicyMappingInhibit || false;
const initialInhibitPolicy = parameters.initialInhibitPolicy || false;
const initialPermittedSubtreesSet = parameters.initialPermittedSubtreesSet || [];
const initialExcludedSubtreesSet = parameters.initialExcludedSubtreesSet || [];
const initialRequiredNameForms = parameters.initialRequiredNameForms || [];
let explicitPolicyIndicator = initialExplicitPolicy;
let policyMappingInhibitIndicator = initialPolicyMappingInhibit;
let inhibitAnyPolicyIndicator = initialInhibitPolicy;
const pendingConstraints = [
false, // For "explicitPolicyPending"
false, // For "policyMappingInhibitPending"
false, // For "inhibitAnyPolicyPending"
];
let explicitPolicyPending = 0;
let policyMappingInhibitPending = 0;
let inhibitAnyPolicyPending = 0;
let permittedSubtrees = initialPermittedSubtreesSet;
let excludedSubtrees = initialExcludedSubtreesSet;
const requiredNameForms = initialRequiredNameForms;
let pathDepth = 1;
//#endregion
//#region Sorting certificates in the chain array
this.certs = await this.sort(passedWhenNotRevValues, crypto);
//#endregion
//#region Work with policies
//#region Support variables
const allPolicies: string[] = []; // Array of all policies (string values)
allPolicies.push(id_AnyPolicy); // Put "anyPolicy" at first place
const policiesAndCerts = []; // In fact "array of array" where rows are for each specific policy, column for each certificate and value is "true/false"
const anyPolicyArray = new Array(this.certs.length - 1); // Minus "trusted anchor"
for (let ii = 0; ii < (this.certs.length - 1); ii++)
anyPolicyArray[ii] = true;
policiesAndCerts.push(anyPolicyArray);
const policyMappings = new Array(this.certs.length - 1); // Array of "PolicyMappings" for each certificate
const certPolicies = new Array(this.certs.length - 1); // Array of "CertificatePolicies" for each certificate
let explicitPolicyStart = (explicitPolicyIndicator) ? (this.certs.length - 1) : (-1);
//#endregion
//#region Gather all necessary information from certificate chain
for (let i = (this.certs.length - 2); i >= 0; i--, pathDepth++) {
const cert = this.certs[i];
if (cert.extensions) {
//#region Get information about certificate extensions
for (let j = 0; j < cert.extensions.length; j++) {
const extension = cert.extensions[j];
//#region CertificatePolicies
if (extension.extnID === id_CertificatePolicies) {
certPolicies[i] = extension.parsedValue;
//#region Remove entry from "anyPolicies" for the certificate
for (let s = 0; s < allPolicies.length; s++) {
if (allPolicies[s] === id_AnyPolicy) {
delete (policiesAndCerts[s])[i];
break;
}
}
//#endregion
for (let k = 0; k < extension.parsedValue.certificatePolicies.length; k++) {
let policyIndex = (-1);
const policyId = extension.parsedValue.certificatePolicies[k].policyIdentifier;
//#region Try to find extension in "allPolicies" array
for (let s = 0; s < allPolicies.length; s++) {
if (policyId === allPolicies[s]) {
policyIndex = s;
break;
}
}
//#endregion
if (policyIndex === (-1)) {
allPolicies.push(policyId);
const certArray = new Array(this.certs.length - 1);
certArray[i] = true;
policiesAndCerts.push(certArray);
}
else
(policiesAndCerts[policyIndex])[i] = true;
}
}
//#endregion
//#region PolicyMappings
if (extension.extnID === id_PolicyMappings) {
if (policyMappingInhibitIndicator) {
return {
result: false,
resultCode: 98,
resultMessage: "Policy mapping prohibited"
};
}
policyMappings[i] = extension.parsedValue;
}
//#endregion
//#region PolicyConstraints
if (extension.extnID === id_PolicyConstraints) {
if (explicitPolicyIndicator === false) {
//#region requireExplicitPolicy
if (extension.parsedValue.requireExplicitPolicy === 0) {
explicitPolicyIndicator = true;
explicitPolicyStart = i;
}
else {
if (pendingConstraints[0] === false) {
pendingConstraints[0] = true;
explicitPolicyPending = extension.parsedValue.requireExplicitPolicy;
}
else
explicitPolicyPending = (explicitPolicyPending > extension.parsedValue.requireExplicitPolicy) ? extension.parsedValue.requireExplicitPolicy : explicitPolicyPending;
}
//#endregion
//#region inhibitPolicyMapping
if (extension.parsedValue.inhibitPolicyMapping === 0)
policyMappingInhibitIndicator = true;
else {
if (pendingConstraints[1] === false) {
pendingConstraints[1] = true;
policyMappingInhibitPending = extension.parsedValue.inhibitPolicyMapping + 1;
}
else
policyMappingInhibitPending = (policyMappingInhibitPending > (extension.parsedValue.inhibitPolicyMapping + 1)) ? (extension.parsedValue.inhibitPolicyMapping + 1) : policyMappingInhibitPending;
}
//#endregion
}
}
//#endregion
//#region InhibitAnyPolicy
if (extension.extnID === id_InhibitAnyPolicy) {
if (inhibitAnyPolicyIndicator === false) {
if (extension.parsedValue.valueBlock.valueDec === 0)
inhibitAnyPolicyIndicator = true;
else {
if (pendingConstraints[2] === false) {
pendingConstraints[2] = true;
inhibitAnyPolicyPending = extension.parsedValue.valueBlock.valueDec;
}
else
inhibitAnyPolicyPending = (inhibitAnyPolicyPending > extension.parsedValue.valueBlock.valueDec) ? extension.parsedValue.valueBlock.valueDec : inhibitAnyPolicyPending;
}
}
}
//#endregion
}
//#endregion
//#region Check "inhibitAnyPolicyIndicator"
if (inhibitAnyPolicyIndicator === true) {
let policyIndex = (-1);
//#region Find "anyPolicy" index
for (let searchAnyPolicy = 0; searchAnyPolicy < allPolicies.length; searchAnyPolicy++) {
if (allPolicies[searchAnyPolicy] === id_AnyPolicy) {
policyIndex = searchAnyPolicy;
break;
}
}
//#endregion
if (policyIndex !== (-1))
delete (policiesAndCerts[0])[i]; // Unset value to "undefined" for "anyPolicies" value for current certificate
}
//#endregion
//#region Process with "pending constraints"
if (explicitPolicyIndicator === false) {
if (pendingConstraints[0] === true) {
explicitPolicyPending--;
if (explicitPolicyPending === 0) {
explicitPolicyIndicator = true;
explicitPolicyStart = i;
pendingConstraints[0] = false;
}
}
}
if (policyMappingInhibitIndicator === false) {
if (pendingConstraints[1] === true) {
policyMappingInhibitPending--;
if (policyMappingInhibitPending === 0) {
policyMappingInhibitIndicator = true;
pendingConstraints[1] = false;
}
}
}
if (inhibitAnyPolicyIndicator === false) {
if (pendingConstraints[2] === true) {
inhibitAnyPolicyPending--;
if (inhibitAnyPolicyPending === 0) {
inhibitAnyPolicyIndicator = true;
pendingConstraints[2] = false;
}
}
}
//#endregion
}
}
//#endregion
//#region Working with policy mappings
for (let i = 0; i < (this.certs.length - 1); i++) {
//#region Check that there is "policy mapping" for level "i + 1"
if ((i < (this.certs.length - 2)) && (typeof policyMappings[i + 1] !== "undefined")) {
for (let k = 0; k < policyMappings[i + 1].mappings.length; k++) {
//#region Check that we do not have "anyPolicy" in current mapping
if ((policyMappings[i + 1].mappings[k].issuerDomainPolicy === id_AnyPolicy) || (policyMappings[i + 1].mappings[k].subjectDomainPolicy === id_AnyPolicy)) {
return {
result: false,
resultCode: 99,
resultMessage: "The \"anyPolicy\" should not be a part of policy mapping scheme"
};
}
//#endregion
//#region Initial variables
let issuerDomainPolicyIndex = (-1);
let subjectDomainPolicyIndex = (-1);
//#endregion
//#region Search for index of policies indexes
for (let n = 0; n < allPolicies.length; n++) {
if (allPolicies[n] === policyMappings[i + 1].mappings[k].issuerDomainPolicy)
issuerDomainPolicyIndex = n;
if (allPolicies[n] === policyMappings[i + 1].mappings[k].subjectDomainPolicy)
subjectDomainPolicyIndex = n;
}
//#endregion
//#region Delete existing "issuerDomainPolicy" because on the level we mapped the policy to another one
if (typeof (policiesAndCerts[issuerDomainPolicyIndex])[i] !== "undefined")
delete (policiesAndCerts[issuerDomainPolicyIndex])[i];
//#endregion
//#region Check all policies for the certificate
for (let j = 0; j < certPolicies[i].certificatePolicies.length; j++) {
if (policyMappings[i + 1].mappings[k].subjectDomainPolicy === certPolicies[i].certificatePolicies[j].policyIdentifier) {
//#region Set mapped policy for current certificate
if ((issuerDomainPolicyIndex !== (-1)) && (subjectDomainPolicyIndex !== (-1))) {
for (let m = 0; m <= i; m++) {
if (typeof (policiesAndCerts[subjectDomainPolicyIndex])[m] !== "undefined") {
(policiesAndCerts[issuerDomainPolicyIndex])[m] = true;
delete (policiesAndCerts[subjectDomainPolicyIndex])[m];
}
}
}
//#endregion
}
}
//#endregion
}
}
//#endregion
}
//#endregion
//#region Working with "explicitPolicyIndicator" and "anyPolicy"
for (let i = 0; i < allPolicies.length; i++) {
if (allPolicies[i] === id_AnyPolicy) {
for (let j = 0; j < explicitPolicyStart; j++)
delete (policiesAndCerts[i])[j];
}
}
//#endregion
//#region Create "set of authorities-constrained policies"
const authConstrPolicies = [];
for (let i = 0; i < policiesAndCerts.length; i++) {
let found = true;
for (let j = 0; j < (this.certs.length - 1); j++) {
let anyPolicyFound = false;
if ((j < explicitPolicyStart) && (allPolicies[i] === id_AnyPolicy) && (allPolicies.length > 1)) {
found = false;
break;
}
if (typeof (policiesAndCerts[i])[j] === "undefined") {
if (j >= explicitPolicyStart) {
//#region Search for "anyPolicy" in the policy set
for (let k = 0; k < allPolicies.length; k++) {
if (allPolicies[k] === id_AnyPolicy) {
if ((policiesAndCerts[k])[j] === true)
anyPolicyFound = true;
break;
}
}
//#endregion
}
if (!anyPolicyFound) {
found = false;
break;
}
}
}
if (found === true)
authConstrPolicies.push(allPolicies[i]);
}
//#endregion
//#region Create "set of user-constrained policies"
let userConstrPolicies: string[] = [];
if ((initialPolicySet.length === 1) && (initialPolicySet[0] === id_AnyPolicy) && (explicitPolicyIndicator === false))
userConstrPolicies = initialPolicySet;
else {
if ((authConstrPolicies.length === 1) && (authConstrPolicies[0] === id_AnyPolicy))
userConstrPolicies = initialPolicySet;
else {
for (let i = 0; i < authConstrPolicies.length; i++) {
for (let j = 0; j < initialPolicySet.length; j++) {
if ((initialPolicySet[j] === authConstrPolicies[i]) || (initialPolicySet[j] === id_AnyPolicy)) {
userConstrPolicies.push(authConstrPolicies[i]);
break;
}
}
}
}
}
//#endregion
//#region Combine output object
const policyResult: CertificateChainValidationEngineVerifyResult = {
result: (userConstrPolicies.length > 0),
resultCode: 0,
resultMessage: (userConstrPolicies.length > 0) ? EMPTY_STRING : "Zero \"userConstrPolicies\" array, no intersections with \"authConstrPolicies\"",
authConstrPolicies,
userConstrPolicies,
explicitPolicyIndicator,
policyMappings,
certificatePath: this.certs
};
if (userConstrPolicies.length === 0)
return policyResult;
//#endregion
//#endregion
//#region Work with name constraints
//#region Check a result from "policy checking" part
if (policyResult.result === false)
return policyResult;
//#endregion
//#region Check all certificates, excluding "trust anchor"
pathDepth = 1;
for (let i = (this.certs.length - 2); i >= 0; i--, pathDepth++) {
const cert = this.certs[i];
//#region Support variables
let subjectAltNames: GeneralName[] = [];
let certPermittedSubtrees: GeneralSubtree[] = [];
let certExcludedSubtrees: GeneralSubtree[] = [];
//#endregion
if (cert.extensions) {
for (let j = 0; j < cert.extensions.length; j++) {
const extension = cert.extensions[j];
//#region NameConstraints
if (extension.extnID === id_NameConstraints) {
if ("permittedSubtrees" in extension.parsedValue)
certPermittedSubtrees = certPermittedSubtrees.concat(extension.parsedValue.permittedSubtrees);
if ("excludedSubtrees" in extension.parsedValue)
certExcludedSubtrees = certExcludedSubtrees.concat(extension.parsedValue.excludedSubtrees);
}
//#endregion
//#region SubjectAltName
if (extension.extnID === id_SubjectAltName)
subjectAltNames = subjectAltNames.concat(extension.parsedValue.altNames);
//#endregion
}
}
//#region Checking for "required name forms"
let formFound = (requiredNameForms.length <= 0);
for (let j = 0; j < requiredNameForms.length; j++) {
switch (requiredNameForms[j].base.type) {
case 4: // directoryName
{
if (requiredNameForms[j].base.value.typesAndValues.length !== cert.subject.typesAndValues.length)
continue;
formFound = true;
for (let k = 0; k < cert.subject.typesAndValues.length; k++) {
if (cert.subject.typesAndValues[k].type !== requiredNameForms[j].base.value.typesAndValues[k].type) {
formFound = false;
break;
}
}
if (formFound === true)
break;
}
break;
default: // ??? Probably here we should reject the certificate ???
}
}
if (formFound === false) {
policyResult.result = false;
policyResult.resultCode = 21;
policyResult.resultMessage = "No necessary name form found";
throw policyResult;
}
//#endregion
//#region Checking for "permited sub-trees"
//#region Make groups for all types of constraints
const constrGroups: GeneralSubtree[][] = [ // Array of array for groupped constraints
[], // rfc822Name
[], // dNSName
[], // directoryName
[], // uniformResourceIdentifier
[], // iPAddress
];
for (let j = 0; j < permittedSubtrees.length; j++) {
switch (permittedSubtrees[j].base.type) {
//#region rfc822Name
case 1:
constrGroups[0].push(permittedSubtrees[j]);
break;
//#endregion
//#region dNSName
case 2:
constrGroups[1].push(permittedSubtrees[j]);
break;
//#endregion
//#region directoryName
case 4:
constrGroups[2].push(permittedSubtrees[j]);
break;
//#endregion
//#region uniformResourceIdentifier
case 6:
constrGroups[3].push(permittedSubtrees[j]);
break;
//#endregion
//#region iPAddress
case 7:
constrGroups[4].push(permittedSubtrees[j]);
break;
//#endregion
//#region default
default:
//#endregion
}
}
//#endregion
//#region Check name constraints groupped by type, one-by-one
for (let p = 0; p < 5; p++) {
let groupPermitted = false;
let valueExists = false;
const group = constrGroups[p];
for (let j = 0; j < group.length; j++) {
switch (p) {
//#region rfc822Name
case 0:
if (subjectAltNames.length > 0) {
for (let k = 0; k < subjectAltNames.length; k++) {
if (subjectAltNames[k].type === 1) // rfc822Name
{
valueExists = true;
groupPermitted = groupPermitted || compareRFC822Name(subjectAltNames[k].value, group[j].base.value);
}
}
}
else // Try to find out "emailAddress" inside "subject"
{
for (let k = 0; k < cert.subject.typesAndValues.length; k++) {
if ((cert.subject.typesAndValues[k].type === "1.2.840.113549.1.9.1") || // PKCS#9 e-mail address
(cert.subject.typesAndValues[k].type === "0.9.2342.19200300.100.1.3")) // RFC1274 "rfc822Mailbox" e-mail address
{
valueExists = true;
groupPermitted = groupPermitted || compareRFC822Name(cert.subject.typesAndValues[k].value.valueBlock.value, group[j].base.value);
}
}
}
break;
//#endregion
//#region dNSName
case 1:
if (subjectAltNames.length > 0) {
for (let k = 0; k < subjectAltNames.length; k++) {
if (subjectAltNames[k].type === 2) // dNSName
{
valueExists = true;
groupPermitted = groupPermitted || compareDNSName(subjectAltNames[k].value, group[j].base.value);
}
}
}
break;
//#endregion
//#region directoryName
case 2:
valueExists = true;
groupPermitted = compareDirectoryName(cert.subject, group[j].base.value);
break;
//#endregion
//#region uniformResourceIdentifier
case 3:
if (subjectAltNames.length > 0) {
for (let k = 0; k < subjectAltNames.length; k++) {
if (subjectAltNames[k].type === 6) // uniformResourceIdentifier
{
valueExists = true;
groupPermitted = groupPermitted || compareUniformResourceIdentifier(subjectAltNames[k].value, group[j].base.value);
}
}
}
break;
//#endregion
//#region iPAddress
case 4:
if (subjectAltNames.length > 0) {
for (let k = 0; k < subjectAltNames.length; k++) {
if (subjectAltNames[k].type === 7) // iPAddress
{
valueExists = true;
groupPermitted = groupPermitted || compareIPAddress(subjectAltNames[k].value, group[j].base.value);
}
}
}
break;
//#endregion
//#region default
default:
//#endregion
}
if (groupPermitted)
break;
}
if ((groupPermitted === false) && (group.length > 0) && valueExists) {
policyResult.result = false;
policyResult.resultCode = 41;
policyResult.resultMessage = "Failed to meet \"permitted sub-trees\" name constraint";
throw policyResult;
}
}
//#endregion
//#endregion
//#region Checking for "excluded sub-trees"
let excluded = false;
for (let j = 0; j < excludedSubtrees.length; j++) {
switch (excludedSubtrees[j].base.type) {
//#region rfc822Name
case 1:
if (subjectAltNames.length >= 0) {
for (let k = 0; k < subjectAltNames.length; k++) {
if (subjectAltNames[k].type === 1) // rfc822Name
excluded = excluded || compareRFC822Name(subjectAltNames[k].value, excludedSubtrees[j].base.value);
}
}
else // Try to find out "emailAddress" inside "subject"
{
for (let k = 0; k < cert.subject.typesAndValues.length; k++) {
if ((cert.subject.typesAndValues[k].type === "1.2.840.113549.1.9.1") || // PKCS#9 e-mail address
(cert.subject.typesAndValues[k].type === "0.9.2342.19200300.100.1.3")) // RFC1274 "rfc822Mailbox" e-mail address
excluded = excluded || compareRFC822Name(cert.subject.typesAndValues[k].value.valueBlock.value, excludedSubtrees[j].base.value);
}
}
break;
//#endregion
//#region dNSName
case 2:
if (subjectAltNames.length > 0) {
for (let k = 0; k < subjectAltNames.length; k++) {
if (subjectAltNames[k].type === 2) // dNSName
excluded = excluded || compareDNSName(subjectAltNames[k].value, excludedSubtrees[j].base.value);
}
}
break;
//#endregion
//#region directoryName
case 4:
excluded = excluded || compareDirectoryName(cert.subject, excludedSubtrees[j].base.value);
break;
//#endregion
//#region uniformResourceIdentifier
case 6:
if (subjectAltNames.length > 0) {
for (let k = 0; k < subjectAltNames.length; k++) {
if (subjectAltNames[k].type === 6) // uniformResourceIdentifier
excluded = excluded || compareUniformResourceIdentifier(subjectAltNames[k].value, excludedSubtrees[j].base.value);
}
}
break;
//#endregion
//#region iPAddress
case 7:
if (subjectAltNames.length > 0) {
for (let k = 0; k < subjectAltNames.length; k++) {
if (subjectAltNames[k].type === 7) // iPAddress
excluded = excluded || compareIPAddress(subjectAltNames[k].value, excludedSubtrees[j].base.value);
}
}
break;
//#endregion
//#region default
default: // No action, but probably here we need to create a warning for "malformed constraint"
//#endregion
}
if (excluded)
break;
}
if (excluded === true) {
policyResult.result = false;
policyResult.resultCode = 42;
policyResult.resultMessage = "Failed to meet \"excluded sub-trees\" name constraint";
throw policyResult;
}
//#endregion
//#region Append "cert_..._subtrees" to "..._subtrees"
permittedSubtrees = permittedSubtrees.concat(certPermittedSubtrees);
excludedSubtrees = excludedSubtrees.concat(certExcludedSubtrees);
//#endregion
}
//#endregion
return policyResult;
//#endregion
} catch (error) {
if (error instanceof Error) {
if (error instanceof ChainValidationError) {
return {
result: false,
resultCode: error.code,
resultMessage: error.message,
error: error,
};
}
return {
result: false,
resultCode: ChainValidationCode.unknown,
resultMessage: error.message,
error: error,
};
}
if (error && typeof error === "object" && "resultMessage" in error) {
return error as CertificateChainValidationEngineVerifyResult;
}
return {
result: false,
resultCode: -1,
resultMessage: `${error}`,
};
}
}
}