summaryrefslogtreecommitdiffstats
path: root/third_party/js/PKI.js/src/BasicOCSPResponse.ts
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/js/PKI.js/src/BasicOCSPResponse.ts')
-rw-r--r--third_party/js/PKI.js/src/BasicOCSPResponse.ts442
1 files changed, 442 insertions, 0 deletions
diff --git a/third_party/js/PKI.js/src/BasicOCSPResponse.ts b/third_party/js/PKI.js/src/BasicOCSPResponse.ts
new file mode 100644
index 0000000000..46efec0108
--- /dev/null
+++ b/third_party/js/PKI.js/src/BasicOCSPResponse.ts
@@ -0,0 +1,442 @@
+import * as asn1js from "asn1js";
+import * as pvutils from "pvutils";
+import * as common from "./common";
+import { ResponseData, ResponseDataJson, ResponseDataSchema } from "./ResponseData";
+import { AlgorithmIdentifier, AlgorithmIdentifierJson, AlgorithmIdentifierSchema } from "./AlgorithmIdentifier";
+import { Certificate, CertificateJson, CertificateSchema, checkCA } from "./Certificate";
+import { CertID } from "./CertID";
+import { RelativeDistinguishedNames } from "./RelativeDistinguishedNames";
+import { CertificateChainValidationEngine } from "./CertificateChainValidationEngine";
+import * as Schema from "./Schema";
+import { PkiObject, PkiObjectParameters } from "./PkiObject";
+import { AsnError } from "./errors";
+import { EMPTY_STRING } from "./constants";
+
+const TBS_RESPONSE_DATA = "tbsResponseData";
+const SIGNATURE_ALGORITHM = "signatureAlgorithm";
+const SIGNATURE = "signature";
+const CERTS = "certs";
+const BASIC_OCSP_RESPONSE = "BasicOCSPResponse";
+const BASIC_OCSP_RESPONSE_TBS_RESPONSE_DATA = `${BASIC_OCSP_RESPONSE}.${TBS_RESPONSE_DATA}`;
+const BASIC_OCSP_RESPONSE_SIGNATURE_ALGORITHM = `${BASIC_OCSP_RESPONSE}.${SIGNATURE_ALGORITHM}`;
+const BASIC_OCSP_RESPONSE_SIGNATURE = `${BASIC_OCSP_RESPONSE}.${SIGNATURE}`;
+const BASIC_OCSP_RESPONSE_CERTS = `${BASIC_OCSP_RESPONSE}.${CERTS}`;
+const CLEAR_PROPS = [
+ BASIC_OCSP_RESPONSE_TBS_RESPONSE_DATA,
+ BASIC_OCSP_RESPONSE_SIGNATURE_ALGORITHM,
+ BASIC_OCSP_RESPONSE_SIGNATURE,
+ BASIC_OCSP_RESPONSE_CERTS
+];
+
+export interface IBasicOCSPResponse {
+ tbsResponseData: ResponseData;
+ signatureAlgorithm: AlgorithmIdentifier;
+ signature: asn1js.BitString;
+ certs?: Certificate[];
+}
+
+export interface CertificateStatus {
+ isForCertificate: boolean;
+ /**
+ * 0 = good, 1 = revoked, 2 = unknown
+ */
+ status: number;
+}
+
+export type BasicOCSPResponseParameters = PkiObjectParameters & Partial<IBasicOCSPResponse>;
+
+export interface BasicOCSPResponseVerifyParams {
+ trustedCerts?: Certificate[];
+}
+
+export interface BasicOCSPResponseJson {
+ tbsResponseData: ResponseDataJson;
+ signatureAlgorithm: AlgorithmIdentifierJson;
+ signature: asn1js.BitStringJson;
+ certs?: CertificateJson[];
+}
+
+/**
+ * Represents the BasicOCSPResponse structure described in [RFC6960](https://datatracker.ietf.org/doc/html/rfc6960)
+ */
+export class BasicOCSPResponse extends PkiObject implements IBasicOCSPResponse {
+
+ public static override CLASS_NAME = "BasicOCSPResponse";
+
+ public tbsResponseData!: ResponseData;
+ public signatureAlgorithm!: AlgorithmIdentifier;
+ public signature!: asn1js.BitString;
+ public certs?: Certificate[];
+
+ /**
+ * Initializes a new instance of the {@link BasicOCSPResponse} class
+ * @param parameters Initialization parameters
+ */
+ constructor(parameters: BasicOCSPResponseParameters = {}) {
+ super();
+
+ this.tbsResponseData = pvutils.getParametersValue(parameters, TBS_RESPONSE_DATA, BasicOCSPResponse.defaultValues(TBS_RESPONSE_DATA));
+ this.signatureAlgorithm = pvutils.getParametersValue(parameters, SIGNATURE_ALGORITHM, BasicOCSPResponse.defaultValues(SIGNATURE_ALGORITHM));
+ this.signature = pvutils.getParametersValue(parameters, SIGNATURE, BasicOCSPResponse.defaultValues(SIGNATURE));
+ if (CERTS in parameters) {
+ this.certs = pvutils.getParametersValue(parameters, CERTS, BasicOCSPResponse.defaultValues(CERTS));
+ }
+
+ 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_RESPONSE_DATA): ResponseData;
+ public static override defaultValues(memberName: typeof SIGNATURE_ALGORITHM): AlgorithmIdentifier;
+ public static override defaultValues(memberName: typeof SIGNATURE): asn1js.BitString;
+ public static override defaultValues(memberName: typeof CERTS): Certificate[];
+ public static override defaultValues(memberName: string): any {
+ switch (memberName) {
+ case TBS_RESPONSE_DATA:
+ return new ResponseData();
+ case SIGNATURE_ALGORITHM:
+ return new AlgorithmIdentifier();
+ case SIGNATURE:
+ return new asn1js.BitString();
+ case CERTS:
+ return [];
+ 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 "type":
+ {
+ let comparisonResult = ((ResponseData.compareWithDefault("tbs", memberValue.tbs)) &&
+ (ResponseData.compareWithDefault("responderID", memberValue.responderID)) &&
+ (ResponseData.compareWithDefault("producedAt", memberValue.producedAt)) &&
+ (ResponseData.compareWithDefault("responses", memberValue.responses)));
+
+ if ("responseExtensions" in memberValue)
+ comparisonResult = comparisonResult && (ResponseData.compareWithDefault("responseExtensions", memberValue.responseExtensions));
+
+ return comparisonResult;
+ }
+ case SIGNATURE_ALGORITHM:
+ return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false));
+ case SIGNATURE:
+ return (memberValue.isEqual(BasicOCSPResponse.defaultValues(memberName)));
+ case CERTS:
+ return (memberValue.length === 0);
+ default:
+ return super.defaultValues(memberName);
+ }
+ }
+
+ /**
+ * @inheritdoc
+ * @asn ASN.1 schema
+ * ```asn
+ * BasicOCSPResponse ::= SEQUENCE {
+ * tbsResponseData ResponseData,
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signature BIT STRING,
+ * certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+ *```
+ */
+ public static override schema(parameters: Schema.SchemaParameters<{
+ tbsResponseData?: ResponseDataSchema;
+ signatureAlgorithm?: AlgorithmIdentifierSchema;
+ signature?: string;
+ certs?: CertificateSchema;
+ }> = {}): Schema.SchemaType {
+ const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || BASIC_OCSP_RESPONSE),
+ value: [
+ ResponseData.schema(names.tbsResponseData || {
+ names: {
+ blockName: BASIC_OCSP_RESPONSE_TBS_RESPONSE_DATA
+ }
+ }),
+ AlgorithmIdentifier.schema(names.signatureAlgorithm || {
+ names: {
+ blockName: BASIC_OCSP_RESPONSE_SIGNATURE_ALGORITHM
+ }
+ }),
+ new asn1js.BitString({ name: (names.signature || BASIC_OCSP_RESPONSE_SIGNATURE) }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Sequence({
+ value: [new asn1js.Repeated({
+ name: BASIC_OCSP_RESPONSE_CERTS,
+ value: Certificate.schema(names.certs || {})
+ })]
+ })
+ ]
+ })
+ ]
+ }));
+ }
+
+ public fromSchema(schema: Schema.SchemaType): void {
+ // Clear input data first
+ pvutils.clearProps(schema, CLEAR_PROPS);
+ //#endregion
+
+ // Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ BasicOCSPResponse.schema()
+ );
+ AsnError.assertSchema(asn1, this.className);
+
+ //#region Get internal properties from parsed schema
+ this.tbsResponseData = new ResponseData({ schema: asn1.result[BASIC_OCSP_RESPONSE_TBS_RESPONSE_DATA] });
+ this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result[BASIC_OCSP_RESPONSE_SIGNATURE_ALGORITHM] });
+ this.signature = asn1.result[BASIC_OCSP_RESPONSE_SIGNATURE];
+
+ if (BASIC_OCSP_RESPONSE_CERTS in asn1.result) {
+ this.certs = Array.from(asn1.result[BASIC_OCSP_RESPONSE_CERTS], element => new Certificate({ schema: element }));
+ }
+ //#endregion
+ }
+
+ public toSchema(): asn1js.Sequence {
+ //#region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(this.tbsResponseData.toSchema());
+ outputArray.push(this.signatureAlgorithm.toSchema());
+ outputArray.push(this.signature);
+
+ //#region Create array of certificates
+ if (this.certs) {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Sequence({
+ value: Array.from(this.certs, o => o.toSchema())
+ })
+ ]
+ }));
+ }
+ //#endregion
+ //#endregion
+
+ //#region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: outputArray
+ }));
+ //#endregion
+ }
+
+ public toJSON(): BasicOCSPResponseJson {
+ const res: BasicOCSPResponseJson = {
+ tbsResponseData: this.tbsResponseData.toJSON(),
+ signatureAlgorithm: this.signatureAlgorithm.toJSON(),
+ signature: this.signature.toJSON(),
+ };
+
+ if (this.certs) {
+ res.certs = Array.from(this.certs, o => o.toJSON());
+ }
+
+ return res;
+ }
+
+ /**
+ * Get OCSP response status for specific certificate
+ * @param certificate Certificate to be checked
+ * @param issuerCertificate Certificate of issuer for certificate to be checked
+ * @param crypto Crypto engine
+ */
+ public async getCertificateStatus(certificate: Certificate, issuerCertificate: Certificate, crypto = common.getCrypto(true)): Promise<CertificateStatus> {
+ //#region Initial variables
+ const result = {
+ isForCertificate: false,
+ status: 2 // 0 = good, 1 = revoked, 2 = unknown
+ };
+
+ const hashesObject: Record<string, number> = {};
+
+ const certIDs: CertID[] = [];
+ //#endregion
+
+ //#region Create all "certIDs" for input certificates
+ for (const response of this.tbsResponseData.responses) {
+ const hashAlgorithm = crypto.getAlgorithmByOID(response.certID.hashAlgorithm.algorithmId, true, "CertID.hashAlgorithm");
+
+ if (!hashesObject[hashAlgorithm.name]) {
+ hashesObject[hashAlgorithm.name] = 1;
+
+ const certID = new CertID();
+
+ certIDs.push(certID);
+ await certID.createForCertificate(certificate, {
+ hashAlgorithm: hashAlgorithm.name,
+ issuerCertificate
+ }, crypto);
+ }
+ }
+ //#endregion
+
+ //#region Compare all response's "certIDs" with identifiers for input certificate
+ for (const response of this.tbsResponseData.responses) {
+ for (const id of certIDs) {
+ if (response.certID.isEqual(id)) {
+ result.isForCertificate = true;
+
+ try {
+ switch (response.certStatus.idBlock.isConstructed) {
+ case true:
+ if (response.certStatus.idBlock.tagNumber === 1)
+ result.status = 1; // revoked
+
+ break;
+ case false:
+ switch (response.certStatus.idBlock.tagNumber) {
+ case 0: // good
+ result.status = 0;
+ break;
+ case 2: // unknown
+ result.status = 2;
+ break;
+ default:
+ }
+
+ break;
+ default:
+ }
+ }
+ catch (ex) {
+ // nothing
+ }
+
+ return result;
+ }
+ }
+ }
+
+ return result;
+ //#endregion
+ }
+
+ /**
+ * Make signature for current OCSP Basic Response
+ * @param privateKey Private key for "subjectPublicKeyInfo" structure
+ * @param hashAlgorithm Hashing algorithm. Default SHA-1
+ * @param crypto Crypto engine
+ */
+ async sign(privateKey: CryptoKey, hashAlgorithm = "SHA-1", crypto = common.getCrypto(true)): Promise<void> {
+ // 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 signatureParams = await crypto.getSignatureParameters(privateKey, hashAlgorithm);
+
+ const algorithm = signatureParams.parameters.algorithm;
+ if (!("name" in algorithm)) {
+ throw new Error("Empty algorithm");
+ }
+ this.signatureAlgorithm = signatureParams.signatureAlgorithm;
+ //#endregion
+
+ //#region Create TBS data for signing
+ this.tbsResponseData.tbsView = new Uint8Array(this.tbsResponseData.toSchema(true).toBER());
+ //#endregion
+
+ //#region Signing TBS data on provided private key
+ const signature = await crypto.signWithPrivateKey(this.tbsResponseData.tbsView, privateKey, { algorithm });
+ this.signature = new asn1js.BitString({ valueHex: signature });
+ //#endregion
+ }
+
+ /**
+ * Verify existing OCSP Basic Response
+ * @param params Additional parameters
+ * @param crypto Crypto engine
+ */
+ public async verify(params: BasicOCSPResponseVerifyParams = {}, crypto = common.getCrypto(true)): Promise<boolean> {
+ //#region Initial variables
+ let signerCert: Certificate | null = null;
+ let certIndex = -1;
+ const trustedCerts: Certificate[] = params.trustedCerts || [];
+ //#endregion
+
+ //#region Check amount of certificates
+ if (!this.certs) {
+ throw new Error("No certificates attached to the BasicOCSPResponse");
+ }
+ //#endregion
+
+ //#region Find correct value for "responderID"
+ switch (true) {
+ case (this.tbsResponseData.responderID instanceof RelativeDistinguishedNames): // [1] Name
+ for (const [index, certificate] of this.certs.entries()) {
+ if (certificate.subject.isEqual(this.tbsResponseData.responderID)) {
+ certIndex = index;
+ break;
+ }
+ }
+ break;
+ case (this.tbsResponseData.responderID instanceof asn1js.OctetString): // [2] KeyHash
+ for (const [index, cert] of this.certs.entries()) {
+ const hash = await crypto.digest({ name: "sha-1" }, cert.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHexView);
+ if (pvutils.isEqualBuffer(hash, this.tbsResponseData.responderID.valueBlock.valueHex)) {
+ certIndex = index;
+ break;
+ }
+ }
+ break;
+ default:
+ throw new Error("Wrong value for responderID");
+ }
+ //#endregion
+
+ //#region Make additional verification for signer's certificate
+ if (certIndex === (-1))
+ throw new Error("Correct certificate was not found in OCSP response");
+
+ signerCert = this.certs[certIndex];
+
+ const additionalCerts: Certificate[] = [signerCert];
+ for (const cert of this.certs) {
+ const caCert = await checkCA(cert, signerCert);
+ if (caCert) {
+ additionalCerts.push(caCert);
+ }
+ }
+ const certChain = new CertificateChainValidationEngine({
+ certs: additionalCerts,
+ trustedCerts,
+ });
+
+ const verificationResult = await certChain.verify({}, crypto);
+ if (!verificationResult.result) {
+ throw new Error("Validation of signer's certificate failed");
+ }
+
+ return crypto.verifyWithPublicKey(this.tbsResponseData.tbsView, this.signature, this.certs[certIndex].subjectPublicKeyInfo, this.signatureAlgorithm);
+ }
+
+}