TSTInfo.ts (17238B)
1 import * as asn1js from "asn1js"; 2 import * as pvtsutils from "pvtsutils"; 3 import * as pvutils from "pvutils"; 4 import * as common from "./common"; 5 import { MessageImprint, HASHED_MESSAGE, HASH_ALGORITHM, MessageImprintSchema, MessageImprintJson } from "./MessageImprint"; 6 import { Accuracy, AccuracyJson, AccuracySchema, MICROS, MILLIS, SECONDS } from "./Accuracy"; 7 import { GeneralName, GeneralNameJson, GeneralNameSchema, TYPE, VALUE } from "./GeneralName"; 8 import { Extension, ExtensionJson, ExtensionSchema } from "./Extension"; 9 import * as Schema from "./Schema"; 10 import { PkiObject, PkiObjectParameters } from "./PkiObject"; 11 import { AsnError } from "./errors"; 12 import { EMPTY_STRING } from "./constants"; 13 14 const VERSION = "version"; 15 const POLICY = "policy"; 16 const MESSAGE_IMPRINT = "messageImprint"; 17 const SERIAL_NUMBER = "serialNumber"; 18 const GEN_TIME = "genTime"; 19 const ORDERING = "ordering"; 20 const NONCE = "nonce"; 21 const ACCURACY = "accuracy"; 22 const TSA = "tsa"; 23 const EXTENSIONS = "extensions"; 24 const TST_INFO = "TSTInfo"; 25 const TST_INFO_VERSION = `${TST_INFO}.${VERSION}`; 26 const TST_INFO_POLICY = `${TST_INFO}.${POLICY}`; 27 const TST_INFO_MESSAGE_IMPRINT = `${TST_INFO}.${MESSAGE_IMPRINT}`; 28 const TST_INFO_SERIAL_NUMBER = `${TST_INFO}.${SERIAL_NUMBER}`; 29 const TST_INFO_GEN_TIME = `${TST_INFO}.${GEN_TIME}`; 30 const TST_INFO_ACCURACY = `${TST_INFO}.${ACCURACY}`; 31 const TST_INFO_ORDERING = `${TST_INFO}.${ORDERING}`; 32 const TST_INFO_NONCE = `${TST_INFO}.${NONCE}`; 33 const TST_INFO_TSA = `${TST_INFO}.${TSA}`; 34 const TST_INFO_EXTENSIONS = `${TST_INFO}.${EXTENSIONS}`; 35 const CLEAR_PROPS = [ 36 TST_INFO_VERSION, 37 TST_INFO_POLICY, 38 TST_INFO_MESSAGE_IMPRINT, 39 TST_INFO_SERIAL_NUMBER, 40 TST_INFO_GEN_TIME, 41 TST_INFO_ACCURACY, 42 TST_INFO_ORDERING, 43 TST_INFO_NONCE, 44 TST_INFO_TSA, 45 TST_INFO_EXTENSIONS 46 ]; 47 48 export interface ITSTInfo { 49 /** 50 * Version of the time-stamp token. 51 * 52 * Conforming time-stamping servers MUST be able to provide version 1 time-stamp tokens. 53 */ 54 version: number; 55 /** 56 * TSA's policy under which the response was produced. 57 * 58 * If a similar field was present in the TimeStampReq, then it MUST have the same value, 59 * otherwise an error (unacceptedPolicy) MUST be returned 60 */ 61 policy: string; 62 /** 63 * The messageImprint MUST have the same value as the similar field in 64 * TimeStampReq, provided that the size of the hash value matches the 65 * expected size of the hash algorithm identified in hashAlgorithm. 66 */ 67 messageImprint: MessageImprint; 68 /** 69 * Integer assigned by the TSA to each TimeStampToken. 70 * 71 * It MUST be unique for each TimeStampToken issued by a given TSA. 72 */ 73 serialNumber: asn1js.Integer; 74 /** 75 * Time at which the time-stamp token has been created by the TSA 76 */ 77 genTime: Date; 78 /** 79 * Represents the time deviation around the UTC time contained in GeneralizedTime 80 */ 81 accuracy?: Accuracy; 82 /** 83 * If the ordering field is missing, or if the ordering field is present 84 * and set to false, then the genTime field only indicates the time at 85 * which the time-stamp token has been created by the TSA.In such a 86 * case, the ordering of time-stamp tokens issued by the same TSA or 87 * different TSAs is only possible when the difference between the 88 * genTime of the first time-stamp token and the genTime of the second 89 * time-stamp token is greater than the sum of the accuracies of the 90 * genTime for each time-stamp token. 91 * 92 * If the ordering field is present and set to true, every time-stamp 93 * token from the same TSA can always be ordered based on the genTime 94 * field, regardless of the genTime accuracy. 95 */ 96 ordering?: boolean; 97 /** 98 * Field MUST be present if it was present in the TimeStampReq. 99 * In such a case it MUST equal the value provided in the TimeStampReq structure. 100 */ 101 nonce?: asn1js.Integer; 102 /** 103 * `tsa` field is to give a hint in identifying the name of the TSA. 104 * If present, it MUST correspond to one of the subject names included 105 * in the certificate that is to be used to verify the token. 106 */ 107 tsa?: GeneralName; 108 /** 109 * Additional information in the future. Extensions is defined in [RFC2459](https://datatracker.ietf.org/doc/html/rfc2459) 110 */ 111 extensions?: Extension[]; 112 } 113 114 export interface TSTInfoJson { 115 version: number; 116 policy: string; 117 messageImprint: MessageImprintJson; 118 serialNumber: asn1js.IntegerJson; 119 genTime: Date; 120 accuracy?: AccuracyJson; 121 ordering?: boolean; 122 nonce?: asn1js.IntegerJson; 123 tsa?: GeneralNameJson; 124 extensions?: ExtensionJson[]; 125 } 126 127 export type TSTInfoParameters = PkiObjectParameters & Partial<ITSTInfo>; 128 129 export interface TSTInfoVerifyParams { 130 data: ArrayBuffer; 131 notBefore?: Date; 132 notAfter?: Date; 133 } 134 135 /** 136 * Represents the TSTInfo structure described in [RFC3161](https://www.ietf.org/rfc/rfc3161.txt) 137 */ 138 export class TSTInfo extends PkiObject implements ITSTInfo { 139 140 public static override CLASS_NAME = "TSTInfo"; 141 142 public version!: number; 143 public policy!: string; 144 public messageImprint!: MessageImprint; 145 public serialNumber!: asn1js.Integer; 146 public genTime!: Date; 147 public accuracy?: Accuracy; 148 public ordering?: boolean; 149 public nonce?: asn1js.Integer; 150 public tsa?: GeneralName; 151 public extensions?: Extension[]; 152 153 /** 154 * Initializes a new instance of the {@link TSTInfo} class 155 * @param parameters Initialization parameters 156 */ 157 constructor(parameters: TSTInfoParameters = {}) { 158 super(); 159 160 this.version = pvutils.getParametersValue(parameters, VERSION, TSTInfo.defaultValues(VERSION)); 161 this.policy = pvutils.getParametersValue(parameters, POLICY, TSTInfo.defaultValues(POLICY)); 162 this.messageImprint = pvutils.getParametersValue(parameters, MESSAGE_IMPRINT, TSTInfo.defaultValues(MESSAGE_IMPRINT)); 163 this.serialNumber = pvutils.getParametersValue(parameters, SERIAL_NUMBER, TSTInfo.defaultValues(SERIAL_NUMBER)); 164 this.genTime = pvutils.getParametersValue(parameters, GEN_TIME, TSTInfo.defaultValues(GEN_TIME)); 165 166 if (ACCURACY in parameters) { 167 this.accuracy = pvutils.getParametersValue(parameters, ACCURACY, TSTInfo.defaultValues(ACCURACY)); 168 } 169 170 if (ORDERING in parameters) { 171 this.ordering = pvutils.getParametersValue(parameters, ORDERING, TSTInfo.defaultValues(ORDERING)); 172 } 173 174 if (NONCE in parameters) { 175 this.nonce = pvutils.getParametersValue(parameters, NONCE, TSTInfo.defaultValues(NONCE)); 176 } 177 178 if (TSA in parameters) { 179 this.tsa = pvutils.getParametersValue(parameters, TSA, TSTInfo.defaultValues(TSA)); 180 } 181 182 if (EXTENSIONS in parameters) { 183 this.extensions = pvutils.getParametersValue(parameters, EXTENSIONS, TSTInfo.defaultValues(EXTENSIONS)); 184 } 185 186 if (parameters.schema) { 187 this.fromSchema(parameters.schema); 188 } 189 } 190 191 /** 192 * Returns default values for all class members 193 * @param memberName String name for a class member 194 * @returns Default value 195 */ 196 public static override defaultValues(memberName: typeof VERSION): number; 197 public static override defaultValues(memberName: typeof POLICY): string; 198 public static override defaultValues(memberName: typeof MESSAGE_IMPRINT): MessageImprint; 199 public static override defaultValues(memberName: typeof SERIAL_NUMBER): asn1js.Integer; 200 public static override defaultValues(memberName: typeof GEN_TIME): Date; 201 public static override defaultValues(memberName: typeof ACCURACY): Accuracy; 202 public static override defaultValues(memberName: typeof ORDERING): boolean; 203 public static override defaultValues(memberName: typeof NONCE): asn1js.Integer; 204 public static override defaultValues(memberName: typeof TSA): GeneralName; 205 public static override defaultValues(memberName: typeof EXTENSIONS): Extension[]; 206 public static override defaultValues(memberName: string): any { 207 switch (memberName) { 208 case VERSION: 209 return 0; 210 case POLICY: 211 return EMPTY_STRING; 212 case MESSAGE_IMPRINT: 213 return new MessageImprint(); 214 case SERIAL_NUMBER: 215 return new asn1js.Integer(); 216 case GEN_TIME: 217 return new Date(0, 0, 0); 218 case ACCURACY: 219 return new Accuracy(); 220 case ORDERING: 221 return false; 222 case NONCE: 223 return new asn1js.Integer(); 224 case TSA: 225 return new GeneralName(); 226 case EXTENSIONS: 227 return []; 228 default: 229 return super.defaultValues(memberName); 230 } 231 } 232 233 /** 234 * Compare values with default values for all class members 235 * @param memberName String name for a class member 236 * @param memberValue Value to compare with default value 237 */ 238 public static compareWithDefault(memberName: string, memberValue: any): boolean { 239 switch (memberName) { 240 case VERSION: 241 case POLICY: 242 case GEN_TIME: 243 case ORDERING: 244 return (memberValue === TSTInfo.defaultValues(ORDERING)); 245 case MESSAGE_IMPRINT: 246 return ((MessageImprint.compareWithDefault(HASH_ALGORITHM, memberValue.hashAlgorithm)) && 247 (MessageImprint.compareWithDefault(HASHED_MESSAGE, memberValue.hashedMessage))); 248 case SERIAL_NUMBER: 249 case NONCE: 250 return (memberValue.isEqual(TSTInfo.defaultValues(NONCE))); 251 case ACCURACY: 252 return ((Accuracy.compareWithDefault(SECONDS, memberValue.seconds)) && 253 (Accuracy.compareWithDefault(MILLIS, memberValue.millis)) && 254 (Accuracy.compareWithDefault(MICROS, memberValue.micros))); 255 case TSA: 256 return ((GeneralName.compareWithDefault(TYPE, memberValue.type)) && 257 (GeneralName.compareWithDefault(VALUE, memberValue.value))); 258 case EXTENSIONS: 259 return (memberValue.length === 0); 260 default: 261 return super.defaultValues(memberName); 262 } 263 } 264 265 /** 266 * @inheritdoc 267 * @asn ASN.1 schema 268 * ```asn 269 * TSTInfo ::= SEQUENCE { 270 * version INTEGER { v1(1) }, 271 * policy TSAPolicyId, 272 * messageImprint MessageImprint, 273 * serialNumber INTEGER, 274 * genTime GeneralizedTime, 275 * accuracy Accuracy OPTIONAL, 276 * ordering BOOLEAN DEFAULT FALSE, 277 * nonce INTEGER OPTIONAL, 278 * tsa [0] GeneralName OPTIONAL, 279 * extensions [1] IMPLICIT Extensions OPTIONAL } 280 *``` 281 */ 282 public static override schema(parameters: Schema.SchemaParameters<{ 283 version?: string; 284 policy?: string; 285 messageImprint?: MessageImprintSchema; 286 serialNumber?: string; 287 genTime?: string; 288 accuracy?: AccuracySchema; 289 ordering?: string; 290 nonce?: string; 291 tsa?: GeneralNameSchema; 292 extensions?: string; 293 extension?: ExtensionSchema; 294 }> = {}): Schema.SchemaType { 295 const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); 296 297 return (new asn1js.Sequence({ 298 name: (names.blockName || TST_INFO), 299 value: [ 300 new asn1js.Integer({ name: (names.version || TST_INFO_VERSION) }), 301 new asn1js.ObjectIdentifier({ name: (names.policy || TST_INFO_POLICY) }), 302 MessageImprint.schema(names.messageImprint || { 303 names: { 304 blockName: TST_INFO_MESSAGE_IMPRINT 305 } 306 }), 307 new asn1js.Integer({ name: (names.serialNumber || TST_INFO_SERIAL_NUMBER) }), 308 new asn1js.GeneralizedTime({ name: (names.genTime || TST_INFO_GEN_TIME) }), 309 Accuracy.schema(names.accuracy || { 310 names: { 311 blockName: TST_INFO_ACCURACY 312 } 313 }), 314 new asn1js.Boolean({ 315 name: (names.ordering || TST_INFO_ORDERING), 316 optional: true 317 }), 318 new asn1js.Integer({ 319 name: (names.nonce || TST_INFO_NONCE), 320 optional: true 321 }), 322 new asn1js.Constructed({ 323 optional: true, 324 idBlock: { 325 tagClass: 3, // CONTEXT-SPECIFIC 326 tagNumber: 0 // [0] 327 }, 328 value: [GeneralName.schema(names.tsa || { 329 names: { 330 blockName: TST_INFO_TSA 331 } 332 })] 333 }), 334 new asn1js.Constructed({ 335 optional: true, 336 idBlock: { 337 tagClass: 3, // CONTEXT-SPECIFIC 338 tagNumber: 1 // [1] 339 }, 340 value: [ 341 new asn1js.Repeated({ 342 name: (names.extensions || TST_INFO_EXTENSIONS), 343 value: Extension.schema(names.extension || {}) 344 }) 345 ] 346 }) // IMPLICIT Extensions 347 ] 348 })); 349 } 350 351 public fromSchema(schema: Schema.SchemaType): void { 352 // Clear input data first 353 pvutils.clearProps(schema, CLEAR_PROPS); 354 355 // Check the schema is valid 356 const asn1 = asn1js.compareSchema(schema, 357 schema, 358 TSTInfo.schema() 359 ); 360 AsnError.assertSchema(asn1, this.className); 361 362 // Get internal properties from parsed schema 363 this.version = asn1.result[TST_INFO_VERSION].valueBlock.valueDec; 364 this.policy = asn1.result[TST_INFO_POLICY].valueBlock.toString(); 365 this.messageImprint = new MessageImprint({ schema: asn1.result[TST_INFO_MESSAGE_IMPRINT] }); 366 this.serialNumber = asn1.result[TST_INFO_SERIAL_NUMBER]; 367 this.genTime = asn1.result[TST_INFO_GEN_TIME].toDate(); 368 if (TST_INFO_ACCURACY in asn1.result) 369 this.accuracy = new Accuracy({ schema: asn1.result[TST_INFO_ACCURACY] }); 370 if (TST_INFO_ORDERING in asn1.result) 371 this.ordering = asn1.result[TST_INFO_ORDERING].valueBlock.value; 372 if (TST_INFO_NONCE in asn1.result) 373 this.nonce = asn1.result[TST_INFO_NONCE]; 374 if (TST_INFO_TSA in asn1.result) 375 this.tsa = new GeneralName({ schema: asn1.result[TST_INFO_TSA] }); 376 if (TST_INFO_EXTENSIONS in asn1.result) 377 this.extensions = Array.from(asn1.result[TST_INFO_EXTENSIONS], element => new Extension({ schema: element })); 378 } 379 380 public toSchema(): asn1js.Sequence { 381 //#region Create array for output sequence 382 const outputArray = []; 383 384 outputArray.push(new asn1js.Integer({ value: this.version })); 385 outputArray.push(new asn1js.ObjectIdentifier({ value: this.policy })); 386 outputArray.push(this.messageImprint.toSchema()); 387 outputArray.push(this.serialNumber); 388 outputArray.push(new asn1js.GeneralizedTime({ valueDate: this.genTime })); 389 if (this.accuracy) 390 outputArray.push(this.accuracy.toSchema()); 391 if (this.ordering !== undefined) 392 outputArray.push(new asn1js.Boolean({ value: this.ordering })); 393 if (this.nonce) 394 outputArray.push(this.nonce); 395 if (this.tsa) { 396 outputArray.push(new asn1js.Constructed({ 397 optional: true, 398 idBlock: { 399 tagClass: 3, // CONTEXT-SPECIFIC 400 tagNumber: 0 // [0] 401 }, 402 value: [this.tsa.toSchema()] 403 })); 404 } 405 406 //#region Create array of extensions 407 if (this.extensions) { 408 outputArray.push(new asn1js.Constructed({ 409 optional: true, 410 idBlock: { 411 tagClass: 3, // CONTEXT-SPECIFIC 412 tagNumber: 1 // [1] 413 }, 414 value: Array.from(this.extensions, o => o.toSchema()) 415 })); 416 } 417 //#endregion 418 //#endregion 419 420 //#region Construct and return new ASN.1 schema for this object 421 return (new asn1js.Sequence({ 422 value: outputArray 423 })); 424 //#endregion 425 } 426 427 public toJSON(): TSTInfoJson { 428 const res: TSTInfoJson = { 429 version: this.version, 430 policy: this.policy, 431 messageImprint: this.messageImprint.toJSON(), 432 serialNumber: this.serialNumber.toJSON(), 433 genTime: this.genTime 434 }; 435 436 if (this.accuracy) 437 res.accuracy = this.accuracy.toJSON(); 438 439 if (this.ordering !== undefined) 440 res.ordering = this.ordering; 441 442 if (this.nonce) 443 res.nonce = this.nonce.toJSON(); 444 445 if (this.tsa) 446 res.tsa = this.tsa.toJSON(); 447 448 if (this.extensions) 449 res.extensions = Array.from(this.extensions, o => o.toJSON()); 450 451 return res; 452 } 453 454 /** 455 * Verify current TST Info value 456 * @param params Input parameters 457 * @param crypto Crypto engine 458 */ 459 public async verify(params: TSTInfoVerifyParams, crypto = common.getCrypto(true)): Promise<boolean> { 460 461 //#region Get initial parameters 462 if (!params.data) { 463 throw new Error("\"data\" is a mandatory attribute for TST_INFO verification"); 464 } 465 const data = params.data; 466 //#endregion 467 468 //#region Check date 469 if (params.notBefore) { 470 if (this.genTime < params.notBefore) 471 throw new Error("Generation time for TSTInfo object is less than notBefore value"); 472 } 473 474 if (params.notAfter) { 475 if (this.genTime > params.notAfter) 476 throw new Error("Generation time for TSTInfo object is more than notAfter value"); 477 } 478 //#endregion 479 480 // Find hashing algorithm 481 const shaAlgorithm = crypto.getAlgorithmByOID(this.messageImprint.hashAlgorithm.algorithmId, true, "MessageImprint.hashAlgorithm"); 482 483 // Calculate message digest for input "data" buffer 484 const hash = await crypto.digest(shaAlgorithm.name, new Uint8Array(data)); 485 return pvtsutils.BufferSourceConverter.isEqual(hash, this.messageImprint.hashedMessage.valueBlock.valueHexView); 486 } 487 488 }