tor-browser

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

ResponseData.ts (12263B)


      1 import * as asn1js from "asn1js";
      2 import * as pvtsutils from "pvtsutils";
      3 import * as pvutils from "pvutils";
      4 import { RelativeDistinguishedNames, RelativeDistinguishedNamesSchema } from "./RelativeDistinguishedNames";
      5 import { SingleResponse, SingleResponseJson, SingleResponseSchema } from "./SingleResponse";
      6 import { Extension, ExtensionJson } from "./Extension";
      7 import { Extensions, ExtensionsSchema } from "./Extensions";
      8 import * as Schema from "./Schema";
      9 import { AsnError } from "./errors";
     10 import { PkiObject, PkiObjectParameters } from "./PkiObject";
     11 import { EMPTY_BUFFER } from "./constants";
     12 
     13 const TBS = "tbs";
     14 const VERSION = "version";
     15 const RESPONDER_ID = "responderID";
     16 const PRODUCED_AT = "producedAt";
     17 const RESPONSES = "responses";
     18 const RESPONSE_EXTENSIONS = "responseExtensions";
     19 const RESPONSE_DATA = "ResponseData";
     20 const RESPONSE_DATA_VERSION = `${RESPONSE_DATA}.${VERSION}`;
     21 const RESPONSE_DATA_RESPONDER_ID = `${RESPONSE_DATA}.${RESPONDER_ID}`;
     22 const RESPONSE_DATA_PRODUCED_AT = `${RESPONSE_DATA}.${PRODUCED_AT}`;
     23 const RESPONSE_DATA_RESPONSES = `${RESPONSE_DATA}.${RESPONSES}`;
     24 const RESPONSE_DATA_RESPONSE_EXTENSIONS = `${RESPONSE_DATA}.${RESPONSE_EXTENSIONS}`;
     25 const CLEAR_PROPS = [
     26  RESPONSE_DATA,
     27  RESPONSE_DATA_VERSION,
     28  RESPONSE_DATA_RESPONDER_ID,
     29  RESPONSE_DATA_PRODUCED_AT,
     30  RESPONSE_DATA_RESPONSES,
     31  RESPONSE_DATA_RESPONSE_EXTENSIONS
     32 ];
     33 
     34 export interface IResponseData {
     35  version?: number;
     36  tbs: ArrayBuffer;
     37  responderID: any;
     38  producedAt: Date;
     39  responses: SingleResponse[];
     40  responseExtensions?: Extension[];
     41 }
     42 
     43 export type ResponseDataParameters = PkiObjectParameters & Partial<IResponseData>;
     44 
     45 export type ResponseDataSchema = Schema.SchemaParameters<{
     46  version?: string;
     47  responderID?: string;
     48  ResponseDataByName?: RelativeDistinguishedNamesSchema;
     49  ResponseDataByKey?: string;
     50  producedAt?: string;
     51  response?: SingleResponseSchema;
     52  extensions?: ExtensionsSchema;
     53 }>;
     54 
     55 export interface ResponseDataJson {
     56  version?: number;
     57  tbs: string;
     58  responderID: any;
     59  producedAt: Date;
     60  responses: SingleResponseJson[];
     61  responseExtensions?: ExtensionJson[];
     62 }
     63 
     64 /**
     65 * Represents an ResponseData described in [RFC6960](https://datatracker.ietf.org/doc/html/rfc6960)
     66 */
     67 export class ResponseData extends PkiObject implements IResponseData {
     68 
     69  public static override CLASS_NAME = "ResponseData";
     70 
     71  public version?: number;
     72  public tbsView!: Uint8Array;
     73  /**
     74   * @deprecated Since version 3.0.0
     75   */
     76  public get tbs(): ArrayBuffer {
     77    return pvtsutils.BufferSourceConverter.toArrayBuffer(this.tbsView);
     78  }
     79 
     80  /**
     81   * @deprecated Since version 3.0.0
     82   */
     83  public set tbs(value: ArrayBuffer) {
     84    this.tbsView = new Uint8Array(value);
     85  }
     86  public responderID: any;
     87  public producedAt!: Date;
     88  public responses!: SingleResponse[];
     89  public responseExtensions?: Extension[];
     90 
     91  /**
     92   * Initializes a new instance of the {@link ResponseData} class
     93   * @param parameters Initialization parameters
     94   */
     95  constructor(parameters: ResponseDataParameters = {}) {
     96    super();
     97 
     98    this.tbsView = new Uint8Array(pvutils.getParametersValue(parameters, TBS, ResponseData.defaultValues(TBS)));
     99    if (VERSION in parameters) {
    100      this.version = pvutils.getParametersValue(parameters, VERSION, ResponseData.defaultValues(VERSION));
    101    }
    102    this.responderID = pvutils.getParametersValue(parameters, RESPONDER_ID, ResponseData.defaultValues(RESPONDER_ID));
    103    this.producedAt = pvutils.getParametersValue(parameters, PRODUCED_AT, ResponseData.defaultValues(PRODUCED_AT));
    104    this.responses = pvutils.getParametersValue(parameters, RESPONSES, ResponseData.defaultValues(RESPONSES));
    105    if (RESPONSE_EXTENSIONS in parameters) {
    106      this.responseExtensions = pvutils.getParametersValue(parameters, RESPONSE_EXTENSIONS, ResponseData.defaultValues(RESPONSE_EXTENSIONS));
    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 TBS): ArrayBuffer;
    120  public static override defaultValues(memberName: typeof VERSION): number;
    121  public static override defaultValues(memberName: typeof RESPONDER_ID): any;
    122  public static override defaultValues(memberName: typeof PRODUCED_AT): Date;
    123  public static override defaultValues(memberName: typeof RESPONSES): SingleResponse[];
    124  public static override defaultValues(memberName: typeof RESPONSE_EXTENSIONS): Extension[];
    125  public static override defaultValues(memberName: string): any {
    126    switch (memberName) {
    127      case VERSION:
    128        return 0;
    129      case TBS:
    130        return EMPTY_BUFFER;
    131      case RESPONDER_ID:
    132        return {};
    133      case PRODUCED_AT:
    134        return new Date(0, 0, 0);
    135      case RESPONSES:
    136      case RESPONSE_EXTENSIONS:
    137        return [];
    138      default:
    139        return super.defaultValues(memberName);
    140    }
    141  }
    142 
    143  /**
    144   * Compare values with default values for all class members
    145   * @param memberName String name for a class member
    146   * @param memberValue Value to compare with default value
    147   */
    148  public static compareWithDefault(memberName: string, memberValue: any): boolean {
    149    switch (memberName) {
    150      // TODO version?
    151      case TBS:
    152        return (memberValue.byteLength === 0);
    153      case RESPONDER_ID:
    154        return (Object.keys(memberValue).length === 0);
    155      case PRODUCED_AT:
    156        return (memberValue === ResponseData.defaultValues(memberName));
    157      case RESPONSES:
    158      case RESPONSE_EXTENSIONS:
    159        return (memberValue.length === 0);
    160      default:
    161        return super.defaultValues(memberName);
    162    }
    163  }
    164 
    165  /**
    166   * @inheritdoc
    167   * @asn ASN.1 schema
    168   * ```asn
    169   * ResponseData ::= SEQUENCE {
    170   *    version              [0] EXPLICIT Version DEFAULT v1,
    171   *    responderID              ResponderID,
    172   *    producedAt               GeneralizedTime,
    173   *    responses                SEQUENCE OF SingleResponse,
    174   *    responseExtensions   [1] EXPLICIT Extensions OPTIONAL }
    175   *```
    176   */
    177  public static override schema(parameters: ResponseDataSchema = {}): Schema.SchemaType {
    178    const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {});
    179 
    180    return (new asn1js.Sequence({
    181      name: (names.blockName || RESPONSE_DATA),
    182      value: [
    183        new asn1js.Constructed({
    184          optional: true,
    185          idBlock: {
    186            tagClass: 3, // CONTEXT-SPECIFIC
    187            tagNumber: 0 // [0]
    188          },
    189          value: [new asn1js.Integer({ name: (names.version || RESPONSE_DATA_VERSION) })]
    190        }),
    191        new asn1js.Choice({
    192          value: [
    193            new asn1js.Constructed({
    194              name: (names.responderID || RESPONSE_DATA_RESPONDER_ID),
    195              idBlock: {
    196                tagClass: 3, // CONTEXT-SPECIFIC
    197                tagNumber: 1 // [1]
    198              },
    199              value: [RelativeDistinguishedNames.schema(names.ResponseDataByName || {
    200                names: {
    201                  blockName: "ResponseData.byName"
    202                }
    203              })]
    204            }),
    205            new asn1js.Constructed({
    206              name: (names.responderID || RESPONSE_DATA_RESPONDER_ID),
    207              idBlock: {
    208                tagClass: 3, // CONTEXT-SPECIFIC
    209                tagNumber: 2 // [2]
    210              },
    211              value: [new asn1js.OctetString({ name: (names.ResponseDataByKey || "ResponseData.byKey") })]
    212            })
    213          ]
    214        }),
    215        new asn1js.GeneralizedTime({ name: (names.producedAt || RESPONSE_DATA_PRODUCED_AT) }),
    216        new asn1js.Sequence({
    217          value: [
    218            new asn1js.Repeated({
    219              name: RESPONSE_DATA_RESPONSES,
    220              value: SingleResponse.schema(names.response || {})
    221            })
    222          ]
    223        }),
    224        new asn1js.Constructed({
    225          optional: true,
    226          idBlock: {
    227            tagClass: 3, // CONTEXT-SPECIFIC
    228            tagNumber: 1 // [1]
    229          },
    230          value: [Extensions.schema(names.extensions || {
    231            names: {
    232              blockName: RESPONSE_DATA_RESPONSE_EXTENSIONS
    233            }
    234          })]
    235        }) // EXPLICIT SEQUENCE value
    236      ]
    237    }));
    238  }
    239 
    240  public fromSchema(schema: Schema.SchemaType): void {
    241    // Clear input data first
    242    pvutils.clearProps(schema, CLEAR_PROPS);
    243 
    244    //#region Check the schema is valid
    245    const asn1 = asn1js.compareSchema(schema,
    246      schema,
    247      ResponseData.schema()
    248    );
    249 
    250    AsnError.assertSchema(asn1, this.className);
    251    //#endregion
    252 
    253    //#region Get internal properties from parsed schema
    254    this.tbsView = (asn1.result.ResponseData as asn1js.Sequence).valueBeforeDecodeView;
    255 
    256    if (RESPONSE_DATA_VERSION in asn1.result)
    257      this.version = asn1.result[RESPONSE_DATA_VERSION].valueBlock.valueDec;
    258 
    259    if (asn1.result[RESPONSE_DATA_RESPONDER_ID].idBlock.tagNumber === 1)
    260      this.responderID = new RelativeDistinguishedNames({ schema: asn1.result[RESPONSE_DATA_RESPONDER_ID].valueBlock.value[0] });
    261    else
    262      this.responderID = asn1.result[RESPONSE_DATA_RESPONDER_ID].valueBlock.value[0]; // OCTET_STRING
    263 
    264    this.producedAt = asn1.result[RESPONSE_DATA_PRODUCED_AT].toDate();
    265    this.responses = Array.from(asn1.result[RESPONSE_DATA_RESPONSES], element => new SingleResponse({ schema: element }));
    266 
    267    if (RESPONSE_DATA_RESPONSE_EXTENSIONS in asn1.result)
    268      this.responseExtensions = Array.from(asn1.result[RESPONSE_DATA_RESPONSE_EXTENSIONS].valueBlock.value, element => new Extension({ schema: element }));
    269    //#endregion
    270  }
    271 
    272  public toSchema(encodeFlag = false): Schema.SchemaType {
    273    //#region Decode stored TBS value
    274    let tbsSchema;
    275 
    276    if (encodeFlag === false) {
    277      if (!this.tbsView.byteLength) {// No stored certificate TBS part
    278        return ResponseData.schema();
    279      }
    280 
    281      const asn1 = asn1js.fromBER(this.tbsView);
    282      AsnError.assert(asn1, "TBS Response Data");
    283      tbsSchema = asn1.result;
    284    }
    285    //#endregion
    286    //#region Create TBS schema via assembling from TBS parts
    287    else {
    288      const outputArray = [];
    289 
    290      if (VERSION in this) {
    291        outputArray.push(new asn1js.Constructed({
    292          idBlock: {
    293            tagClass: 3, // CONTEXT-SPECIFIC
    294            tagNumber: 0 // [0]
    295          },
    296          value: [new asn1js.Integer({ value: this.version })]
    297        }));
    298      }
    299 
    300      if (this.responderID instanceof RelativeDistinguishedNames) {
    301        outputArray.push(new asn1js.Constructed({
    302          idBlock: {
    303            tagClass: 3, // CONTEXT-SPECIFIC
    304            tagNumber: 1 // [1]
    305          },
    306          value: [this.responderID.toSchema()]
    307        }));
    308      } else {
    309        outputArray.push(new asn1js.Constructed({
    310          idBlock: {
    311            tagClass: 3, // CONTEXT-SPECIFIC
    312            tagNumber: 2 // [2]
    313          },
    314          value: [this.responderID]
    315        }));
    316      }
    317 
    318      outputArray.push(new asn1js.GeneralizedTime({ valueDate: this.producedAt }));
    319 
    320      outputArray.push(new asn1js.Sequence({
    321        value: Array.from(this.responses, o => o.toSchema())
    322      }));
    323 
    324      if (this.responseExtensions) {
    325        outputArray.push(new asn1js.Constructed({
    326          idBlock: {
    327            tagClass: 3, // CONTEXT-SPECIFIC
    328            tagNumber: 1 // [1]
    329          },
    330          value: [new asn1js.Sequence({
    331            value: Array.from(this.responseExtensions, o => o.toSchema())
    332          })]
    333        }));
    334      }
    335 
    336      tbsSchema = new asn1js.Sequence({
    337        value: outputArray
    338      });
    339    }
    340    //#endregion
    341 
    342    //#region Construct and return new ASN.1 schema for this object
    343    return tbsSchema;
    344    //#endregion
    345  }
    346 
    347  public toJSON(): ResponseDataJson {
    348    const res = {} as ResponseDataJson;
    349 
    350    if (VERSION in this) {
    351      res.version = this.version;
    352    }
    353 
    354    if (this.responderID) {
    355      res.responderID = this.responderID;
    356    }
    357 
    358    if (this.producedAt) {
    359      res.producedAt = this.producedAt;
    360    }
    361 
    362    if (this.responses) {
    363      res.responses = Array.from(this.responses, o => o.toJSON());
    364    }
    365 
    366    if (this.responseExtensions) {
    367      res.responseExtensions = Array.from(this.responseExtensions, o => o.toJSON());
    368    }
    369 
    370    return res;
    371  }
    372 
    373 }