From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../js/PKI.js/src/CertificateRevocationList.ts | 561 +++++++++++++++++++++ 1 file changed, 561 insertions(+) create mode 100644 third_party/js/PKI.js/src/CertificateRevocationList.ts (limited to 'third_party/js/PKI.js/src/CertificateRevocationList.ts') diff --git a/third_party/js/PKI.js/src/CertificateRevocationList.ts b/third_party/js/PKI.js/src/CertificateRevocationList.ts new file mode 100644 index 0000000000..a17bb638a3 --- /dev/null +++ b/third_party/js/PKI.js/src/CertificateRevocationList.ts @@ -0,0 +1,561 @@ +import * as asn1js from "asn1js"; +import * as pvtsutils from "pvtsutils"; +import * as pvutils from "pvutils"; +import * as common from "./common"; +import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier"; +import { RelativeDistinguishedNames, RelativeDistinguishedNamesJson, RelativeDistinguishedNamesSchema } from "./RelativeDistinguishedNames"; +import { Time, TimeJson, TimeSchema } from "./Time"; +import { RevokedCertificate, RevokedCertificateJson } from "./RevokedCertificate"; +import { Extensions, ExtensionsJson, ExtensionsSchema } from "./Extensions"; +import * as Schema from "./Schema"; +import { Certificate } from "./Certificate"; +import { PublicKeyInfo } from "./PublicKeyInfo"; +import { id_AuthorityInfoAccess, id_AuthorityKeyIdentifier, id_BaseCRLNumber, id_CertificateIssuer, id_CRLNumber, id_CRLReason, id_FreshestCRL, id_InvalidityDate, id_IssuerAltName, id_IssuingDistributionPoint } from "./ObjectIdentifiers"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { EMPTY_BUFFER } from "./constants"; + +const TBS = "tbs"; +const VERSION = "version"; +const SIGNATURE = "signature"; +const ISSUER = "issuer"; +const THIS_UPDATE = "thisUpdate"; +const NEXT_UPDATE = "nextUpdate"; +const REVOKED_CERTIFICATES = "revokedCertificates"; +const CRL_EXTENSIONS = "crlExtensions"; +const SIGNATURE_ALGORITHM = "signatureAlgorithm"; +const SIGNATURE_VALUE = "signatureValue"; +const TBS_CERT_LIST = "tbsCertList"; +const TBS_CERT_LIST_VERSION = `${TBS_CERT_LIST}.version`; +const TBS_CERT_LIST_SIGNATURE = `${TBS_CERT_LIST}.signature`; +const TBS_CERT_LIST_ISSUER = `${TBS_CERT_LIST}.issuer`; +const TBS_CERT_LIST_THIS_UPDATE = `${TBS_CERT_LIST}.thisUpdate`; +const TBS_CERT_LIST_NEXT_UPDATE = `${TBS_CERT_LIST}.nextUpdate`; +const TBS_CERT_LIST_REVOKED_CERTIFICATES = `${TBS_CERT_LIST}.revokedCertificates`; +const TBS_CERT_LIST_EXTENSIONS = `${TBS_CERT_LIST}.extensions`; +const CLEAR_PROPS = [ + TBS_CERT_LIST, + TBS_CERT_LIST_VERSION, + TBS_CERT_LIST_SIGNATURE, + TBS_CERT_LIST_ISSUER, + TBS_CERT_LIST_THIS_UPDATE, + TBS_CERT_LIST_NEXT_UPDATE, + TBS_CERT_LIST_REVOKED_CERTIFICATES, + TBS_CERT_LIST_EXTENSIONS, + SIGNATURE_ALGORITHM, + SIGNATURE_VALUE +]; + +export interface ICertificateRevocationList { + tbs: ArrayBuffer; + version: number; + signature: AlgorithmIdentifier; + issuer: RelativeDistinguishedNames; + thisUpdate: Time; + nextUpdate?: Time; + revokedCertificates?: RevokedCertificate[]; + crlExtensions?: Extensions; + signatureAlgorithm: AlgorithmIdentifier; + signatureValue: asn1js.BitString; +} + +export type TBSCertListSchema = Schema.SchemaParameters<{ + tbsCertListVersion?: string; + signature?: AlgorithmIdentifierSchema; + issuer?: RelativeDistinguishedNamesSchema; + tbsCertListThisUpdate?: TimeSchema; + tbsCertListNextUpdate?: TimeSchema; + tbsCertListRevokedCertificates?: string; + crlExtensions?: ExtensionsSchema; +}>; + +export interface CertificateRevocationListJson { + tbs: string; + version: number; + signature: AlgorithmIdentifierJson; + issuer: RelativeDistinguishedNamesJson; + thisUpdate: TimeJson; + nextUpdate?: TimeJson; + revokedCertificates?: RevokedCertificateJson[]; + crlExtensions?: ExtensionsJson; + signatureAlgorithm: AlgorithmIdentifierJson; + signatureValue: asn1js.BitStringJson; +} + +function tbsCertList(parameters: TBSCertListSchema = {}): Schema.SchemaType { + //TBSCertList ::= SEQUENCE { + // version Version OPTIONAL, + // -- if present, MUST be v2 + // signature AlgorithmIdentifier, + // issuer Name, + // thisUpdate Time, + // nextUpdate Time OPTIONAL, + // revokedCertificates SEQUENCE OF SEQUENCE { + // userCertificate CertificateSerialNumber, + // revocationDate Time, + // crlEntryExtensions Extensions OPTIONAL + // -- if present, version MUST be v2 + // } OPTIONAL, + // crlExtensions [0] EXPLICIT Extensions OPTIONAL + // -- if present, version MUST be v2 + //} + const names = pvutils.getParametersValue>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || TBS_CERT_LIST), + value: [ + new asn1js.Integer({ + optional: true, + name: (names.tbsCertListVersion || TBS_CERT_LIST_VERSION), + value: 2 + }), // EXPLICIT integer value (v2) + AlgorithmIdentifier.schema(names.signature || { + names: { + blockName: TBS_CERT_LIST_SIGNATURE + } + }), + RelativeDistinguishedNames.schema(names.issuer || { + names: { + blockName: TBS_CERT_LIST_ISSUER + } + }), + Time.schema(names.tbsCertListThisUpdate || { + names: { + utcTimeName: TBS_CERT_LIST_THIS_UPDATE, + generalTimeName: TBS_CERT_LIST_THIS_UPDATE + } + }), + Time.schema(names.tbsCertListNextUpdate || { + names: { + utcTimeName: TBS_CERT_LIST_NEXT_UPDATE, + generalTimeName: TBS_CERT_LIST_NEXT_UPDATE + } + }, true), + new asn1js.Sequence({ + optional: true, + value: [ + new asn1js.Repeated({ + name: (names.tbsCertListRevokedCertificates || TBS_CERT_LIST_REVOKED_CERTIFICATES), + value: new asn1js.Sequence({ + value: [ + new asn1js.Integer(), + Time.schema(), + Extensions.schema({}, true) + ] + }) + }) + ] + }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [Extensions.schema(names.crlExtensions || { + names: { + blockName: TBS_CERT_LIST_EXTENSIONS + } + })] + }) // EXPLICIT SEQUENCE value + ] + })); +} + +export type CertificateRevocationListParameters = PkiObjectParameters & Partial; + +export interface CertificateRevocationListVerifyParams { + issuerCertificate?: Certificate; + publicKeyInfo?: PublicKeyInfo; +} + +const WELL_KNOWN_EXTENSIONS = [ + id_AuthorityKeyIdentifier, + id_IssuerAltName, + id_CRLNumber, + id_BaseCRLNumber, + id_IssuingDistributionPoint, + id_FreshestCRL, + id_AuthorityInfoAccess, + id_CRLReason, + id_InvalidityDate, + id_CertificateIssuer, +]; +/** + * Represents the CertificateRevocationList structure described in [RFC5280](https://datatracker.ietf.org/doc/html/rfc5280) + */ +export class CertificateRevocationList extends PkiObject implements ICertificateRevocationList { + + public static override CLASS_NAME = "CertificateRevocationList"; + + public tbsView!: Uint8Array; + /** + * @deprecated Since version 3.0.0 + */ + public get tbs(): ArrayBuffer { + return pvtsutils.BufferSourceConverter.toArrayBuffer(this.tbsView); + } + + /** + * @deprecated Since version 3.0.0 + */ + public set tbs(value: ArrayBuffer) { + this.tbsView = new Uint8Array(value); + } + public version!: number; + public signature!: AlgorithmIdentifier; + public issuer!: RelativeDistinguishedNames; + public thisUpdate!: Time; + public nextUpdate?: Time; + public revokedCertificates?: RevokedCertificate[]; + public crlExtensions?: Extensions; + public signatureAlgorithm!: AlgorithmIdentifier; + public signatureValue!: asn1js.BitString; + + /** + * Initializes a new instance of the {@link CertificateRevocationList} class + * @param parameters Initialization parameters + */ + constructor(parameters: CertificateRevocationListParameters = {}) { + super(); + + this.tbsView = new Uint8Array(pvutils.getParametersValue(parameters, TBS, CertificateRevocationList.defaultValues(TBS))); + this.version = pvutils.getParametersValue(parameters, VERSION, CertificateRevocationList.defaultValues(VERSION)); + this.signature = pvutils.getParametersValue(parameters, SIGNATURE, CertificateRevocationList.defaultValues(SIGNATURE)); + this.issuer = pvutils.getParametersValue(parameters, ISSUER, CertificateRevocationList.defaultValues(ISSUER)); + this.thisUpdate = pvutils.getParametersValue(parameters, THIS_UPDATE, CertificateRevocationList.defaultValues(THIS_UPDATE)); + if (NEXT_UPDATE in parameters) { + this.nextUpdate = pvutils.getParametersValue(parameters, NEXT_UPDATE, CertificateRevocationList.defaultValues(NEXT_UPDATE)); + } + if (REVOKED_CERTIFICATES in parameters) { + this.revokedCertificates = pvutils.getParametersValue(parameters, REVOKED_CERTIFICATES, CertificateRevocationList.defaultValues(REVOKED_CERTIFICATES)); + } + if (CRL_EXTENSIONS in parameters) { + this.crlExtensions = pvutils.getParametersValue(parameters, CRL_EXTENSIONS, CertificateRevocationList.defaultValues(CRL_EXTENSIONS)); + } + this.signatureAlgorithm = pvutils.getParametersValue(parameters, SIGNATURE_ALGORITHM, CertificateRevocationList.defaultValues(SIGNATURE_ALGORITHM)); + this.signatureValue = pvutils.getParametersValue(parameters, SIGNATURE_VALUE, CertificateRevocationList.defaultValues(SIGNATURE_VALUE)); + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof TBS): ArrayBuffer; + public static override defaultValues(memberName: typeof VERSION): number; + public static override defaultValues(memberName: typeof SIGNATURE): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof ISSUER): RelativeDistinguishedNames; + public static override defaultValues(memberName: typeof THIS_UPDATE): Time; + public static override defaultValues(memberName: typeof NEXT_UPDATE): Time; + public static override defaultValues(memberName: typeof REVOKED_CERTIFICATES): RevokedCertificate[]; + public static override defaultValues(memberName: typeof CRL_EXTENSIONS): Extensions; + public static override defaultValues(memberName: typeof SIGNATURE_ALGORITHM): AlgorithmIdentifier; + public static override defaultValues(memberName: typeof SIGNATURE_VALUE): asn1js.BitString; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case TBS: + return EMPTY_BUFFER; + case VERSION: + return 0; + case SIGNATURE: + return new AlgorithmIdentifier(); + case ISSUER: + return new RelativeDistinguishedNames(); + case THIS_UPDATE: + return new Time(); + case NEXT_UPDATE: + return new Time(); + case REVOKED_CERTIFICATES: + return []; + case CRL_EXTENSIONS: + return new Extensions(); + case SIGNATURE_ALGORITHM: + return new AlgorithmIdentifier(); + case SIGNATURE_VALUE: + return new asn1js.BitString(); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * CertificateList ::= SEQUENCE { + * tbsCertList TBSCertList, + * signatureAlgorithm AlgorithmIdentifier, + * signatureValue BIT STRING } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + tbsCertListVersion?: string; + signature?: AlgorithmIdentifierSchema; + issuer?: RelativeDistinguishedNamesSchema; + tbsCertListThisUpdate?: TimeSchema; + tbsCertListNextUpdate?: TimeSchema; + tbsCertListRevokedCertificates?: string; + crlExtensions?: ExtensionsSchema; + signatureAlgorithm?: AlgorithmIdentifierSchema; + signatureValue?: string; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || "CertificateList"), + value: [ + tbsCertList(parameters), + AlgorithmIdentifier.schema(names.signatureAlgorithm || { + names: { + blockName: SIGNATURE_ALGORITHM + } + }), + new asn1js.BitString({ name: (names.signatureValue || SIGNATURE_VALUE) }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + CertificateRevocationList.schema() + ); + AsnError.assertSchema(asn1, this.className); + + //#region Get internal properties from parsed schema + this.tbsView = (asn1.result.tbsCertList as asn1js.Sequence).valueBeforeDecodeView; + + if (TBS_CERT_LIST_VERSION in asn1.result) { + this.version = asn1.result[TBS_CERT_LIST_VERSION].valueBlock.valueDec; + } + this.signature = new AlgorithmIdentifier({ schema: asn1.result[TBS_CERT_LIST_SIGNATURE] }); + this.issuer = new RelativeDistinguishedNames({ schema: asn1.result[TBS_CERT_LIST_ISSUER] }); + this.thisUpdate = new Time({ schema: asn1.result[TBS_CERT_LIST_THIS_UPDATE] }); + if (TBS_CERT_LIST_NEXT_UPDATE in asn1.result) { + this.nextUpdate = new Time({ schema: asn1.result[TBS_CERT_LIST_NEXT_UPDATE] }); + } + if (TBS_CERT_LIST_REVOKED_CERTIFICATES in asn1.result) { + this.revokedCertificates = Array.from(asn1.result[TBS_CERT_LIST_REVOKED_CERTIFICATES], element => new RevokedCertificate({ schema: element })); + } + if (TBS_CERT_LIST_EXTENSIONS in asn1.result) { + this.crlExtensions = new Extensions({ schema: asn1.result[TBS_CERT_LIST_EXTENSIONS] }); + } + + this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm }); + this.signatureValue = asn1.result.signatureValue; + //#endregion + } + + protected encodeTBS() { + //#region Create array for output sequence + const outputArray: any[] = []; + + if (this.version !== CertificateRevocationList.defaultValues(VERSION)) { + outputArray.push(new asn1js.Integer({ value: this.version })); + } + + outputArray.push(this.signature.toSchema()); + outputArray.push(this.issuer.toSchema()); + outputArray.push(this.thisUpdate.toSchema()); + + if (this.nextUpdate) { + outputArray.push(this.nextUpdate.toSchema()); + } + + if (this.revokedCertificates) { + outputArray.push(new asn1js.Sequence({ + value: Array.from(this.revokedCertificates, o => o.toSchema()) + })); + } + + if (this.crlExtensions) { + outputArray.push(new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + this.crlExtensions.toSchema() + ] + })); + } + //#endregion + + return (new asn1js.Sequence({ + value: outputArray + })); + } + + /** + * Convert current object to asn1js object and set correct values + * @returns asn1js object + */ + public toSchema(encodeFlag = false) { + //#region Decode stored TBS value + let tbsSchema; + + if (!encodeFlag) { + if (!this.tbsView.byteLength) { // No stored TBS part + return CertificateRevocationList.schema(); + } + + const asn1 = asn1js.fromBER(this.tbsView); + AsnError.assert(asn1, "TBS Certificate Revocation List"); + tbsSchema = asn1.result; + } + //#endregion + //#region Create TBS schema via assembling from TBS parts + else { + tbsSchema = this.encodeTBS(); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: [ + tbsSchema, + this.signatureAlgorithm.toSchema(), + this.signatureValue + ] + })); + //#endregion + } + + public toJSON(): CertificateRevocationListJson { + const res: CertificateRevocationListJson = { + tbs: pvtsutils.Convert.ToHex(this.tbsView), + version: this.version, + signature: this.signature.toJSON(), + issuer: this.issuer.toJSON(), + thisUpdate: this.thisUpdate.toJSON(), + signatureAlgorithm: this.signatureAlgorithm.toJSON(), + signatureValue: this.signatureValue.toJSON() + }; + + if (this.version !== CertificateRevocationList.defaultValues(VERSION)) + res.version = this.version; + + if (this.nextUpdate) { + res.nextUpdate = this.nextUpdate.toJSON(); + } + + if (this.revokedCertificates) { + res.revokedCertificates = Array.from(this.revokedCertificates, o => o.toJSON()); + } + + if (this.crlExtensions) { + res.crlExtensions = this.crlExtensions.toJSON(); + } + + return res; + } + + /** + * Returns `true` if supplied certificate is revoked, otherwise `false` + * @param certificate + */ + public isCertificateRevoked(certificate: Certificate): boolean { + // Check that issuer of the input certificate is the same with issuer of this CRL + if (!this.issuer.isEqual(certificate.issuer)) { + return false; + } + + // Check that there are revoked certificates in this CRL + if (!this.revokedCertificates) { + return false; + } + + // Search for input certificate in revoked certificates array + for (const revokedCertificate of this.revokedCertificates) { + if (revokedCertificate.userCertificate.isEqual(certificate.serialNumber)) { + return true; + } + } + + return false; + } + + /** + * Make a signature for existing CRL data + * @param privateKey Private key for "subjectPublicKeyInfo" structure + * @param hashAlgorithm Hashing algorithm. Default SHA-1 + * @param crypto Crypto engine + */ + public async sign(privateKey: CryptoKey, hashAlgorithm = "SHA-1", crypto = common.getCrypto(true)): Promise { + // Get a private key from function parameter + if (!privateKey) { + throw new Error("Need to provide a private key for signing"); + } + + //#region Get a "default parameters" for current algorithm and set correct signature algorithm + const signatureParameters = await crypto.getSignatureParameters(privateKey, hashAlgorithm); + const { parameters } = signatureParameters; + this.signature = signatureParameters.signatureAlgorithm; + this.signatureAlgorithm = signatureParameters.signatureAlgorithm; + //#endregion + + //#region Create TBS data for signing + this.tbsView = new Uint8Array(this.encodeTBS().toBER()); + //#endregion + + //#region Signing TBS data on provided private key + const signature = await crypto.signWithPrivateKey(this.tbsView, privateKey, parameters as any); + this.signatureValue = new asn1js.BitString({ valueHex: signature }); + //#endregion + } + + /** + * Verify existing signature + * @param parameters + * @param crypto Crypto engine + */ + public async verify(parameters: CertificateRevocationListVerifyParams = {}, crypto = common.getCrypto(true)): Promise { + let subjectPublicKeyInfo: PublicKeyInfo | undefined; + + //#region Get information about CRL issuer certificate + if (parameters.issuerCertificate) { // "issuerCertificate" must be of type "Certificate" + subjectPublicKeyInfo = parameters.issuerCertificate.subjectPublicKeyInfo; + + // The CRL issuer name and "issuerCertificate" subject name are not equal + if (!this.issuer.isEqual(parameters.issuerCertificate.subject)) { + return false; + } + } + + //#region In case if there is only public key during verification + if (parameters.publicKeyInfo) { + subjectPublicKeyInfo = parameters.publicKeyInfo; // Must be of type "PublicKeyInfo" + } + //#endregion + + if (!subjectPublicKeyInfo) { + throw new Error("Issuer's certificate must be provided as an input parameter"); + } + //#endregion + + //#region Check the CRL for unknown critical extensions + if (this.crlExtensions) { + for (const extension of this.crlExtensions.extensions) { + if (extension.critical) { + // We can not be sure that unknown extension has no value for CRL signature + if (!WELL_KNOWN_EXTENSIONS.includes(extension.extnID)) + return false; + } + } + } + //#endregion + + return crypto.verifyWithPublicKey(this.tbsView, this.signatureValue, subjectPublicKeyInfo, this.signatureAlgorithm); + } + +} -- cgit v1.2.3