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 --- third_party/js/PKI.js/src/OCSPResponse.ts | 335 ++++++++++++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 third_party/js/PKI.js/src/OCSPResponse.ts (limited to 'third_party/js/PKI.js/src/OCSPResponse.ts') diff --git a/third_party/js/PKI.js/src/OCSPResponse.ts b/third_party/js/PKI.js/src/OCSPResponse.ts new file mode 100644 index 0000000000..8bb263d2e9 --- /dev/null +++ b/third_party/js/PKI.js/src/OCSPResponse.ts @@ -0,0 +1,335 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { ResponseBytes, ResponseBytesJson, ResponseBytesSchema } from "./ResponseBytes"; +import { BasicOCSPResponse } from "./BasicOCSPResponse"; +import * as Schema from "./Schema"; +import { Certificate } from "./Certificate"; +import { id_PKIX_OCSP_Basic } from "./ObjectIdentifiers"; +import { AsnError } from "./errors"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import * as common from "./common"; + +const RESPONSE_STATUS = "responseStatus"; +const RESPONSE_BYTES = "responseBytes"; + +export interface IOCSPResponse { + responseStatus: asn1js.Enumerated; + responseBytes?: ResponseBytes; +} + +export interface OCSPResponseJson { + responseStatus: asn1js.EnumeratedJson; + responseBytes?: ResponseBytesJson; +} + +export type OCSPResponseParameters = PkiObjectParameters & Partial; + +/** + * Represents an OCSP response described in [RFC6960 Section 4.2](https://datatracker.ietf.org/doc/html/rfc6960#section-4.2) + * + * @example The following example demonstrates how to verify OCSP response + * ```js + * const asnOcspResp = asn1js.fromBER(ocspRespRaw); + * const ocspResp = new pkijs.OCSPResponse({ schema: asnOcspResp.result }); + * + * if (!ocspResp.responseBytes) { + * throw new Error("No \"ResponseBytes\" in the OCSP Response - nothing to verify"); + * } + * + * const asnOcspRespBasic = asn1js.fromBER(ocspResp.responseBytes.response.valueBlock.valueHex); + * const ocspBasicResp = new pkijs.BasicOCSPResponse({ schema: asnOcspRespBasic.result }); + * const ok = await ocspBasicResp.verify({ trustedCerts: [cert] }); + * ``` + * + * @example The following example demonstrates how to create OCSP response + * ```js + * const ocspBasicResp = new pkijs.BasicOCSPResponse(); + * + * // Create specific TST info structure to sign + * ocspBasicResp.tbsResponseData.responderID = issuerCert.subject; + * ocspBasicResp.tbsResponseData.producedAt = new Date(); + * + * const certID = new pkijs.CertID(); + * await certID.createForCertificate(cert, { + * hashAlgorithm: "SHA-256", + * issuerCertificate: issuerCert, + * }); + * const response = new pkijs.SingleResponse({ + * certID, + * }); + * response.certStatus = new asn1js.Primitive({ + * idBlock: { + * tagClass: 3, // CONTEXT-SPECIFIC + * tagNumber: 0 // [0] + * }, + * lenBlockLength: 1 // The length contains one byte 0x00 + * }); // status - success + * response.thisUpdate = new Date(); + * + * ocspBasicResp.tbsResponseData.responses.push(response); + * + * // Add certificates for chain OCSP response validation + * ocspBasicResp.certs = [issuerCert]; + * + * await ocspBasicResp.sign(keys.privateKey, "SHA-256"); + * + * // Finally create completed OCSP response structure + * const ocspBasicRespRaw = ocspBasicResp.toSchema().toBER(false); + * + * const ocspResp = new pkijs.OCSPResponse({ + * responseStatus: new asn1js.Enumerated({ value: 0 }), // success + * responseBytes: new pkijs.ResponseBytes({ + * responseType: pkijs.id_PKIX_OCSP_Basic, + * response: new asn1js.OctetString({ valueHex: ocspBasicRespRaw }), + * }), + * }); + * + * const ocspRespRaw = ocspResp.toSchema().toBER(); + * ``` + */ +export class OCSPResponse extends PkiObject implements IOCSPResponse { + + public static override CLASS_NAME = "OCSPResponse"; + + public responseStatus!: asn1js.Enumerated; + public responseBytes?: ResponseBytes; + + /** + * Initializes a new instance of the {@link OCSPResponse} class + * @param parameters Initialization parameters + */ + constructor(parameters: OCSPResponseParameters = {}) { + super(); + + this.responseStatus = pvutils.getParametersValue(parameters, RESPONSE_STATUS, OCSPResponse.defaultValues(RESPONSE_STATUS)); + if (RESPONSE_BYTES in parameters) { + this.responseBytes = pvutils.getParametersValue(parameters, RESPONSE_BYTES, OCSPResponse.defaultValues(RESPONSE_BYTES)); + } + + 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 RESPONSE_STATUS): asn1js.Enumerated; + public static override defaultValues(memberName: typeof RESPONSE_BYTES): ResponseBytes; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case RESPONSE_STATUS: + return new asn1js.Enumerated(); + case RESPONSE_BYTES: + return new ResponseBytes(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case RESPONSE_STATUS: + return (memberValue.isEqual(OCSPResponse.defaultValues(memberName))); + case RESPONSE_BYTES: + return ((ResponseBytes.compareWithDefault("responseType", memberValue.responseType)) && + (ResponseBytes.compareWithDefault("response", memberValue.response))); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * OCSPResponse ::= SEQUENCE { + * responseStatus OCSPResponseStatus, + * responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } + * + * OCSPResponseStatus ::= ENUMERATED { + * successful (0), -- Response has valid confirmations + * malformedRequest (1), -- Illegal confirmation request + * internalError (2), -- Internal error in issuer + * tryLater (3), -- Try again later + * -- (4) is not used + * sigRequired (5), -- Must sign the request + * unauthorized (6) -- Request unauthorized + * } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + responseStatus?: string; + responseBytes?: ResponseBytesSchema; + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || "OCSPResponse"), + value: [ + new asn1js.Enumerated({ name: (names.responseStatus || RESPONSE_STATUS) }), + new asn1js.Constructed({ + optional: true, + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [ + ResponseBytes.schema(names.responseBytes || { + names: { + blockName: RESPONSE_BYTES + } + }) + ] + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, [ + RESPONSE_STATUS, + RESPONSE_BYTES + ]); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + OCSPResponse.schema() + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.responseStatus = asn1.result.responseStatus; + if (RESPONSE_BYTES in asn1.result) + this.responseBytes = new ResponseBytes({ schema: asn1.result.responseBytes }); + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(this.responseStatus); + if (this.responseBytes) { + outputArray.push(new asn1js.Constructed({ + idBlock: { + tagClass: 3, // CONTEXT-SPECIFIC + tagNumber: 0 // [0] + }, + value: [this.responseBytes.toSchema()] + })); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): OCSPResponseJson { + const res: OCSPResponseJson = { + responseStatus: this.responseStatus.toJSON() + }; + + if (this.responseBytes) { + res.responseBytes = this.responseBytes.toJSON(); + } + + return res; + } + + /** + * Get OCSP response status for specific certificate + * @param certificate + * @param issuerCertificate + * @param crypto Crypto engine + */ + public async getCertificateStatus(certificate: Certificate, issuerCertificate: Certificate, crypto = common.getCrypto(true)) { + //#region Initial variables + let basicResponse; + + const result = { + isForCertificate: false, + status: 2 // 0 = good, 1 = revoked, 2 = unknown + }; + //#endregion + + //#region Check that RESPONSE_BYTES contain "OCSP_BASIC_RESPONSE" + if (!this.responseBytes) + return result; + + if (this.responseBytes.responseType !== id_PKIX_OCSP_Basic) // id-pkix-ocsp-basic + return result; + + try { + const asn1Basic = asn1js.fromBER(this.responseBytes.response.valueBlock.valueHexView); + AsnError.assert(asn1Basic, "Basic OCSP response"); + basicResponse = new BasicOCSPResponse({ schema: asn1Basic.result }); + } + catch (ex) { + return result; + } + //#endregion + + return basicResponse.getCertificateStatus(certificate, issuerCertificate, crypto); + } + + /** + * Make a signature for current OCSP Response + * @param privateKey Private key for "subjectPublicKeyInfo" structure + * @param hashAlgorithm Hashing algorithm. Default SHA-1 + */ + public async sign(privateKey: CryptoKey, hashAlgorithm?: string, crypto = common.getCrypto(true)) { + //#region Check that ResponseData has type BasicOCSPResponse and sign it + if (this.responseBytes && this.responseBytes.responseType === id_PKIX_OCSP_Basic) { + const basicResponse = BasicOCSPResponse.fromBER(this.responseBytes.response.valueBlock.valueHexView); + + return basicResponse.sign(privateKey, hashAlgorithm, crypto); + } + + throw new Error(`Unknown ResponseBytes type: ${this.responseBytes?.responseType || "Unknown"}`); + //#endregion + } + + /** + * Verify current OCSP Response + * @param issuerCertificate In order to decrease size of resp issuer cert could be omitted. In such case you need manually provide it. + * @param crypto Crypto engine + */ + public async verify(issuerCertificate: Certificate | null = null, crypto = common.getCrypto(true)): Promise { + //#region Check that ResponseBytes exists in the object + if ((RESPONSE_BYTES in this) === false) + throw new Error("Empty ResponseBytes field"); + //#endregion + + //#region Check that ResponseData has type BasicOCSPResponse and verify it + if (this.responseBytes && this.responseBytes.responseType === id_PKIX_OCSP_Basic) { + const basicResponse = BasicOCSPResponse.fromBER(this.responseBytes.response.valueBlock.valueHexView); + + if (issuerCertificate !== null) { + if (!basicResponse.certs) { + basicResponse.certs = []; + } + + basicResponse.certs.push(issuerCertificate); + } + + return basicResponse.verify({}, crypto); + } + + throw new Error(`Unknown ResponseBytes type: ${this.responseBytes?.responseType || "Unknown"}`); + //#endregion + } + +} + -- cgit v1.2.3