tor-browser

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

OCSPResponse.ts (11227B)


      1 import * as asn1js from "asn1js";
      2 import * as pvutils from "pvutils";
      3 import { ResponseBytes, ResponseBytesJson, ResponseBytesSchema } from "./ResponseBytes";
      4 import { BasicOCSPResponse } from "./BasicOCSPResponse";
      5 import * as Schema from "./Schema";
      6 import { Certificate } from "./Certificate";
      7 import { id_PKIX_OCSP_Basic } from "./ObjectIdentifiers";
      8 import { AsnError } from "./errors";
      9 import { PkiObject, PkiObjectParameters } from "./PkiObject";
     10 import * as common from "./common";
     11 
     12 const RESPONSE_STATUS = "responseStatus";
     13 const RESPONSE_BYTES = "responseBytes";
     14 
     15 export interface IOCSPResponse {
     16  responseStatus: asn1js.Enumerated;
     17  responseBytes?: ResponseBytes;
     18 }
     19 
     20 export interface OCSPResponseJson {
     21  responseStatus: asn1js.EnumeratedJson;
     22  responseBytes?: ResponseBytesJson;
     23 }
     24 
     25 export type OCSPResponseParameters = PkiObjectParameters & Partial<IOCSPResponse>;
     26 
     27 /**
     28 * Represents an OCSP response described in [RFC6960 Section 4.2](https://datatracker.ietf.org/doc/html/rfc6960#section-4.2)
     29 *
     30 * @example The following example demonstrates how to verify OCSP response
     31 * ```js
     32 * const asnOcspResp = asn1js.fromBER(ocspRespRaw);
     33 * const ocspResp = new pkijs.OCSPResponse({ schema: asnOcspResp.result });
     34 *
     35 * if (!ocspResp.responseBytes) {
     36 *   throw new Error("No \"ResponseBytes\" in the OCSP Response - nothing to verify");
     37 * }
     38 *
     39 * const asnOcspRespBasic = asn1js.fromBER(ocspResp.responseBytes.response.valueBlock.valueHex);
     40 * const ocspBasicResp = new pkijs.BasicOCSPResponse({ schema: asnOcspRespBasic.result });
     41 * const ok = await ocspBasicResp.verify({ trustedCerts: [cert] });
     42 * ```
     43 *
     44 * @example The following example demonstrates how to create OCSP response
     45 * ```js
     46 * const ocspBasicResp = new pkijs.BasicOCSPResponse();
     47 *
     48 * // Create specific TST info structure to sign
     49 * ocspBasicResp.tbsResponseData.responderID = issuerCert.subject;
     50 * ocspBasicResp.tbsResponseData.producedAt = new Date();
     51 *
     52 * const certID = new pkijs.CertID();
     53 * await certID.createForCertificate(cert, {
     54 *   hashAlgorithm: "SHA-256",
     55 *   issuerCertificate: issuerCert,
     56 * });
     57 * const response = new pkijs.SingleResponse({
     58 *   certID,
     59 * });
     60 * response.certStatus = new asn1js.Primitive({
     61 *   idBlock: {
     62 *     tagClass: 3, // CONTEXT-SPECIFIC
     63 *     tagNumber: 0 // [0]
     64 *   },
     65 *   lenBlockLength: 1 // The length contains one byte 0x00
     66 * }); // status - success
     67 * response.thisUpdate = new Date();
     68 *
     69 * ocspBasicResp.tbsResponseData.responses.push(response);
     70 *
     71 * // Add certificates for chain OCSP response validation
     72 * ocspBasicResp.certs = [issuerCert];
     73 *
     74 * await ocspBasicResp.sign(keys.privateKey, "SHA-256");
     75 *
     76 * // Finally create completed OCSP response structure
     77 * const ocspBasicRespRaw = ocspBasicResp.toSchema().toBER(false);
     78 *
     79 * const ocspResp = new pkijs.OCSPResponse({
     80 *   responseStatus: new asn1js.Enumerated({ value: 0 }), // success
     81 *   responseBytes: new pkijs.ResponseBytes({
     82 *     responseType: pkijs.id_PKIX_OCSP_Basic,
     83 *     response: new asn1js.OctetString({ valueHex: ocspBasicRespRaw }),
     84 *   }),
     85 * });
     86 *
     87 * const ocspRespRaw = ocspResp.toSchema().toBER();
     88 * ```
     89 */
     90 export class OCSPResponse extends PkiObject implements IOCSPResponse {
     91 
     92  public static override CLASS_NAME = "OCSPResponse";
     93 
     94  public responseStatus!: asn1js.Enumerated;
     95  public responseBytes?: ResponseBytes;
     96 
     97  /**
     98   * Initializes a new instance of the {@link OCSPResponse} class
     99   * @param parameters Initialization parameters
    100   */
    101  constructor(parameters: OCSPResponseParameters = {}) {
    102    super();
    103 
    104    this.responseStatus = pvutils.getParametersValue(parameters, RESPONSE_STATUS, OCSPResponse.defaultValues(RESPONSE_STATUS));
    105    if (RESPONSE_BYTES in parameters) {
    106      this.responseBytes = pvutils.getParametersValue(parameters, RESPONSE_BYTES, OCSPResponse.defaultValues(RESPONSE_BYTES));
    107    }
    108 
    109    if (parameters.schema) {
    110      this.fromSchema(parameters.schema);
    111    }
    112  }
    113 
    114  /**
    115   * Returns default values for all class members
    116   * @param memberName String name for a class member
    117   * @returns Default value
    118   */
    119  public static override defaultValues(memberName: typeof RESPONSE_STATUS): asn1js.Enumerated;
    120  public static override defaultValues(memberName: typeof RESPONSE_BYTES): ResponseBytes;
    121  public static override defaultValues(memberName: string): any {
    122    switch (memberName) {
    123      case RESPONSE_STATUS:
    124        return new asn1js.Enumerated();
    125      case RESPONSE_BYTES:
    126        return new ResponseBytes();
    127      default:
    128        return super.defaultValues(memberName);
    129    }
    130  }
    131 
    132  /**
    133   * Compare values with default values for all class members
    134   * @param memberName String name for a class member
    135   * @param memberValue Value to compare with default value
    136   */
    137  public static compareWithDefault(memberName: string, memberValue: any): boolean {
    138    switch (memberName) {
    139      case RESPONSE_STATUS:
    140        return (memberValue.isEqual(OCSPResponse.defaultValues(memberName)));
    141      case RESPONSE_BYTES:
    142        return ((ResponseBytes.compareWithDefault("responseType", memberValue.responseType)) &&
    143          (ResponseBytes.compareWithDefault("response", memberValue.response)));
    144      default:
    145        return super.defaultValues(memberName);
    146    }
    147  }
    148 
    149  /**
    150   * @inheritdoc
    151   * @asn ASN.1 schema
    152   * ```asn
    153   * OCSPResponse ::= SEQUENCE {
    154   *    responseStatus         OCSPResponseStatus,
    155   *    responseBytes          [0] EXPLICIT ResponseBytes OPTIONAL }
    156   *
    157   * OCSPResponseStatus ::= ENUMERATED {
    158   *    successful            (0),  -- Response has valid confirmations
    159   *    malformedRequest      (1),  -- Illegal confirmation request
    160   *    internalError         (2),  -- Internal error in issuer
    161   *    tryLater              (3),  -- Try again later
    162   *    -- (4) is not used
    163   *    sigRequired           (5),  -- Must sign the request
    164   *    unauthorized          (6)   -- Request unauthorized
    165   * }
    166   *```
    167   */
    168  public static override schema(parameters: Schema.SchemaParameters<{
    169    responseStatus?: string;
    170    responseBytes?: ResponseBytesSchema;
    171  }> = {}): Schema.SchemaType {
    172    const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {});
    173 
    174    return (new asn1js.Sequence({
    175      name: (names.blockName || "OCSPResponse"),
    176      value: [
    177        new asn1js.Enumerated({ name: (names.responseStatus || RESPONSE_STATUS) }),
    178        new asn1js.Constructed({
    179          optional: true,
    180          idBlock: {
    181            tagClass: 3, // CONTEXT-SPECIFIC
    182            tagNumber: 0 // [0]
    183          },
    184          value: [
    185            ResponseBytes.schema(names.responseBytes || {
    186              names: {
    187                blockName: RESPONSE_BYTES
    188              }
    189            })
    190          ]
    191        })
    192      ]
    193    }));
    194  }
    195 
    196  public fromSchema(schema: Schema.SchemaType): void {
    197    // Clear input data first
    198    pvutils.clearProps(schema, [
    199      RESPONSE_STATUS,
    200      RESPONSE_BYTES
    201    ]);
    202 
    203    // Check the schema is valid
    204    const asn1 = asn1js.compareSchema(schema,
    205      schema,
    206      OCSPResponse.schema()
    207    );
    208    AsnError.assertSchema(asn1, this.className);
    209 
    210    // Get internal properties from parsed schema
    211    this.responseStatus = asn1.result.responseStatus;
    212    if (RESPONSE_BYTES in asn1.result)
    213      this.responseBytes = new ResponseBytes({ schema: asn1.result.responseBytes });
    214  }
    215 
    216  public toSchema(): asn1js.Sequence {
    217    //#region Create array for output sequence
    218    const outputArray = [];
    219 
    220    outputArray.push(this.responseStatus);
    221    if (this.responseBytes) {
    222      outputArray.push(new asn1js.Constructed({
    223        idBlock: {
    224          tagClass: 3, // CONTEXT-SPECIFIC
    225          tagNumber: 0 // [0]
    226        },
    227        value: [this.responseBytes.toSchema()]
    228      }));
    229    }
    230    //#endregion
    231 
    232    //#region Construct and return new ASN.1 schema for this object
    233    return (new asn1js.Sequence({
    234      value: outputArray
    235    }));
    236    //#endregion
    237  }
    238 
    239  public toJSON(): OCSPResponseJson {
    240    const res: OCSPResponseJson = {
    241      responseStatus: this.responseStatus.toJSON()
    242    };
    243 
    244    if (this.responseBytes) {
    245      res.responseBytes = this.responseBytes.toJSON();
    246    }
    247 
    248    return res;
    249  }
    250 
    251  /**
    252   * Get OCSP response status for specific certificate
    253   * @param certificate
    254   * @param issuerCertificate
    255   * @param crypto Crypto engine
    256   */
    257  public async getCertificateStatus(certificate: Certificate, issuerCertificate: Certificate, crypto = common.getCrypto(true)) {
    258    //#region Initial variables
    259    let basicResponse;
    260 
    261    const result = {
    262      isForCertificate: false,
    263      status: 2 // 0 = good, 1 = revoked, 2 = unknown
    264    };
    265    //#endregion
    266 
    267    //#region Check that RESPONSE_BYTES contain "OCSP_BASIC_RESPONSE"
    268    if (!this.responseBytes)
    269      return result;
    270 
    271    if (this.responseBytes.responseType !== id_PKIX_OCSP_Basic) // id-pkix-ocsp-basic
    272      return result;
    273 
    274    try {
    275      const asn1Basic = asn1js.fromBER(this.responseBytes.response.valueBlock.valueHexView);
    276      AsnError.assert(asn1Basic, "Basic OCSP response");
    277      basicResponse = new BasicOCSPResponse({ schema: asn1Basic.result });
    278    }
    279    catch {
    280      return result;
    281    }
    282    //#endregion
    283 
    284    return basicResponse.getCertificateStatus(certificate, issuerCertificate, crypto);
    285  }
    286 
    287  /**
    288   * Make a signature for current OCSP Response
    289   * @param privateKey Private key for "subjectPublicKeyInfo" structure
    290   * @param hashAlgorithm Hashing algorithm. Default SHA-1
    291   */
    292  public async sign(privateKey: CryptoKey, hashAlgorithm?: string, crypto = common.getCrypto(true)) {
    293    //#region Check that ResponseData has type BasicOCSPResponse and sign it
    294    if (this.responseBytes && this.responseBytes.responseType === id_PKIX_OCSP_Basic) {
    295      const basicResponse = BasicOCSPResponse.fromBER(this.responseBytes.response.valueBlock.valueHexView as BufferSource);
    296 
    297      return basicResponse.sign(privateKey, hashAlgorithm, crypto);
    298    }
    299 
    300    throw new Error(`Unknown ResponseBytes type: ${this.responseBytes?.responseType || "Unknown"}`);
    301    //#endregion
    302  }
    303 
    304  /**
    305   * Verify current OCSP Response
    306   * @param issuerCertificate In order to decrease size of resp issuer cert could be omitted. In such case you need manually provide it.
    307   * @param crypto Crypto engine
    308   */
    309  public async verify(issuerCertificate: Certificate | null = null, crypto = common.getCrypto(true)): Promise<boolean> {
    310    //#region Check that ResponseBytes exists in the object
    311    if ((RESPONSE_BYTES in this) === false)
    312      throw new Error("Empty ResponseBytes field");
    313    //#endregion
    314 
    315    //#region Check that ResponseData has type BasicOCSPResponse and verify it
    316    if (this.responseBytes && this.responseBytes.responseType === id_PKIX_OCSP_Basic) {
    317      const basicResponse = BasicOCSPResponse.fromBER(this.responseBytes.response.valueBlock.valueHexView as BufferSource);
    318 
    319      if (issuerCertificate !== null) {
    320        if (!basicResponse.certs) {
    321          basicResponse.certs = [];
    322        }
    323 
    324        basicResponse.certs.push(issuerCertificate);
    325      }
    326 
    327      return basicResponse.verify({}, crypto);
    328    }
    329 
    330    throw new Error(`Unknown ResponseBytes type: ${this.responseBytes?.responseType || "Unknown"}`);
    331    //#endregion
    332  }
    333 
    334 }