diff options
Diffstat (limited to 'third_party/js/PKI.js/src/TimeStampResp.ts')
-rw-r--r-- | third_party/js/PKI.js/src/TimeStampResp.ts | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/third_party/js/PKI.js/src/TimeStampResp.ts b/third_party/js/PKI.js/src/TimeStampResp.ts new file mode 100644 index 0000000000..bb400f9f7a --- /dev/null +++ b/third_party/js/PKI.js/src/TimeStampResp.ts @@ -0,0 +1,286 @@ +import * as asn1js from "asn1js"; +import * as pvutils from "pvutils"; +import { PKIStatusInfo, PKIStatusInfoJson, PKIStatusInfoSchema } from "./PKIStatusInfo"; +import { ContentInfo, ContentInfoJson, ContentInfoSchema } from "./ContentInfo"; +import { SignedData } from "./SignedData"; +import * as Schema from "./Schema"; +import { id_ContentType_SignedData } from "./ObjectIdentifiers"; +import { Certificate } from "./Certificate"; +import { PkiObject, PkiObjectParameters } from "./PkiObject"; +import { AsnError } from "./errors"; +import { EMPTY_BUFFER, EMPTY_STRING } from "./constants"; +import * as common from "./common"; + +const STATUS = "status"; +const TIME_STAMP_TOKEN = "timeStampToken"; +const TIME_STAMP_RESP = "TimeStampResp"; +const TIME_STAMP_RESP_STATUS = `${TIME_STAMP_RESP}.${STATUS}`; +const TIME_STAMP_RESP_TOKEN = `${TIME_STAMP_RESP}.${TIME_STAMP_TOKEN}`; +const CLEAR_PROPS = [ + TIME_STAMP_RESP_STATUS, + TIME_STAMP_RESP_TOKEN +]; + +export interface ITimeStampResp { + /** + * Time-Stamp status + */ + status: PKIStatusInfo; + /** + * Time-Stamp token + */ + timeStampToken?: ContentInfo; +} + +export interface TimeStampRespJson { + status: PKIStatusInfoJson; + timeStampToken?: ContentInfoJson; +} + +export interface TimeStampRespVerifyParams { + signer?: number; + trustedCerts?: Certificate[]; + data?: ArrayBuffer; +} + +export type TimeStampRespParameters = PkiObjectParameters & Partial<ITimeStampResp>; + +/** + * Represents the TimeStampResp structure described in [RFC3161](https://www.ietf.org/rfc/rfc3161.txt) + * + * @example The following example demonstrates how to create and sign Time-Stamp Response + * ```js + * // Generate random serial number + * const serialNumber = pkijs.getRandomValues(new Uint8Array(10)).buffer; + * + * // Create specific TST info structure to sign + * const tstInfo = new pkijs.TSTInfo({ + * version: 1, + * policy: tspReq.reqPolicy, + * messageImprint: tspReq.messageImprint, + * serialNumber: new asn1js.Integer({ valueHex: serialNumber }), + * genTime: new Date(), + * ordering: true, + * accuracy: new pkijs.Accuracy({ + * seconds: 1, + * millis: 1, + * micros: 10 + * }), + * nonce: tspReq.nonce, + * }); + * + * // Create and sign CMS Signed Data with TSTInfo + * const cmsSigned = new pkijs.SignedData({ + * version: 3, + * encapContentInfo: new pkijs.EncapsulatedContentInfo({ + * eContentType: "1.2.840.113549.1.9.16.1.4", // "tSTInfo" content type + * eContent: new asn1js.OctetString({ valueHex: tstInfo.toSchema().toBER() }), + * }), + * signerInfos: [ + * new pkijs.SignerInfo({ + * version: 1, + * sid: new pkijs.IssuerAndSerialNumber({ + * issuer: cert.issuer, + * serialNumber: cert.serialNumber + * }) + * }) + * ], + * certificates: [cert] + * }); + * + * await cmsSigned.sign(keys.privateKey, 0, "SHA-256"); + * + * // Create CMS Content Info + * const cmsContent = new pkijs.ContentInfo({ + * contentType: pkijs.ContentInfo.SIGNED_DATA, + * content: cmsSigned.toSchema(true) + * }); + * + * // Finally create completed TSP response structure + * const tspResp = new pkijs.TimeStampResp({ + * status: new pkijs.PKIStatusInfo({ status: pkijs.PKIStatus.granted }), + * timeStampToken: new pkijs.ContentInfo({ schema: cmsContent.toSchema() }) + * }); + * + * const tspRespRaw = tspResp.toSchema().toBER(); + * ``` + */ +export class TimeStampResp extends PkiObject implements ITimeStampResp { + + public static override CLASS_NAME = "TimeStampResp"; + + public status!: PKIStatusInfo; + public timeStampToken?: ContentInfo; + + /** + * Initializes a new instance of the {@link TimeStampResp} class + * @param parameters Initialization parameters + */ + constructor(parameters: TimeStampRespParameters = {}) { + super(); + + this.status = pvutils.getParametersValue(parameters, STATUS, TimeStampResp.defaultValues(STATUS)); + if (TIME_STAMP_TOKEN in parameters) { + this.timeStampToken = pvutils.getParametersValue(parameters, TIME_STAMP_TOKEN, TimeStampResp.defaultValues(TIME_STAMP_TOKEN)); + } + + if (parameters.schema) { + this.fromSchema(parameters.schema); + } + } + + /** + * Returns default values for all class members + * @param memberName String name for a class member + * @returns Default value + */ + public static override defaultValues(memberName: typeof STATUS): PKIStatusInfo; + public static override defaultValues(memberName: typeof TIME_STAMP_TOKEN): ContentInfo; + public static override defaultValues(memberName: string): any { + switch (memberName) { + case STATUS: + return new PKIStatusInfo(); + case TIME_STAMP_TOKEN: + return new ContentInfo(); + default: + return super.defaultValues(memberName); + } + } + + /** + * Compare values with default values for all class members + * @param memberName String name for a class member + * @param memberValue Value to compare with default value + */ + public static compareWithDefault(memberName: string, memberValue: any): boolean { + switch (memberName) { + case STATUS: + return ((PKIStatusInfo.compareWithDefault(STATUS, memberValue.status)) && + (("statusStrings" in memberValue) === false) && + (("failInfo" in memberValue) === false)); + case TIME_STAMP_TOKEN: + return ((memberValue.contentType === EMPTY_STRING) && + (memberValue.content instanceof asn1js.Any)); + default: + return super.defaultValues(memberName); + } + } + + /** + * @inheritdoc + * @asn ASN.1 schema + * ```asn + * TimeStampResp ::= SEQUENCE { + * status PKIStatusInfo, + * timeStampToken TimeStampToken OPTIONAL } + *``` + */ + public static override schema(parameters: Schema.SchemaParameters<{ + status?: PKIStatusInfoSchema, + timeStampToken?: ContentInfoSchema, + }> = {}): Schema.SchemaType { + const names = pvutils.getParametersValue<NonNullable<typeof parameters.names>>(parameters, "names", {}); + + return (new asn1js.Sequence({ + name: (names.blockName || TIME_STAMP_RESP), + value: [ + PKIStatusInfo.schema(names.status || { + names: { + blockName: TIME_STAMP_RESP_STATUS + } + }), + ContentInfo.schema(names.timeStampToken || { + names: { + blockName: TIME_STAMP_RESP_TOKEN, + optional: true + } + }) + ] + })); + } + + public fromSchema(schema: Schema.SchemaType): void { + // Clear input data first + pvutils.clearProps(schema, CLEAR_PROPS); + + // Check the schema is valid + const asn1 = asn1js.compareSchema(schema, + schema, + TimeStampResp.schema() + ); + AsnError.assertSchema(asn1, this.className); + + // Get internal properties from parsed schema + this.status = new PKIStatusInfo({ schema: asn1.result[TIME_STAMP_RESP_STATUS] }); + if (TIME_STAMP_RESP_TOKEN in asn1.result) + this.timeStampToken = new ContentInfo({ schema: asn1.result[TIME_STAMP_RESP_TOKEN] }); + } + + public toSchema(): asn1js.Sequence { + //#region Create array for output sequence + const outputArray = []; + + outputArray.push(this.status.toSchema()); + if (this.timeStampToken) { + outputArray.push(this.timeStampToken.toSchema()); + } + //#endregion + + //#region Construct and return new ASN.1 schema for this object + return (new asn1js.Sequence({ + value: outputArray + })); + //#endregion + } + + public toJSON(): TimeStampRespJson { + const res: TimeStampRespJson = { + status: this.status.toJSON() + }; + + if (this.timeStampToken) { + res.timeStampToken = this.timeStampToken.toJSON(); + } + + return res; + } + + /** + * Sign current TSP Response + * @param privateKey Private key for "subjectPublicKeyInfo" structure + * @param hashAlgorithm Hashing algorithm. Default SHA-1 + * @param crypto Crypto engine + */ + public async sign(privateKey: CryptoKey, hashAlgorithm?: string, crypto = common.getCrypto(true)) { + this.assertContentType(); + + // Sign internal signed data value + const signed = new SignedData({ schema: this.timeStampToken.content }); + + return signed.sign(privateKey, 0, hashAlgorithm, undefined, crypto); + } + + /** + * Verify current TSP Response + * @param verificationParameters Input parameters for verification + * @param crypto Crypto engine + */ + public async verify(verificationParameters: TimeStampRespVerifyParams = { signer: 0, trustedCerts: [], data: EMPTY_BUFFER }, crypto = common.getCrypto(true)): Promise<boolean> { + this.assertContentType(); + + // Verify internal signed data value + const signed = new SignedData({ schema: this.timeStampToken.content }); + + return signed.verify(verificationParameters, crypto); + } + + private assertContentType(): asserts this is { timeStampToken: ContentInfo; } { + if (!this.timeStampToken) { + throw new Error("timeStampToken is absent in TSP response"); + } + if (this.timeStampToken.contentType !== id_ContentType_SignedData) { // Must be a CMS signed data + throw new Error(`Wrong format of timeStampToken: ${this.timeStampToken.contentType}`); + } + } +} + |