import * as asn1js from "asn1js"; import { getParametersValue, clearProps } from "pvutils"; import CertID from "./CertID.js"; import Extension from "./Extension.js"; import Extensions from "./Extensions.js"; //************************************************************************************** /** * Class from RFC6960 */ export default class SingleResponse { //********************************************************************************** /** * Constructor for SingleResponse class * @param {Object} [parameters={}] * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from */ constructor(parameters = {}) { //region Internal properties of the object /** * @type {CertID} * @desc certID */ this.certID = getParametersValue(parameters, "certID", SingleResponse.defaultValues("certID")); /** * @type {Object} * @desc certStatus */ this.certStatus = getParametersValue(parameters, "certStatus", SingleResponse.defaultValues("certStatus")); /** * @type {Date} * @desc thisUpdate */ this.thisUpdate = getParametersValue(parameters, "thisUpdate", SingleResponse.defaultValues("thisUpdate")); if("nextUpdate" in parameters) /** * @type {Date} * @desc nextUpdate */ this.nextUpdate = getParametersValue(parameters, "nextUpdate", SingleResponse.defaultValues("nextUpdate")); if("singleExtensions" in parameters) /** * @type {Array.} * @desc singleExtensions */ this.singleExtensions = getParametersValue(parameters, "singleExtensions", SingleResponse.defaultValues("singleExtensions")); //endregion //region If input argument array contains "schema" for this object if("schema" in parameters) this.fromSchema(parameters.schema); //endregion } //********************************************************************************** /** * Return default values for all class members * @param {string} memberName String name for a class member */ static defaultValues(memberName) { switch(memberName) { case "certID": return new CertID(); case "certStatus": return {}; case "thisUpdate": case "nextUpdate": return new Date(0, 0, 0); case "singleExtensions": return []; default: throw new Error(`Invalid member name for SingleResponse class: ${memberName}`); } } //********************************************************************************** /** * Compare values with default values for all class members * @param {string} memberName String name for a class member * @param {*} memberValue Value to compare with default value */ static compareWithDefault(memberName, memberValue) { switch(memberName) { case "certID": // noinspection OverlyComplexBooleanExpressionJS return ((CertID.compareWithDefault("hashAlgorithm", memberValue.hashAlgorithm)) && (CertID.compareWithDefault("issuerNameHash", memberValue.issuerNameHash)) && (CertID.compareWithDefault("issuerKeyHash", memberValue.issuerKeyHash)) && (CertID.compareWithDefault("serialNumber", memberValue.serialNumber))); case "certStatus": return (Object.keys(memberValue).length === 0); case "thisUpdate": case "nextUpdate": return (memberValue === SingleResponse.defaultValues(memberName)); default: throw new Error(`Invalid member name for SingleResponse class: ${memberName}`); } } //********************************************************************************** /** * Return value of pre-defined ASN.1 schema for current class * * ASN.1 schema: * ```asn1 * SingleResponse ::= SEQUENCE { * certID CertID, * certStatus CertStatus, * thisUpdate GeneralizedTime, * nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, * singleExtensions [1] EXPLICIT Extensions OPTIONAL } * * CertStatus ::= CHOICE { * good [0] IMPLICIT NULL, * revoked [1] IMPLICIT RevokedInfo, * unknown [2] IMPLICIT UnknownInfo } * * RevokedInfo ::= SEQUENCE { * revocationTime GeneralizedTime, * revocationReason [0] EXPLICIT CRLReason OPTIONAL } * * UnknownInfo ::= NULL * ``` * * @param {Object} parameters Input parameters for the schema * @returns {Object} asn1js schema object */ static schema(parameters = {}) { /** * @type {Object} * @property {string} [blockName] * @property {string} [certID] * @property {string} [certStatus] * @property {string} [thisUpdate] * @property {string} [nextUpdate] * @property {string} [singleExtensions] */ const names = getParametersValue(parameters, "names", {}); return (new asn1js.Sequence({ name: (names.blockName || ""), value: [ CertID.schema(names.certID || {}), new asn1js.Choice({ value: [ new asn1js.Primitive({ name: (names.certStatus || ""), idBlock: { tagClass: 3, // CONTEXT-SPECIFIC tagNumber: 0 // [0] }, lenBlockLength: 1 // The length contains one byte 0x00 }), // IMPLICIT NULL (no "valueBlock") new asn1js.Constructed({ name: (names.certStatus || ""), idBlock: { tagClass: 3, // CONTEXT-SPECIFIC tagNumber: 1 // [1] }, value: [ new asn1js.GeneralizedTime(), new asn1js.Constructed({ optional: true, idBlock: { tagClass: 3, // CONTEXT-SPECIFIC tagNumber: 0 // [0] }, value: [new asn1js.Enumerated()] }) ] }), new asn1js.Primitive({ name: (names.certStatus || ""), idBlock: { tagClass: 3, // CONTEXT-SPECIFIC tagNumber: 2 // [2] }, lenBlock: { length: 1 } }) // IMPLICIT NULL (no "valueBlock") ] }), new asn1js.GeneralizedTime({ name: (names.thisUpdate || "") }), new asn1js.Constructed({ optional: true, idBlock: { tagClass: 3, // CONTEXT-SPECIFIC tagNumber: 0 // [0] }, value: [new asn1js.GeneralizedTime({ name: (names.nextUpdate || "") })] }), new asn1js.Constructed({ optional: true, idBlock: { tagClass: 3, // CONTEXT-SPECIFIC tagNumber: 1 // [1] }, value: [Extensions.schema(names.singleExtensions || {})] }) // EXPLICIT SEQUENCE value ] })); } //********************************************************************************** /** * Convert parsed asn1js object into current class * @param {!Object} schema */ fromSchema(schema) { //region Clear input data first clearProps(schema, [ "certID", "certStatus", "thisUpdate", "nextUpdate", "singleExtensions" ]); //endregion //region Check the schema is valid const asn1 = asn1js.compareSchema(schema, schema, SingleResponse.schema({ names: { certID: { names: { blockName: "certID" } }, certStatus: "certStatus", thisUpdate: "thisUpdate", nextUpdate: "nextUpdate", singleExtensions: { names: { blockName: "singleExtensions" } } } }) ); if(asn1.verified === false) throw new Error("Object's schema was not verified against input data for SingleResponse"); //endregion //region Get internal properties from parsed schema this.certID = new CertID({ schema: asn1.result.certID }); this.certStatus = asn1.result.certStatus; this.thisUpdate = asn1.result.thisUpdate.toDate(); if("nextUpdate" in asn1.result) this.nextUpdate = asn1.result.nextUpdate.toDate(); if("singleExtensions" in asn1.result) this.singleExtensions = Array.from(asn1.result.singleExtensions.valueBlock.value, element => new Extension({ schema: element })); //endregion } //********************************************************************************** /** * Convert current object to asn1js object and set correct values * @returns {Object} asn1js object */ toSchema() { //region Create value array for output sequence const outputArray = []; outputArray.push(this.certID.toSchema()); outputArray.push(this.certStatus); outputArray.push(new asn1js.GeneralizedTime({ valueDate: this.thisUpdate })); if("nextUpdate" in this) { outputArray.push(new asn1js.Constructed({ idBlock: { tagClass: 3, // CONTEXT-SPECIFIC tagNumber: 0 // [0] }, value: [new asn1js.GeneralizedTime({ valueDate: this.nextUpdate })] })); } if("singleExtensions" in this) { outputArray.push(new asn1js.Sequence({ value: Array.from(this.singleExtensions, element => element.toSchema()) })); } //endregion //region Construct and return new ASN.1 schema for this object return (new asn1js.Sequence({ value: outputArray })); //endregion } //********************************************************************************** /** * Convertion for the class to JSON object * @returns {Object} */ toJSON() { const _object = { certID: this.certID.toJSON(), certStatus: this.certStatus.toJSON(), thisUpdate: this.thisUpdate }; if("nextUpdate" in this) _object.nextUpdate = this.nextUpdate; if("singleExtensions" in this) _object.singleExtensions = Array.from(this.singleExtensions, element => element.toJSON()); return _object; } //********************************************************************************** } //**************************************************************************************