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 }