TimeStampResp.ts (9076B)
1 import * as asn1js from "asn1js"; 2 import * as pvutils from "pvutils"; 3 import { PKIStatusInfo, PKIStatusInfoJson, PKIStatusInfoSchema } from "./PKIStatusInfo"; 4 import { ContentInfo, ContentInfoJson, ContentInfoSchema } from "./ContentInfo"; 5 import { SignedData } from "./SignedData"; 6 import * as Schema from "./Schema"; 7 import { id_ContentType_SignedData } from "./ObjectIdentifiers"; 8 import { Certificate } from "./Certificate"; 9 import { PkiObject, PkiObjectParameters } from "./PkiObject"; 10 import { AsnError } from "./errors"; 11 import { EMPTY_BUFFER, EMPTY_STRING } from "./constants"; 12 import * as common from "./common"; 13 14 const STATUS = "status"; 15 const TIME_STAMP_TOKEN = "timeStampToken"; 16 const TIME_STAMP_RESP = "TimeStampResp"; 17 const TIME_STAMP_RESP_STATUS = `${TIME_STAMP_RESP}.${STATUS}`; 18 const TIME_STAMP_RESP_TOKEN = `${TIME_STAMP_RESP}.${TIME_STAMP_TOKEN}`; 19 const CLEAR_PROPS = [ 20 TIME_STAMP_RESP_STATUS, 21 TIME_STAMP_RESP_TOKEN 22 ]; 23 24 export interface ITimeStampResp { 25 /** 26 * Time-Stamp status 27 */ 28 status: PKIStatusInfo; 29 /** 30 * Time-Stamp token 31 */ 32 timeStampToken?: ContentInfo; 33 } 34 35 export interface TimeStampRespJson { 36 status: PKIStatusInfoJson; 37 timeStampToken?: ContentInfoJson; 38 } 39 40 export interface TimeStampRespVerifyParams { 41 signer?: number; 42 trustedCerts?: Certificate[]; 43 data?: ArrayBuffer; 44 } 45 46 export type TimeStampRespParameters = PkiObjectParameters & Partial<ITimeStampResp>; 47 48 /** 49 * Represents the TimeStampResp structure described in [RFC3161](https://www.ietf.org/rfc/rfc3161.txt) 50 * 51 * @example The following example demonstrates how to create and sign Time-Stamp Response 52 * ```js 53 * // Generate random serial number 54 * const serialNumber = pkijs.getRandomValues(new Uint8Array(10)).buffer; 55 * 56 * // Create specific TST info structure to sign 57 * const tstInfo = new pkijs.TSTInfo({ 58 * version: 1, 59 * policy: tspReq.reqPolicy, 60 * messageImprint: tspReq.messageImprint, 61 * serialNumber: new asn1js.Integer({ valueHex: serialNumber }), 62 * genTime: new Date(), 63 * ordering: true, 64 * accuracy: new pkijs.Accuracy({ 65 * seconds: 1, 66 * millis: 1, 67 * micros: 10 68 * }), 69 * nonce: tspReq.nonce, 70 * }); 71 * 72 * // Create and sign CMS Signed Data with TSTInfo 73 * const cmsSigned = new pkijs.SignedData({ 74 * version: 3, 75 * encapContentInfo: new pkijs.EncapsulatedContentInfo({ 76 * eContentType: "1.2.840.113549.1.9.16.1.4", // "tSTInfo" content type 77 * eContent: new asn1js.OctetString({ valueHex: tstInfo.toSchema().toBER() }), 78 * }), 79 * signerInfos: [ 80 * new pkijs.SignerInfo({ 81 * version: 1, 82 * sid: new pkijs.IssuerAndSerialNumber({ 83 * issuer: cert.issuer, 84 * serialNumber: cert.serialNumber 85 * }) 86 * }) 87 * ], 88 * certificates: [cert] 89 * }); 90 * 91 * await cmsSigned.sign(keys.privateKey, 0, "SHA-256"); 92 * 93 * // Create CMS Content Info 94 * const cmsContent = new pkijs.ContentInfo({ 95 * contentType: pkijs.ContentInfo.SIGNED_DATA, 96 * content: cmsSigned.toSchema(true) 97 * }); 98 * 99 * // Finally create completed TSP response structure 100 * const tspResp = new pkijs.TimeStampResp({ 101 * status: new pkijs.PKIStatusInfo({ status: pkijs.PKIStatus.granted }), 102 * timeStampToken: new pkijs.ContentInfo({ schema: cmsContent.toSchema() }) 103 * }); 104 * 105 * const tspRespRaw = tspResp.toSchema().toBER(); 106 * ``` 107 */ 108 export class TimeStampResp extends PkiObject implements ITimeStampResp { 109 110 public static override CLASS_NAME = "TimeStampResp"; 111 112 public status!: PKIStatusInfo; 113 public timeStampToken?: ContentInfo; 114 115 /** 116 * Initializes a new instance of the {@link TimeStampResp} class 117 * @param parameters Initialization parameters 118 */ 119 constructor(parameters: TimeStampRespParameters = {}) { 120 super(); 121 122 this.status = pvutils.getParametersValue(parameters, STATUS, TimeStampResp.defaultValues(STATUS)); 123 if (TIME_STAMP_TOKEN in parameters) { 124 this.timeStampToken = pvutils.getParametersValue(parameters, TIME_STAMP_TOKEN, TimeStampResp.defaultValues(TIME_STAMP_TOKEN)); 125 } 126 127 if (parameters.schema) { 128 this.fromSchema(parameters.schema); 129 } 130 } 131 132 /** 133 * Returns default values for all class members 134 * @param memberName String name for a class member 135 * @returns Default value 136 */ 137 public static override defaultValues(memberName: typeof STATUS): PKIStatusInfo; 138 public static override defaultValues(memberName: typeof TIME_STAMP_TOKEN): ContentInfo; 139 public static override defaultValues(memberName: string): any { 140 switch (memberName) { 141 case STATUS: 142 return new PKIStatusInfo(); 143 case TIME_STAMP_TOKEN: 144 return new ContentInfo(); 145 default: 146 return super.defaultValues(memberName); 147 } 148 } 149 150 /** 151 * Compare values with default values for all class members 152 * @param memberName String name for a class member 153 * @param memberValue Value to compare with default value 154 */ 155 public static compareWithDefault(memberName: string, memberValue: any): boolean { 156 switch (memberName) { 157 case STATUS: 158 return ((PKIStatusInfo.compareWithDefault(STATUS, memberValue.status)) && 159 (("statusStrings" in memberValue) === false) && 160 (("failInfo" in memberValue) === false)); 161 case TIME_STAMP_TOKEN: 162 return ((memberValue.contentType === EMPTY_STRING) && 163 (memberValue.content instanceof asn1js.Any)); 164 default: 165 return super.defaultValues(memberName); 166 } 167 } 168 169 /** 170 * @inheritdoc 171 * @asn ASN.1 schema 172 * ```asn 173 * TimeStampResp ::= SEQUENCE { 174 * status PKIStatusInfo, 175 * timeStampToken TimeStampToken OPTIONAL } 176 *``` 177 */ 178 public static override schema(parameters: Schema.SchemaParameters<{ 179 status?: PKIStatusInfoSchema, 180 timeStampToken?: ContentInfoSchema, 181 }> = {}): Schema.SchemaType { 182 const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); 183 184 return (new asn1js.Sequence({ 185 name: (names.blockName || TIME_STAMP_RESP), 186 value: [ 187 PKIStatusInfo.schema(names.status || { 188 names: { 189 blockName: TIME_STAMP_RESP_STATUS 190 } 191 }), 192 ContentInfo.schema(names.timeStampToken || { 193 names: { 194 blockName: TIME_STAMP_RESP_TOKEN, 195 optional: true 196 } 197 }) 198 ] 199 })); 200 } 201 202 public fromSchema(schema: Schema.SchemaType): void { 203 // Clear input data first 204 pvutils.clearProps(schema, CLEAR_PROPS); 205 206 // Check the schema is valid 207 const asn1 = asn1js.compareSchema(schema, 208 schema, 209 TimeStampResp.schema() 210 ); 211 AsnError.assertSchema(asn1, this.className); 212 213 // Get internal properties from parsed schema 214 this.status = new PKIStatusInfo({ schema: asn1.result[TIME_STAMP_RESP_STATUS] }); 215 if (TIME_STAMP_RESP_TOKEN in asn1.result) 216 this.timeStampToken = new ContentInfo({ schema: asn1.result[TIME_STAMP_RESP_TOKEN] }); 217 } 218 219 public toSchema(): asn1js.Sequence { 220 //#region Create array for output sequence 221 const outputArray = []; 222 223 outputArray.push(this.status.toSchema()); 224 if (this.timeStampToken) { 225 outputArray.push(this.timeStampToken.toSchema()); 226 } 227 //#endregion 228 229 //#region Construct and return new ASN.1 schema for this object 230 return (new asn1js.Sequence({ 231 value: outputArray 232 })); 233 //#endregion 234 } 235 236 public toJSON(): TimeStampRespJson { 237 const res: TimeStampRespJson = { 238 status: this.status.toJSON() 239 }; 240 241 if (this.timeStampToken) { 242 res.timeStampToken = this.timeStampToken.toJSON(); 243 } 244 245 return res; 246 } 247 248 /** 249 * Sign current TSP Response 250 * @param privateKey Private key for "subjectPublicKeyInfo" structure 251 * @param hashAlgorithm Hashing algorithm. Default SHA-1 252 * @param crypto Crypto engine 253 */ 254 public async sign(privateKey: CryptoKey, hashAlgorithm?: string, crypto = common.getCrypto(true)) { 255 this.assertContentType(); 256 257 // Sign internal signed data value 258 const signed = new SignedData({ schema: this.timeStampToken.content }); 259 260 return signed.sign(privateKey, 0, hashAlgorithm, undefined, crypto); 261 } 262 263 /** 264 * Verify current TSP Response 265 * @param verificationParameters Input parameters for verification 266 * @param crypto Crypto engine 267 */ 268 public async verify(verificationParameters: TimeStampRespVerifyParams = { signer: 0, trustedCerts: [], data: EMPTY_BUFFER }, crypto = common.getCrypto(true)): Promise<boolean> { 269 this.assertContentType(); 270 271 // Verify internal signed data value 272 const signed = new SignedData({ schema: this.timeStampToken.content }); 273 274 return signed.verify(verificationParameters, crypto); 275 } 276 277 private assertContentType(): asserts this is { timeStampToken: ContentInfo; } { 278 if (!this.timeStampToken) { 279 throw new Error("timeStampToken is absent in TSP response"); 280 } 281 if (this.timeStampToken.contentType !== id_ContentType_SignedData) { // Must be a CMS signed data 282 throw new Error(`Wrong format of timeStampToken: ${this.timeStampToken.contentType}`); 283 } 284 } 285 }