+
+
+
+
+
+
+
+
+
+
+
+
+
+
Local Network Any Permission
+
Granted Permissions
+
+
+
*Minimum 8 characters with upper, lowercase, 0-9, and one of !@#$%^&*()+-
+
+
+
+
+
+
+
+
+
+
+
+
*Minimum 8 characters with upper, lowercase, 0-9, and one of !@#$%^&*()+-
+
+
+
+
+
+
+
+
+
Digest / None Digest / TLS Kerberos / None Kerberos / TLS Auth / Security
+
+
+
+
*Minimum 8 characters with upper, lowercase, 0-9, and one of !@#$%^&*()+-
+
+
+
+
*Alternate hostname for Kerberos token request
+
+
+
+
+
+
+
+ Warning: Some power actions may result in data loss and may disconnect the desktop, terminal or disk redirection sessions.
+
+
+
+
+
+
Primary display Secondary display Third display Consent Display
+
+
+
+
+
+
+ RLE8, Color Fast
+ RLE16, Color
+ RLE4G, Gray Fastest
+ RLE8G, Gray Fast
+ RAW8, Color Slow
+ RAW16, Color Very Slow
+
+
Image Encoding
+
+
+
+
+
Software KVM
+
+
+ 50%
+ 40%
+ 30%
+ 20%
+ 10%
+ 5%
+ 1%
+
+
Quality
+
+
+
+ 100%
+ 87.5%
+ 75%
+ 62.5%
+ 50%
+ 37.5%
+ 25%
+ 12.5%
+
+
Scaling
+
+
+
+
+
+
+
+
+
+ Redirection Port
+ KVM Remote Desktop
+ IDE-Redirection
+ Serial-over-LAN
+
+
+ Not Required
+ Required for KVM only
+ Always Required
+
+
+
+
+
+
+
+
+
+
+
+ WPA2 PSK
+
+ WPA PSK
+
+
+
+
Authentication
+
+
+
+ CCMP-AES
+ TKIP-RC4
+ WEP
+ None
+
+
Encryption
+
+
+
+
+
+
+
+ Valid Record
+ Scrambled Record
+
+
+
+
+ Version 1
+ Version 2
+ Version 3
+ Version 4
+
+
Setup.bin version
+
+
+
+ Records will not be consumed
+ Records will be consumed
+
+
Record consumption
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Use encryption. The file will contain Intel® AMT credentials, saving using a password may help protect this data.
+
+
+
+
+
*Minimum 8 characters with upper, lowercase, 0-9, and one of !@#$%^&*()+-
+
+
+
+
+
+
+ This will save the entire state of Intel® AMT for this machine into file. Passwords will not be saved, but some sensitive data may be included.
+
+
+
+
+
+ Disabled
+ ICMP response
+ RMCP response
+ ICMP & RMCP response
+
+
+
+
+
+
+ Disabled
+ Disabled, DHCP update
+ Enabled
+
+
Dynamic DNS client
+
+
+
+
Defaut Interval is 1440 minutes, Default TTL is 900 seconds.
+
+
+
+
+
+
+
+ Power up
+ Reset
+ Power cycle
+ Power down
+ OS Wake from Standby
+ OS Power Saving
+ Set boot options
+
+
Remote Command
+
+
+
+
+ None
+ Force CD/DVD Boot
+ Force PXE Boot
+ Force Hard Disk Boot
+ Force Diagnostic Boot
+
+ Force OCR UEFI Boot Option
+ Force OCR UEFI HTTPS Boot
+
+
+
Boot Source
+
+
+
+ None
+ Index 1
+ Index 2
+ Index 3
+ Index 4
+
+
Boot Media
+
+
+
+
+
+
+
+ Boot to floppy
+ Boot to CDROM
+
+
IDER Boot Device
+
+
+
+ System Default
+ Quiet
+ Verbose
+ Blank Screen
+
+
Verbocity
+
+
+
+
WARNING: TLS is not used, password will be sent in the clear.
+
+
+
+
+
+
+
+
+
+
+
+
Wake date (year-month-day)
+
+
+
+
+
+
Wake time (hour:min:sec)
+
+
+
+
+
+
Interval (days-hours-min)
+
+
+
+
+ Keep alarm
+ Delete on completion
+
+
+
After wake
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/index.html b/index.html
index ef3737e..a28b300 100644
--- a/index.html
+++ b/index.html
@@ -4823,14 +4823,27 @@
// ###BEGIN###{Mode-NodeWebkit}
// Perform Intel ME authentication if available.
- /*
- if (amtversion >= 15) {
- var nonce = '12345678901234567890'; // TODO: This is a test nonce, we will need to use a random one.
- stack.AMT_GeneralSettings_AMTAuthenticate(btoa(nonce), function (stack, name, response, status) {
- console.log('Intel AMT Auth', name, response, status);
+ if ((amtversion >= 15) && (wsstack.comm.xtlsCertificate != null)) {
+ stack.amtauthnonce = require('crypto').randomBytes(10).toString('hex');
+ stack.AMT_GeneralSettings_AMTAuthenticate(stack.amtauthnonce, function (stack, name, response, status) {
+ if (status == 200) {
+ stack.amtauth = response.Body;
+ stack.amtauth.CertificatesDer = [];
+ var certs = [], certsbin = atob(stack.amtauth.Certificates), cptr = 0;
+ for (var i = 0; i < stack.amtauth.LengthOfCertificates.length; i++) {
+ var bin = certsbin.substring(cptr, cptr + stack.amtauth.LengthOfCertificates[i]);
+ stack.amtauth.CertificatesDer.push(bin);
+ certs.push(forge.pki.certificateFromAsn1(forge.asn1.fromDer(bin))); // Node-forge does not support ECC, but we are using a modified Node-forge that can still parse the cert.
+ cptr += stack.amtauth.LengthOfCertificates[i];
+ }
+ stack.amtauth.Certificates = certs;
+ stack.amtauth.ClientNonce = stack.amtauthnonce;
+ delete stack.amtauth.LengthOfCertificates;
+ stack.amtauth.uuidStr = guidToStr(stack.amtauth.UUID).toLowerCase();
+ }
+ delete stack.amtauthnonce;
});
}
- */
// ###END###{Mode-NodeWebkit}
// ###BEGIN###{ComputerSelector}
@@ -5250,7 +5263,11 @@
if (y != null && y.length > 0) host += '.' + y;
if (host.length == 0) { host = ('
' + "None" + ' '); } else { host = EscapeHtml(host); }
x += TableEntry("Name & Domain", addLinkConditional(host, 'showEditNameDlg()', xxAccountAdminName));
- if (HardwareInventory) x += TableEntry("System ID", guidToStr(HardwareInventory['CIM_ComputerSystemPackage'].response['PlatformGUID'].toLowerCase()));
+ if (amtstack.amtauth && amtstack.amtauth.uuidStr) {
+ x += TableEntry("System ID", amtstack.amtauth.uuidStr);
+ } else if (HardwareInventory) {
+ x += TableEntry("System ID", guidToStr(HardwareInventory['CIM_ComputerSystemPackage'].response['PlatformGUID'].toLowerCase()));
+ }
if (amtlogicalelements) {
var mode = '', scs = getItem(amtlogicalelements, 'CreationClassName', 'AMT_SetupAndConfigurationService');
// ###BEGIN###{!Look-Intel-SBT}
@@ -5431,6 +5448,10 @@
buttons += AddButton("Run Script...", 'script_runScriptDlg()') + ' ';
// ###END###{Scripting}
x += TableEnd(buttons);
+
+ // Show authentic CSME if present
+ if (amtstack.amtauth) { x += '
'; }
+
QH('id_TableSysStatus', x);
// ###BEGIN###{NetworkSettings}
@@ -5638,6 +5659,43 @@
}
}
+ function showAuthCsme() {
+ if (xxdialogMode) return;
+ var x = '
' + "Intel® AMT supports authentic CSME feature, however MeshCommander cannot verify the authenticity yet." + '
';
+ x += addHtmlValue("FW Version", amtstack.amtauth.FWVersion);
+ x += addHtmlValue("FQDN", amtstack.amtauth.FQDN ? amtstack.amtauth.FQDN : ('
' + "None" + ' '));
+ x += '
';
+ for (var i in amtstack.amtauth.Certificates) {
+ var cert = amtstack.amtauth.Certificates[i];
+ x += ' ';
+ x += '' + EscapeHtml(cert.subject.getField('CN').value) + ' ';
+ // ###BEGIN###{FileSaver}
+ x += amtstack.amtauth.CertificatesDer[i].length + " bytes, " + '' + "Download" + ' ';
+ // ###END###{FileSaver}
+ // ###BEGIN###{!FileSaver}
+ x += amtstack.amtauth.CertificatesDer[i].length + " bytes";
+ // ###END###{!FileSaver}
+ }
+ x += '
';
+ setDialogMode(11, "Authentic CSME", 1, null, x);
+ }
+
+ // ###BEGIN###{FileSaver}
+ function downloadAuthCert(h) {
+ h = parseInt(h);
+ // ###BEGIN###{!Mode-NodeWebkit}
+ saveAs(data2blob(amtstack.amtauth.CertificatesDer[h]), amtstack.amtauth.Certificates[h].subject.getField('CN').value + '.cer');
+ // ###END###{!Mode-NodeWebkit}
+ // ###BEGIN###{Mode-NodeWebkit}
+ var chooser = document.createElement('input');
+ chooser.setAttribute('type', 'file');
+ chooser.setAttribute('nwsaveas', amtstack.amtauth.Certificates[h].subject.getField('CN').value + '.cer');
+ chooser.addEventListener('change', function () { require('fs').writeFile(this.value, amtstack.amtauth.CertificatesDer[h], 'binary', function () { }); }, false);
+ chooser.click();
+ // ###END###{Mode-NodeWebkit}
+ }
+ // ###END###{FileSaver}
+
// ###BEGIN###{WsmanBrowser}
// ###BEGIN###{FileSaver}
var IntelAmtEntireState;
@@ -6235,7 +6293,7 @@
if (certificateStore.length > 0) {
x += '
';
for (var i in certificateStore) {
- var certificate = certificateStore[i], name = certificate.cert.subject.getField('CN').value
+ var certificate = certificateStore[i], name = certificate.cert.subject.getField('CN').value;
x += '' + name + ' ';
}
x += ' ' + "Certificate" + '
';
diff --git a/pki.js/AccessDescription.js b/pki.js/AccessDescription.js
new file mode 100644
index 0000000..d7ddfa2
--- /dev/null
+++ b/pki.js/AccessDescription.js
@@ -0,0 +1,153 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import GeneralName from "./GeneralName.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class AccessDescription
+{
+ //**********************************************************************************
+ /**
+ * Constructor for AccessDescription 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 {string}
+ * @desc The type and format of the information are specified by the accessMethod field. This profile defines two accessMethod OIDs: id-ad-caIssuers and id-ad-ocsp
+ */
+ this.accessMethod = getParametersValue(parameters, "accessMethod", AccessDescription.defaultValues("accessMethod"));
+ /**
+ * @type {GeneralName}
+ * @desc The accessLocation field specifies the location of the information
+ */
+ this.accessLocation = getParametersValue(parameters, "accessLocation", AccessDescription.defaultValues("accessLocation"));
+ //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 "accessMethod":
+ return "";
+ case "accessLocation":
+ return new GeneralName();
+ default:
+ throw new Error(`Invalid member name for AccessDescription class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * AccessDescription ::= SEQUENCE {
+ * accessMethod OBJECT IDENTIFIER,
+ * accessLocation GeneralName }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [accessMethod]
+ * @property {string} [accessLocation]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.accessMethod || "") }),
+ GeneralName.schema(names.accessLocation || {})
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "accessMethod",
+ "accessLocation"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ AccessDescription.schema({
+ names: {
+ accessMethod: "accessMethod",
+ accessLocation: {
+ names: {
+ blockName: "accessLocation"
+ }
+ }
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for AccessDescription");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.accessMethod = asn1.result.accessMethod.valueBlock.toString();
+ this.accessLocation = new GeneralName({ schema: asn1.result.accessLocation });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ new asn1js.ObjectIdentifier({ value: this.accessMethod }),
+ this.accessLocation.toSchema()
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ accessMethod: this.accessMethod,
+ accessLocation: this.accessLocation.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/Accuracy.js b/pki.js/Accuracy.js
new file mode 100644
index 0000000..3153a67
--- /dev/null
+++ b/pki.js/Accuracy.js
@@ -0,0 +1,249 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC3161. Accuracy represents the time deviation around the UTC time contained in GeneralizedTime.
+ */
+export default class Accuracy
+{
+ //**********************************************************************************
+ /**
+ * Constructor for Accuracy class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ if("seconds" in parameters)
+ /**
+ * @type {number}
+ * @desc seconds
+ */
+ this.seconds = getParametersValue(parameters, "seconds", Accuracy.defaultValues("seconds"));
+
+ if("millis" in parameters)
+ /**
+ * @type {number}
+ * @desc millis
+ */
+ this.millis = getParametersValue(parameters, "millis", Accuracy.defaultValues("millis"));
+
+ if("micros" in parameters)
+ /**
+ * @type {number}
+ * @desc micros
+ */
+ this.micros = getParametersValue(parameters, "micros", Accuracy.defaultValues("micros"));
+ //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 "seconds":
+ case "millis":
+ case "micros":
+ return 0;
+ default:
+ throw new Error(`Invalid member name for Accuracy 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 "seconds":
+ case "millis":
+ case "micros":
+ return (memberValue === Accuracy.defaultValues(memberName));
+ default:
+ throw new Error(`Invalid member name for Accuracy class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * Accuracy ::= SEQUENCE {
+ * seconds INTEGER OPTIONAL,
+ * millis [0] INTEGER (1..999) OPTIONAL,
+ * micros [1] INTEGER (1..999) OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [seconds]
+ * @property {string} [millis]
+ * @property {string} [micros]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ optional: true,
+ value: [
+ new asn1js.Integer({
+ optional: true,
+ name: (names.seconds || "")
+ }),
+ new asn1js.Primitive({
+ name: (names.millis || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ }
+ }),
+ new asn1js.Primitive({
+ name: (names.micros || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ }
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "seconds",
+ "millis",
+ "micros"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ Accuracy.schema({
+ names: {
+ seconds: "seconds",
+ millis: "millis",
+ micros: "micros"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for Accuracy");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("seconds" in asn1.result)
+ this.seconds = asn1.result.seconds.valueBlock.valueDec;
+
+ if("millis" in asn1.result)
+ {
+ const intMillis = new asn1js.Integer({ valueHex: asn1.result.millis.valueBlock.valueHex });
+ this.millis = intMillis.valueBlock.valueDec;
+ }
+
+ if("micros" in asn1.result)
+ {
+ const intMicros = new asn1js.Integer({ valueHex: asn1.result.micros.valueBlock.valueHex });
+ this.micros = intMicros.valueBlock.valueDec;
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array of output sequence
+ const outputArray = [];
+
+ if("seconds" in this)
+ outputArray.push(new asn1js.Integer({ value: this.seconds }));
+
+ if("millis" in this)
+ {
+ const intMillis = new asn1js.Integer({ value: this.millis });
+
+ outputArray.push(new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ valueHex: intMillis.valueBlock.valueHex
+ }));
+ }
+
+ if("micros" in this)
+ {
+ const intMicros = new asn1js.Integer({ value: this.micros });
+
+ outputArray.push(new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ valueHex: intMicros.valueBlock.valueHex
+ }));
+ }
+ //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 = {};
+
+ if("seconds" in this)
+ _object.seconds = this.seconds;
+
+ if("millis" in this)
+ _object.millis = this.millis;
+
+ if("micros" in this)
+ _object.micros = this.micros;
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/AlgorithmIdentifier.js b/pki.js/AlgorithmIdentifier.js
new file mode 100644
index 0000000..c0ac890
--- /dev/null
+++ b/pki.js/AlgorithmIdentifier.js
@@ -0,0 +1,212 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class AlgorithmIdentifier
+{
+ //**********************************************************************************
+ /**
+ * Constructor for AlgorithmIdentifier class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ * @property {string} [algorithmId] ObjectIdentifier for algorithm (string representation)
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ /**
+ * @type {string}
+ * @desc ObjectIdentifier for algorithm (string representation)
+ */
+ this.algorithmId = getParametersValue(parameters, "algorithmId", AlgorithmIdentifier.defaultValues("algorithmId"));
+
+ if("algorithmParams" in parameters)
+ /**
+ * @type {Object}
+ * @desc Any algorithm parameters
+ */
+ this.algorithmParams = getParametersValue(parameters, "algorithmParams", AlgorithmIdentifier.defaultValues("algorithmParams"));
+ //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 "algorithmId":
+ return "";
+ case "algorithmParams":
+ return new asn1js.Any();
+ default:
+ throw new Error(`Invalid member name for AlgorithmIdentifier 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 "algorithmId":
+ return (memberValue === "");
+ case "algorithmParams":
+ return (memberValue instanceof asn1js.Any);
+ default:
+ throw new Error(`Invalid member name for AlgorithmIdentifier class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * AlgorithmIdentifier ::= Sequence {
+ * algorithm OBJECT IDENTIFIER,
+ * parameters ANY DEFINED BY algorithm OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} algorithmIdentifier ObjectIdentifier for the algorithm
+ * @property {string} algorithmParams Any algorithm parameters
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ optional: (names.optional || false),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.algorithmIdentifier || "") }),
+ new asn1js.Any({ name: (names.algorithmParams || ""), optional: true })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "algorithm",
+ "params"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ AlgorithmIdentifier.schema({
+ names: {
+ algorithmIdentifier: "algorithm",
+ algorithmParams: "params"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for AlgorithmIdentifier");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.algorithmId = asn1.result.algorithm.valueBlock.toString();
+ if("params" in asn1.result)
+ this.algorithmParams = asn1.result.params;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.ObjectIdentifier({ value: this.algorithmId }));
+ if(("algorithmParams" in this) && ((this.algorithmParams instanceof asn1js.Any) === false))
+ outputArray.push(this.algorithmParams);
+ //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 = {
+ algorithmId: this.algorithmId
+ };
+
+ if(("algorithmParams" in this) && ((this.algorithmParams instanceof asn1js.Any) === false))
+ object.algorithmParams = this.algorithmParams.toJSON();
+
+ return object;
+ }
+ //**********************************************************************************
+ /**
+ * Check that two "AlgorithmIdentifiers" are equal
+ * @param {AlgorithmIdentifier} algorithmIdentifier
+ * @returns {boolean}
+ */
+ isEqual(algorithmIdentifier)
+ {
+ //region Check input type
+ if((algorithmIdentifier instanceof AlgorithmIdentifier) === false)
+ return false;
+ //endregion
+
+ //region Check "algorithm_id"
+ if(this.algorithmId !== algorithmIdentifier.algorithmId)
+ return false;
+ //endregion
+
+ //region Check "algorithm_params"
+ if("algorithmParams" in this)
+ {
+ if("algorithmParams" in algorithmIdentifier)
+ return JSON.stringify(this.algorithmParams) === JSON.stringify(algorithmIdentifier.algorithmParams);
+
+ return false;
+ }
+
+ if("algorithmParams" in algorithmIdentifier)
+ return false;
+ //endregion
+
+ return true;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/AltName.js b/pki.js/AltName.js
new file mode 100644
index 0000000..657d06d
--- /dev/null
+++ b/pki.js/AltName.js
@@ -0,0 +1,135 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import GeneralName from "./GeneralName.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class AltName
+{
+ //**********************************************************************************
+ /**
+ * Constructor for AltName 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 {Array.
}
+ * @desc Array of alternative names in GeneralName type
+ */
+ this.altNames = getParametersValue(parameters, "altNames", AltName.defaultValues("altNames"));
+ //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 "altNames":
+ return [];
+ default:
+ throw new Error(`Invalid member name for AltName class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * AltName ::= GeneralNames
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [altNames]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.altNames || ""),
+ value: GeneralName.schema()
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "altNames"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ AltName.schema({
+ names: {
+ altNames: "altNames"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for AltName");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("altNames" in asn1.result)
+ this.altNames = Array.from(asn1.result.altNames, element => new GeneralName({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: Array.from(this.altNames, element => element.toSchema())
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ altNames: Array.from(this.altNames, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/Attribute.js b/pki.js/Attribute.js
new file mode 100644
index 0000000..d0c67b8
--- /dev/null
+++ b/pki.js/Attribute.js
@@ -0,0 +1,177 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC2986
+ */
+export default class Attribute {
+ //**********************************************************************************
+ /**
+ * Constructor for Attribute 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 {string}
+ * @desc ObjectIdentifier for attribute (string representation)
+ */
+ this.type = getParametersValue(parameters, "type", Attribute.defaultValues("type"));
+ /**
+ * @type {Array}
+ * @desc Any attribute values
+ */
+ this.values = getParametersValue(parameters, "values", Attribute.defaultValues("values"));
+ //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 "type":
+ return "";
+ case "values":
+ return [];
+ default:
+ throw new Error(`Invalid member name for Attribute 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 "type":
+ return (memberValue === "");
+ case "values":
+ return (memberValue.length === 0);
+ default:
+ throw new Error(`Invalid member name for Attribute class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE {
+ * type ATTRIBUTE.&id({IOSet}),
+ * values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type})
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [type]
+ * @property {string} [setName]
+ * @property {string} [values]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.type || "") }),
+ new asn1js.Set({
+ name: (names.setName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.values || ""),
+ value: new asn1js.Any()
+ })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "type",
+ "values"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ Attribute.schema({
+ names: {
+ type: "type",
+ values: "values"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for Attribute");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.type = asn1.result.type.valueBlock.toString();
+ this.values = asn1.result.values;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ new asn1js.ObjectIdentifier({ value: this.type }),
+ new asn1js.Set({
+ value: this.values
+ })
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ type: this.type,
+ values: Array.from(this.values, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/AttributeCertificateV1.js b/pki.js/AttributeCertificateV1.js
new file mode 100644
index 0000000..a2458df
--- /dev/null
+++ b/pki.js/AttributeCertificateV1.js
@@ -0,0 +1,871 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import GeneralNames from "./GeneralNames.js";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import Attribute from "./Attribute.js";
+import Extensions from "./Extensions.js";
+//**************************************************************************************
+/**
+ * Class from RFC5755
+ */
+export class AttCertValidityPeriod
+{
+ //**********************************************************************************
+ /**
+ * Constructor for AttCertValidityPeriod 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 {GeneralizedTime}
+ * @desc notBeforeTime
+ */
+ this.notBeforeTime = getParametersValue(parameters, "notBeforeTime", AttCertValidityPeriod.defaultValues("notBeforeTime"));
+ /**
+ * @type {GeneralizedTime}
+ * @desc notAfterTime
+ */
+ this.notAfterTime = getParametersValue(parameters, "notAfterTime", AttCertValidityPeriod.defaultValues("notAfterTime"));
+ //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 "notBeforeTime":
+ case "notAfterTime":
+ return new Date(0, 0, 0);
+ default:
+ throw new Error(`Invalid member name for AttCertValidityPeriod class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * AttCertValidityPeriod ::= SEQUENCE {
+ * notBeforeTime GeneralizedTime,
+ * notAfterTime GeneralizedTime
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [notBeforeTime]
+ * @property {string} [notAfterTime]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.GeneralizedTime({ name: (names.notBeforeTime || "") }),
+ new asn1js.GeneralizedTime({ name: (names.notAfterTime || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "notBeforeTime",
+ "notAfterTime"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ AttCertValidityPeriod.schema({
+ names: {
+ notBeforeTime: "notBeforeTime",
+ notAfterTime: "notAfterTime"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for AttCertValidityPeriod");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.notBeforeTime = asn1.result.notBeforeTime.toDate();
+ this.notAfterTime = asn1.result.notAfterTime.toDate();
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ new asn1js.GeneralizedTime({ valueDate: this.notBeforeTime }),
+ new asn1js.GeneralizedTime({ valueDate: this.notAfterTime }),
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ notBeforeTime: this.notBeforeTime,
+ notAfterTime: this.notAfterTime
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
+/**
+ * Class from RFC5755
+ */
+export class IssuerSerial
+{
+ //**********************************************************************************
+ /**
+ * Constructor for IssuerSerial 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 {RelativeDistinguishedNames}
+ * @desc issuer
+ */
+ this.issuer = getParametersValue(parameters, "issuer", IssuerSerial.defaultValues("issuer"));
+ /**
+ * @type {Integer}
+ * @desc serialNumber
+ */
+ this.serialNumber = getParametersValue(parameters, "serialNumber", IssuerSerial.defaultValues("serialNumber"));
+
+ if("issuerUID" in parameters)
+ /**
+ * @type {BitString}
+ * @desc issuerUID
+ */
+ this.issuerUID = getParametersValue(parameters, "issuerUID", IssuerSerial.defaultValues("issuerUID"));
+ //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 "issuer":
+ return new GeneralNames();
+ case "serialNumber":
+ return new asn1js.Integer();
+ case "issuerUID":
+ return new asn1js.BitString();
+ default:
+ throw new Error(`Invalid member name for IssuerSerial class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * IssuerSerial ::= SEQUENCE {
+ * issuer GeneralNames,
+ * serial CertificateSerialNumber,
+ * issuerUID UniqueIdentifier OPTIONAL
+ * }
+ *
+ * CertificateSerialNumber ::= INTEGER
+ * UniqueIdentifier ::= BIT STRING
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [issuer]
+ * @property {string} [serialNumber]
+ * @property {string} [issuerUID]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ GeneralNames.schema(names.issuer || {}),
+ new asn1js.Integer({ name: (names.serialNumber || "") }),
+ new asn1js.BitString({
+ optional: true,
+ name: (names.issuerUID || "")
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "issuer",
+ "serialNumber",
+ "issuerUID"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ IssuerSerial.schema({
+ names: {
+ issuer: {
+ names: {
+ blockName: "issuer"
+ }
+ },
+ serialNumber: "serialNumber",
+ issuerUID: "issuerUID"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for IssuerSerial");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.issuer = new GeneralNames({ schema: asn1.result.issuer });
+ this.serialNumber = asn1.result.serialNumber;
+
+ if("issuerUID" in asn1.result)
+ this.issuerUID = asn1.result.issuerUID;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ const result = new asn1js.Sequence({
+ value: [
+ this.issuer.toSchema(),
+ this.serialNumber
+ ]
+ });
+
+ if("issuerUID" in this)
+ result.valueBlock.value.push(this.issuerUID);
+
+ //region Construct and return new ASN.1 schema for this object
+ return result;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const result = {
+ issuer: this.issuer.toJSON(),
+ serialNumber: this.serialNumber.toJSON()
+ };
+
+ if("issuerUID" in this)
+ result.issuerUID = this.issuerUID.toJSON();
+
+ return result;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
+/**
+ * Class from RFC5755
+ */
+export class AttributeCertificateInfoV1
+{
+ //**********************************************************************************
+ /**
+ * Constructor for AttributeCertificateInfoV1 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 {Number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", AttributeCertificateInfoV1.defaultValues("version"));
+
+ if("baseCertificateID" in parameters)
+ /**
+ * @type {IssuerSerial}
+ * @desc baseCertificateID
+ */
+ this.baseCertificateID = getParametersValue(parameters, "baseCertificateID", AttributeCertificateInfoV1.defaultValues("baseCertificateID"));
+
+ if("subjectName" in parameters)
+ /**
+ * @type {GeneralNames}
+ * @desc subjectName
+ */
+ this.subjectName = getParametersValue(parameters, "subjectName", AttributeCertificateInfoV1.defaultValues("subjectName"));
+
+ /**
+ * @type {GeneralNames}
+ * @desc issuer
+ */
+ this.issuer = getParametersValue(parameters, "issuer", AttributeCertificateInfoV1.defaultValues("issuer"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc signature
+ */
+ this.signature = getParametersValue(parameters, "signature", AttributeCertificateInfoV1.defaultValues("signature"));
+ /**
+ * @type {Integer}
+ * @desc serialNumber
+ */
+ this.serialNumber = getParametersValue(parameters, "serialNumber", AttributeCertificateInfoV1.defaultValues("serialNumber"));
+ /**
+ * @type {AttCertValidityPeriod}
+ * @desc attrCertValidityPeriod
+ */
+ this.attrCertValidityPeriod = getParametersValue(parameters, "attrCertValidityPeriod", AttributeCertificateInfoV1.defaultValues("attrCertValidityPeriod"));
+ /**
+ * @type {Array.}
+ * @desc attributes
+ */
+ this.attributes = getParametersValue(parameters, "attributes", AttributeCertificateInfoV1.defaultValues("attributes"));
+
+ if("issuerUniqueID" in parameters)
+ /**
+ * @type {BitString}
+ * @desc issuerUniqueID
+ */
+ this.issuerUniqueID = getParametersValue(parameters, "issuerUniqueID", AttributeCertificateInfoV1.defaultValues("issuerUniqueID"));
+
+ if("extensions" in parameters)
+ /**
+ * @type {Extensions}
+ * @desc extensions
+ */
+ this.extensions = getParametersValue(parameters, "extensions", AttributeCertificateInfoV1.defaultValues("extensions"));
+ //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 "version":
+ return 0;
+ case "baseCertificateID":
+ return new IssuerSerial();
+ case "subjectName":
+ return new GeneralNames();
+ case "issuer":
+ return {};
+ case "signature":
+ return new AlgorithmIdentifier();
+ case "serialNumber":
+ return new asn1js.Integer();
+ case "attrCertValidityPeriod":
+ return new AttCertValidityPeriod();
+ case "attributes":
+ return [];
+ case "issuerUniqueID":
+ return new asn1js.BitString();
+ case "extensions":
+ return new Extensions();
+ default:
+ throw new Error(`Invalid member name for AttributeCertificateInfoV1 class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * AttributeCertificateInfo ::= SEQUENCE {
+ * version Version DEFAULT v1,
+ * subject CHOICE {
+ * baseCertificateID [0] IssuerSerial, -- associated with a Public Key Certificate
+ * subjectName [1] GeneralNames }, -- associated with a name
+ * issuer GeneralNames, -- CA issuing the attribute certificate
+ * signature AlgorithmIdentifier,
+ * serialNumber CertificateSerialNumber,
+ * attrCertValidityPeriod AttCertValidityPeriod,
+ * attributes SEQUENCE OF Attribute,
+ * issuerUniqueID UniqueIdentifier OPTIONAL,
+ * extensions Extensions OPTIONAL
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [issuer]
+ * @property {string} [serialNumber]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Integer({ name: (names.version || "") }),
+ new asn1js.Choice({
+ value: [
+ new asn1js.Constructed({
+ name: (names.baseCertificateID || ""),
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 0 // [0]
+ },
+ value: IssuerSerial.schema().valueBlock.value
+ }),
+ new asn1js.Constructed({
+ name: (names.subjectName || ""),
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 1 // [2]
+ },
+ value: GeneralNames.schema().valueBlock.value
+ }),
+ ]
+ }),
+ GeneralNames.schema({
+ names: {
+ blockName: (names.issuer || "")
+ }
+ }),
+ AlgorithmIdentifier.schema(names.signature || {}),
+ new asn1js.Integer({ name: (names.serialNumber || "") }),
+ AttCertValidityPeriod.schema(names.attrCertValidityPeriod || {}),
+ new asn1js.Sequence({
+ name: (names.attributes || ""),
+ value: [
+ new asn1js.Repeated({
+ value: Attribute.schema()
+ })
+ ]
+ }),
+ new asn1js.BitString({
+ optional: true,
+ name: (names.issuerUniqueID || "")
+ }),
+ Extensions.schema(names.extensions || {}, true)
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "version",
+ "baseCertificateID",
+ "subjectName",
+ "issuer",
+ "signature",
+ "serialNumber",
+ "attrCertValidityPeriod",
+ "attributes",
+ "issuerUniqueID",
+ "extensions"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ AttributeCertificateInfoV1.schema({
+ names: {
+ version: "version",
+ baseCertificateID: "baseCertificateID",
+ subjectName: "subjectName",
+ issuer: "issuer",
+ signature: {
+ names: {
+ blockName: "signature"
+ }
+ },
+ serialNumber: "serialNumber",
+ attrCertValidityPeriod: {
+ names: {
+ blockName: "attrCertValidityPeriod"
+ }
+ },
+ attributes: "attributes",
+ issuerUniqueID: "issuerUniqueID",
+ extensions: {
+ names: {
+ blockName: "extensions"
+ }
+ }
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for AttributeCertificateInfoV1");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result.version.valueBlock.valueDec;
+
+ if("baseCertificateID" in asn1.result)
+ {
+ this.baseCertificateID = new IssuerSerial({
+ schema: new asn1js.Sequence({
+ value: asn1.result.baseCertificateID.valueBlock.value
+ })
+ });
+ }
+
+ if("subjectName" in asn1.result)
+ {
+ this.subjectName = new GeneralNames({
+ schema: new asn1js.Sequence({
+ value: asn1.result.subjectName.valueBlock.value
+ })
+ });
+ }
+
+ this.issuer = asn1.result.issuer;
+ this.signature = new AlgorithmIdentifier({ schema: asn1.result.signature });
+ this.serialNumber = asn1.result.serialNumber;
+ this.attrCertValidityPeriod = new AttCertValidityPeriod({ schema: asn1.result.attrCertValidityPeriod });
+ this.attributes = Array.from(asn1.result.attributes.valueBlock.value, element => new Attribute({ schema: element }));
+
+ if("issuerUniqueID" in asn1.result)
+ this.issuerUniqueID = asn1.result.issuerUniqueID;
+
+ if("extensions" in asn1.result)
+ this.extensions = new Extensions({ schema: asn1.result.extensions });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ const result = new asn1js.Sequence({
+ value: [new asn1js.Integer({ value: this.version })]
+ });
+
+ if("baseCertificateID" in this)
+ {
+ result.valueBlock.value.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 0 // [0]
+ },
+ value: this.baseCertificateID.toSchema().valueBlock.value
+ }));
+ }
+
+ if("subjectName" in this)
+ {
+ result.valueBlock.value.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 1 // [1]
+ },
+ value: this.subjectName.toSchema().valueBlock.value
+ }));
+ }
+
+ result.valueBlock.value.push(this.issuer.toSchema());
+ result.valueBlock.value.push(this.signature.toSchema());
+ result.valueBlock.value.push(this.serialNumber);
+ result.valueBlock.value.push(this.attrCertValidityPeriod.toSchema());
+ result.valueBlock.value.push(new asn1js.Sequence({
+ value: Array.from(this.attributes, element => element.toSchema())
+ }));
+
+ if("issuerUniqueID" in this)
+ result.valueBlock.value.push(this.issuerUniqueID);
+
+ if("extensions" in this)
+ result.valueBlock.value.push(this.extensions.toSchema());
+
+ return result;
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const result = {
+ version: this.version
+ };
+
+ if("baseCertificateID" in this)
+ result.baseCertificateID = this.baseCertificateID.toJSON();
+
+ if("subjectName" in this)
+ result.subjectName = this.subjectName.toJSON();
+
+ result.issuer = this.issuer.toJSON();
+ result.signature = this.signature.toJSON();
+ result.serialNumber = this.serialNumber.toJSON();
+ result.attrCertValidityPeriod = this.attrCertValidityPeriod.toJSON();
+ result.attributes = Array.from(this.attributes, element => element.toJSON());
+
+ if("issuerUniqueID" in this)
+ result.issuerUniqueID = this.issuerUniqueID.toJSON();
+
+ if("extensions" in this)
+ result.extensions = this.extensions.toJSON();
+
+ return result;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
+/**
+ * Class from X.509:1997
+ */
+export default class AttributeCertificateV1
+{
+ //**********************************************************************************
+ /**
+ * Constructor for AttributeCertificateV1 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 {AttributeCertificateInfoV1}
+ * @desc acinfo
+ */
+ this.acinfo = getParametersValue(parameters, "acinfo", AttributeCertificateV1.defaultValues("acinfo"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc signatureAlgorithm
+ */
+ this.signatureAlgorithm = getParametersValue(parameters, "signatureAlgorithm", AttributeCertificateV1.defaultValues("signatureAlgorithm"));
+ /**
+ * @type {BitString}
+ * @desc signatureValue
+ */
+ this.signatureValue = getParametersValue(parameters, "signatureValue", AttributeCertificateV1.defaultValues("signatureValue"));
+ //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 "acinfo":
+ return new AttributeCertificateInfoV1();
+ case "signatureAlgorithm":
+ return new AlgorithmIdentifier();
+ case "signatureValue":
+ return new asn1js.BitString();
+ default:
+ throw new Error(`Invalid member name for AttributeCertificateV1 class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * AttributeCertificate ::= SEQUENCE {
+ * acinfo AttributeCertificateInfoV1,
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signatureValue BIT STRING
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {Object} [acinfo]
+ * @property {Object} [signatureAlgorithm]
+ * @property {string} [signatureValue]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ AttributeCertificateInfoV1.schema(names.acinfo || {}),
+ AlgorithmIdentifier.schema(names.signatureAlgorithm || {}),
+ new asn1js.BitString({ name: (names.signatureValue || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "acinfo",
+ "signatureValue",
+ "signatureAlgorithm"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ AttributeCertificateV1.schema({
+ names: {
+ acinfo: {
+ names: {
+ blockName: "acinfo"
+ }
+ },
+ signatureAlgorithm: {
+ names: {
+ blockName: "signatureAlgorithm"
+ }
+ },
+ signatureValue: "signatureValue"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for AttributeCertificateV1");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.acinfo = new AttributeCertificateInfoV1({ schema: asn1.result.acinfo });
+ this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm });
+ this.signatureValue = asn1.result.signatureValue;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ return (new asn1js.Sequence({
+ value: [
+ this.acinfo.toSchema(),
+ this.signatureAlgorithm.toSchema(),
+ this.signatureValue
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ acinfo: this.acinfo.toJSON(),
+ signatureAlgorithm: this.signatureAlgorithm.toJSON(),
+ signatureValue: this.signatureValue.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/AttributeCertificateV2.js b/pki.js/AttributeCertificateV2.js
new file mode 100644
index 0000000..97930b9
--- /dev/null
+++ b/pki.js/AttributeCertificateV2.js
@@ -0,0 +1,1187 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import GeneralNames from "./GeneralNames.js";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import Attribute from "./Attribute.js";
+import Extensions from "./Extensions.js";
+import { AttCertValidityPeriod, IssuerSerial } from "./AttributeCertificateV1.js";
+//**************************************************************************************
+/**
+ * Class from RFC5755
+ */
+export class ObjectDigestInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for ObjectDigestInfo 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 {Enumerated}
+ * @desc digestedObjectType
+ */
+ this.digestedObjectType = getParametersValue(parameters, "digestedObjectType", ObjectDigestInfo.defaultValues("digestedObjectType"));
+
+ if("otherObjectTypeID" in parameters)
+ /**
+ * @type {ObjectIdentifier}
+ * @desc otherObjectTypeID
+ */
+ this.otherObjectTypeID = getParametersValue(parameters, "otherObjectTypeID", ObjectDigestInfo.defaultValues("otherObjectTypeID"));
+
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc digestAlgorithm
+ */
+ this.digestAlgorithm = getParametersValue(parameters, "digestAlgorithm", ObjectDigestInfo.defaultValues("digestAlgorithm"));
+ /**
+ * @type {BitString}
+ * @desc objectDigest
+ */
+ this.objectDigest = getParametersValue(parameters, "objectDigest", ObjectDigestInfo.defaultValues("objectDigest"));
+ //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 "digestedObjectType":
+ return new asn1js.Enumerated();
+ case "otherObjectTypeID":
+ return new asn1js.ObjectIdentifier();
+ case "digestAlgorithm":
+ return new AlgorithmIdentifier();
+ case "objectDigest":
+ return new asn1js.BitString();
+ default:
+ throw new Error(`Invalid member name for ObjectDigestInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * ObjectDigestInfo ::= SEQUENCE {
+ * digestedObjectType ENUMERATED {
+ * publicKey (0),
+ * publicKeyCert (1),
+ * otherObjectTypes (2) },
+ * -- otherObjectTypes MUST NOT
+ * -- be used in this profile
+ * otherObjectTypeID OBJECT IDENTIFIER OPTIONAL,
+ * digestAlgorithm AlgorithmIdentifier,
+ * objectDigest BIT STRING
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [digestedObjectType]
+ * @property {string} [otherObjectTypeID]
+ * @property {string} [digestAlgorithm]
+ * @property {string} [objectDigest]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Enumerated({ name: (names.digestedObjectType || "") }),
+ new asn1js.ObjectIdentifier({
+ optional: true,
+ name: (names.otherObjectTypeID || "")
+ }),
+ AlgorithmIdentifier.schema(names.digestAlgorithm || {}),
+ new asn1js.BitString({ name: (names.objectDigest || "") }),
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "digestedObjectType",
+ "otherObjectTypeID",
+ "digestAlgorithm",
+ "objectDigest"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ ObjectDigestInfo.schema({
+ names: {
+ digestedObjectType: "digestedObjectType",
+ otherObjectTypeID: "otherObjectTypeID",
+ digestAlgorithm: {
+ names: {
+ blockName: "digestAlgorithm"
+ }
+ },
+ objectDigest: "objectDigest"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for ObjectDigestInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.digestedObjectType = asn1.result.digestedObjectType;
+
+ if("otherObjectTypeID" in asn1.result)
+ this.otherObjectTypeID = asn1.result.otherObjectTypeID;
+
+ this.digestAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.digestAlgorithm });
+ this.objectDigest = asn1.result.objectDigest;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ const result = new asn1js.Sequence({
+ value: [this.digestedObjectType]
+ });
+
+ if("otherObjectTypeID" in this)
+ result.value.push(this.otherObjectTypeID);
+
+ result.value.push(this.digestAlgorithm.toSchema());
+ result.value.push(this.objectDigest);
+
+ return result;
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const result = {
+ digestedObjectType: this.digestedObjectType.toJSON()
+ };
+
+ if("otherObjectTypeID" in this)
+ result.otherObjectTypeID = this.otherObjectTypeID.toJSON();
+
+ result.digestAlgorithm = this.digestAlgorithm.toJSON();
+ result.objectDigest = this.objectDigest.toJSON();
+
+ return result;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
+/**
+ * Class from RFC5755
+ */
+export class V2Form
+{
+ //**********************************************************************************
+ /**
+ * Constructor for V2Form class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ if("issuerName" in parameters)
+ /**
+ * @type {GeneralNames}
+ * @desc issuerName
+ */
+ this.issuerName = getParametersValue(parameters, "issuerName", V2Form.defaultValues("issuerName"));
+
+ if("baseCertificateID" in parameters)
+ /**
+ * @type {IssuerSerial}
+ * @desc baseCertificateID
+ */
+ this.baseCertificateID = getParametersValue(parameters, "baseCertificateID", V2Form.defaultValues("baseCertificateID"));
+
+ if("objectDigestInfo" in parameters)
+ /**
+ * @type {ObjectDigestInfo}
+ * @desc objectDigestInfo
+ */
+ this.objectDigestInfo = getParametersValue(parameters, "objectDigestInfo", V2Form.defaultValues("objectDigestInfo"));
+ //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 "issuerName":
+ return new GeneralNames();
+ case "baseCertificateID":
+ return new IssuerSerial();
+ case "objectDigestInfo":
+ return new ObjectDigestInfo();
+ default:
+ throw new Error(`Invalid member name for V2Form class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * V2Form ::= SEQUENCE {
+ * issuerName GeneralNames OPTIONAL,
+ * baseCertificateID [0] IssuerSerial OPTIONAL,
+ * objectDigestInfo [1] ObjectDigestInfo OPTIONAL
+ * -- issuerName MUST be present in this profile
+ * -- baseCertificateID and objectDigestInfo MUST NOT
+ * -- be present in this profile
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [issuerName]
+ * @property {string} [baseCertificateID]
+ * @property {string} [objectDigestInfo]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ GeneralNames.schema({
+ names: {
+ blockName: names.issuerName
+ }
+ }, true),
+ new asn1js.Constructed({
+ optional: true,
+ name: (names.baseCertificateID || ""),
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 0 // [0]
+ },
+ value: IssuerSerial.schema().valueBlock.value
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ name: (names.objectDigestInfo || ""),
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 1 // [1]
+ },
+ value: ObjectDigestInfo.schema().valueBlock.value
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "issuerName",
+ "baseCertificateID",
+ "objectDigestInfo"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ V2Form.schema({
+ names: {
+ issuerName: "issuerName",
+ baseCertificateID: "baseCertificateID",
+ objectDigestInfo: "objectDigestInfo"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for V2Form");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("issuerName" in asn1.result)
+ this.issuerName = new GeneralNames({ schema: asn1.result.issuerName });
+
+ if("baseCertificateID" in asn1.result)
+ {
+ this.baseCertificateID = new IssuerSerial({
+ schema: new asn1js.Sequence({
+ value: asn1.result.baseCertificateID.valueBlock.value
+ })
+ });
+ }
+
+ if("objectDigestInfo" in asn1.result)
+ {
+ this.objectDigestInfo = new ObjectDigestInfo({
+ schema: new asn1js.Sequence({
+ value: asn1.result.objectDigestInfo.valueBlock.value
+ })
+ });
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ const result = new asn1js.Sequence();
+
+ if("issuerName" in this)
+ result.valueBlock.value.push(this.issuerName.toSchema());
+
+ if("baseCertificateID" in this)
+ {
+ result.valueBlock.value.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 0 // [0]
+ },
+ value: this.baseCertificateID.toSchema().valueBlock.value
+ }));
+ }
+
+ if("objectDigestInfo" in this)
+ {
+ result.valueBlock.value.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 1 // [1]
+ },
+ value: this.objectDigestInfo.toSchema().valueBlock.value
+ }));
+ }
+
+ //region Construct and return new ASN.1 schema for this object
+ return result;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const result = {};
+
+ if("issuerName" in this)
+ result.issuerName = this.issuerName.toJSON();
+
+ if("baseCertificateID" in this)
+ result.baseCertificateID = this.baseCertificateID.toJSON();
+
+ if("objectDigestInfo" in this)
+ result.objectDigestInfo = this.objectDigestInfo.toJSON();
+
+ return result;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
+/**
+ * Class from RFC5755
+ */
+export class Holder
+{
+ //**********************************************************************************
+ /**
+ * Constructor for Holder class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ if("baseCertificateID" in parameters)
+ /**
+ * @type {IssuerSerial}
+ * @desc baseCertificateID
+ */
+ this.baseCertificateID = getParametersValue(parameters, "baseCertificateID", Holder.defaultValues("baseCertificateID"));
+
+ if("entityName" in parameters)
+ /**
+ * @type {GeneralNames}
+ * @desc entityName
+ */
+ this.entityName = getParametersValue(parameters, "entityName", Holder.defaultValues("entityName"));
+
+ if("objectDigestInfo" in parameters)
+ /**
+ * @type {ObjectDigestInfo}
+ * @desc objectDigestInfo
+ */
+ this.objectDigestInfo = getParametersValue(parameters, "objectDigestInfo", Holder.defaultValues("objectDigestInfo"));
+ //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 "baseCertificateID":
+ return new IssuerSerial();
+ case "entityName":
+ return new GeneralNames();
+ case "objectDigestInfo":
+ return new ObjectDigestInfo();
+ default:
+ throw new Error(`Invalid member name for Holder class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * Holder ::= SEQUENCE {
+ * baseCertificateID [0] IssuerSerial OPTIONAL,
+ * -- the issuer and serial number of
+ * -- the holder's Public Key Certificate
+ * entityName [1] GeneralNames OPTIONAL,
+ * -- the name of the claimant or role
+ * objectDigestInfo [2] ObjectDigestInfo OPTIONAL
+ * -- used to directly authenticate the holder,
+ * -- for example, an executable
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [baseCertificateID]
+ * @property {string} [entityName]
+ * @property {string} [objectDigestInfo]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Constructed({
+ optional: true,
+ name: (names.baseCertificateID || ""),
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 0 // [0]
+ },
+ value: IssuerSerial.schema().valueBlock.value
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ name: (names.entityName || ""),
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 1 // [2]
+ },
+ value: GeneralNames.schema().valueBlock.value
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ name: (names.objectDigestInfo || ""),
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 2 // [2]
+ },
+ value: ObjectDigestInfo.schema().valueBlock.value
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "baseCertificateID",
+ "entityName",
+ "objectDigestInfo"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ Holder.schema({
+ names: {
+ baseCertificateID: "baseCertificateID",
+ entityName: "entityName",
+ objectDigestInfo: "objectDigestInfo"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for Holder");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("baseCertificateID" in asn1.result)
+ {
+ this.baseCertificateID = new IssuerSerial({
+ schema: new asn1js.Sequence({
+ value: asn1.result.baseCertificateID.valueBlock.value
+ })
+ });
+ }
+
+ if("entityName" in asn1.result)
+ {
+ this.entityName = new GeneralNames({
+ schema: new asn1js.Sequence({
+ value: asn1.result.entityName.valueBlock.value
+ })
+ });
+ }
+
+ if("objectDigestInfo" in asn1.result)
+ {
+ this.objectDigestInfo = new ObjectDigestInfo({
+ schema: new asn1js.Sequence({
+ value: asn1.result.objectDigestInfo.valueBlock.value
+ })
+ });
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ const result = new asn1js.Sequence();
+
+ if("baseCertificateID" in this)
+ {
+ result.valueBlock.value.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 0 // [0]
+ },
+ value: this.baseCertificateID.toSchema().valueBlock.value
+ }));
+ }
+
+ if("entityName" in this)
+ {
+ result.valueBlock.value.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 1 // [1]
+ },
+ value: this.entityName.toSchema().valueBlock.value
+ }));
+ }
+
+ if("objectDigestInfo" in this)
+ {
+ result.valueBlock.value.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 2 // [2]
+ },
+ value: this.objectDigestInfo.toSchema().valueBlock.value
+ }));
+ }
+
+ return result;
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const result = {};
+
+ if("baseCertificateID" in this)
+ result.baseCertificateID = this.baseCertificateID.toJSON();
+
+ if("entityName" in this)
+ result.entityName = this.entityName.toJSON();
+
+ if("objectDigestInfo" in this)
+ result.objectDigestInfo = this.objectDigestInfo.toJSON();
+
+ return result;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
+/**
+ * Class from RFC5755
+ */
+export class AttributeCertificateInfoV2
+{
+ //**********************************************************************************
+ /**
+ * Constructor for AttributeCertificateInfoV2 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 {Number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", AttributeCertificateInfoV2.defaultValues("version"));
+ /**
+ * @type {Holder}
+ * @desc holder
+ */
+ this.holder = getParametersValue(parameters, "holder", AttributeCertificateInfoV2.defaultValues("holder"));
+ /**
+ * @type {GeneralNames|V2Form}
+ * @desc issuer
+ */
+ this.issuer = getParametersValue(parameters, "issuer", AttributeCertificateInfoV2.defaultValues("issuer"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc signature
+ */
+ this.signature = getParametersValue(parameters, "signature", AttributeCertificateInfoV2.defaultValues("signature"));
+ /**
+ * @type {Integer}
+ * @desc serialNumber
+ */
+ this.serialNumber = getParametersValue(parameters, "serialNumber", AttributeCertificateInfoV2.defaultValues("serialNumber"));
+ /**
+ * @type {AttCertValidityPeriod}
+ * @desc attrCertValidityPeriod
+ */
+ this.attrCertValidityPeriod = getParametersValue(parameters, "attrCertValidityPeriod", AttributeCertificateInfoV2.defaultValues("attrCertValidityPeriod"));
+ /**
+ * @type {Array.}
+ * @desc attributes
+ */
+ this.attributes = getParametersValue(parameters, "attributes", AttributeCertificateInfoV2.defaultValues("attributes"));
+
+ if("issuerUniqueID" in parameters)
+ /**
+ * @type {BitString}
+ * @desc issuerUniqueID
+ */
+ this.issuerUniqueID = getParametersValue(parameters, "issuerUniqueID", AttributeCertificateInfoV2.defaultValues("issuerUniqueID"));
+
+ if("extensions" in parameters)
+ /**
+ * @type {Extensions}
+ * @desc extensions
+ */
+ this.extensions = getParametersValue(parameters, "extensions", AttributeCertificateInfoV2.defaultValues("extensions"));
+ //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 "version":
+ return 1;
+ case "holder":
+ return new Holder();
+ case "issuer":
+ return {};
+ case "signature":
+ return new AlgorithmIdentifier();
+ case "serialNumber":
+ return new asn1js.Integer();
+ case "attrCertValidityPeriod":
+ return new AttCertValidityPeriod();
+ case "attributes":
+ return [];
+ case "issuerUniqueID":
+ return new asn1js.BitString();
+ case "extensions":
+ return new Extensions();
+ default:
+ throw new Error(`Invalid member name for AttributeCertificateInfoV2 class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * AttributeCertificateInfoV2 ::= SEQUENCE {
+ * version AttCertVersion, -- version is v2
+ * holder Holder,
+ * issuer AttCertIssuer,
+ * signature AlgorithmIdentifier,
+ * serialNumber CertificateSerialNumber,
+ * attrCertValidityPeriod AttCertValidityPeriod,
+ * attributes SEQUENCE OF Attribute,
+ * issuerUniqueID UniqueIdentifier OPTIONAL,
+ * extensions Extensions OPTIONAL
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [issuer]
+ * @property {string} [serialNumber]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Integer({ name: (names.version || "") }),
+ Holder.schema(names.holder || {}),
+ new asn1js.Choice({
+ value: [
+ GeneralNames.schema({
+ names: {
+ blockName: (names.issuer || "")
+ }
+ }),
+ new asn1js.Constructed({
+ name: (names.issuer || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: V2Form.schema().valueBlock.value
+ })
+ ]
+ }),
+ AlgorithmIdentifier.schema(names.signature || {}),
+ new asn1js.Integer({ name: (names.serialNumber || "") }),
+ AttCertValidityPeriod.schema(names.attrCertValidityPeriod || {}),
+ new asn1js.Sequence({
+ name: (names.attributes || ""),
+ value: [
+ new asn1js.Repeated({
+ value: Attribute.schema()
+ })
+ ]
+ }),
+ new asn1js.BitString({
+ optional: true,
+ name: (names.issuerUniqueID || "")
+ }),
+ Extensions.schema(names.extensions || {}, true)
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "version",
+ "holder",
+ "issuer",
+ "signature",
+ "serialNumber",
+ "attrCertValidityPeriod",
+ "attributes",
+ "issuerUniqueID",
+ "extensions"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ AttributeCertificateInfoV2.schema({
+ names: {
+ version: "version",
+ holder: {
+ names: {
+ blockName: "holder"
+ }
+ },
+ issuer: "issuer",
+ signature: {
+ names: {
+ blockName: "signature"
+ }
+ },
+ serialNumber: "serialNumber",
+ attrCertValidityPeriod: {
+ names: {
+ blockName: "attrCertValidityPeriod"
+ }
+ },
+ attributes: "attributes",
+ issuerUniqueID: "issuerUniqueID",
+ extensions: {
+ names: {
+ blockName: "extensions"
+ }
+ }
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for AttributeCertificateInfoV2");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result.version.valueBlock.valueDec;
+ this.holder = new Holder({ schema: asn1.result.holder });
+
+ switch(asn1.result.issuer.idBlock.tagClass)
+ {
+ case 3: // V2Form
+ this.issuer = new V2Form({
+ schema: new asn1js.Sequence({
+ value: asn1.result.issuer.valueBlock.value
+ })
+ });
+ break;
+ case 1: // GeneralNames (should not be used)
+ default:
+ throw new Error("Incorect value for 'issuer' in AttributeCertificateInfoV2");
+ }
+
+ this.signature = new AlgorithmIdentifier({ schema: asn1.result.signature });
+ this.serialNumber = asn1.result.serialNumber;
+ this.attrCertValidityPeriod = new AttCertValidityPeriod({ schema: asn1.result.attrCertValidityPeriod });
+ this.attributes = Array.from(asn1.result.attributes.valueBlock.value, element => new Attribute({ schema: element }));
+
+ if("issuerUniqueID" in asn1.result)
+ this.issuerUniqueID = asn1.result.issuerUniqueID;
+
+ if("extensions" in asn1.result)
+ this.extensions = new Extensions({ schema: asn1.result.extensions });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ const result = new asn1js.Sequence({
+ value: [
+ new asn1js.Integer({ value: this.version }),
+ this.holder.toSchema(),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: this.issuer.toSchema().valueBlock.value
+ }),
+ this.signature.toSchema(),
+ this.serialNumber,
+ this.attrCertValidityPeriod.toSchema(),
+ new asn1js.Sequence({
+ value: Array.from(this.attributes, element => element.toSchema())
+ })
+ ]
+ });
+
+ if("issuerUniqueID" in this)
+ result.valueBlock.value.push(this.issuerUniqueID);
+
+ if("extensions" in this)
+ result.valueBlock.value.push(this.extensions.toSchema());
+
+ return result;
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const result = {
+ version: this.version,
+ holder: this.holder.toJSON(),
+ issuer: this.issuer.toJSON(),
+ signature: this.signature.toJSON(),
+ serialNumber: this.serialNumber.toJSON(),
+ attrCertValidityPeriod: this.attrCertValidityPeriod.toJSON(),
+ attributes: Array.from(this.attributes, element => element.toJSON())
+ };
+
+ if("issuerUniqueID" in this)
+ result.issuerUniqueID = this.issuerUniqueID.toJSON();
+
+ if("extensions" in this)
+ result.extensions = this.extensions.toJSON();
+
+ return result;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
+/**
+ * Class from RFC5755
+ */
+export default class AttributeCertificateV2
+{
+ //**********************************************************************************
+ /**
+ * Constructor for AttributeCertificateV2 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 {AttributeCertificateInfoV2}
+ * @desc acinfo
+ */
+ this.acinfo = getParametersValue(parameters, "acinfo", AttributeCertificateV2.defaultValues("acinfo"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc signatureAlgorithm
+ */
+ this.signatureAlgorithm = getParametersValue(parameters, "signatureAlgorithm", AttributeCertificateV2.defaultValues("signatureAlgorithm"));
+ /**
+ * @type {BitString}
+ * @desc signatureValue
+ */
+ this.signatureValue = getParametersValue(parameters, "signatureValue", AttributeCertificateV2.defaultValues("signatureValue"));
+ //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 "acinfo":
+ return new AttributeCertificateInfoV2();
+ case "signatureAlgorithm":
+ return new AlgorithmIdentifier();
+ case "signatureValue":
+ return new asn1js.BitString();
+ default:
+ throw new Error(`Invalid member name for AttributeCertificateV2 class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * AttributeCertificate ::= SEQUENCE {
+ * acinfo AttributeCertificateInfoV2,
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signatureValue BIT STRING
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {Object} [acinfo]
+ * @property {Object} [signatureAlgorithm]
+ * @property {string} [signatureValue]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ AttributeCertificateInfoV2.schema(names.acinfo || {}),
+ AlgorithmIdentifier.schema(names.signatureAlgorithm || {}),
+ new asn1js.BitString({ name: (names.signatureValue || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "acinfo",
+ "signatureAlgorithm",
+ "signatureValue"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ AttributeCertificateV2.schema({
+ names: {
+ acinfo: {
+ names: {
+ blockName: "acinfo"
+ }
+ },
+ signatureAlgorithm: {
+ names: {
+ blockName: "signatureAlgorithm"
+ }
+ },
+ signatureValue: "signatureValue"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for AttributeCertificateV2");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.acinfo = new AttributeCertificateInfoV2({ schema: asn1.result.acinfo });
+ this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm });
+ this.signatureValue = asn1.result.signatureValue;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ return (new asn1js.Sequence({
+ value: [
+ this.acinfo.toSchema(),
+ this.signatureAlgorithm.toSchema(),
+ this.signatureValue
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ acinfo: this.acinfo.toJSON(),
+ signatureAlgorithm: this.signatureAlgorithm.toJSON(),
+ signatureValue: this.signatureValue.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/AttributeTypeAndValue.js b/pki.js/AttributeTypeAndValue.js
new file mode 100644
index 0000000..63cf673
--- /dev/null
+++ b/pki.js/AttributeTypeAndValue.js
@@ -0,0 +1,232 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, isEqualBuffer, clearProps } from "pvutils";
+import { stringPrep } from "./common.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class AttributeTypeAndValue
+{
+ //**********************************************************************************
+ /**
+ * Constructor for AttributeTypeAndValue 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 {string}
+ * @desc type
+ */
+ this.type = getParametersValue(parameters, "type", AttributeTypeAndValue.defaultValues("type"));
+ /**
+ * @type {Object}
+ * @desc Value of the AttributeTypeAndValue class
+ */
+ this.value = getParametersValue(parameters, "value", AttributeTypeAndValue.defaultValues("value"));
+ //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 "type":
+ return "";
+ case "value":
+ return {};
+ default:
+ throw new Error(`Invalid member name for AttributeTypeAndValue class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * AttributeTypeAndValue ::= Sequence {
+ * type AttributeType,
+ * value AttributeValue }
+ *
+ * AttributeType ::= OBJECT IDENTIFIER
+ *
+ * AttributeValue ::= ANY -- DEFINED BY AttributeType
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName] Name for entire block
+ * @property {string} [type] Name for "type" element
+ * @property {string} [value] Name for "value" element
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.type || "") }),
+ new asn1js.Any({ name: (names.value || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ static blockName()
+ {
+ return "AttributeTypeAndValue";
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "type",
+ "typeValue"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ AttributeTypeAndValue.schema({
+ names: {
+ type: "type",
+ value: "typeValue"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for AttributeTypeAndValue");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.type = asn1.result.type.valueBlock.toString();
+ // noinspection JSUnresolvedVariable
+ this.value = asn1.result.typeValue;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ new asn1js.ObjectIdentifier({ value: this.type }),
+ this.value
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const _object = {
+ type: this.type
+ };
+
+ if(Object.keys(this.value).length !== 0)
+ _object.value = this.value.toJSON();
+ else
+ _object.value = this.value;
+
+ return _object;
+ }
+ //**********************************************************************************
+ /**
+ * Compare two AttributeTypeAndValue values, or AttributeTypeAndValue with ArrayBuffer value
+ * @param {(AttributeTypeAndValue|ArrayBuffer)} compareTo The value compare to current
+ * @returns {boolean}
+ */
+ isEqual(compareTo)
+ {
+ const stringBlockNames = [
+ asn1js.Utf8String.blockName(),
+ asn1js.BmpString.blockName(),
+ asn1js.UniversalString.blockName(),
+ asn1js.NumericString.blockName(),
+ asn1js.PrintableString.blockName(),
+ asn1js.TeletexString.blockName(),
+ asn1js.VideotexString.blockName(),
+ asn1js.IA5String.blockName(),
+ asn1js.GraphicString.blockName(),
+ asn1js.VisibleString.blockName(),
+ asn1js.GeneralString.blockName(),
+ asn1js.CharacterString.blockName()
+ ];
+
+ if(compareTo.constructor.blockName() === AttributeTypeAndValue.blockName())
+ {
+ if(this.type !== compareTo.type)
+ return false;
+
+ //region Check we do have both strings
+ let isString = false;
+ const thisName = this.value.constructor.blockName();
+
+ if(thisName === compareTo.value.constructor.blockName())
+ {
+ for(const name of stringBlockNames)
+ {
+ if(thisName === name)
+ {
+ isString = true;
+ break;
+ }
+ }
+ }
+ //endregion
+
+ if(isString)
+ {
+ const value1 = stringPrep(this.value.valueBlock.value);
+ const value2 = stringPrep(compareTo.value.valueBlock.value);
+
+ if(value1.localeCompare(value2) !== 0)
+ return false;
+ }
+ else // Comparing as two ArrayBuffers
+ {
+ if(isEqualBuffer(this.value.valueBeforeDecode, compareTo.value.valueBeforeDecode) === false)
+ return false;
+ }
+
+ return true;
+ }
+
+ if(compareTo instanceof ArrayBuffer)
+ return isEqualBuffer(this.value.valueBeforeDecode, compareTo);
+
+ return false;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/AuthenticatedSafe.js b/pki.js/AuthenticatedSafe.js
new file mode 100644
index 0000000..33b67d9
--- /dev/null
+++ b/pki.js/AuthenticatedSafe.js
@@ -0,0 +1,513 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, utilConcatBuf, clearProps } from "pvutils";
+import ContentInfo from "./ContentInfo.js";
+import SafeContents from "./SafeContents.js";
+import EnvelopedData from "./EnvelopedData.js";
+import EncryptedData from "./EncryptedData.js";
+//**************************************************************************************
+/**
+ * Class from RFC7292
+ */
+export default class AuthenticatedSafe
+{
+ //**********************************************************************************
+ /**
+ * Constructor for AuthenticatedSafe 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 {Array.}
+ * @desc safeContents
+ */
+ this.safeContents = getParametersValue(parameters, "safeContents", AuthenticatedSafe.defaultValues("safeContents"));
+
+ if("parsedValue" in parameters)
+ /**
+ * @type {*}
+ * @desc parsedValue
+ */
+ this.parsedValue = getParametersValue(parameters, "parsedValue", AuthenticatedSafe.defaultValues("parsedValue"));
+ //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 "safeContents":
+ return [];
+ case "parsedValue":
+ return {};
+ default:
+ throw new Error(`Invalid member name for AuthenticatedSafe 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 "safeContents":
+ return (memberValue.length === 0);
+ case "parsedValue":
+ return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0));
+ default:
+ throw new Error(`Invalid member name for AuthenticatedSafe class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * AuthenticatedSafe ::= SEQUENCE OF ContentInfo
+ * -- Data if unencrypted
+ * -- EncryptedData if password-encrypted
+ * -- EnvelopedData if public key-encrypted
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [contentInfos]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.contentInfos || ""),
+ value: ContentInfo.schema()
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "contentInfos"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ AuthenticatedSafe.schema({
+ names: {
+ contentInfos: "contentInfos"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for AuthenticatedSafe");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.safeContents = Array.from(asn1.result.contentInfos, element => new ContentInfo({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: Array.from(this.safeContents, element => element.toSchema())
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ safeContents: Array.from(this.safeContents, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+ parseInternalValues(parameters)
+ {
+ //region Check input data from "parameters"
+ if((parameters instanceof Object) === false)
+ return Promise.reject("The \"parameters\" must has \"Object\" type");
+
+ if(("safeContents" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"safeContents\"");
+
+ if((parameters.safeContents instanceof Array) === false)
+ return Promise.reject("The \"parameters.safeContents\" must has \"Array\" type");
+
+ if(parameters.safeContents.length !== this.safeContents.length)
+ return Promise.reject("Length of \"parameters.safeContents\" must be equal to \"this.safeContents.length\"");
+ //endregion
+
+ //region Initial variables
+ let sequence = Promise.resolve();
+ //endregion
+
+ //region Create value for "this.parsedValue.authenticatedSafe"
+ this.parsedValue = {
+ safeContents: []
+ };
+
+ for(const [index, content] of this.safeContents.entries())
+ {
+ switch(content.contentType)
+ {
+ //region data
+ case "1.2.840.113549.1.7.1":
+ {
+ //region Check that we do have OCTETSTRING as "content"
+ if((content.content instanceof asn1js.OctetString) === false)
+ return Promise.reject("Wrong type of \"this.safeContents[j].content\"");
+ //endregion
+
+ //region Check we have "constructive encoding" for AuthSafe content
+ let authSafeContent = new ArrayBuffer(0);
+
+ if(content.content.valueBlock.isConstructed)
+ {
+ for(const contentValue of content.content.valueBlock.value)
+ authSafeContent = utilConcatBuf(authSafeContent, contentValue.valueBlock.valueHex);
+ }
+ else
+ authSafeContent = content.content.valueBlock.valueHex;
+ //endregion
+
+ //region Parse internal ASN.1 data
+ const asn1 = asn1js.fromBER(authSafeContent);
+ if(asn1.offset === (-1))
+ return Promise.reject("Error during parsing of ASN.1 data inside \"content.content\"");
+ //endregion
+
+ //region Finilly initialize initial values of "SafeContents" type
+ this.parsedValue.safeContents.push({
+ privacyMode: 0, // No privacy, clear data
+ value: new SafeContents({ schema: asn1.result })
+ });
+ //endregion
+ }
+ break;
+ //endregion
+ //region envelopedData
+ case "1.2.840.113549.1.7.3":
+ {
+ //region Initial variables
+ const cmsEnveloped = new EnvelopedData({ schema: content.content });
+ //endregion
+
+ //region Check mandatory parameters
+ if(("recipientCertificate" in parameters.safeContents[index]) === false)
+ return Promise.reject("Absent mandatory parameter \"recipientCertificate\" in \"parameters.safeContents[j]\"");
+
+ const recipientCertificate = parameters.safeContents[index].recipientCertificate;
+
+ if(("recipientKey" in parameters.safeContents[index]) === false)
+ return Promise.reject("Absent mandatory parameter \"recipientKey\" in \"parameters.safeContents[j]\"");
+
+ // noinspection JSUnresolvedVariable
+ const recipientKey = parameters.safeContents[index].recipientKey;
+ //endregion
+
+ //region Decrypt CMS EnvelopedData using first recipient information
+ sequence = sequence.then(
+ () => cmsEnveloped.decrypt(0, {
+ recipientCertificate,
+ recipientPrivateKey: recipientKey
+ })
+ );
+
+ sequence = sequence.then(
+ /**
+ * @param {ArrayBuffer} result
+ */
+ result =>
+ {
+ const asn1 = asn1js.fromBER(result);
+ if(asn1.offset === (-1))
+ return Promise.reject("Error during parsing of decrypted data");
+
+ this.parsedValue.safeContents.push({
+ privacyMode: 2, // Public-key privacy mode
+ value: new SafeContents({ schema: asn1.result })
+ });
+
+ return Promise.resolve();
+ }
+ );
+ //endregion
+ }
+ break;
+ //endregion
+ //region encryptedData
+ case "1.2.840.113549.1.7.6":
+ {
+ //region Initial variables
+ const cmsEncrypted = new EncryptedData({ schema: content.content });
+ //endregion
+
+ //region Check mandatory parameters
+ if(("password" in parameters.safeContents[index]) === false)
+ return Promise.reject("Absent mandatory parameter \"password\" in \"parameters.safeContents[j]\"");
+
+ const password = parameters.safeContents[index].password;
+ //endregion
+
+ //region Decrypt CMS EncryptedData using password
+ sequence = sequence.then(
+ () => cmsEncrypted.decrypt({
+ password
+ }),
+ error => Promise.reject(error)
+ );
+ //endregion
+
+ //region Initialize internal data
+ sequence = sequence.then(
+ /**
+ * @param {ArrayBuffer} result
+ */
+ result =>
+ {
+ const asn1 = asn1js.fromBER(result);
+ if(asn1.offset === (-1))
+ return Promise.reject("Error during parsing of decrypted data");
+
+ this.parsedValue.safeContents.push({
+ privacyMode: 1, // Password-based privacy mode
+ value: new SafeContents({ schema: asn1.result })
+ });
+
+ return Promise.resolve();
+ },
+ error => Promise.reject(error)
+ );
+ //endregion
+ }
+ break;
+ //endregion
+ //region default
+ default:
+ throw new Error(`Unknown "contentType" for AuthenticatedSafe: " ${content.contentType}`);
+ //endregion
+ }
+ }
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ makeInternalValues(parameters)
+ {
+ //region Check data in "parsedValue"
+ if(("parsedValue" in this) === false)
+ return Promise.reject("Please run \"parseValues\" first or add \"parsedValue\" manually");
+
+ if((this.parsedValue instanceof Object) === false)
+ return Promise.reject("The \"this.parsedValue\" must has \"Object\" type");
+
+ if((this.parsedValue.safeContents instanceof Array) === false)
+ return Promise.reject("The \"this.parsedValue.safeContents\" must has \"Array\" type");
+ //endregion
+
+ //region Check input data from "parameters"
+ if((parameters instanceof Object) === false)
+ return Promise.reject("The \"parameters\" must has \"Object\" type");
+
+ if(("safeContents" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"safeContents\"");
+
+ if((parameters.safeContents instanceof Array) === false)
+ return Promise.reject("The \"parameters.safeContents\" must has \"Array\" type");
+
+ if(parameters.safeContents.length !== this.parsedValue.safeContents.length)
+ return Promise.reject("Length of \"parameters.safeContents\" must be equal to \"this.parsedValue.safeContents\"");
+ //endregion
+
+ //region Initial variables
+ let sequence = Promise.resolve();
+ //endregion
+
+ //region Create internal values from already parsed values
+ this.safeContents = [];
+
+ for(const [index, content] of this.parsedValue.safeContents.entries())
+ {
+ //region Check current "content" value
+ if(("privacyMode" in content) === false)
+ return Promise.reject("The \"privacyMode\" is a mandatory parameter for \"content\"");
+
+ if(("value" in content) === false)
+ return Promise.reject("The \"value\" is a mandatory parameter for \"content\"");
+
+ if((content.value instanceof SafeContents) === false)
+ return Promise.reject("The \"content.value\" must has \"SafeContents\" type");
+ //endregion
+
+ switch(content.privacyMode)
+ {
+ //region No privacy
+ case 0:
+ {
+ const contentBuffer = content.value.toSchema().toBER(false);
+
+ sequence = sequence.then(
+ () =>
+ {
+ this.safeContents.push(new ContentInfo({
+ contentType: "1.2.840.113549.1.7.1",
+ content: new asn1js.OctetString({ valueHex: contentBuffer })
+ }));
+ });
+ }
+ break;
+ //endregion
+ //region Privacy with password
+ case 1:
+ {
+ //region Initial variables
+ const cmsEncrypted = new EncryptedData();
+
+ const currentParameters = parameters.safeContents[index];
+ currentParameters.contentToEncrypt = content.value.toSchema().toBER(false);
+ //endregion
+
+ //region Encrypt CMS EncryptedData using password
+ sequence = sequence.then(
+ () => cmsEncrypted.encrypt(currentParameters),
+ error => Promise.reject(error)
+ );
+ //endregion
+
+ //region Store result content in CMS_CONTENT_INFO type
+ sequence = sequence.then(
+ () =>
+ {
+ this.safeContents.push(new ContentInfo({
+ contentType: "1.2.840.113549.1.7.6",
+ content: cmsEncrypted.toSchema()
+ }));
+ },
+ error => Promise.reject(error)
+ );
+ //endregion
+ }
+ break;
+ //endregion
+ //region Privacy with public key
+ case 2:
+ {
+ //region Initial variables
+ const cmsEnveloped = new EnvelopedData();
+ const contentToEncrypt = content.value.toSchema().toBER(false);
+ //endregion
+
+ //region Check mandatory parameters
+ if(("encryptingCertificate" in parameters.safeContents[index]) === false)
+ return Promise.reject("Absent mandatory parameter \"encryptingCertificate\" in \"parameters.safeContents[i]\"");
+
+ if(("encryptionAlgorithm" in parameters.safeContents[index]) === false)
+ return Promise.reject("Absent mandatory parameter \"encryptionAlgorithm\" in \"parameters.safeContents[i]\"");
+
+ switch(true)
+ {
+ case (parameters.safeContents[index].encryptionAlgorithm.name.toLowerCase() === "aes-cbc"):
+ case (parameters.safeContents[index].encryptionAlgorithm.name.toLowerCase() === "aes-gcm"):
+ break;
+ default:
+ return Promise.reject(`Incorrect parameter "encryptionAlgorithm" in "parameters.safeContents[i]": ${parameters.safeContents[index].encryptionAlgorithm}`);
+ }
+
+ switch(true)
+ {
+ case (parameters.safeContents[index].encryptionAlgorithm.length === 128):
+ case (parameters.safeContents[index].encryptionAlgorithm.length === 192):
+ case (parameters.safeContents[index].encryptionAlgorithm.length === 256):
+ break;
+ default:
+ return Promise.reject(`Incorrect parameter "encryptionAlgorithm.length" in "parameters.safeContents[i]": ${parameters.safeContents[index].encryptionAlgorithm.length}`);
+ }
+ //endregion
+
+ //region Making correct "encryptionAlgorithm" variable
+ const encryptionAlgorithm = parameters.safeContents[index].encryptionAlgorithm;
+ //endregion
+
+ //region Append recipient for enveloped data
+ cmsEnveloped.addRecipientByCertificate(parameters.safeContents[index].encryptingCertificate);
+ //endregion
+
+ //region Making encryption
+ sequence = sequence.then(
+ () => cmsEnveloped.encrypt(encryptionAlgorithm, contentToEncrypt)
+ );
+
+ sequence = sequence.then(
+ () =>
+ {
+ this.safeContents.push(new ContentInfo({
+ contentType: "1.2.840.113549.1.7.3",
+ content: cmsEnveloped.toSchema()
+ }));
+ }
+ );
+ //endregion
+ }
+ break;
+ //endregion
+ //region default
+ default:
+ return Promise.reject(`Incorrect value for "content.privacyMode": ${content.privacyMode}`);
+ //endregion
+ }
+ }
+ //endregion
+
+ //region Return result of the function
+ return sequence.then(
+ () => this,
+ error => Promise.reject(`Error during parsing: ${error}`)
+ );
+ //endregion
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/AuthorityKeyIdentifier.js b/pki.js/AuthorityKeyIdentifier.js
new file mode 100644
index 0000000..0def109
--- /dev/null
+++ b/pki.js/AuthorityKeyIdentifier.js
@@ -0,0 +1,244 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import GeneralName from "./GeneralName.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class AuthorityKeyIdentifier
+{
+ //**********************************************************************************
+ /**
+ * Constructor for AuthorityKeyIdentifier class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ if("keyIdentifier" in parameters)
+ /**
+ * @type {OctetString}
+ * @desc keyIdentifier
+ */
+ this.keyIdentifier = getParametersValue(parameters, "keyIdentifier", AuthorityKeyIdentifier.defaultValues("keyIdentifier"));
+
+ if("authorityCertIssuer" in parameters)
+ /**
+ * @type {Array.}
+ * @desc authorityCertIssuer
+ */
+ this.authorityCertIssuer = getParametersValue(parameters, "authorityCertIssuer", AuthorityKeyIdentifier.defaultValues("authorityCertIssuer"));
+
+ if("authorityCertSerialNumber" in parameters)
+ /**
+ * @type {Integer}
+ * @desc authorityCertIssuer
+ */
+ this.authorityCertSerialNumber = getParametersValue(parameters, "authorityCertSerialNumber", AuthorityKeyIdentifier.defaultValues("authorityCertSerialNumber"));
+ //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 "keyIdentifier":
+ return new asn1js.OctetString();
+ case "authorityCertIssuer":
+ return [];
+ case "authorityCertSerialNumber":
+ return new asn1js.Integer();
+ default:
+ throw new Error(`Invalid member name for AuthorityKeyIdentifier class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * AuthorityKeyIdentifier OID ::= 2.5.29.35
+ *
+ * AuthorityKeyIdentifier ::= SEQUENCE {
+ * keyIdentifier [0] KeyIdentifier OPTIONAL,
+ * authorityCertIssuer [1] GeneralNames OPTIONAL,
+ * authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
+ *
+ * KeyIdentifier ::= OCTET STRING
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [keyIdentifier]
+ * @property {string} [authorityCertIssuer]
+ * @property {string} [authorityCertSerialNumber]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Primitive({
+ name: (names.keyIdentifier || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ }
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [
+ new asn1js.Repeated({
+ name: (names.authorityCertIssuer || ""),
+ value: GeneralName.schema()
+ })
+ ]
+ }),
+ new asn1js.Primitive({
+ name: (names.authorityCertSerialNumber || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ }
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "keyIdentifier",
+ "authorityCertIssuer",
+ "authorityCertSerialNumber"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ AuthorityKeyIdentifier.schema({
+ names: {
+ keyIdentifier: "keyIdentifier",
+ authorityCertIssuer: "authorityCertIssuer",
+ authorityCertSerialNumber: "authorityCertSerialNumber"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for AuthorityKeyIdentifier");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("keyIdentifier" in asn1.result)
+ this.keyIdentifier = new asn1js.OctetString({ valueHex: asn1.result.keyIdentifier.valueBlock.valueHex });
+
+ if("authorityCertIssuer" in asn1.result)
+ this.authorityCertIssuer = Array.from(asn1.result.authorityCertIssuer, element => new GeneralName({ schema: element }));
+
+ if("authorityCertSerialNumber" in asn1.result)
+ this.authorityCertSerialNumber = new asn1js.Integer({ valueHex: asn1.result.authorityCertSerialNumber.valueBlock.valueHex });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ if("keyIdentifier" in this)
+ {
+ outputArray.push(new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ valueHex: this.keyIdentifier.valueBlock.valueHex
+ }));
+ }
+
+ if("authorityCertIssuer" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: Array.from(this.authorityCertIssuer, element => element.toSchema())
+ }));
+ }
+
+ if("authorityCertSerialNumber" in this)
+ {
+ outputArray.push(new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ valueHex: this.authorityCertSerialNumber.valueBlock.valueHex
+ }));
+ }
+ //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 = {};
+
+ if("keyIdentifier" in this)
+ object.keyIdentifier = this.keyIdentifier.toJSON();
+
+ if("authorityCertIssuer" in this)
+ object.authorityCertIssuer = Array.from(this.authorityCertIssuer, element => element.toJSON());
+
+ if("authorityCertSerialNumber" in this)
+ object.authorityCertSerialNumber = this.authorityCertSerialNumber.toJSON();
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/BasicConstraints.js b/pki.js/BasicConstraints.js
new file mode 100644
index 0000000..69905cc
--- /dev/null
+++ b/pki.js/BasicConstraints.js
@@ -0,0 +1,186 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class BasicConstraints
+{
+ //**********************************************************************************
+ /**
+ * Constructor for BasicConstraints class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ * @property {Object} [cA]
+ * @property {Object} [pathLenConstraint]
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ /**
+ * @type {boolean}
+ * @desc cA
+ */
+ this.cA = getParametersValue(parameters, "cA", false);
+
+ if("pathLenConstraint" in parameters)
+ /**
+ * @type {number|Integer}
+ * @desc pathLenConstraint
+ */
+ this.pathLenConstraint = getParametersValue(parameters, "pathLenConstraint", 0);
+ //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 "cA":
+ return false;
+ default:
+ throw new Error(`Invalid member name for BasicConstraints class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * BasicConstraints ::= SEQUENCE {
+ * cA BOOLEAN DEFAULT FALSE,
+ * pathLenConstraint INTEGER (0..MAX) OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [cA]
+ * @property {string} [pathLenConstraint]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Boolean({
+ optional: true,
+ name: (names.cA || "")
+ }),
+ new asn1js.Integer({
+ optional: true,
+ name: (names.pathLenConstraint || "")
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "cA",
+ "pathLenConstraint"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ BasicConstraints.schema({
+ names: {
+ cA: "cA",
+ pathLenConstraint: "pathLenConstraint"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for BasicConstraints");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("cA" in asn1.result)
+ this.cA = asn1.result.cA.valueBlock.value;
+
+ if("pathLenConstraint" in asn1.result)
+ {
+ if(asn1.result.pathLenConstraint.valueBlock.isHexOnly)
+ this.pathLenConstraint = asn1.result.pathLenConstraint;
+ else
+ this.pathLenConstraint = asn1.result.pathLenConstraint.valueBlock.valueDec;
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ if(this.cA !== BasicConstraints.defaultValues("cA"))
+ outputArray.push(new asn1js.Boolean({ value: this.cA }));
+
+ if("pathLenConstraint" in this)
+ {
+ if(this.pathLenConstraint instanceof asn1js.Integer)
+ outputArray.push(this.pathLenConstraint);
+ else
+ outputArray.push(new asn1js.Integer({ value: this.pathLenConstraint }));
+ }
+ //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 = {};
+
+ if(this.cA !== BasicConstraints.defaultValues("cA"))
+ object.cA = this.cA;
+
+ if("pathLenConstraint" in this)
+ {
+ if(this.pathLenConstraint instanceof asn1js.Integer)
+ object.pathLenConstraint = this.pathLenConstraint.toJSON();
+ else
+ object.pathLenConstraint = this.pathLenConstraint;
+ }
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/BasicOCSPResponse.js b/pki.js/BasicOCSPResponse.js
new file mode 100644
index 0000000..b32ea7b
--- /dev/null
+++ b/pki.js/BasicOCSPResponse.js
@@ -0,0 +1,562 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, isEqualBuffer, clearProps } from "pvutils";
+import { getAlgorithmByOID, getCrypto, getEngine } from "./common.js";
+import ResponseData from "./ResponseData.js";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import Certificate from "./Certificate.js";
+import CertID from "./CertID.js";
+import RelativeDistinguishedNames from "./RelativeDistinguishedNames.js";
+import CertificateChainValidationEngine from "./CertificateChainValidationEngine.js";
+//**************************************************************************************
+/**
+ * Class from RFC6960
+ */
+export default class BasicOCSPResponse
+{
+ //**********************************************************************************
+ /**
+ * Constructor for BasicOCSPResponse 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 {ResponseData}
+ * @desc tbsResponseData
+ */
+ this.tbsResponseData = getParametersValue(parameters, "tbsResponseData", BasicOCSPResponse.defaultValues("tbsResponseData"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc signatureAlgorithm
+ */
+ this.signatureAlgorithm = getParametersValue(parameters, "signatureAlgorithm", BasicOCSPResponse.defaultValues("signatureAlgorithm"));
+ /**
+ * @type {BitString}
+ * @desc signature
+ */
+ this.signature = getParametersValue(parameters, "signature", BasicOCSPResponse.defaultValues("signature"));
+
+ if("certs" in parameters)
+ /**
+ * @type {Array.}
+ * @desc certs
+ */
+ this.certs = getParametersValue(parameters, "certs", BasicOCSPResponse.defaultValues("certs"));
+ //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 "tbsResponseData":
+ return new ResponseData();
+ case "signatureAlgorithm":
+ return new AlgorithmIdentifier();
+ case "signature":
+ return new asn1js.BitString();
+ case "certs":
+ return [];
+ default:
+ throw new Error(`Invalid member name for BasicOCSPResponse 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 "type":
+ {
+ // noinspection OverlyComplexBooleanExpressionJS
+ let comparisonResult = ((ResponseData.compareWithDefault("tbs", memberValue.tbs)) &&
+ (ResponseData.compareWithDefault("responderID", memberValue.responderID)) &&
+ (ResponseData.compareWithDefault("producedAt", memberValue.producedAt)) &&
+ (ResponseData.compareWithDefault("responses", memberValue.responses)));
+
+ if("responseExtensions" in memberValue)
+ comparisonResult = comparisonResult && (ResponseData.compareWithDefault("responseExtensions", memberValue.responseExtensions));
+
+ return comparisonResult;
+ }
+ case "signatureAlgorithm":
+ return ((memberValue.algorithmId === "") && (("algorithmParams" in memberValue) === false));
+ case "signature":
+ return (memberValue.isEqual(BasicOCSPResponse.defaultValues(memberName)));
+ case "certs":
+ return (memberValue.length === 0);
+ default:
+ throw new Error(`Invalid member name for BasicOCSPResponse class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * BasicOCSPResponse ::= SEQUENCE {
+ * tbsResponseData ResponseData,
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signature BIT STRING,
+ * certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [tbsResponseData]
+ * @property {string} [signatureAlgorithm]
+ * @property {string} [signature]
+ * @property {string} [certs]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || "BasicOCSPResponse"),
+ value: [
+ ResponseData.schema(names.tbsResponseData || {
+ names: {
+ blockName: "BasicOCSPResponse.tbsResponseData"
+ }
+ }),
+ AlgorithmIdentifier.schema(names.signatureAlgorithm || {
+ names: {
+ blockName: "BasicOCSPResponse.signatureAlgorithm"
+ }
+ }),
+ new asn1js.BitString({ name: (names.signature || "BasicOCSPResponse.signature") }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Sequence({
+ value: [new asn1js.Repeated({
+ name: "BasicOCSPResponse.certs",
+ value: Certificate.schema(names.certs || {})
+ })]
+ })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "BasicOCSPResponse.tbsResponseData",
+ "BasicOCSPResponse.signatureAlgorithm",
+ "BasicOCSPResponse.signature",
+ "BasicOCSPResponse.certs"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ BasicOCSPResponse.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for BasicOCSPResponse");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.tbsResponseData = new ResponseData({ schema: asn1.result["BasicOCSPResponse.tbsResponseData"] });
+ this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result["BasicOCSPResponse.signatureAlgorithm"] });
+ this.signature = asn1.result["BasicOCSPResponse.signature"];
+
+ if("BasicOCSPResponse.certs" in asn1.result)
+ this.certs = Array.from(asn1.result["BasicOCSPResponse.certs"], element => new Certificate({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(this.tbsResponseData.toSchema());
+ outputArray.push(this.signatureAlgorithm.toSchema());
+ outputArray.push(this.signature);
+
+ //region Create array of certificates
+ if("certs" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Sequence({
+ value: Array.from(this.certs, element => element.toSchema())
+ })
+ ]
+ }));
+ }
+ //endregion
+ //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 = {
+ tbsResponseData: this.tbsResponseData.toJSON(),
+ signatureAlgorithm: this.signatureAlgorithm.toJSON(),
+ signature: this.signature.toJSON()
+ };
+
+ if("certs" in this)
+ _object.certs = Array.from(this.certs, element => element.toJSON());
+
+ return _object;
+ }
+ //**********************************************************************************
+ /**
+ * Get OCSP response status for specific certificate
+ * @param {Certificate} certificate Certificate to be checked
+ * @param {Certificate} issuerCertificate Certificate of issuer for certificate to be checked
+ * @returns {Promise}
+ */
+ getCertificateStatus(certificate, issuerCertificate)
+ {
+ //region Initial variables
+ let sequence = Promise.resolve();
+
+ const result = {
+ isForCertificate: false,
+ status: 2 // 0 = good, 1 = revoked, 2 = unknown
+ };
+
+ const hashesObject = {};
+
+ const certIDs = [];
+ const certIDPromises = [];
+ //endregion
+
+ //region Create all "certIDs" for input certificates
+ for(const response of this.tbsResponseData.responses)
+ {
+ const hashAlgorithm = getAlgorithmByOID(response.certID.hashAlgorithm.algorithmId);
+ if(("name" in hashAlgorithm) === false)
+ return Promise.reject(`Wrong CertID hashing algorithm: ${response.certID.hashAlgorithm.algorithmId}`);
+
+ if((hashAlgorithm.name in hashesObject) === false)
+ {
+ hashesObject[hashAlgorithm.name] = 1;
+
+ const certID = new CertID();
+
+ certIDs.push(certID);
+ certIDPromises.push(certID.createForCertificate(certificate, {
+ hashAlgorithm: hashAlgorithm.name,
+ issuerCertificate
+ }));
+ }
+ }
+
+ sequence = sequence.then(() =>
+ Promise.all(certIDPromises)
+ );
+ //endregion
+
+ //region Compare all response's "certIDs" with identifiers for input certificate
+ sequence = sequence.then(() =>
+ {
+ for(const response of this.tbsResponseData.responses)
+ {
+ for(const id of certIDs)
+ {
+ if(response.certID.isEqual(id))
+ {
+ result.isForCertificate = true;
+
+ try
+ {
+ switch(response.certStatus.idBlock.isConstructed)
+ {
+ case true:
+ if(response.certStatus.idBlock.tagNumber === 1)
+ result.status = 1; // revoked
+
+ break;
+ case false:
+ switch(response.certStatus.idBlock.tagNumber)
+ {
+ case 0: // good
+ result.status = 0;
+ break;
+ case 2: // unknown
+ result.status = 2;
+ break;
+ default:
+ }
+
+ break;
+ default:
+ }
+ }
+ catch(ex)
+ {
+ }
+
+ return result;
+ }
+ }
+ }
+
+ return result;
+ });
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ /**
+ * Make signature for current OCSP Basic Response
+ * @param {Object} privateKey Private key for "subjectPublicKeyInfo" structure
+ * @param {string} [hashAlgorithm="SHA-1"] Hashing algorithm. Default SHA-1
+ * @returns {Promise}
+ */
+ sign(privateKey, hashAlgorithm = "SHA-1")
+ {
+ //region Initial checking
+ //region Get a private key from function parameter
+ if(typeof privateKey === "undefined")
+ return Promise.reject("Need to provide a private key for signing");
+ //endregion
+ //endregion
+
+ //region Initial variables
+ let sequence = Promise.resolve();
+ let parameters;
+
+ const engine = getEngine();
+ //endregion
+
+ //region Get a "default parameters" for current algorithm and set correct signature algorithm
+ sequence = sequence.then(() => engine.subtle.getSignatureParameters(privateKey, hashAlgorithm));
+
+ sequence = sequence.then(result =>
+ {
+ parameters = result.parameters;
+ this.signatureAlgorithm = result.signatureAlgorithm;
+ });
+ //endregion
+
+ //region Create TBS data for signing
+ sequence = sequence.then(() =>
+ {
+ this.tbsResponseData.tbs = this.tbsResponseData.toSchema(true).toBER(false);
+ });
+ //endregion
+
+ //region Signing TBS data on provided private key
+ sequence = sequence.then(() => engine.subtle.signWithPrivateKey(this.tbsResponseData.tbs, privateKey, parameters));
+
+ sequence = sequence.then(result =>
+ {
+ this.signature = new asn1js.BitString({ valueHex: result });
+ });
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ /**
+ * Verify existing OCSP Basic Response
+ * @param {Object} parameters Additional parameters
+ * @returns {Promise}
+ */
+ verify(parameters = {})
+ {
+ //region Initial variables
+ let signerCert = null;
+
+ let certIndex = -1;
+
+ let sequence = Promise.resolve();
+
+ let trustedCerts = [];
+
+ const _this = this;
+
+ const engine = getEngine();
+ //endregion
+
+ //region Check amount of certificates
+ if(("certs" in this) === false)
+ return Promise.reject("No certificates attached to the BasicOCSPResponce");
+ //endregion
+
+ //region Get input values
+ if("trustedCerts" in parameters)
+ trustedCerts = parameters.trustedCerts;
+ //endregion
+
+ //region Aux functions
+ /**
+ * Check CA flag for the certificate
+ * @param {Certificate} cert Certificate to find CA flag for
+ * @returns {*}
+ */
+ function checkCA(cert)
+ {
+ //region Do not include signer's certificate
+ if((cert.issuer.isEqual(signerCert.issuer) === true) && (cert.serialNumber.isEqual(signerCert.serialNumber) === true))
+ return null;
+ //endregion
+
+ let isCA = false;
+
+ for(const extension of cert.extensions)
+ {
+ if(extension.extnID === "2.5.29.19") // BasicConstraints
+ {
+ if("cA" in extension.parsedValue)
+ {
+ if(extension.parsedValue.cA === true)
+ isCA = true;
+ }
+ }
+ }
+
+ if(isCA)
+ return cert;
+
+ return null;
+ }
+ //endregion
+
+ //region Get a "crypto" extension
+ const crypto = getCrypto();
+ if(typeof crypto === "undefined")
+ return Promise.reject("Unable to create WebCrypto object");
+ //endregion
+
+ //region Find correct value for "responderID"
+ switch(true)
+ {
+ case (this.tbsResponseData.responderID instanceof RelativeDistinguishedNames): // [1] Name
+ sequence = sequence.then(() =>
+ {
+ for(const [index, certificate] of _this.certs.entries())
+ {
+ if(certificate.subject.isEqual(_this.tbsResponseData.responderID))
+ {
+ certIndex = index;
+ break;
+ }
+ }
+ });
+ break;
+ case (this.tbsResponseData.responderID instanceof asn1js.OctetString): // [2] KeyHash
+ sequence = sequence.then(() => Promise.all(Array.from(_this.certs, element =>
+ crypto.digest({ name: "sha-1" }, new Uint8Array(element.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex)))).then(results =>
+ {
+ for(const [index, ] of _this.certs.entries())
+ {
+ if(isEqualBuffer(results[index], _this.tbsResponseData.responderID.valueBlock.valueHex))
+ {
+ certIndex = index;
+ break;
+ }
+ }
+ }));
+ break;
+ default:
+ return Promise.reject("Wrong value for responderID");
+ }
+ //endregion
+
+ //region Make additional verification for signer's certificate
+ sequence = sequence.then(() =>
+ {
+ if(certIndex === (-1))
+ return Promise.reject("Correct certificate was not found in OCSP response");
+
+ signerCert = this.certs[certIndex];
+
+ return Promise.all(Array.from(_this.certs, element => checkCA(element))).then(promiseResults =>
+ {
+ const additionalCerts = [];
+ additionalCerts.push(signerCert);
+
+ for(const promiseResult of promiseResults)
+ {
+ if(promiseResult !== null)
+ additionalCerts.push(promiseResult);
+ }
+
+ const certChain = new CertificateChainValidationEngine({
+ certs: additionalCerts,
+ trustedCerts
+ });
+
+ return certChain.verify().then(verificationResult =>
+ {
+ if(verificationResult.result === true)
+ return Promise.resolve();
+
+ return Promise.reject("Validation of signer's certificate failed");
+ }, error =>
+ Promise.reject(`Validation of signer's certificate failed with error: ${((error instanceof Object) ? error.resultMessage : error)}`)
+ );
+ }, promiseError =>
+ Promise.reject(`Error during checking certificates for CA flag: ${promiseError}`)
+ );
+ });
+ //endregion
+
+ sequence = sequence.then(() => engine.subtle.verifyWithPublicKey(this.tbsResponseData.tbs, this.signature, this.certs[certIndex].subjectPublicKeyInfo, this.signatureAlgorithm));
+
+ return sequence;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/CAVersion.js b/pki.js/CAVersion.js
new file mode 100644
index 0000000..af6cd4b
--- /dev/null
+++ b/pki.js/CAVersion.js
@@ -0,0 +1,181 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, utilConcatBuf } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from https://docs.microsoft.com/en-us/windows/desktop/seccrypto/certification-authority-renewal
+ */
+export default class CAVersion
+{
+ //**********************************************************************************
+ /**
+ * Constructor for CAVersion 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 {number}
+ * @desc certificateIndex
+ */
+ this.certificateIndex = getParametersValue(parameters, "certificateIndex", CAVersion.defaultValues("certificateIndex"));
+
+ /**
+ * @type {number}
+ * @desc keyIndex
+ */
+ this.keyIndex = getParametersValue(parameters, "keyIndex", CAVersion.defaultValues("keyIndex"));
+ //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 "certificateIndex":
+ case "keyIndex":
+ return 0;
+ default:
+ throw new Error(`Invalid member name for CAVersion class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * CAVersion ::= INTEGER
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ return (new asn1js.Integer());
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Check the schema is valid
+ if(schema.constructor.blockName() !== asn1js.Integer.blockName())
+ throw new Error("Object's schema was not verified against input data for CAVersion");
+ //endregion
+
+ //region Check length of the input value and correct it if needed
+ let value = schema.valueBlock.valueHex.slice(0);
+ const valueView = new Uint8Array(value);
+
+ switch(true)
+ {
+ case (value.byteLength < 4):
+ {
+ const tempValue = new ArrayBuffer(4);
+ const tempValueView = new Uint8Array(tempValue);
+
+ tempValueView.set(valueView, 4 - value.byteLength);
+
+ value = tempValue.slice(0);
+ }
+ break;
+ case (value.byteLength > 4):
+ {
+ const tempValue = new ArrayBuffer(4);
+ const tempValueView = new Uint8Array(tempValue);
+
+ tempValueView.set(valueView.slice(0, 4));
+
+ value = tempValue.slice(0);
+ }
+ break;
+ default:
+ }
+ //endregion
+
+ //region Get internal properties from parsed schema
+ const keyIndexBuffer = value.slice(0, 2);
+ const keyIndexView8 = new Uint8Array(keyIndexBuffer);
+ let temp = keyIndexView8[0];
+ keyIndexView8[0] = keyIndexView8[1];
+ keyIndexView8[1] = temp;
+
+ const keyIndexView16 = new Uint16Array(keyIndexBuffer);
+
+ this.keyIndex = keyIndexView16[0];
+
+ const certificateIndexBuffer = value.slice(2);
+ const certificateIndexView8 = new Uint8Array(certificateIndexBuffer);
+ temp = certificateIndexView8[0];
+ certificateIndexView8[0] = certificateIndexView8[1];
+ certificateIndexView8[1] = temp;
+
+ const certificateIndexView16 = new Uint16Array(certificateIndexBuffer);
+
+ this.certificateIndex = certificateIndexView16[0];
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create raw values
+ const certificateIndexBuffer = new ArrayBuffer(2);
+ const certificateIndexView = new Uint16Array(certificateIndexBuffer);
+
+ certificateIndexView[0] = this.certificateIndex;
+
+ const certificateIndexView8 = new Uint8Array(certificateIndexBuffer);
+ let temp = certificateIndexView8[0];
+ certificateIndexView8[0] = certificateIndexView8[1];
+ certificateIndexView8[1] = temp;
+
+ const keyIndexBuffer = new ArrayBuffer(2);
+ const keyIndexView = new Uint16Array(keyIndexBuffer);
+
+ keyIndexView[0] = this.keyIndex;
+
+ const keyIndexView8 = new Uint8Array(keyIndexBuffer);
+ temp = keyIndexView8[0];
+ keyIndexView8[0] = keyIndexView8[1];
+ keyIndexView8[1] = temp;
+ //endregion
+
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Integer({
+ valueHex: utilConcatBuf(keyIndexBuffer, certificateIndexBuffer)
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ certificateIndex: this.certificateIndex,
+ keyIndex: this.keyIndex
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/CRLBag.js b/pki.js/CRLBag.js
new file mode 100644
index 0000000..0ba447d
--- /dev/null
+++ b/pki.js/CRLBag.js
@@ -0,0 +1,209 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import CertificateRevocationList from "./CertificateRevocationList.js";
+//**************************************************************************************
+/**
+ * Class from RFC7292
+ */
+export default class CRLBag
+{
+ //**********************************************************************************
+ /**
+ * Constructor for CRLBag 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 {string}
+ * @desc crlId
+ */
+ this.crlId = getParametersValue(parameters, "crlId", CRLBag.defaultValues("crlId"));
+ /**
+ * @type {*}
+ * @desc crlValue
+ */
+ this.crlValue = getParametersValue(parameters, "crlValue", CRLBag.defaultValues("crlValue"));
+
+ if("parsedValue" in parameters)
+ /**
+ * @type {*}
+ * @desc parsedValue
+ */
+ this.parsedValue = getParametersValue(parameters, "parsedValue", CRLBag.defaultValues("parsedValue"));
+ //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 "crlId":
+ return "";
+ case "crlValue":
+ return (new asn1js.Any());
+ case "parsedValue":
+ return {};
+ default:
+ throw new Error(`Invalid member name for CRLBag 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 "crlId":
+ return (memberValue === "");
+ case "crlValue":
+ return (memberValue instanceof asn1js.Any);
+ case "parsedValue":
+ return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0));
+ default:
+ throw new Error(`Invalid member name for CRLBag class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * CRLBag ::= SEQUENCE {
+ * crlId BAG-TYPE.&id ({CRLTypes}),
+ * crlValue [0] EXPLICIT BAG-TYPE.&Type ({CRLTypes}{@crlId})
+ *}
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [id]
+ * @property {string} [value]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.id || "id") }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [new asn1js.Any({ name: (names.value || "value") })] // EXPLICIT ANY value
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "crlId",
+ "crlValue"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ CRLBag.schema({
+ names: {
+ id: "crlId",
+ value: "crlValue"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for CRLBag");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.crlId = asn1.result.crlId.valueBlock.toString();
+ this.crlValue = asn1.result.crlValue;
+
+ switch(this.crlId)
+ {
+ case "1.2.840.113549.1.9.23.1": // x509CRL
+ {
+ const asn1Inner = asn1js.fromBER(this.certValue.valueBlock.valueHex);
+ this.parsedValue = new CertificateRevocationList({ schema: asn1Inner.result });
+ }
+ break;
+ default:
+ throw new Error(`Incorrect "crlId" value in CRLBag: ${this.crlId}`);
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ if("parsedValue" in this)
+ {
+ this.certId = "1.2.840.113549.1.9.23.1";
+ this.certValue = new asn1js.OctetString({ valueHex: this.parsedValue.toSchema().toBER(false) });
+ }
+
+ return (new asn1js.Sequence({
+ value: [
+ new asn1js.ObjectIdentifier({ value: this.crlId }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [this.crlValue.toSchema()]
+ })
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ crlId: this.crlId,
+ crlValue: this.crlValue.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/CRLDistributionPoints.js b/pki.js/CRLDistributionPoints.js
new file mode 100644
index 0000000..eca23dd
--- /dev/null
+++ b/pki.js/CRLDistributionPoints.js
@@ -0,0 +1,134 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import DistributionPoint from "./DistributionPoint.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class CRLDistributionPoints
+{
+ //**********************************************************************************
+ /**
+ * Constructor for CRLDistributionPoints 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 {Array.}
+ * @desc distributionPoints
+ */
+ this.distributionPoints = getParametersValue(parameters, "distributionPoints", CRLDistributionPoints.defaultValues("distributionPoints"));
+ //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 "distributionPoints":
+ return [];
+ default:
+ throw new Error(`Invalid member name for CRLDistributionPoints class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [distributionPoints]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.distributionPoints || ""),
+ value: DistributionPoint.schema()
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "distributionPoints"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ CRLDistributionPoints.schema({
+ names: {
+ distributionPoints: "distributionPoints"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for CRLDistributionPoints");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.distributionPoints = Array.from(asn1.result.distributionPoints, element => new DistributionPoint({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: Array.from(this.distributionPoints, element => element.toSchema())
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ distributionPoints: Array.from(this.distributionPoints, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/CertBag.js b/pki.js/CertBag.js
new file mode 100644
index 0000000..f8709e9
--- /dev/null
+++ b/pki.js/CertBag.js
@@ -0,0 +1,229 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import Certificate from "./Certificate.js";
+import AttributeCertificateV2 from "./AttributeCertificateV2.js";
+//**************************************************************************************
+/**
+ * Class from RFC7292
+ */
+export default class CertBag
+{
+ //**********************************************************************************
+ /**
+ * Constructor for CertBag 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 {string}
+ * @desc certId
+ */
+ this.certId = getParametersValue(parameters, "certId", CertBag.defaultValues("certId"));
+ /**
+ * @type {*}
+ * @desc certValue
+ */
+ this.certValue = getParametersValue(parameters, "certValue", CertBag.defaultValues("certValue"));
+
+ if("parsedValue" in parameters)
+ /**
+ * @type {*}
+ * @desc parsedValue
+ */
+ this.parsedValue = getParametersValue(parameters, "parsedValue", CertBag.defaultValues("parsedValue"));
+ //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 "";
+ case "certValue":
+ return (new asn1js.Any());
+ case "parsedValue":
+ return {};
+ default:
+ throw new Error(`Invalid member name for CertBag 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":
+ return (memberValue === "");
+ case "certValue":
+ return (memberValue instanceof asn1js.Any);
+ case "parsedValue":
+ return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0));
+ default:
+ throw new Error(`Invalid member name for CertBag class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * CertBag ::= SEQUENCE {
+ * certId BAG-TYPE.&id ({CertTypes}),
+ * certValue [0] EXPLICIT BAG-TYPE.&Type ({CertTypes}{@certId})
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [id]
+ * @property {string} [value]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.id || "id") }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [new asn1js.Any({ name: (names.value || "value") })] // EXPLICIT ANY value
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "certId",
+ "certValue"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ CertBag.schema({
+ names: {
+ id: "certId",
+ value: "certValue"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for CertBag");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.certId = asn1.result.certId.valueBlock.toString();
+ this.certValue = asn1.result.certValue;
+
+ switch(this.certId)
+ {
+ case "1.2.840.113549.1.9.22.1": // x509Certificate
+ {
+ const asn1Inner = asn1js.fromBER(this.certValue.valueBlock.valueHex);
+
+ try
+ {
+ this.parsedValue = new Certificate({ schema: asn1Inner.result });
+ }
+ catch(ex) // In some realizations the same OID used for attribute certificates
+ {
+ this.parsedValue = new AttributeCertificateV2({ schema: asn1Inner.result });
+ }
+ }
+ break;
+ case "1.2.840.113549.1.9.22.3": // attributeCertificate - (!!!) THIS OID IS SUBJECT FOR CHANGE IN FUTURE (!!!)
+ {
+ const asn1Inner = asn1js.fromBER(this.certValue.valueBlock.valueHex);
+ this.parsedValue = new AttributeCertificateV2({ schema: asn1Inner.result });
+ }
+ break;
+ case "1.2.840.113549.1.9.22.2": // sdsiCertificate
+ default:
+ throw new Error(`Incorrect "certId" value in CertBag: ${this.certId}`);
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ if("parsedValue" in this)
+ {
+ if("acinfo" in this.parsedValue) // attributeCertificate
+ this.certId = "1.2.840.113549.1.9.22.3";
+ else // x509Certificate
+ this.certId = "1.2.840.113549.1.9.22.1";
+
+ this.certValue = new asn1js.OctetString({ valueHex: this.parsedValue.toSchema().toBER(false) });
+ }
+
+ return (new asn1js.Sequence({
+ value: [
+ new asn1js.ObjectIdentifier({ value: this.certId }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [(("toSchema" in this.certValue) ? this.certValue.toSchema() : this.certValue)]
+ })
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ certId: this.certId,
+ certValue: this.certValue.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/CertID.js b/pki.js/CertID.js
new file mode 100644
index 0000000..860dc6f
--- /dev/null
+++ b/pki.js/CertID.js
@@ -0,0 +1,306 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, isEqualBuffer, clearProps } from "pvutils";
+import { getCrypto, getOIDByAlgorithm } from "./common.js";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+//**************************************************************************************
+/**
+ * Class from RFC6960
+ */
+export default class CertID
+{
+ //**********************************************************************************
+ /**
+ * Constructor for CertID 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 {AlgorithmIdentifier}
+ * @desc hashAlgorithm
+ */
+ this.hashAlgorithm = getParametersValue(parameters, "hashAlgorithm", CertID.defaultValues("hashAlgorithm"));
+ /**
+ * @type {OctetString}
+ * @desc issuerNameHash
+ */
+ this.issuerNameHash = getParametersValue(parameters, "issuerNameHash", CertID.defaultValues("issuerNameHash"));
+ /**
+ * @type {OctetString}
+ * @desc issuerKeyHash
+ */
+ this.issuerKeyHash = getParametersValue(parameters, "issuerKeyHash", CertID.defaultValues("issuerKeyHash"));
+ /**
+ * @type {Integer}
+ * @desc serialNumber
+ */
+ this.serialNumber = getParametersValue(parameters, "serialNumber", CertID.defaultValues("serialNumber"));
+ //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 "hashAlgorithm":
+ return new AlgorithmIdentifier();
+ case "issuerNameHash":
+ case "issuerKeyHash":
+ return new asn1js.OctetString();
+ case "serialNumber":
+ return new asn1js.Integer();
+ default:
+ throw new Error(`Invalid member name for CertID 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 "hashAlgorithm":
+ return ((memberValue.algorithmId === "") && (("algorithmParams" in memberValue) === false));
+ case "issuerNameHash":
+ case "issuerKeyHash":
+ case "serialNumber":
+ return (memberValue.isEqual(CertID.defaultValues(memberName)));
+ default:
+ throw new Error(`Invalid member name for CertID class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * CertID ::= SEQUENCE {
+ * hashAlgorithm AlgorithmIdentifier,
+ * issuerNameHash OCTET STRING, -- Hash of issuer's DN
+ * issuerKeyHash OCTET STRING, -- Hash of issuer's public key
+ * serialNumber CertificateSerialNumber }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [hashAlgorithm]
+ * @property {string} [hashAlgorithmObject]
+ * @property {string} [issuerNameHash]
+ * @property {string} [issuerKeyHash]
+ * @property {string} [serialNumber]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ AlgorithmIdentifier.schema(names.hashAlgorithmObject || {
+ names: {
+ blockName: (names.hashAlgorithm || "")
+ }
+ }),
+ new asn1js.OctetString({ name: (names.issuerNameHash || "") }),
+ new asn1js.OctetString({ name: (names.issuerKeyHash || "") }),
+ new asn1js.Integer({ name: (names.serialNumber || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "hashAlgorithm",
+ "issuerNameHash",
+ "issuerKeyHash",
+ "serialNumber"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ CertID.schema({
+ names: {
+ hashAlgorithm: "hashAlgorithm",
+ issuerNameHash: "issuerNameHash",
+ issuerKeyHash: "issuerKeyHash",
+ serialNumber: "serialNumber"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for CertID");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.hashAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.hashAlgorithm });
+ this.issuerNameHash = asn1.result.issuerNameHash;
+ this.issuerKeyHash = asn1.result.issuerKeyHash;
+ this.serialNumber = asn1.result.serialNumber;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ this.hashAlgorithm.toSchema(),
+ this.issuerNameHash,
+ this.issuerKeyHash,
+ this.serialNumber
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ hashAlgorithm: this.hashAlgorithm.toJSON(),
+ issuerNameHash: this.issuerNameHash.toJSON(),
+ issuerKeyHash: this.issuerKeyHash.toJSON(),
+ serialNumber: this.serialNumber.toJSON()
+ };
+ }
+ //**********************************************************************************
+ /**
+ * Check that two "CertIDs" are equal
+ * @param {CertID} certificateID Identifier of the certificate to be checked
+ * @returns {boolean}
+ */
+ isEqual(certificateID)
+ {
+ //region Check "hashAlgorithm"
+ if(!this.hashAlgorithm.algorithmId === certificateID.hashAlgorithm.algorithmId)
+ return false;
+ //endregion
+
+ //region Check "issuerNameHash"
+ if(isEqualBuffer(this.issuerNameHash.valueBlock.valueHex, certificateID.issuerNameHash.valueBlock.valueHex) === false)
+ return false;
+ //endregion
+
+ //region Check "issuerKeyHash"
+ if(isEqualBuffer(this.issuerKeyHash.valueBlock.valueHex, certificateID.issuerKeyHash.valueBlock.valueHex) === false)
+ return false;
+ //endregion
+
+ //region Check "serialNumber"
+ if(!this.serialNumber.isEqual(certificateID.serialNumber))
+ return false;
+ //endregion
+
+ return true;
+ }
+ //**********************************************************************************
+ /**
+ * Making OCSP certificate identifier for specific certificate
+ * @param {Certificate} certificate Certificate making OCSP Request for
+ * @param {Object} parameters Additional parameters
+ * @returns {Promise}
+ */
+ createForCertificate(certificate, parameters)
+ {
+ //region Initial variables
+ let sequence = Promise.resolve();
+
+ let issuerCertificate;
+ //endregion
+
+ //region Get a "crypto" extension
+ const crypto = getCrypto();
+ if(typeof crypto === "undefined")
+ return Promise.reject("Unable to create WebCrypto object");
+ //endregion
+
+ //region Check input parameters
+ if(("hashAlgorithm" in parameters) === false)
+ return Promise.reject("Parameter \"hashAlgorithm\" is mandatory for \"OCSP_REQUEST.createForCertificate\"");
+
+ const hashOID = getOIDByAlgorithm({ name: parameters.hashAlgorithm });
+ if(hashOID === "")
+ return Promise.reject(`Incorrect "hashAlgorithm": ${this.hashAlgorithm}`);
+
+ this.hashAlgorithm = new AlgorithmIdentifier({
+ algorithmId: hashOID,
+ algorithmParams: new asn1js.Null()
+ });
+
+ if("issuerCertificate" in parameters)
+ issuerCertificate = parameters.issuerCertificate;
+ else
+ return Promise.reject("Parameter \"issuerCertificate\" is mandatory for \"OCSP_REQUEST.createForCertificate\"");
+ //endregion
+
+ //region Initialize "serialNumber" field
+ this.serialNumber = certificate.serialNumber;
+ //endregion
+
+ //region Create "issuerNameHash"
+ sequence = sequence.then(() =>
+ crypto.digest({ name: parameters.hashAlgorithm }, issuerCertificate.subject.toSchema().toBER(false)),
+ error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ //region Create "issuerKeyHash"
+ sequence = sequence.then(result =>
+ {
+ this.issuerNameHash = new asn1js.OctetString({ valueHex: result });
+
+ const issuerKeyBuffer = issuerCertificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex;
+
+ return crypto.digest({ name: parameters.hashAlgorithm }, issuerKeyBuffer);
+ }, error =>
+ Promise.reject(error)
+ ).then(result =>
+ {
+ this.issuerKeyHash = new asn1js.OctetString({ valueHex: result });
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/Certificate.js b/pki.js/Certificate.js
new file mode 100644
index 0000000..9a5e143
--- /dev/null
+++ b/pki.js/Certificate.js
@@ -0,0 +1,625 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, bufferToHexCodes, clearProps } from "pvutils";
+import { getCrypto, getEngine } from "./common.js";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import RelativeDistinguishedNames from "./RelativeDistinguishedNames.js";
+import Time from "./Time.js";
+import PublicKeyInfo from "./PublicKeyInfo.js";
+import Extension from "./Extension.js";
+import Extensions from "./Extensions.js";
+//**************************************************************************************
+function tbsCertificate(parameters = {})
+{
+ //TBSCertificate ::= SEQUENCE {
+ // version [0] EXPLICIT Version DEFAULT v1,
+ // serialNumber CertificateSerialNumber,
+ // signature AlgorithmIdentifier,
+ // issuer Name,
+ // validity Validity,
+ // subject Name,
+ // subjectPublicKeyInfo SubjectPublicKeyInfo,
+ // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
+ // -- If present, version MUST be v2 or v3
+ // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
+ // -- If present, version MUST be v2 or v3
+ // extensions [3] EXPLICIT Extensions OPTIONAL
+ // -- If present, version MUST be v3
+ //}
+
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [tbsCertificateVersion]
+ * @property {string} [tbsCertificateSerialNumber]
+ * @property {string} [signature]
+ * @property {string} [issuer]
+ * @property {string} [tbsCertificateValidity]
+ * @property {string} [notBefore]
+ * @property {string} [notAfter]
+ * @property {string} [subject]
+ * @property {string} [subjectPublicKeyInfo]
+ * @property {string} [tbsCertificateIssuerUniqueID]
+ * @property {string} [tbsCertificateSubjectUniqueID]
+ * @property {string} [extensions]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || "tbsCertificate"),
+ value: [
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Integer({ name: (names.tbsCertificateVersion || "tbsCertificate.version") }) // EXPLICIT integer value
+ ]
+ }),
+ new asn1js.Integer({ name: (names.tbsCertificateSerialNumber || "tbsCertificate.serialNumber") }),
+ AlgorithmIdentifier.schema(names.signature || {
+ names: {
+ blockName: "tbsCertificate.signature"
+ }
+ }),
+ RelativeDistinguishedNames.schema(names.issuer || {
+ names: {
+ blockName: "tbsCertificate.issuer"
+ }
+ }),
+ new asn1js.Sequence({
+ name: (names.tbsCertificateValidity || "tbsCertificate.validity"),
+ value: [
+ Time.schema(names.notBefore || {
+ names: {
+ utcTimeName: "tbsCertificate.notBefore",
+ generalTimeName: "tbsCertificate.notBefore"
+ }
+ }),
+ Time.schema(names.notAfter || {
+ names: {
+ utcTimeName: "tbsCertificate.notAfter",
+ generalTimeName: "tbsCertificate.notAfter"
+ }
+ })
+ ]
+ }),
+ RelativeDistinguishedNames.schema(names.subject || {
+ names: {
+ blockName: "tbsCertificate.subject"
+ }
+ }),
+ PublicKeyInfo.schema(names.subjectPublicKeyInfo || {
+ names: {
+ blockName: "tbsCertificate.subjectPublicKeyInfo"
+ }
+ }),
+ new asn1js.Primitive({
+ name: (names.tbsCertificateIssuerUniqueID || "tbsCertificate.issuerUniqueID"),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ }
+ }), // IMPLICIT bistring value
+ new asn1js.Primitive({
+ name: (names.tbsCertificateSubjectUniqueID || "tbsCertificate.subjectUniqueID"),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ }
+ }), // IMPLICIT bistring value
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 3 // [3]
+ },
+ value: [Extensions.schema(names.extensions || {
+ names: {
+ blockName: "tbsCertificate.extensions"
+ }
+ })]
+ }) // EXPLICIT SEQUENCE value
+ ]
+ }));
+}
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class Certificate
+{
+ //**********************************************************************************
+ /**
+ * Constructor for Certificate 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 {ArrayBuffer}
+ * @desc ToBeSigned (TBS) part of the certificate
+ */
+ this.tbs = getParametersValue(parameters, "tbs", Certificate.defaultValues("tbs"));
+ /**
+ * @type {number}
+ * @desc Version number
+ */
+ this.version = getParametersValue(parameters, "version", Certificate.defaultValues("version"));
+ /**
+ * @type {Integer}
+ * @desc Serial number of the certificate
+ */
+ this.serialNumber = getParametersValue(parameters, "serialNumber", Certificate.defaultValues("serialNumber"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc This field contains the algorithm identifier for the algorithm used by the CA to sign the certificate
+ */
+ this.signature = getParametersValue(parameters, "signature", Certificate.defaultValues("signature"));
+ /**
+ * @type {RelativeDistinguishedNames}
+ * @desc The issuer field identifies the entity that has signed and issued the certificate
+ */
+ this.issuer = getParametersValue(parameters, "issuer", Certificate.defaultValues("issuer"));
+ /**
+ * @type {Time}
+ * @desc The date on which the certificate validity period begins
+ */
+ this.notBefore = getParametersValue(parameters, "notBefore", Certificate.defaultValues("notBefore"));
+ /**
+ * @type {Time}
+ * @desc The date on which the certificate validity period ends
+ */
+ this.notAfter = getParametersValue(parameters, "notAfter", Certificate.defaultValues("notAfter"));
+ /**
+ * @type {RelativeDistinguishedNames}
+ * @desc The subject field identifies the entity associated with the public key stored in the subject public key field
+ */
+ this.subject = getParametersValue(parameters, "subject", Certificate.defaultValues("subject"));
+ /**
+ * @type {PublicKeyInfo}
+ * @desc This field is used to carry the public key and identify the algorithm with which the key is used
+ */
+ this.subjectPublicKeyInfo = getParametersValue(parameters, "subjectPublicKeyInfo", Certificate.defaultValues("subjectPublicKeyInfo"));
+
+ if("issuerUniqueID" in parameters)
+ /**
+ * @type {ArrayBuffer}
+ * @desc The subject and issuer unique identifiers are present in the certificate to handle the possibility of reuse of subject and/or issuer names over time
+ */
+ this.issuerUniqueID = getParametersValue(parameters, "issuerUniqueID", Certificate.defaultValues("issuerUniqueID"));
+
+ if("subjectUniqueID" in parameters)
+ /**
+ * @type {ArrayBuffer}
+ * @desc The subject and issuer unique identifiers are present in the certificate to handle the possibility of reuse of subject and/or issuer names over time
+ */
+ this.subjectUniqueID = getParametersValue(parameters, "subjectUniqueID", Certificate.defaultValues("subjectUniqueID"));
+
+ if("extensions" in parameters)
+ /**
+ * @type {Array}
+ * @desc If present, this field is a SEQUENCE of one or more certificate extensions
+ */
+ this.extensions = getParametersValue(parameters, "extensions", Certificate.defaultValues("extensions"));
+
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc The signatureAlgorithm field contains the identifier for the cryptographic algorithm used by the CA to sign this certificate
+ */
+ this.signatureAlgorithm = getParametersValue(parameters, "signatureAlgorithm", Certificate.defaultValues("signatureAlgorithm"));
+ /**
+ * @type {BitString}
+ * @desc The signatureValue field contains a digital signature computed upon the ASN.1 DER encoded tbsCertificate
+ */
+ this.signatureValue = getParametersValue(parameters, "signatureValue", Certificate.defaultValues("signatureValue"));
+ //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 "tbs":
+ return new ArrayBuffer(0);
+ case "version":
+ return 0;
+ case "serialNumber":
+ return new asn1js.Integer();
+ case "signature":
+ return new AlgorithmIdentifier();
+ case "issuer":
+ return new RelativeDistinguishedNames();
+ case "notBefore":
+ return new Time();
+ case "notAfter":
+ return new Time();
+ case "subject":
+ return new RelativeDistinguishedNames();
+ case "subjectPublicKeyInfo":
+ return new PublicKeyInfo();
+ case "issuerUniqueID":
+ return new ArrayBuffer(0);
+ case "subjectUniqueID":
+ return new ArrayBuffer(0);
+ case "extensions":
+ return [];
+ case "signatureAlgorithm":
+ return new AlgorithmIdentifier();
+ case "signatureValue":
+ return new asn1js.BitString();
+ default:
+ throw new Error(`Invalid member name for Certificate class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * Certificate ::= SEQUENCE {
+ * tbsCertificate TBSCertificate,
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signatureValue BIT STRING }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [tbsCertificate]
+ * @property {string} [signatureAlgorithm]
+ * @property {string} [signatureValue]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ tbsCertificate(names.tbsCertificate),
+ AlgorithmIdentifier.schema(names.signatureAlgorithm || {
+ names: {
+ blockName: "signatureAlgorithm"
+ }
+ }),
+ new asn1js.BitString({ name: (names.signatureValue || "signatureValue") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "tbsCertificate",
+ "tbsCertificate.extensions",
+ "tbsCertificate.version",
+ "tbsCertificate.serialNumber",
+ "tbsCertificate.signature",
+ "tbsCertificate.issuer",
+ "tbsCertificate.notBefore",
+ "tbsCertificate.notAfter",
+ "tbsCertificate.subject",
+ "tbsCertificate.subjectPublicKeyInfo",
+ "tbsCertificate.issuerUniqueID",
+ "tbsCertificate.subjectUniqueID",
+ "signatureAlgorithm",
+ "signatureValue"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ Certificate.schema({
+ names: {
+ tbsCertificate: {
+ names: {
+ extensions: {
+ names: {
+ extensions: "tbsCertificate.extensions"
+ }
+ }
+ }
+ }
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for Certificate");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.tbs = asn1.result.tbsCertificate.valueBeforeDecode;
+
+ if("tbsCertificate.version" in asn1.result)
+ this.version = asn1.result["tbsCertificate.version"].valueBlock.valueDec;
+ this.serialNumber = asn1.result["tbsCertificate.serialNumber"];
+ this.signature = new AlgorithmIdentifier({ schema: asn1.result["tbsCertificate.signature"] });
+ this.issuer = new RelativeDistinguishedNames({ schema: asn1.result["tbsCertificate.issuer"] });
+ this.notBefore = new Time({ schema: asn1.result["tbsCertificate.notBefore"] });
+ this.notAfter = new Time({ schema: asn1.result["tbsCertificate.notAfter"] });
+ this.subject = new RelativeDistinguishedNames({ schema: asn1.result["tbsCertificate.subject"] });
+ this.subjectPublicKeyInfo = new PublicKeyInfo({ schema: asn1.result["tbsCertificate.subjectPublicKeyInfo"] });
+ if("tbsCertificate.issuerUniqueID" in asn1.result)
+ this.issuerUniqueID = asn1.result["tbsCertificate.issuerUniqueID"].valueBlock.valueHex;
+ if("tbsCertificate.subjectUniqueID" in asn1.result)
+ this.subjectUniqueID = asn1.result["tbsCertificate.subjectUniqueID"].valueBlock.valueHex;
+ if("tbsCertificate.extensions" in asn1.result)
+ this.extensions = Array.from(asn1.result["tbsCertificate.extensions"], element => new Extension({ schema: element }));
+
+ this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm });
+ this.signatureValue = asn1.result.signatureValue;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Create ASN.1 schema for existing values of TBS part for the certificate
+ */
+ encodeTBS()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ if(("version" in this) && (this.version !== Certificate.defaultValues("version")))
+ {
+ outputArray.push(new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Integer({ value: this.version }) // EXPLICIT integer value
+ ]
+ }));
+ }
+
+ outputArray.push(this.serialNumber);
+ outputArray.push(this.signature.toSchema());
+ outputArray.push(this.issuer.toSchema());
+
+ outputArray.push(new asn1js.Sequence({
+ value: [
+ this.notBefore.toSchema(),
+ this.notAfter.toSchema()
+ ]
+ }));
+
+ outputArray.push(this.subject.toSchema());
+ outputArray.push(this.subjectPublicKeyInfo.toSchema());
+
+ if("issuerUniqueID" in this)
+ {
+ outputArray.push(new asn1js.Primitive({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ valueHex: this.issuerUniqueID
+ }));
+ }
+ if("subjectUniqueID" in this)
+ {
+ outputArray.push(new asn1js.Primitive({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ valueHex: this.subjectUniqueID
+ }));
+ }
+
+ if("extensions" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 3 // [3]
+ },
+ value: [new asn1js.Sequence({
+ value: Array.from(this.extensions, element => element.toSchema())
+ })]
+ }));
+ }
+ //endregion
+
+ //region Create and return output sequence
+ return (new asn1js.Sequence({
+ value: outputArray
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema(encodeFlag = false)
+ {
+ let tbsSchema = {};
+
+ //region Decode stored TBS value
+ if(encodeFlag === false)
+ {
+ if(this.tbs.length === 0) // No stored certificate TBS part
+ return Certificate.schema().value[0];
+
+ tbsSchema = asn1js.fromBER(this.tbs).result;
+ }
+ //endregion
+ //region Create TBS schema via assembling from TBS parts
+ else
+ tbsSchema = this.encodeTBS();
+ //endregion
+
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ tbsSchema,
+ this.signatureAlgorithm.toSchema(),
+ this.signatureValue
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const object = {
+ tbs: bufferToHexCodes(this.tbs, 0, this.tbs.byteLength),
+ serialNumber: this.serialNumber.toJSON(),
+ signature: this.signature.toJSON(),
+ issuer: this.issuer.toJSON(),
+ notBefore: this.notBefore.toJSON(),
+ notAfter: this.notAfter.toJSON(),
+ subject: this.subject.toJSON(),
+ subjectPublicKeyInfo: this.subjectPublicKeyInfo.toJSON(),
+ signatureAlgorithm: this.signatureAlgorithm.toJSON(),
+ signatureValue: this.signatureValue.toJSON()
+ };
+
+ if(("version" in this) && (this.version !== Certificate.defaultValues("version")))
+ object.version = this.version;
+
+ if("issuerUniqueID" in this)
+ object.issuerUniqueID = bufferToHexCodes(this.issuerUniqueID, 0, this.issuerUniqueID.byteLength);
+
+ if("subjectUniqueID" in this)
+ object.subjectUniqueID = bufferToHexCodes(this.subjectUniqueID, 0, this.subjectUniqueID.byteLength);
+
+ if("extensions" in this)
+ object.extensions = Array.from(this.extensions, element => element.toJSON());
+
+ return object;
+ }
+ //**********************************************************************************
+ /**
+ * Importing public key for current certificate
+ */
+ getPublicKey(parameters = null)
+ {
+ return getEngine().subtle.getPublicKey(this.subjectPublicKeyInfo, this.signatureAlgorithm, parameters);
+ }
+ //**********************************************************************************
+ /**
+ * Get hash value for subject public key (default SHA-1)
+ * @param {String} [hashAlgorithm=SHA-1] Hashing algorithm name
+ */
+ getKeyHash(hashAlgorithm = "SHA-1")
+ {
+ //region Get a "crypto" extension
+ const crypto = getCrypto();
+ if(typeof crypto === "undefined")
+ return Promise.reject("Unable to create WebCrypto object");
+ //endregion
+
+ return crypto.digest({ name: hashAlgorithm }, new Uint8Array(this.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex));
+ }
+ //**********************************************************************************
+ /**
+ * Make a signature for current value from TBS section
+ * @param {Object} privateKey Private key for "subjectPublicKeyInfo" structure
+ * @param {string} [hashAlgorithm="SHA-1"] Hashing algorithm
+ */
+ sign(privateKey, hashAlgorithm = "SHA-1")
+ {
+ //region Initial checking
+ //region Check private key
+ if(typeof privateKey === "undefined")
+ return Promise.reject("Need to provide a private key for signing");
+ //endregion
+ //endregion
+
+ //region Initial variables
+ let sequence = Promise.resolve();
+ let parameters;
+
+ const engine = getEngine();
+ //endregion
+
+ //region Get a "default parameters" for current algorithm and set correct signature algorithm
+ sequence = sequence.then(() => engine.subtle.getSignatureParameters(privateKey, hashAlgorithm));
+
+ sequence = sequence.then(result =>
+ {
+ parameters = result.parameters;
+ this.signature = result.signatureAlgorithm;
+ this.signatureAlgorithm = result.signatureAlgorithm;
+ });
+ //endregion
+
+ //region Create TBS data for signing
+ sequence = sequence.then(() =>
+ {
+ this.tbs = this.encodeTBS().toBER(false);
+ });
+ //endregion
+
+ //region Signing TBS data on provided private key
+ sequence = sequence.then(() => engine.subtle.signWithPrivateKey(this.tbs, privateKey, parameters));
+
+ sequence = sequence.then(result =>
+ {
+ this.signatureValue = new asn1js.BitString({ valueHex: result });
+ });
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ verify(issuerCertificate = null)
+ {
+ //region Global variables
+ let subjectPublicKeyInfo = {};
+ //endregion
+
+ //region Set correct "subjectPublicKeyInfo" value
+ if(issuerCertificate !== null)
+ subjectPublicKeyInfo = issuerCertificate.subjectPublicKeyInfo;
+ else
+ {
+ if(this.issuer.isEqual(this.subject)) // Self-signed certificate
+ subjectPublicKeyInfo = this.subjectPublicKeyInfo;
+ }
+
+ if((subjectPublicKeyInfo instanceof PublicKeyInfo) === false)
+ return Promise.reject("Please provide issuer certificate as a parameter");
+ //endregion
+
+ return getEngine().subtle.verifyWithPublicKey(this.tbs, this.signatureValue, subjectPublicKeyInfo, this.signatureAlgorithm);
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/CertificateChainValidationEngine.js b/pki.js/CertificateChainValidationEngine.js
new file mode 100644
index 0000000..6b36c83
--- /dev/null
+++ b/pki.js/CertificateChainValidationEngine.js
@@ -0,0 +1,1910 @@
+import { getParametersValue, isEqualBuffer } from "pvutils";
+import { getAlgorithmByOID, stringPrep } from "./common.js";
+//**************************************************************************************
+export default class CertificateChainValidationEngine
+{
+ //**********************************************************************************
+ /**
+ * Constructor for CertificateChainValidationEngine 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 {Array.}
+ * @desc Array of pre-defined trusted (by user) certificates
+ */
+ this.trustedCerts = getParametersValue(parameters, "trustedCerts", this.defaultValues("trustedCerts"));
+ /**
+ * @type {Array.}
+ * @desc Array with certificate chain. Could be only one end-user certificate in there!
+ */
+ this.certs = getParametersValue(parameters, "certs", this.defaultValues("certs"));
+ /**
+ * @type {Array.}
+ * @desc Array of all CRLs for all certificates from certificate chain
+ */
+ this.crls = getParametersValue(parameters, "crls", this.defaultValues("crls"));
+ /**
+ * @type {Array}
+ * @desc Array of all OCSP responses
+ */
+ this.ocsps = getParametersValue(parameters, "ocsps", this.defaultValues("ocsps"));
+ /**
+ * @type {Date}
+ * @desc The date at which the check would be
+ */
+ this.checkDate = getParametersValue(parameters, "checkDate", this.defaultValues("checkDate"));
+ /**
+ * @type {Function}
+ * @desc The date at which the check would be
+ */
+ this.findOrigin = getParametersValue(parameters, "findOrigin", this.defaultValues("findOrigin"));
+ /**
+ * @type {Function}
+ * @desc The date at which the check would be
+ */
+ this.findIssuer = getParametersValue(parameters, "findIssuer", this.defaultValues("findIssuer"));
+ //endregion
+ }
+ //**********************************************************************************
+ static defaultFindOrigin(certificate, validationEngine)
+ {
+ //region Firstly encode TBS for certificate
+ if(certificate.tbs.byteLength === 0)
+ certificate.tbs = certificate.encodeTBS();
+ //endregion
+
+ //region Search in Intermediate Certificates
+ for(const localCert of validationEngine.certs)
+ {
+ //region Firstly encode TBS for certificate
+ if(localCert.tbs.byteLength === 0)
+ localCert.tbs = localCert.encodeTBS();
+ //endregion
+
+ if(isEqualBuffer(certificate.tbs, localCert.tbs))
+ return "Intermediate Certificates";
+ }
+ //endregion
+
+ //region Search in Trusted Certificates
+ for(const trustedCert of validationEngine.trustedCerts)
+ {
+ //region Firstly encode TBS for certificate
+ if(trustedCert.tbs.byteLength === 0)
+ trustedCert.tbs = trustedCert.encodeTBS();
+ //endregion
+
+ if(isEqualBuffer(certificate.tbs, trustedCert.tbs))
+ return "Trusted Certificates";
+ }
+ //endregion
+
+ return "Unknown";
+ }
+ //**********************************************************************************
+ async defaultFindIssuer(certificate, validationEngine)
+ {
+ //region Initial variables
+ let result = [];
+
+ let keyIdentifier = null;
+
+ let authorityCertIssuer = null;
+ let authorityCertSerialNumber = null;
+ //endregion
+
+ //region Speed-up searching in case of self-signed certificates
+ if(certificate.subject.isEqual(certificate.issuer))
+ {
+ try
+ {
+ const verificationResult = await certificate.verify();
+ if(verificationResult === true)
+ return [certificate];
+ }
+ catch(ex)
+ {
+ }
+ }
+ //endregion
+
+ //region Find values to speed-up search
+ if("extensions" in certificate)
+ {
+ for(const extension of certificate.extensions)
+ {
+ if(extension.extnID === "2.5.29.35") // AuthorityKeyIdentifier
+ {
+ if("keyIdentifier" in extension.parsedValue)
+ keyIdentifier = extension.parsedValue.keyIdentifier;
+ else
+ {
+ if("authorityCertIssuer" in extension.parsedValue)
+ authorityCertIssuer = extension.parsedValue.authorityCertIssuer;
+
+ if("authorityCertSerialNumber" in extension.parsedValue)
+ authorityCertSerialNumber = extension.parsedValue.authorityCertSerialNumber;
+ }
+
+ break;
+ }
+ }
+ }
+ //endregion
+
+ //region Aux function
+ function checkCertificate(possibleIssuer)
+ {
+ //region Firstly search for appropriate extensions
+ if(keyIdentifier !== null)
+ {
+ if("extensions" in possibleIssuer)
+ {
+ let extensionFound = false;
+
+ for(const extension of possibleIssuer.extensions)
+ {
+ if(extension.extnID === "2.5.29.14") // SubjectKeyIdentifier
+ {
+ extensionFound = true;
+
+ if(isEqualBuffer(extension.parsedValue.valueBlock.valueHex, keyIdentifier.valueBlock.valueHex))
+ result.push(possibleIssuer);
+
+ break;
+ }
+ }
+
+ if(extensionFound)
+ return;
+ }
+ }
+ //endregion
+
+ //region Now search for authorityCertSerialNumber
+ let authorityCertSerialNumberEqual = false;
+
+ if(authorityCertSerialNumber !== null)
+ authorityCertSerialNumberEqual = possibleIssuer.serialNumber.isEqual(authorityCertSerialNumber);
+ //endregion
+
+ //region And at least search for Issuer data
+ if(authorityCertIssuer !== null)
+ {
+ if(possibleIssuer.subject.isEqual(authorityCertIssuer))
+ {
+ if(authorityCertSerialNumberEqual)
+ result.push(possibleIssuer);
+ }
+ }
+ else
+ {
+ if(certificate.issuer.isEqual(possibleIssuer.subject))
+ result.push(possibleIssuer);
+ }
+ //endregion
+ }
+ //endregion
+
+ //region Search in Trusted Certificates
+ for(const trustedCert of validationEngine.trustedCerts)
+ checkCertificate(trustedCert);
+ //endregion
+
+ //region Search in Intermediate Certificates
+ for(const intermediateCert of validationEngine.certs)
+ checkCertificate(intermediateCert);
+ //endregion
+
+ //region Now perform certificate verification checking
+ for(let i = 0; i < result.length; i++)
+ {
+ try
+ {
+ const verificationResult = await certificate.verify(result[i]);
+ if(verificationResult === false)
+ result.splice(i, 1);
+ }
+ catch(ex)
+ {
+ result.splice(i, 1); // Something wrong, remove the certificate
+ }
+ }
+ //endregion
+
+ return result;
+ }
+ //**********************************************************************************
+ /**
+ * Return default values for all class members
+ * @param {string} memberName String name for a class member
+ */
+ defaultValues(memberName)
+ {
+ switch(memberName)
+ {
+ case "trustedCerts":
+ return [];
+ case "certs":
+ return [];
+ case "crls":
+ return [];
+ case "ocsps":
+ return [];
+ case "checkDate":
+ return new Date();
+ case "findOrigin":
+ return CertificateChainValidationEngine.defaultFindOrigin;
+ case "findIssuer":
+ return this.defaultFindIssuer;
+ default:
+ throw new Error(`Invalid member name for CertificateChainValidationEngine class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ async sort(passedWhenNotRevValues = false)
+ {
+ //region Initial variables
+ const localCerts = [];
+ const _this = this;
+ //endregion
+
+ //region Building certificate path
+ async function buildPath(certificate)
+ {
+ const result = [];
+
+ //region Aux function checking array for unique elements
+ function checkUnique(array)
+ {
+ let unique = true;
+
+ for(let i = 0; i < array.length; i++)
+ {
+ for(let j = 0; j < array.length; j++)
+ {
+ if(j === i)
+ continue;
+
+ if(array[i] === array[j])
+ {
+ unique = false;
+ break;
+ }
+ }
+
+ if(!unique)
+ break;
+ }
+
+ return unique;
+ }
+
+ //endregion
+
+ const findIssuerResult = await _this.findIssuer(certificate, _this);
+ if(findIssuerResult.length === 0)
+ throw new Error("No valid certificate paths found");
+
+ for(let i = 0; i < findIssuerResult.length; i++)
+ {
+ if(isEqualBuffer(findIssuerResult[i].tbs, certificate.tbs))
+ {
+ result.push([findIssuerResult[i]]);
+ continue;
+ }
+
+ const buildPathResult = await buildPath(findIssuerResult[i]);
+
+ for(let j = 0; j < buildPathResult.length; j++)
+ {
+ const copy = buildPathResult[j].slice();
+ copy.splice(0, 0, findIssuerResult[i]);
+
+ if(checkUnique(copy))
+ result.push(copy);
+ else
+ result.push(buildPathResult[j]);
+ }
+ }
+
+ return result;
+ }
+ //endregion
+
+ //region Find CRL for specific certificate
+ async function findCRL(certificate)
+ {
+ //region Initial variables
+ const issuerCertificates = [];
+ const crls = [];
+ const crlsAndCertificates = [];
+ //endregion
+
+ //region Find all possible CRL issuers
+ issuerCertificates.push(...localCerts.filter(element => certificate.issuer.isEqual(element.subject)));
+ if(issuerCertificates.length === 0)
+ {
+ return {
+ status: 1,
+ statusMessage: "No certificate's issuers"
+ };
+ }
+ //endregion
+
+ //region Find all CRLs for certificate's issuer
+ crls.push(..._this.crls.filter(element => element.issuer.isEqual(certificate.issuer)));
+ if(crls.length === 0)
+ {
+ return {
+ status: 2,
+ statusMessage: "No CRLs for specific certificate issuer"
+ };
+ }
+ //endregion
+
+ //region Find specific certificate of issuer for each CRL
+ for(let i = 0; i < crls.length; i++)
+ {
+ //region Check "nextUpdate" for the CRL
+ // The "nextUpdate" is older than "checkDate".
+ // Thus we should do have another, updated CRL.
+ // Thus the CRL assumed to be invalid.
+ if(crls[i].nextUpdate.value < _this.checkDate)
+ continue;
+ //endregion
+
+ for(let j = 0; j < issuerCertificates.length; j++)
+ {
+ try
+ {
+ const result = await crls[i].verify({ issuerCertificate: issuerCertificates[j] });
+ if(result)
+ {
+ crlsAndCertificates.push({
+ crl: crls[i],
+ certificate: issuerCertificates[j]
+ });
+
+ break;
+ }
+ }
+ catch(ex)
+ {
+ }
+ }
+ }
+ //endregion
+
+ if(crlsAndCertificates.length)
+ {
+ return {
+ status: 0,
+ statusMessage: "",
+ result: crlsAndCertificates
+ };
+ }
+
+ return {
+ status: 3,
+ statusMessage: "No valid CRLs found"
+ };
+ }
+ //endregion
+
+ //region Find OCSP for specific certificate
+ async function findOCSP(certificate, issuerCertificate)
+ {
+ //region Get hash algorithm from certificate
+ const hashAlgorithm = getAlgorithmByOID(certificate.signatureAlgorithm.algorithmId);
+ if(("name" in hashAlgorithm) === false)
+ return 1;
+ if(("hash" in hashAlgorithm) === false)
+ return 1;
+ //endregion
+
+ //region Search for OCSP response for the certificate
+ for(let i = 0; i < _this.ocsps.length; i++)
+ {
+ const result = await _this.ocsps[i].getCertificateStatus(certificate, issuerCertificate);
+ if(result.isForCertificate)
+ {
+ if(result.status === 0)
+ return 0;
+
+ return 1;
+ }
+ }
+ //endregion
+
+ return 2;
+ }
+ //endregion
+
+ //region Check for certificate to be CA
+ async function checkForCA(certificate, needToCheckCRL = false)
+ {
+ //region Initial variables
+ let isCA = false;
+ let mustBeCA = false;
+ let keyUsagePresent = false;
+ let cRLSign = false;
+ //endregion
+
+ if("extensions" in certificate)
+ {
+ for(let j = 0; j < certificate.extensions.length; j++)
+ {
+ if((certificate.extensions[j].critical === true) &&
+ (("parsedValue" in certificate.extensions[j]) === false))
+ {
+ return {
+ result: false,
+ resultCode: 6,
+ resultMessage: `Unable to parse critical certificate extension: ${certificate.extensions[j].extnID}`
+ };
+ }
+
+ if(certificate.extensions[j].extnID === "2.5.29.15") // KeyUsage
+ {
+ keyUsagePresent = true;
+
+ const view = new Uint8Array(certificate.extensions[j].parsedValue.valueBlock.valueHex);
+
+ if((view[0] & 0x04) === 0x04) // Set flag "keyCertSign"
+ mustBeCA = true;
+
+ if((view[0] & 0x02) === 0x02) // Set flag "cRLSign"
+ cRLSign = true;
+ }
+
+ if(certificate.extensions[j].extnID === "2.5.29.19") // BasicConstraints
+ {
+ if("cA" in certificate.extensions[j].parsedValue)
+ {
+ if(certificate.extensions[j].parsedValue.cA === true)
+ isCA = true;
+ }
+ }
+ }
+
+ if((mustBeCA === true) && (isCA === false))
+ {
+ return {
+ result: false,
+ resultCode: 3,
+ resultMessage: "Unable to build certificate chain - using \"keyCertSign\" flag set without BasicConstaints"
+ };
+ }
+
+ if((keyUsagePresent === true) && (isCA === true) && (mustBeCA === false))
+ {
+ return {
+ result: false,
+ resultCode: 4,
+ resultMessage: "Unable to build certificate chain - \"keyCertSign\" flag was not set"
+ };
+ }
+
+ // noinspection OverlyComplexBooleanExpressionJS
+ if((isCA === true) && (keyUsagePresent === true) && ((needToCheckCRL) && (cRLSign === false)))
+ {
+ return {
+ result: false,
+ resultCode: 5,
+ resultMessage: "Unable to build certificate chain - intermediate certificate must have \"cRLSign\" key usage flag"
+ };
+ }
+ }
+
+ if(isCA === false)
+ {
+ return {
+ result: false,
+ resultCode: 7,
+ resultMessage: "Unable to build certificate chain - more than one possible end-user certificate"
+ };
+ }
+
+ return {
+ result: true,
+ resultCode: 0,
+ resultMessage: ""
+ };
+ }
+ //endregion
+
+ //region Basic check for certificate path
+ async function basicCheck(path, checkDate)
+ {
+ //region Check that all dates are valid
+ for(let i = 0; i < path.length; i++)
+ {
+ if((path[i].notBefore.value > checkDate) ||
+ (path[i].notAfter.value < checkDate))
+ {
+ return {
+ result: false,
+ resultCode: 8,
+ resultMessage: "The certificate is either not yet valid or expired"
+ };
+ }
+ }
+ //endregion
+
+ //region Check certificate name chain
+
+ // We should have at least two certificates: end entity and trusted root
+ if(path.length < 2)
+ {
+ return {
+ result: false,
+ resultCode: 9,
+ resultMessage: "Too short certificate path"
+ };
+ }
+
+ for(let i = (path.length - 2); i >= 0; i--)
+ {
+ //region Check that we do not have a "self-signed" certificate
+ if(path[i].issuer.isEqual(path[i].subject) === false)
+ {
+ if(path[i].issuer.isEqual(path[i + 1].subject) === false)
+ {
+ return {
+ result: false,
+ resultCode: 10,
+ resultMessage: "Incorrect name chaining"
+ };
+ }
+ }
+ //endregion
+ }
+ //endregion
+
+ //region Check each certificate (except "trusted root") to be non-revoked
+ if((_this.crls.length !== 0) || (_this.ocsps.length !== 0)) // If CRLs and OCSPs are empty then we consider all certificates to be valid
+ {
+ for(let i = 0; i < (path.length - 1); i++)
+ {
+ //region Initial variables
+ let ocspResult = 2;
+ let crlResult = {
+ status: 0,
+ statusMessage: ""
+ };
+ //endregion
+
+ //region Check OCSPs first
+ if(_this.ocsps.length !== 0)
+ {
+ ocspResult = await findOCSP(path[i], path[i + 1]);
+
+ switch(ocspResult)
+ {
+ case 0:
+ continue;
+ case 1:
+ return {
+ result: false,
+ resultCode: 12,
+ resultMessage: "One of certificates was revoked via OCSP response"
+ };
+ case 2: // continue to check the certificate with CRL
+ break;
+ default:
+ }
+ }
+ //endregion
+
+ //region Check CRLs
+ if(_this.crls.length !== 0)
+ {
+ crlResult = await findCRL(path[i]);
+
+ if(crlResult.status === 0)
+ {
+ for(let j = 0; j < crlResult.result.length; j++)
+ {
+ //region Check that the CRL issuer certificate have not been revoked
+ const isCertificateRevoked = crlResult.result[j].crl.isCertificateRevoked(path[i]);
+ if(isCertificateRevoked)
+ {
+ return {
+ result: false,
+ resultCode: 12,
+ resultMessage: "One of certificates had been revoked"
+ };
+ }
+ //endregion
+
+ //region Check that the CRL issuer certificate is a CA certificate
+ const isCertificateCA = await checkForCA(crlResult.result[j].certificate, true);
+ if(isCertificateCA.result === false)
+ {
+ return {
+ result: false,
+ resultCode: 13,
+ resultMessage: "CRL issuer certificate is not a CA certificate or does not have crlSign flag"
+ };
+ }
+ //endregion
+ }
+ }
+ else
+ {
+ if(passedWhenNotRevValues === false)
+ {
+ throw {
+ result: false,
+ resultCode: 11,
+ resultMessage: `No revocation values found for one of certificates: ${crlResult.statusMessage}`
+ };
+ }
+ }
+ }
+ else
+ {
+ if(ocspResult === 2)
+ {
+ return {
+ result: false,
+ resultCode: 11,
+ resultMessage: "No revocation values found for one of certificates"
+ };
+ }
+ }
+ //endregion
+
+ //region Check we do have links to revocation values inside issuer's certificate
+ if((ocspResult === 2) && (crlResult.status === 2) && passedWhenNotRevValues)
+ {
+ const issuerCertificate = path[i + 1];
+ let extensionFound = false;
+
+ if("extensions" in issuerCertificate)
+ {
+ for(const extension of issuerCertificate.extensions)
+ {
+ switch(extension.extnID)
+ {
+ case "2.5.29.31": // CRLDistributionPoints
+ case "2.5.29.46": // FreshestCRL
+ case "1.3.6.1.5.5.7.1.1": // AuthorityInfoAccess
+ extensionFound = true;
+ break;
+ default:
+ }
+ }
+ }
+
+ if(extensionFound)
+ {
+ throw {
+ result: false,
+ resultCode: 11,
+ resultMessage: `No revocation values found for one of certificates: ${crlResult.statusMessage}`
+ };
+ }
+ }
+ //endregion
+ }
+ }
+ //endregion
+
+ //region Check each certificate (except "end entity") in the path to be a CA certificate
+ for(let i = 1; i < path.length; i++)
+ {
+ const result = await checkForCA(path[i]);
+ if(result.result === false)
+ {
+ return {
+ result: false,
+ resultCode: 14,
+ resultMessage: "One of intermediate certificates is not a CA certificate"
+ };
+ }
+ }
+ //endregion
+
+ return {
+ result: true
+ };
+ }
+ //endregion
+
+ //region Do main work
+ //region Initialize "localCerts" by value of "_this.certs" + "_this.trustedCerts" arrays
+ localCerts.push(..._this.trustedCerts);
+ localCerts.push(..._this.certs);
+ //endregion
+
+ //region Check all certificates for been unique
+ for(let i = 0; i < localCerts.length; i++)
+ {
+ for(let j = 0; j < localCerts.length; j++)
+ {
+ if(i === j)
+ continue;
+
+ if(isEqualBuffer(localCerts[i].tbs, localCerts[j].tbs))
+ {
+ localCerts.splice(j, 1);
+ i = 0;
+ break;
+ }
+ }
+ }
+ //endregion
+
+ //region Initial variables
+ let result;
+ const certificatePath = [localCerts[localCerts.length - 1]]; // The "end entity" certificate must be the least in "certs" array
+ //endregion
+
+ //region Build path for "end entity" certificate
+ result = await buildPath(localCerts[localCerts.length - 1]);
+ if(result.length === 0)
+ {
+ return {
+ result: false,
+ resultCode: 60,
+ resultMessage: "Unable to find certificate path"
+ };
+ }
+ //endregion
+
+ //region Exclude certificate paths not ended with "trusted roots"
+ for(let i = 0; i < result.length; i++)
+ {
+ let found = false;
+
+ for(let j = 0; j < (result[i]).length; j++)
+ {
+ const certificate = (result[i])[j];
+
+ for(let k = 0; k < _this.trustedCerts.length; k++)
+ {
+ if(isEqualBuffer(certificate.tbs, _this.trustedCerts[k].tbs))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if(found)
+ break;
+ }
+
+ if(!found)
+ {
+ result.splice(i, 1);
+ i = 0;
+ }
+ }
+
+ if(result.length === 0)
+ {
+ throw {
+ result: false,
+ resultCode: 97,
+ resultMessage: "No valid certificate paths found"
+ };
+ }
+ //endregion
+
+ //region Find shortest certificate path (for the moment it is the only criteria)
+ let shortestLength = result[0].length;
+ let shortestIndex = 0;
+
+ for(let i = 0; i < result.length; i++)
+ {
+ if(result[i].length < shortestLength)
+ {
+ shortestLength = result[i].length;
+ shortestIndex = i;
+ }
+ }
+ //endregion
+
+ //region Create certificate path for basic check
+ for(let i = 0; i < result[shortestIndex].length; i++)
+ certificatePath.push((result[shortestIndex])[i]);
+ //endregion
+
+ //region Perform basic checking for all certificates in the path
+ result = await basicCheck(certificatePath, _this.checkDate);
+ if(result.result === false)
+ throw result;
+ //endregion
+
+ return certificatePath;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Major verification function for certificate chain.
+ * @param {{initialPolicySet, initialExplicitPolicy, initialPolicyMappingInhibit, initialInhibitPolicy, initialPermittedSubtreesSet, initialExcludedSubtreesSet, initialRequiredNameForms}} [parameters]
+ * @returns {Promise}
+ */
+ async verify(parameters = {})
+ {
+ //region Auxiliary functions for name constraints checking
+ function compareDNSName(name, constraint)
+ {
+ /// Compare two dNSName values
+ /// DNS from name
+ /// Constraint for DNS from name
+ /// Boolean result - valid or invalid the "name" against the "constraint"
+
+ //region Make a "string preparation" for both name and constrain
+ const namePrepared = stringPrep(name);
+ const constraintPrepared = stringPrep(constraint);
+ //endregion
+
+ //region Make a "splitted" versions of "constraint" and "name"
+ const nameSplitted = namePrepared.split(".");
+ const constraintSplitted = constraintPrepared.split(".");
+ //endregion
+
+ //region Length calculation and additional check
+ const nameLen = nameSplitted.length;
+ const constrLen = constraintSplitted.length;
+
+ if((nameLen === 0) || (constrLen === 0) || (nameLen < constrLen))
+ return false;
+ //endregion
+
+ //region Check that no part of "name" has zero length
+ for(let i = 0; i < nameLen; i++)
+ {
+ if(nameSplitted[i].length === 0)
+ return false;
+ }
+ //endregion
+
+ //region Check that no part of "constraint" has zero length
+ for(let i = 0; i < constrLen; i++)
+ {
+ if(constraintSplitted[i].length === 0)
+ {
+ if(i === 0)
+ {
+ if(constrLen === 1)
+ return false;
+
+ continue;
+ }
+
+ return false;
+ }
+ }
+ //endregion
+
+ //region Check that "name" has a tail as "constraint"
+
+ for(let i = 0; i < constrLen; i++)
+ {
+ if(constraintSplitted[constrLen - 1 - i].length === 0)
+ continue;
+
+ if(nameSplitted[nameLen - 1 - i].localeCompare(constraintSplitted[constrLen - 1 - i]) !== 0)
+ return false;
+ }
+ //endregion
+
+ return true;
+ }
+
+ function compareRFC822Name(name, constraint)
+ {
+ /// Compare two rfc822Name values
+ /// E-mail address from name
+ /// Constraint for e-mail address from name
+ /// Boolean result - valid or invalid the "name" against the "constraint"
+
+ //region Make a "string preparation" for both name and constrain
+ const namePrepared = stringPrep(name);
+ const constraintPrepared = stringPrep(constraint);
+ //endregion
+
+ //region Make a "splitted" versions of "constraint" and "name"
+ const nameSplitted = namePrepared.split("@");
+ const constraintSplitted = constraintPrepared.split("@");
+ //endregion
+
+ //region Splitted array length checking
+ if((nameSplitted.length === 0) || (constraintSplitted.length === 0) || (nameSplitted.length < constraintSplitted.length))
+ return false;
+ //endregion
+
+ if(constraintSplitted.length === 1)
+ {
+ const result = compareDNSName(nameSplitted[1], constraintSplitted[0]);
+
+ if(result)
+ {
+ //region Make a "splitted" versions of domain name from "constraint" and "name"
+ const ns = nameSplitted[1].split(".");
+ const cs = constraintSplitted[0].split(".");
+ //endregion
+
+ if(cs[0].length === 0)
+ return true;
+
+ return ns.length === cs.length;
+ }
+
+ return false;
+ }
+
+ return (namePrepared.localeCompare(constraintPrepared) === 0);
+ }
+
+ function compareUniformResourceIdentifier(name, constraint)
+ {
+ /// Compare two uniformResourceIdentifier values
+ /// uniformResourceIdentifier from name
+ /// Constraint for uniformResourceIdentifier from name
+ /// Boolean result - valid or invalid the "name" against the "constraint"
+
+ //region Make a "string preparation" for both name and constrain
+ let namePrepared = stringPrep(name);
+ const constraintPrepared = stringPrep(constraint);
+ //endregion
+
+ //region Find out a major URI part to compare with
+ const ns = namePrepared.split("/");
+ const cs = constraintPrepared.split("/");
+
+ if(cs.length > 1) // Malformed constraint
+ return false;
+
+ if(ns.length > 1) // Full URI string
+ {
+ for(let i = 0; i < ns.length; i++)
+ {
+ if((ns[i].length > 0) && (ns[i].charAt(ns[i].length - 1) !== ":"))
+ {
+ const nsPort = ns[i].split(":");
+ namePrepared = nsPort[0];
+ break;
+ }
+ }
+ }
+ //endregion
+
+ const result = compareDNSName(namePrepared, constraintPrepared);
+
+ if(result)
+ {
+ //region Make a "splitted" versions of "constraint" and "name"
+ const nameSplitted = namePrepared.split(".");
+ const constraintSplitted = constraintPrepared.split(".");
+ //endregion
+
+ if(constraintSplitted[0].length === 0)
+ return true;
+
+ return nameSplitted.length === constraintSplitted.length;
+ }
+
+ return false;
+ }
+
+ function compareIPAddress(name, constraint)
+ {
+ /// Compare two iPAddress values
+ /// iPAddress from name
+ /// Constraint for iPAddress from name
+ /// Boolean result - valid or invalid the "name" against the "constraint"
+
+ //region Common variables
+ const nameView = new Uint8Array(name.valueBlock.valueHex);
+ const constraintView = new Uint8Array(constraint.valueBlock.valueHex);
+ //endregion
+
+ //region Work with IPv4 addresses
+ if((nameView.length === 4) && (constraintView.length === 8))
+ {
+ for(let i = 0; i < 4; i++)
+ {
+ if((nameView[i] ^ constraintView[i]) & constraintView[i + 4])
+ return false;
+ }
+
+ return true;
+ }
+ //endregion
+
+ //region Work with IPv6 addresses
+ if((nameView.length === 16) && (constraintView.length === 32))
+ {
+ for(let i = 0; i < 16; i++)
+ {
+ if((nameView[i] ^ constraintView[i]) & constraintView[i + 16])
+ return false;
+ }
+
+ return true;
+ }
+ //endregion
+
+ return false;
+ }
+
+ function compareDirectoryName(name, constraint)
+ {
+ /// Compare two directoryName values
+ /// directoryName from name
+ /// Constraint for directoryName from name
+ /// Boolean flag - should be comparision interrupted after first match or we need to match all "constraints" parts
+ /// Boolean result - valid or invalid the "name" against the "constraint"
+
+ //region Initial check
+ if((name.typesAndValues.length === 0) || (constraint.typesAndValues.length === 0))
+ return true;
+
+ if(name.typesAndValues.length < constraint.typesAndValues.length)
+ return false;
+ //endregion
+
+ //region Initial variables
+ let result = true;
+ let nameStart = 0;
+ //endregion
+
+ for(let i = 0; i < constraint.typesAndValues.length; i++)
+ {
+ let localResult = false;
+
+ for(let j = nameStart; j < name.typesAndValues.length; j++)
+ {
+ localResult = name.typesAndValues[j].isEqual(constraint.typesAndValues[i]);
+
+ if(name.typesAndValues[j].type === constraint.typesAndValues[i].type)
+ result = result && localResult;
+
+ if(localResult === true)
+ {
+ if((nameStart === 0) || (nameStart === j))
+ {
+ nameStart = j + 1;
+ break;
+ }
+ else // Structure of "name" must be the same with "constraint"
+ return false;
+ }
+ }
+
+ if(localResult === false)
+ return false;
+ }
+
+ return (nameStart === 0) ? false : result;
+ }
+ //endregion
+
+ try
+ {
+ //region Initial checks
+ if(this.certs.length === 0)
+ throw "Empty certificate array";
+ //endregion
+
+ //region Get input variables
+ let passedWhenNotRevValues = false;
+
+ if("passedWhenNotRevValues" in parameters)
+ passedWhenNotRevValues = parameters.passedWhenNotRevValues;
+
+ let initialPolicySet = [];
+ initialPolicySet.push("2.5.29.32.0"); // "anyPolicy"
+
+ let initialExplicitPolicy = false;
+ let initialPolicyMappingInhibit = false;
+ let initialInhibitPolicy = false;
+
+ let initialPermittedSubtreesSet = []; // Array of "simpl.x509.GeneralSubtree"
+ let initialExcludedSubtreesSet = []; // Array of "simpl.x509.GeneralSubtree"
+ let initialRequiredNameForms = []; // Array of "simpl.x509.GeneralSubtree"
+
+ if("initialPolicySet" in parameters)
+ initialPolicySet = parameters.initialPolicySet;
+
+ if("initialExplicitPolicy" in parameters)
+ initialExplicitPolicy = parameters.initialExplicitPolicy;
+
+ if("initialPolicyMappingInhibit" in parameters)
+ initialPolicyMappingInhibit = parameters.initialPolicyMappingInhibit;
+
+ if("initialInhibitPolicy" in parameters)
+ initialInhibitPolicy = parameters.initialInhibitPolicy;
+
+ if("initialPermittedSubtreesSet" in parameters)
+ initialPermittedSubtreesSet = parameters.initialPermittedSubtreesSet;
+
+ if("initialExcludedSubtreesSet" in parameters)
+ initialExcludedSubtreesSet = parameters.initialExcludedSubtreesSet;
+
+ if("initialRequiredNameForms" in parameters)
+ initialRequiredNameForms = parameters.initialRequiredNameForms;
+
+ let explicitPolicyIndicator = initialExplicitPolicy;
+ let policyMappingInhibitIndicator = initialPolicyMappingInhibit;
+ let inhibitAnyPolicyIndicator = initialInhibitPolicy;
+
+ const pendingConstraints = new Array(3);
+ pendingConstraints[0] = false; // For "explicitPolicyPending"
+ pendingConstraints[1] = false; // For "policyMappingInhibitPending"
+ pendingConstraints[2] = false; // For "inhibitAnyPolicyPending"
+
+ let explicitPolicyPending = 0;
+ let policyMappingInhibitPending = 0;
+ let inhibitAnyPolicyPending = 0;
+
+ let permittedSubtrees = initialPermittedSubtreesSet;
+ let excludedSubtrees = initialExcludedSubtreesSet;
+ const requiredNameForms = initialRequiredNameForms;
+
+ let pathDepth = 1;
+ //endregion
+
+ //region Sorting certificates in the chain array
+ this.certs = await this.sort(passedWhenNotRevValues);
+ //endregion
+
+ //region Work with policies
+ //region Support variables
+ const allPolicies = []; // Array of all policies (string values)
+ allPolicies.push("2.5.29.32.0"); // Put "anyPolicy" at first place
+
+ const policiesAndCerts = []; // In fact "array of array" where rows are for each specific policy, column for each certificate and value is "true/false"
+
+ const anyPolicyArray = new Array(this.certs.length - 1); // Minus "trusted anchor"
+ for(let ii = 0; ii < (this.certs.length - 1); ii++)
+ anyPolicyArray[ii] = true;
+
+ policiesAndCerts.push(anyPolicyArray);
+
+ const policyMappings = new Array(this.certs.length - 1); // Array of "PolicyMappings" for each certificate
+ const certPolicies = new Array(this.certs.length - 1); // Array of "CertificatePolicies" for each certificate
+
+ let explicitPolicyStart = (explicitPolicyIndicator) ? (this.certs.length - 1) : (-1);
+ //endregion
+
+ //region Gather all neccessary information from certificate chain
+ for(let i = (this.certs.length - 2); i >= 0; i--, pathDepth++)
+ {
+ if("extensions" in this.certs[i])
+ {
+ //region Get information about certificate extensions
+ for(let j = 0; j < this.certs[i].extensions.length; j++)
+ {
+ //region CertificatePolicies
+ if(this.certs[i].extensions[j].extnID === "2.5.29.32")
+ {
+ certPolicies[i] = this.certs[i].extensions[j].parsedValue;
+
+ //region Remove entry from "anyPolicies" for the certificate
+ for(let s = 0; s < allPolicies.length; s++)
+ {
+ if(allPolicies[s] === "2.5.29.32.0")
+ {
+ delete (policiesAndCerts[s])[i];
+ break;
+ }
+ }
+ //endregion
+
+ for(let k = 0; k < this.certs[i].extensions[j].parsedValue.certificatePolicies.length; k++)
+ {
+ let policyIndex = (-1);
+
+ //region Try to find extension in "allPolicies" array
+ for(let s = 0; s < allPolicies.length; s++)
+ {
+ if(this.certs[i].extensions[j].parsedValue.certificatePolicies[k].policyIdentifier === allPolicies[s])
+ {
+ policyIndex = s;
+ break;
+ }
+ }
+ //endregion
+
+ if(policyIndex === (-1))
+ {
+ allPolicies.push(this.certs[i].extensions[j].parsedValue.certificatePolicies[k].policyIdentifier);
+
+ const certArray = new Array(this.certs.length - 1);
+ certArray[i] = true;
+
+ policiesAndCerts.push(certArray);
+ }
+ else
+ (policiesAndCerts[policyIndex])[i] = true;
+ }
+ }
+ //endregion
+
+ //region PolicyMappings
+ if(this.certs[i].extensions[j].extnID === "2.5.29.33")
+ {
+ if(policyMappingInhibitIndicator)
+ {
+ return {
+ result: false,
+ resultCode: 98,
+ resultMessage: "Policy mapping prohibited"
+ };
+ }
+
+ policyMappings[i] = this.certs[i].extensions[j].parsedValue;
+ }
+ //endregion
+
+ //region PolicyConstraints
+ if(this.certs[i].extensions[j].extnID === "2.5.29.36")
+ {
+ if(explicitPolicyIndicator === false)
+ {
+ //region requireExplicitPolicy
+ if(this.certs[i].extensions[j].parsedValue.requireExplicitPolicy === 0)
+ {
+ explicitPolicyIndicator = true;
+ explicitPolicyStart = i;
+ }
+ else
+ {
+ if(pendingConstraints[0] === false)
+ {
+ pendingConstraints[0] = true;
+ explicitPolicyPending = this.certs[i].extensions[j].parsedValue.requireExplicitPolicy;
+ }
+ else
+ explicitPolicyPending = (explicitPolicyPending > this.certs[i].extensions[j].parsedValue.requireExplicitPolicy) ? this.certs[i].extensions[j].parsedValue.requireExplicitPolicy : explicitPolicyPending;
+ }
+ //endregion
+
+ //region inhibitPolicyMapping
+ if(this.certs[i].extensions[j].parsedValue.inhibitPolicyMapping === 0)
+ policyMappingInhibitIndicator = true;
+ else
+ {
+ if(pendingConstraints[1] === false)
+ {
+ pendingConstraints[1] = true;
+ policyMappingInhibitPending = this.certs[i].extensions[j].parsedValue.inhibitPolicyMapping + 1;
+ }
+ else
+ policyMappingInhibitPending = (policyMappingInhibitPending > (this.certs[i].extensions[j].parsedValue.inhibitPolicyMapping + 1)) ? (this.certs[i].extensions[j].parsedValue.inhibitPolicyMapping + 1) : policyMappingInhibitPending;
+ }
+ //endregion
+ }
+ }
+ //endregion
+
+ //region InhibitAnyPolicy
+ if(this.certs[i].extensions[j].extnID === "2.5.29.54")
+ {
+ if(inhibitAnyPolicyIndicator === false)
+ {
+ if(this.certs[i].extensions[j].parsedValue.valueBlock.valueDec === 0)
+ inhibitAnyPolicyIndicator = true;
+ else
+ {
+ if(pendingConstraints[2] === false)
+ {
+ pendingConstraints[2] = true;
+ inhibitAnyPolicyPending = this.certs[i].extensions[j].parsedValue.valueBlock.valueDec;
+ }
+ else
+ inhibitAnyPolicyPending = (inhibitAnyPolicyPending > this.certs[i].extensions[j].parsedValue.valueBlock.valueDec) ? this.certs[i].extensions[j].parsedValue.valueBlock.valueDec : inhibitAnyPolicyPending;
+ }
+ }
+ }
+ //endregion
+ }
+ //endregion
+
+ //region Check "inhibitAnyPolicyIndicator"
+ if(inhibitAnyPolicyIndicator === true)
+ {
+ let policyIndex = (-1);
+
+ //region Find "anyPolicy" index
+ for(let searchAnyPolicy = 0; searchAnyPolicy < allPolicies.length; searchAnyPolicy++)
+ {
+ if(allPolicies[searchAnyPolicy] === "2.5.29.32.0")
+ {
+ policyIndex = searchAnyPolicy;
+ break;
+ }
+ }
+ //endregion
+
+ if(policyIndex !== (-1))
+ delete (policiesAndCerts[0])[i]; // Unset value to "undefined" for "anyPolicies" value for current certificate
+ }
+ //endregion
+
+ //region Process with "pending constraints"
+ if(explicitPolicyIndicator === false)
+ {
+ if(pendingConstraints[0] === true)
+ {
+ explicitPolicyPending--;
+ if(explicitPolicyPending === 0)
+ {
+ explicitPolicyIndicator = true;
+ explicitPolicyStart = i;
+
+ pendingConstraints[0] = false;
+ }
+ }
+ }
+
+ if(policyMappingInhibitIndicator === false)
+ {
+ if(pendingConstraints[1] === true)
+ {
+ policyMappingInhibitPending--;
+ if(policyMappingInhibitPending === 0)
+ {
+ policyMappingInhibitIndicator = true;
+ pendingConstraints[1] = false;
+ }
+ }
+ }
+
+ if(inhibitAnyPolicyIndicator === false)
+ {
+ if(pendingConstraints[2] === true)
+ {
+ inhibitAnyPolicyPending--;
+ if(inhibitAnyPolicyPending === 0)
+ {
+ inhibitAnyPolicyIndicator = true;
+ pendingConstraints[2] = false;
+ }
+ }
+ }
+ //endregion
+ }
+ }
+ //endregion
+
+ //region Working with policy mappings
+ for(let i = 0; i < (this.certs.length - 1); i++)
+ {
+ //region Check that there is "policy mapping" for level "i + 1"
+ if((i < (this.certs.length - 2)) && (typeof policyMappings[i + 1] !== "undefined"))
+ {
+ for(let k = 0; k < policyMappings[i + 1].mappings.length; k++)
+ {
+ //region Check that we do not have "anyPolicy" in current mapping
+ if((policyMappings[i + 1].mappings[k].issuerDomainPolicy === "2.5.29.32.0") || (policyMappings[i + 1].mappings[k].subjectDomainPolicy === "2.5.29.32.0"))
+ {
+ return {
+ result: false,
+ resultCode: 99,
+ resultMessage: "The \"anyPolicy\" should not be a part of policy mapping scheme"
+ };
+ }
+ //endregion
+
+ //region Initial variables
+ let issuerDomainPolicyIndex = (-1);
+ let subjectDomainPolicyIndex = (-1);
+ //endregion
+
+ //region Search for index of policies indedes
+ for(let n = 0; n < allPolicies.length; n++)
+ {
+ if(allPolicies[n] === policyMappings[i + 1].mappings[k].issuerDomainPolicy)
+ issuerDomainPolicyIndex = n;
+
+ if(allPolicies[n] === policyMappings[i + 1].mappings[k].subjectDomainPolicy)
+ subjectDomainPolicyIndex = n;
+ }
+ //endregion
+
+ //region Delete existing "issuerDomainPolicy" because on the level we mapped the policy to another one
+ if(typeof (policiesAndCerts[issuerDomainPolicyIndex])[i] !== "undefined")
+ delete (policiesAndCerts[issuerDomainPolicyIndex])[i];
+ //endregion
+
+ //region Check all policies for the certificate
+ for(let j = 0; j < certPolicies[i].certificatePolicies.length; j++)
+ {
+ if(policyMappings[i + 1].mappings[k].subjectDomainPolicy === certPolicies[i].certificatePolicies[j].policyIdentifier)
+ {
+ //region Set mapped policy for current certificate
+ if((issuerDomainPolicyIndex !== (-1)) && (subjectDomainPolicyIndex !== (-1)))
+ {
+ for(let m = 0; m <= i; m++)
+ {
+ if(typeof (policiesAndCerts[subjectDomainPolicyIndex])[m] !== "undefined")
+ {
+ (policiesAndCerts[issuerDomainPolicyIndex])[m] = true;
+ delete (policiesAndCerts[subjectDomainPolicyIndex])[m];
+ }
+ }
+ }
+ //endregion
+ }
+ }
+ //endregion
+ }
+ }
+ //endregion
+ }
+ //endregion
+
+ //region Working with "explicitPolicyIndicator" and "anyPolicy"
+ for(let i = 0; i < allPolicies.length; i++)
+ {
+ if(allPolicies[i] === "2.5.29.32.0")
+ {
+ for(let j = 0; j < explicitPolicyStart; j++)
+ delete (policiesAndCerts[i])[j];
+ }
+ }
+ //endregion
+
+ //region Create "set of authorities-constrained policies"
+ const authConstrPolicies = [];
+
+ for(let i = 0; i < policiesAndCerts.length; i++)
+ {
+ let found = true;
+
+ for(let j = 0; j < (this.certs.length - 1); j++)
+ {
+ let anyPolicyFound = false;
+
+ if((j < explicitPolicyStart) && (allPolicies[i] === "2.5.29.32.0") && (allPolicies.length > 1))
+ {
+ found = false;
+ break;
+ }
+
+ if(typeof (policiesAndCerts[i])[j] === "undefined")
+ {
+ if(j >= explicitPolicyStart)
+ {
+ //region Search for "anyPolicy" in the policy set
+ for(let k = 0; k < allPolicies.length; k++)
+ {
+ if(allPolicies[k] === "2.5.29.32.0")
+ {
+ if((policiesAndCerts[k])[j] === true)
+ anyPolicyFound = true;
+
+ break;
+ }
+ }
+ //endregion
+ }
+
+ if(!anyPolicyFound)
+ {
+ found = false;
+ break;
+ }
+ }
+ }
+
+ if(found === true)
+ authConstrPolicies.push(allPolicies[i]);
+ }
+ //endregion
+
+ //region Create "set of user-constrained policies"
+ let userConstrPolicies = [];
+
+ if((initialPolicySet.length === 1) && (initialPolicySet[0] === "2.5.29.32.0") && (explicitPolicyIndicator === false))
+ userConstrPolicies = initialPolicySet;
+ else
+ {
+ if((authConstrPolicies.length === 1) && (authConstrPolicies[0] === "2.5.29.32.0"))
+ userConstrPolicies = initialPolicySet;
+ else
+ {
+ for(let i = 0; i < authConstrPolicies.length; i++)
+ {
+ for(let j = 0; j < initialPolicySet.length; j++)
+ {
+ if((initialPolicySet[j] === authConstrPolicies[i]) || (initialPolicySet[j] === "2.5.29.32.0"))
+ {
+ userConstrPolicies.push(authConstrPolicies[i]);
+ break;
+ }
+ }
+ }
+ }
+ }
+ //endregion
+
+ //region Combine output object
+ const policyResult = {
+ result: (userConstrPolicies.length > 0),
+ resultCode: 0,
+ resultMessage: (userConstrPolicies.length > 0) ? "" : "Zero \"userConstrPolicies\" array, no intersections with \"authConstrPolicies\"",
+ authConstrPolicies,
+ userConstrPolicies,
+ explicitPolicyIndicator,
+ policyMappings,
+ certificatePath: this.certs
+ };
+
+ if(userConstrPolicies.length === 0)
+ return policyResult;
+ //endregion
+ //endregion
+
+ //region Work with name constraints
+ //region Check a result from "policy checking" part
+ if(policyResult.result === false)
+ return policyResult;
+ //endregion
+
+ //region Check all certificates, excluding "trust anchor"
+ pathDepth = 1;
+
+ for(let i = (this.certs.length - 2); i >= 0; i--, pathDepth++)
+ {
+ //region Support variables
+ let subjectAltNames = [];
+
+ let certPermittedSubtrees = [];
+ let certExcludedSubtrees = [];
+ //endregion
+
+ if("extensions" in this.certs[i])
+ {
+ for(let j = 0; j < this.certs[i].extensions.length; j++)
+ {
+ //region NameConstraints
+ if(this.certs[i].extensions[j].extnID === "2.5.29.30")
+ {
+ if("permittedSubtrees" in this.certs[i].extensions[j].parsedValue)
+ certPermittedSubtrees = certPermittedSubtrees.concat(this.certs[i].extensions[j].parsedValue.permittedSubtrees);
+
+ if("excludedSubtrees" in this.certs[i].extensions[j].parsedValue)
+ certExcludedSubtrees = certExcludedSubtrees.concat(this.certs[i].extensions[j].parsedValue.excludedSubtrees);
+ }
+ //endregion
+
+ //region SubjectAltName
+ if(this.certs[i].extensions[j].extnID === "2.5.29.17")
+ subjectAltNames = subjectAltNames.concat(this.certs[i].extensions[j].parsedValue.altNames);
+ //endregion
+ }
+ }
+
+ //region Checking for "required name forms"
+ let formFound = (requiredNameForms.length <= 0);
+
+ for(let j = 0; j < requiredNameForms.length; j++)
+ {
+ switch(requiredNameForms[j].base.type)
+ {
+ case 4: // directoryName
+ {
+ if(requiredNameForms[j].base.value.typesAndValues.length !== this.certs[i].subject.typesAndValues.length)
+ continue;
+
+ formFound = true;
+
+ for(let k = 0; k < this.certs[i].subject.typesAndValues.length; k++)
+ {
+ if(this.certs[i].subject.typesAndValues[k].type !== requiredNameForms[j].base.value.typesAndValues[k].type)
+ {
+ formFound = false;
+ break;
+ }
+ }
+
+ if(formFound === true)
+ break;
+ }
+ break;
+ default: // ??? Probably here we should reject the certificate ???
+ }
+ }
+
+ if(formFound === false)
+ {
+ policyResult.result = false;
+ policyResult.resultCode = 21;
+ policyResult.resultMessage = "No neccessary name form found";
+
+ throw policyResult;
+ }
+ //endregion
+
+ //region Checking for "permited sub-trees"
+ //region Make groups for all types of constraints
+ const constrGroups = []; // Array of array for groupped constraints
+ constrGroups[0] = []; // rfc822Name
+ constrGroups[1] = []; // dNSName
+ constrGroups[2] = []; // directoryName
+ constrGroups[3] = []; // uniformResourceIdentifier
+ constrGroups[4] = []; // iPAddress
+
+ for(let j = 0; j < permittedSubtrees.length; j++)
+ {
+ switch(permittedSubtrees[j].base.type)
+ {
+ //region rfc822Name
+ case 1:
+ constrGroups[0].push(permittedSubtrees[j]);
+ break;
+ //endregion
+ //region dNSName
+ case 2:
+ constrGroups[1].push(permittedSubtrees[j]);
+ break;
+ //endregion
+ //region directoryName
+ case 4:
+ constrGroups[2].push(permittedSubtrees[j]);
+ break;
+ //endregion
+ //region uniformResourceIdentifier
+ case 6:
+ constrGroups[3].push(permittedSubtrees[j]);
+ break;
+ //endregion
+ //region iPAddress
+ case 7:
+ constrGroups[4].push(permittedSubtrees[j]);
+ break;
+ //endregion
+ //region default
+ default:
+ //endregion
+ }
+ }
+ //endregion
+
+ //region Check name constraints groupped by type, one-by-one
+ for(let p = 0; p < 5; p++)
+ {
+ let groupPermitted = false;
+ let valueExists = false;
+ const group = constrGroups[p];
+
+ for(let j = 0; j < group.length; j++)
+ {
+ switch(p)
+ {
+ //region rfc822Name
+ case 0:
+ if(subjectAltNames.length > 0)
+ {
+ for(let k = 0; k < subjectAltNames.length; k++)
+ {
+ if(subjectAltNames[k].type === 1) // rfc822Name
+ {
+ valueExists = true;
+ groupPermitted = groupPermitted || compareRFC822Name(subjectAltNames[k].value, group[j].base.value);
+ }
+ }
+ }
+ else // Try to find out "emailAddress" inside "subject"
+ {
+ for(let k = 0; k < this.certs[i].subject.typesAndValues.length; k++)
+ {
+ if((this.certs[i].subject.typesAndValues[k].type === "1.2.840.113549.1.9.1") || // PKCS#9 e-mail address
+ (this.certs[i].subject.typesAndValues[k].type === "0.9.2342.19200300.100.1.3")) // RFC1274 "rfc822Mailbox" e-mail address
+ {
+ valueExists = true;
+ groupPermitted = groupPermitted || compareRFC822Name(this.certs[i].subject.typesAndValues[k].value.valueBlock.value, group[j].base.value);
+ }
+ }
+ }
+ break;
+ //endregion
+ //region dNSName
+ case 1:
+ if(subjectAltNames.length > 0)
+ {
+ for(let k = 0; k < subjectAltNames.length; k++)
+ {
+ if(subjectAltNames[k].type === 2) // dNSName
+ {
+ valueExists = true;
+ groupPermitted = groupPermitted || compareDNSName(subjectAltNames[k].value, group[j].base.value);
+ }
+ }
+ }
+ break;
+ //endregion
+ //region directoryName
+ case 2:
+ valueExists = true;
+ groupPermitted = compareDirectoryName(this.certs[i].subject, group[j].base.value);
+ break;
+ //endregion
+ //region uniformResourceIdentifier
+ case 3:
+ if(subjectAltNames.length > 0)
+ {
+ for(let k = 0; k < subjectAltNames.length; k++)
+ {
+ if(subjectAltNames[k].type === 6) // uniformResourceIdentifier
+ {
+ valueExists = true;
+ groupPermitted = groupPermitted || compareUniformResourceIdentifier(subjectAltNames[k].value, group[j].base.value);
+ }
+ }
+ }
+ break;
+ //endregion
+ //region iPAddress
+ case 4:
+ if(subjectAltNames.length > 0)
+ {
+ for(let k = 0; k < subjectAltNames.length; k++)
+ {
+ if(subjectAltNames[k].type === 7) // iPAddress
+ {
+ valueExists = true;
+ groupPermitted = groupPermitted || compareIPAddress(subjectAltNames[k].value, group[j].base.value);
+ }
+ }
+ }
+ break;
+ //endregion
+ //region default
+ default:
+ //endregion
+ }
+
+ if(groupPermitted)
+ break;
+ }
+
+ if((groupPermitted === false) && (group.length > 0) && valueExists)
+ {
+ policyResult.result = false;
+ policyResult.resultCode = 41;
+ policyResult.resultMessage = "Failed to meet \"permitted sub-trees\" name constraint";
+
+ throw policyResult;
+ }
+ }
+ //endregion
+ //endregion
+
+ //region Checking for "excluded sub-trees"
+ let excluded = false;
+
+ for(let j = 0; j < excludedSubtrees.length; j++)
+ {
+ switch(excludedSubtrees[j].base.type)
+ {
+ //region rfc822Name
+ case 1:
+ if(subjectAltNames.length >= 0)
+ {
+ for(let k = 0; k < subjectAltNames.length; k++)
+ {
+ if(subjectAltNames[k].type === 1) // rfc822Name
+ excluded = excluded || compareRFC822Name(subjectAltNames[k].value, excludedSubtrees[j].base.value);
+ }
+ }
+ else // Try to find out "emailAddress" inside "subject"
+ {
+ for(let k = 0; k < this.certs[i].subject.typesAndValues.length; k++)
+ {
+ if((this.certs[i].subject.typesAndValues[k].type === "1.2.840.113549.1.9.1") || // PKCS#9 e-mail address
+ (this.certs[i].subject.typesAndValues[k].type === "0.9.2342.19200300.100.1.3")) // RFC1274 "rfc822Mailbox" e-mail address
+ excluded = excluded || compareRFC822Name(this.certs[i].subject.typesAndValues[k].value.valueBlock.value, excludedSubtrees[j].base.value);
+ }
+ }
+ break;
+ //endregion
+ //region dNSName
+ case 2:
+ if(subjectAltNames.length > 0)
+ {
+ for(let k = 0; k < subjectAltNames.length; k++)
+ {
+ if(subjectAltNames[k].type === 2) // dNSName
+ excluded = excluded || compareDNSName(subjectAltNames[k].value, excludedSubtrees[j].base.value);
+ }
+ }
+ break;
+ //endregion
+ //region directoryName
+ case 4:
+ excluded = excluded || compareDirectoryName(this.certs[i].subject, excludedSubtrees[j].base.value);
+ break;
+ //endregion
+ //region uniformResourceIdentifier
+ case 6:
+ if(subjectAltNames.length > 0)
+ {
+ for(let k = 0; k < subjectAltNames.length; k++)
+ {
+ if(subjectAltNames[k].type === 6) // uniformResourceIdentifier
+ excluded = excluded || compareUniformResourceIdentifier(subjectAltNames[k].value, excludedSubtrees[j].base.value);
+ }
+ }
+ break;
+ //endregion
+ //region iPAddress
+ case 7:
+ if(subjectAltNames.length > 0)
+ {
+ for(let k = 0; k < subjectAltNames.length; k++)
+ {
+ if(subjectAltNames[k].type === 7) // iPAddress
+ excluded = excluded || compareIPAddress(subjectAltNames[k].value, excludedSubtrees[j].base.value);
+ }
+ }
+ break;
+ //endregion
+ //region default
+ default: // No action, but probably here we need to create a warning for "malformed constraint"
+ //endregion
+ }
+
+ if(excluded)
+ break;
+ }
+
+ if(excluded === true)
+ {
+ policyResult.result = false;
+ policyResult.resultCode = 42;
+ policyResult.resultMessage = "Failed to meet \"excluded sub-trees\" name constraint";
+
+ throw policyResult;
+ }
+ //endregion
+
+ //region Append "cert_..._subtrees" to "..._subtrees"
+ permittedSubtrees = permittedSubtrees.concat(certPermittedSubtrees);
+ excludedSubtrees = excludedSubtrees.concat(certExcludedSubtrees);
+ //endregion
+ }
+ //endregion
+
+ return policyResult;
+ //endregion
+ }
+ catch(error)
+ {
+ if(error instanceof Object)
+ {
+ if("resultMessage" in error)
+ return error;
+
+ if("message" in error)
+ {
+ return {
+ result: false,
+ resultCode: -1,
+ resultMessage: error.message
+ };
+ }
+ }
+
+ return {
+ result: false,
+ resultCode: -1,
+ resultMessage: error
+ };
+ }
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/CertificatePolicies.js b/pki.js/CertificatePolicies.js
new file mode 100644
index 0000000..2dbcc64
--- /dev/null
+++ b/pki.js/CertificatePolicies.js
@@ -0,0 +1,134 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import PolicyInformation from "./PolicyInformation.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class CertificatePolicies
+{
+ //**********************************************************************************
+ /**
+ * Constructor for CertificatePolicies 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 {Array.}
+ * @desc certificatePolicies
+ */
+ this.certificatePolicies = getParametersValue(parameters, "certificatePolicies", CertificatePolicies.defaultValues("certificatePolicies"));
+ //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 "certificatePolicies":
+ return [];
+ default:
+ throw new Error(`Invalid member name for CertificatePolicies class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [certificatePolicies]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.certificatePolicies || ""),
+ value: PolicyInformation.schema()
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "certificatePolicies"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ CertificatePolicies.schema({
+ names: {
+ certificatePolicies: "certificatePolicies"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for CertificatePolicies");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.certificatePolicies = Array.from(asn1.result.certificatePolicies, element => new PolicyInformation({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: Array.from(this.certificatePolicies, element => element.toSchema())
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ certificatePolicies: Array.from(this.certificatePolicies, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/CertificateRevocationList.js b/pki.js/CertificateRevocationList.js
new file mode 100644
index 0000000..a384b5f
--- /dev/null
+++ b/pki.js/CertificateRevocationList.js
@@ -0,0 +1,538 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, bufferToHexCodes, clearProps } from "pvutils";
+import { getEngine } from "./common.js";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import RelativeDistinguishedNames from "./RelativeDistinguishedNames.js";
+import Time from "./Time.js";
+import RevokedCertificate from "./RevokedCertificate.js";
+import Extensions from "./Extensions.js";
+//**************************************************************************************
+function tbsCertList(parameters = {})
+{
+ //TBSCertList ::= SEQUENCE {
+ // version Version OPTIONAL,
+ // -- if present, MUST be v2
+ // signature AlgorithmIdentifier,
+ // issuer Name,
+ // thisUpdate Time,
+ // nextUpdate Time OPTIONAL,
+ // revokedCertificates SEQUENCE OF SEQUENCE {
+ // userCertificate CertificateSerialNumber,
+ // revocationDate Time,
+ // crlEntryExtensions Extensions OPTIONAL
+ // -- if present, version MUST be v2
+ // } OPTIONAL,
+ // crlExtensions [0] EXPLICIT Extensions OPTIONAL
+ // -- if present, version MUST be v2
+ //}
+
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [tbsCertListVersion]
+ * @property {string} [signature]
+ * @property {string} [issuer]
+ * @property {string} [tbsCertListThisUpdate]
+ * @property {string} [tbsCertListNextUpdate]
+ * @property {string} [tbsCertListRevokedCertificates]
+ * @property {string} [crlExtensions]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || "tbsCertList"),
+ value: [
+ new asn1js.Integer({
+ optional: true,
+ name: (names.tbsCertListVersion || "tbsCertList.version"),
+ value: 2
+ }), // EXPLICIT integer value (v2)
+ AlgorithmIdentifier.schema(names.signature || {
+ names: {
+ blockName: "tbsCertList.signature"
+ }
+ }),
+ RelativeDistinguishedNames.schema(names.issuer || {
+ names: {
+ blockName: "tbsCertList.issuer"
+ }
+ }),
+ Time.schema(names.tbsCertListThisUpdate || {
+ names: {
+ utcTimeName: "tbsCertList.thisUpdate",
+ generalTimeName: "tbsCertList.thisUpdate"
+ }
+ }),
+ Time.schema(names.tbsCertListNextUpdate || {
+ names: {
+ utcTimeName: "tbsCertList.nextUpdate",
+ generalTimeName: "tbsCertList.nextUpdate"
+ }
+ }, true),
+ new asn1js.Sequence({
+ optional: true,
+ value: [
+ new asn1js.Repeated({
+ name: (names.tbsCertListRevokedCertificates || "tbsCertList.revokedCertificates"),
+ value: new asn1js.Sequence({
+ value: [
+ new asn1js.Integer(),
+ Time.schema(),
+ Extensions.schema({}, true)
+ ]
+ })
+ })
+ ]
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [Extensions.schema(names.crlExtensions || {
+ names: {
+ blockName: "tbsCertList.extensions"
+ }
+ })]
+ }) // EXPLICIT SEQUENCE value
+ ]
+ }));
+}
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class CertificateRevocationList {
+ //**********************************************************************************
+ /**
+ * Constructor for Attribute 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 {ArrayBuffer}
+ * @desc tbs
+ */
+ this.tbs = getParametersValue(parameters, "tbs", CertificateRevocationList.defaultValues("tbs"));
+ /**
+ * @type {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", CertificateRevocationList.defaultValues("version"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc signature
+ */
+ this.signature = getParametersValue(parameters, "signature", CertificateRevocationList.defaultValues("signature"));
+ /**
+ * @type {RelativeDistinguishedNames}
+ * @desc issuer
+ */
+ this.issuer = getParametersValue(parameters, "issuer", CertificateRevocationList.defaultValues("issuer"));
+ /**
+ * @type {Time}
+ * @desc thisUpdate
+ */
+ this.thisUpdate = getParametersValue(parameters, "thisUpdate", CertificateRevocationList.defaultValues("thisUpdate"));
+
+ if("nextUpdate" in parameters)
+ /**
+ * @type {Time}
+ * @desc nextUpdate
+ */
+ this.nextUpdate = getParametersValue(parameters, "nextUpdate", CertificateRevocationList.defaultValues("nextUpdate"));
+
+ if("revokedCertificates" in parameters)
+ /**
+ * @type {Array.}
+ * @desc revokedCertificates
+ */
+ this.revokedCertificates = getParametersValue(parameters, "revokedCertificates", CertificateRevocationList.defaultValues("revokedCertificates"));
+
+ if("crlExtensions" in parameters)
+ /**
+ * @type {Extensions}
+ * @desc crlExtensions
+ */
+ this.crlExtensions = getParametersValue(parameters, "crlExtensions", CertificateRevocationList.defaultValues("crlExtensions"));
+
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc signatureAlgorithm
+ */
+ this.signatureAlgorithm = getParametersValue(parameters, "signatureAlgorithm", CertificateRevocationList.defaultValues("signatureAlgorithm"));
+ /**
+ * @type {BitString}
+ * @desc signatureValue
+ */
+ this.signatureValue = getParametersValue(parameters, "signatureValue", CertificateRevocationList.defaultValues("signatureValue"));
+ //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 "tbs":
+ return new ArrayBuffer(0);
+ case "version":
+ return 1;
+ case "signature":
+ return new AlgorithmIdentifier();
+ case "issuer":
+ return new RelativeDistinguishedNames();
+ case "thisUpdate":
+ return new Time();
+ case "nextUpdate":
+ return new Time();
+ case "revokedCertificates":
+ return [];
+ case "crlExtensions":
+ return new Extensions();
+ case "signatureAlgorithm":
+ return new AlgorithmIdentifier();
+ case "signatureValue":
+ return new asn1js.BitString();
+ default:
+ throw new Error(`Invalid member name for CertificateRevocationList class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * CertificateList ::= SEQUENCE {
+ * tbsCertList TBSCertList,
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signatureValue BIT STRING }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [signatureAlgorithm]
+ * @property {string} [signatureValue]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || "CertificateList"),
+ value: [
+ tbsCertList(parameters),
+ AlgorithmIdentifier.schema(names.signatureAlgorithm || {
+ names: {
+ blockName: "signatureAlgorithm"
+ }
+ }),
+ new asn1js.BitString({ name: (names.signatureValue || "signatureValue") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "tbsCertList",
+ "tbsCertList.version",
+ "tbsCertList.signature",
+ "tbsCertList.issuer",
+ "tbsCertList.thisUpdate",
+ "tbsCertList.nextUpdate",
+ "tbsCertList.revokedCertificates",
+ "tbsCertList.extensions",
+ "signatureAlgorithm",
+ "signatureValue"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ CertificateRevocationList.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for CertificateRevocationList");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ // noinspection JSUnresolvedVariable
+ this.tbs = asn1.result.tbsCertList.valueBeforeDecode;
+
+ if("tbsCertList.version" in asn1.result)
+ this.version = asn1.result["tbsCertList.version"].valueBlock.valueDec;
+ this.signature = new AlgorithmIdentifier({ schema: asn1.result["tbsCertList.signature"] });
+ this.issuer = new RelativeDistinguishedNames({ schema: asn1.result["tbsCertList.issuer"] });
+ this.thisUpdate = new Time({ schema: asn1.result["tbsCertList.thisUpdate"] });
+ if("tbsCertList.nextUpdate" in asn1.result)
+ this.nextUpdate = new Time({ schema: asn1.result["tbsCertList.nextUpdate"] });
+ if("tbsCertList.revokedCertificates" in asn1.result)
+ this.revokedCertificates = Array.from(asn1.result["tbsCertList.revokedCertificates"], element => new RevokedCertificate({ schema: element }));
+ if("tbsCertList.extensions" in asn1.result)
+ this.crlExtensions = new Extensions({ schema: asn1.result["tbsCertList.extensions"] });
+
+ this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm });
+ this.signatureValue = asn1.result.signatureValue;
+ //endregion
+ }
+ //**********************************************************************************
+ encodeTBS()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ if(this.version !== CertificateRevocationList.defaultValues("version"))
+ outputArray.push(new asn1js.Integer({ value: this.version }));
+
+ outputArray.push(this.signature.toSchema());
+ outputArray.push(this.issuer.toSchema());
+ outputArray.push(this.thisUpdate.toSchema());
+
+ if("nextUpdate" in this)
+ outputArray.push(this.nextUpdate.toSchema());
+
+ if("revokedCertificates" in this)
+ {
+ outputArray.push(new asn1js.Sequence({
+ value: Array.from(this.revokedCertificates, element => element.toSchema())
+ }));
+ }
+
+ if("crlExtensions" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ this.crlExtensions.toSchema()
+ ]
+ }));
+ }
+ //endregion
+
+ return (new asn1js.Sequence({
+ value: outputArray
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema(encodeFlag = false)
+ {
+ //region Decode stored TBS value
+ let tbsSchema;
+
+ if(encodeFlag === false)
+ {
+ if(this.tbs.length === 0) // No stored TBS part
+ return CertificateRevocationList.schema();
+
+ tbsSchema = asn1js.fromBER(this.tbs).result;
+ }
+ //endregion
+ //region Create TBS schema via assembling from TBS parts
+ else
+ tbsSchema = this.encodeTBS();
+ //endregion
+
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ tbsSchema,
+ this.signatureAlgorithm.toSchema(),
+ this.signatureValue
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const object = {
+ tbs: bufferToHexCodes(this.tbs, 0, this.tbs.byteLength),
+ signature: this.signature.toJSON(),
+ issuer: this.issuer.toJSON(),
+ thisUpdate: this.thisUpdate.toJSON(),
+ signatureAlgorithm: this.signatureAlgorithm.toJSON(),
+ signatureValue: this.signatureValue.toJSON()
+ };
+
+ if(this.version !== CertificateRevocationList.defaultValues("version"))
+ object.version = this.version;
+
+ if("nextUpdate" in this)
+ object.nextUpdate = this.nextUpdate.toJSON();
+
+ if("revokedCertificates" in this)
+ object.revokedCertificates = Array.from(this.revokedCertificates, element => element.toJSON());
+
+ if("crlExtensions" in this)
+ object.crlExtensions = this.crlExtensions.toJSON();
+
+ return object;
+ }
+ //**********************************************************************************
+ isCertificateRevoked(certificate)
+ {
+ //region Check that issuer of the input certificate is the same with issuer of this CRL
+ if(this.issuer.isEqual(certificate.issuer) === false)
+ return false;
+ //endregion
+
+ //region Check that there are revoked certificates in this CRL
+ if(("revokedCertificates" in this) === false)
+ return false;
+ //endregion
+
+ //region Search for input certificate in revoked certificates array
+ for(const revokedCertificate of this.revokedCertificates)
+ {
+ if(revokedCertificate.userCertificate.isEqual(certificate.serialNumber))
+ return true;
+ }
+ //endregion
+
+ return false;
+ }
+ //**********************************************************************************
+ /**
+ * Make a signature for existing CRL data
+ * @param {Object} privateKey Private key for "subjectPublicKeyInfo" structure
+ * @param {string} [hashAlgorithm] Hashing algorithm. Default SHA-1
+ */
+ sign(privateKey, hashAlgorithm = "SHA-1")
+ {
+ //region Initial checking
+ //region Get a private key from function parameter
+ if(typeof privateKey === "undefined")
+ return Promise.reject("Need to provide a private key for signing");
+ //endregion
+ //endregion
+
+ //region Initial variables
+ let sequence = Promise.resolve();
+ let parameters;
+
+ const engine = getEngine();
+ //endregion
+
+ //region Get a "default parameters" for current algorithm and set correct signature algorithm
+ sequence = sequence.then(() => engine.subtle.getSignatureParameters(privateKey, hashAlgorithm));
+
+ sequence = sequence.then(result =>
+ {
+ parameters = result.parameters;
+ this.signature = result.signatureAlgorithm;
+ this.signatureAlgorithm = result.signatureAlgorithm;
+ });
+ //endregion
+
+ //region Create TBS data for signing
+ sequence = sequence.then(() =>
+ {
+ this.tbs = this.encodeTBS().toBER(false);
+ });
+ //endregion
+
+ //region Signing TBS data on provided private key
+ sequence = sequence.then(() => engine.subtle.signWithPrivateKey(this.tbs, privateKey, parameters));
+
+ sequence = sequence.then(result =>
+ {
+ this.signatureValue = new asn1js.BitString({ valueHex: result });
+ });
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ /**
+ * Verify existing signature
+ * @param {{[issuerCertificate]: Object, [publicKeyInfo]: Object}} parameters
+ * @returns {*}
+ */
+ verify(parameters = {})
+ {
+ //region Global variables
+ let sequence = Promise.resolve();
+
+ let subjectPublicKeyInfo = -1;
+
+ const engine = getEngine();
+ //endregion
+
+ //region Get information about CRL issuer certificate
+ if("issuerCertificate" in parameters) // "issuerCertificate" must be of type "Certificate"
+ {
+ subjectPublicKeyInfo = parameters.issuerCertificate.subjectPublicKeyInfo;
+
+ // The CRL issuer name and "issuerCertificate" subject name are not equal
+ if(this.issuer.isEqual(parameters.issuerCertificate.subject) === false)
+ return Promise.resolve(false);
+ }
+
+ //region In case if there is only public key during verification
+ if("publicKeyInfo" in parameters)
+ subjectPublicKeyInfo = parameters.publicKeyInfo; // Must be of type "PublicKeyInfo"
+ //endregion
+
+ if(("subjectPublicKey" in subjectPublicKeyInfo) === false)
+ return Promise.reject("Issuer's certificate must be provided as an input parameter");
+ //endregion
+
+ //region Check the CRL for unknown critical extensions
+ if("crlExtensions" in this)
+ {
+ for(const extension of this.crlExtensions.extensions)
+ {
+ if(extension.critical)
+ {
+ // We can not be sure that unknown extension has no value for CRL signature
+ if(("parsedValue" in extension) === false)
+ return Promise.resolve(false);
+ }
+ }
+ }
+ //endregion
+
+ sequence = sequence.then(() => engine.subtle.verifyWithPublicKey(this.tbs, this.signatureValue, subjectPublicKeyInfo, this.signatureAlgorithm));
+
+ return sequence;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/CertificateSet.js b/pki.js/CertificateSet.js
new file mode 100644
index 0000000..8924086
--- /dev/null
+++ b/pki.js/CertificateSet.js
@@ -0,0 +1,235 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import Certificate from "./Certificate.js";
+import AttributeCertificateV1 from "./AttributeCertificateV1.js";
+import AttributeCertificateV2 from "./AttributeCertificateV2.js";
+import OtherCertificateFormat from "./OtherCertificateFormat.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class CertificateSet
+{
+ //**********************************************************************************
+ /**
+ * Constructor for CertificateSet 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 {Array}
+ * @desc certificates
+ */
+ this.certificates = getParametersValue(parameters, "certificates", CertificateSet.defaultValues("certificates"));
+ //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 "certificates":
+ return [];
+ default:
+ throw new Error(`Invalid member name for Attribute class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * CertificateSet ::= SET OF CertificateChoices
+ *
+ * CertificateChoices ::= CHOICE {
+ * certificate Certificate,
+ * extendedCertificate [0] IMPLICIT ExtendedCertificate, -- Obsolete
+ * v1AttrCert [1] IMPLICIT AttributeCertificateV1, -- Obsolete
+ * v2AttrCert [2] IMPLICIT AttributeCertificateV2,
+ * other [3] IMPLICIT OtherCertificateFormat }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (
+ new asn1js.Set({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.certificates || "certificates"),
+ value: new asn1js.Choice({
+ value: [
+ Certificate.schema(),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Any()
+ ]
+ }), // JUST A STUB
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: AttributeCertificateV1.schema().valueBlock.value
+ }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ value: AttributeCertificateV2.schema().valueBlock.value
+ }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 3 // [3]
+ },
+ value: OtherCertificateFormat.schema().valueBlock.value
+ })
+ ]
+ })
+ })
+ ]
+ })
+ );
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "certificates"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ CertificateSet.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for CertificateSet");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.certificates = Array.from(asn1.result.certificates || [], element =>
+ {
+ const initialTagNumber = element.idBlock.tagNumber;
+
+ if(element.idBlock.tagClass === 1)
+ return new Certificate({ schema: element });
+
+ //region Making "Sequence" from "Constructed" value
+ const elementSequence = new asn1js.Sequence({
+ value: element.valueBlock.value
+ });
+ //endregion
+
+ switch(initialTagNumber)
+ {
+ case 1:
+ return new AttributeCertificateV1({ schema: elementSequence });
+ case 2:
+ return new AttributeCertificateV2({ schema: elementSequence });
+ case 3:
+ return new OtherCertificateFormat({ schema: elementSequence });
+ case 0:
+ default:
+ }
+
+ return element;
+ });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Set({
+ value: Array.from(this.certificates, element =>
+ {
+ switch(true)
+ {
+ case (element instanceof Certificate):
+ return element.toSchema();
+ case (element instanceof AttributeCertificateV1):
+ return new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 1 // [1]
+ },
+ value: element.toSchema().valueBlock.value
+ });
+ case (element instanceof AttributeCertificateV2):
+ return new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 2 // [2]
+ },
+ value: element.toSchema().valueBlock.value
+ });
+ case (element instanceof OtherCertificateFormat):
+ return new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 3 // [3]
+ },
+ value: element.toSchema().valueBlock.value
+ });
+ default:
+ }
+
+ return element;
+ })
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ certificates: Array.from(this.certificates, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/CertificateTemplate.js b/pki.js/CertificateTemplate.js
new file mode 100644
index 0000000..4f9813f
--- /dev/null
+++ b/pki.js/CertificateTemplate.js
@@ -0,0 +1,191 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from "[MS-WCCE]: Windows Client Certificate Enrollment Protocol"
+ */
+export default class CertificateTemplate
+{
+ //**********************************************************************************
+ /**
+ * Constructor for CertificateTemplate 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 {string}
+ * @desc templateID
+ */
+ this.templateID = getParametersValue(parameters, "templateID", CertificateTemplate.defaultValues("templateID"));
+
+ if("templateMajorVersion" in parameters)
+ /**
+ * @type {number}
+ * @desc templateMajorVersion
+ */
+ this.templateMajorVersion = getParametersValue(parameters, "templateMajorVersion", CertificateTemplate.defaultValues("templateMajorVersion"));
+
+ if("templateMinorVersion" in parameters)
+ /**
+ * @type {number}
+ * @desc templateMinorVersion
+ */
+ this.templateMinorVersion = getParametersValue(parameters, "templateMinorVersion", CertificateTemplate.defaultValues("templateMinorVersion"));
+ //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 "templateID":
+ return "";
+ case "templateMajorVersion":
+ case "templateMinorVersion":
+ return 0;
+ default:
+ throw new Error(`Invalid member name for CertificateTemplate class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * CertificateTemplateOID ::= SEQUENCE {
+ * templateID OBJECT IDENTIFIER,
+ * templateMajorVersion INTEGER (0..4294967295) OPTIONAL,
+ * templateMinorVersion INTEGER (0..4294967295) OPTIONAL
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [templateID]
+ * @property {string} [templateMajorVersion]
+ * @property {string} [templateMinorVersion]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.templateID || "") }),
+ new asn1js.Integer({
+ name: (names.templateMajorVersion || ""),
+ optional: true
+ }),
+ new asn1js.Integer({
+ name: (names.templateMinorVersion || ""),
+ optional: true
+ }),
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "templateID",
+ "templateMajorVersion",
+ "templateMinorVersion"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ let asn1 = asn1js.compareSchema(schema,
+ schema,
+ CertificateTemplate.schema({
+ names: {
+ templateID: "templateID",
+ templateMajorVersion: "templateMajorVersion",
+ templateMinorVersion: "templateMinorVersion"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for CertificateTemplate");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.templateID = asn1.result.templateID.valueBlock.toString();
+
+ if("templateMajorVersion" in asn1.result)
+ this.templateMajorVersion = asn1.result.templateMajorVersion.valueBlock.valueDec;
+
+ if("templateMinorVersion" in asn1.result)
+ this.templateMinorVersion = asn1.result.templateMinorVersion.valueBlock.valueDec;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.ObjectIdentifier({ value: this.templateID }));
+
+ if("templateMajorVersion" in this)
+ outputArray.push(new asn1js.Integer({ value: this.templateMajorVersion }));
+
+ if("templateMinorVersion" in this)
+ outputArray.push(new asn1js.Integer({ value: this.templateMinorVersion }));
+ //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 = {
+ extnID: this.templateID
+ };
+
+ if("templateMajorVersion" in this)
+ object.templateMajorVersion = this.templateMajorVersion;
+
+ if("templateMinorVersion" in this)
+ object.templateMinorVersion = this.templateMinorVersion;
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/CertificationRequest.js b/pki.js/CertificationRequest.js
new file mode 100644
index 0000000..d6f1c84
--- /dev/null
+++ b/pki.js/CertificationRequest.js
@@ -0,0 +1,383 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, bufferToHexCodes, clearProps } from "pvutils";
+import { getEngine } from "./common.js";
+import PublicKeyInfo from "./PublicKeyInfo.js";
+import RelativeDistinguishedNames from "./RelativeDistinguishedNames.js";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import Attribute from "./Attribute.js";
+//**************************************************************************************
+function CertificationRequestInfo(parameters = {})
+{
+ //CertificationRequestInfo ::= SEQUENCE {
+ // version INTEGER { v1(0) } (v1,...),
+ // subject Name,
+ // subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
+ // attributes [0] Attributes{{ CRIAttributes }}
+ //}
+
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [CertificationRequestInfo]
+ * @property {string} [CertificationRequestInfoVersion]
+ * @property {string} [subject]
+ * @property {string} [CertificationRequestInfoAttributes]
+ * @property {string} [attributes]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.CertificationRequestInfo || "CertificationRequestInfo"),
+ value: [
+ new asn1js.Integer({ name: (names.CertificationRequestInfoVersion || "CertificationRequestInfo.version") }),
+ RelativeDistinguishedNames.schema(names.subject || {
+ names: {
+ blockName: "CertificationRequestInfo.subject"
+ }
+ }),
+ PublicKeyInfo.schema({
+ names: {
+ blockName: "CertificationRequestInfo.subjectPublicKeyInfo"
+ }
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Repeated({
+ optional: true, // Because OpenSSL makes wrong "attributes" field
+ name: (names.CertificationRequestInfoAttributes || "CertificationRequestInfo.attributes"),
+ value: Attribute.schema(names.attributes || {})
+ })
+ ]
+ })
+ ]
+ }));
+}
+//**************************************************************************************
+/**
+ * Class from RFC2986
+ */
+export default class CertificationRequest
+{
+ //**********************************************************************************
+ /**
+ * Constructor for Attribute 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 {ArrayBuffer}
+ * @desc tbs
+ */
+ this.tbs = getParametersValue(parameters, "tbs", CertificationRequest.defaultValues("tbs"));
+ /**
+ * @type {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", CertificationRequest.defaultValues("version"));
+ /**
+ * @type {RelativeDistinguishedNames}
+ * @desc subject
+ */
+ this.subject = getParametersValue(parameters, "subject", CertificationRequest.defaultValues("subject"));
+ /**
+ * @type {PublicKeyInfo}
+ * @desc subjectPublicKeyInfo
+ */
+ this.subjectPublicKeyInfo = getParametersValue(parameters, "subjectPublicKeyInfo", CertificationRequest.defaultValues("subjectPublicKeyInfo"));
+
+ if("attributes" in parameters)
+ /**
+ * @type {Array.}
+ * @desc attributes
+ */
+ this.attributes = getParametersValue(parameters, "attributes", CertificationRequest.defaultValues("attributes"));
+
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc signatureAlgorithm
+ */
+ this.signatureAlgorithm = getParametersValue(parameters, "signatureAlgorithm", CertificationRequest.defaultValues("signatureAlgorithm"));
+ /**
+ * @type {BitString}
+ * @desc signatureAlgorithm
+ */
+ this.signatureValue = getParametersValue(parameters, "signatureValue", CertificationRequest.defaultValues("signatureValue"));
+ //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 "tbs":
+ return new ArrayBuffer(0);
+ case "version":
+ return 0;
+ case "subject":
+ return new RelativeDistinguishedNames();
+ case "subjectPublicKeyInfo":
+ return new PublicKeyInfo();
+ case "attributes":
+ return [];
+ case "signatureAlgorithm":
+ return new AlgorithmIdentifier();
+ case "signatureValue":
+ return new asn1js.BitString();
+ default:
+ throw new Error(`Invalid member name for CertificationRequest class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * CertificationRequest ::= SEQUENCE {
+ * certificationRequestInfo CertificationRequestInfo,
+ * signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }},
+ * signature BIT STRING
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [certificationRequestInfo]
+ * @property {string} [signatureAlgorithm]
+ * @property {string} [signatureValue]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ value: [
+ CertificationRequestInfo(names.certificationRequestInfo || {}),
+ new asn1js.Sequence({
+ name: (names.signatureAlgorithm || "signatureAlgorithm"),
+ value: [
+ new asn1js.ObjectIdentifier(),
+ new asn1js.Any({ optional: true })
+ ]
+ }),
+ new asn1js.BitString({ name: (names.signatureValue || "signatureValue") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "CertificationRequestInfo",
+ "CertificationRequestInfo.version",
+ "CertificationRequestInfo.subject",
+ "CertificationRequestInfo.subjectPublicKeyInfo",
+ "CertificationRequestInfo.attributes",
+ "signatureAlgorithm",
+ "signatureValue"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ CertificationRequest.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for CertificationRequest");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.tbs = asn1.result.CertificationRequestInfo.valueBeforeDecode;
+
+ this.version = asn1.result["CertificationRequestInfo.version"].valueBlock.valueDec;
+ this.subject = new RelativeDistinguishedNames({ schema: asn1.result["CertificationRequestInfo.subject"] });
+ this.subjectPublicKeyInfo = new PublicKeyInfo({ schema: asn1.result["CertificationRequestInfo.subjectPublicKeyInfo"] });
+ if("CertificationRequestInfo.attributes" in asn1.result)
+ this.attributes = Array.from(asn1.result["CertificationRequestInfo.attributes"], element => new Attribute({ schema: element }));
+
+ this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm });
+ this.signatureValue = asn1.result.signatureValue;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Aux function making ASN1js Sequence from current TBS
+ * @returns {Sequence}
+ */
+ encodeTBS()
+ {
+ //region Create array for output sequence
+ const outputArray = [
+ new asn1js.Integer({ value: this.version }),
+ this.subject.toSchema(),
+ this.subjectPublicKeyInfo.toSchema()
+ ];
+
+ if("attributes" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: Array.from(this.attributes, element => element.toSchema())
+ }));
+ }
+ //endregion
+
+ return (new asn1js.Sequence({
+ value: outputArray
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema(encodeFlag = false)
+ {
+ //region Decode stored TBS value
+ let tbsSchema;
+
+ if(encodeFlag === false)
+ {
+ if(this.tbs.byteLength === 0) // No stored TBS part
+ return CertificationRequest.schema();
+
+ tbsSchema = asn1js.fromBER(this.tbs).result;
+ }
+ //endregion
+ //region Create TBS schema via assembling from TBS parts
+ else
+ tbsSchema = this.encodeTBS();
+ //endregion
+
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ tbsSchema,
+ this.signatureAlgorithm.toSchema(),
+ this.signatureValue
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const object = {
+ tbs: bufferToHexCodes(this.tbs, 0, this.tbs.byteLength),
+ version: this.version,
+ subject: this.subject.toJSON(),
+ subjectPublicKeyInfo: this.subjectPublicKeyInfo.toJSON(),
+ signatureAlgorithm: this.signatureAlgorithm.toJSON(),
+ signatureValue: this.signatureValue.toJSON()
+ };
+
+ if("attributes" in this)
+ object.attributes = Array.from(this.attributes, element => element.toJSON());
+
+ return object;
+ }
+ //**********************************************************************************
+ /**
+ * Makes signature for currect certification request
+ * @param {Object} privateKey WebCrypto private key
+ * @param {string} [hashAlgorithm=SHA-1] String representing current hashing algorithm
+ */
+ sign(privateKey, hashAlgorithm = "SHA-1")
+ {
+ //region Initial checking
+ //region Get a private key from function parameter
+ if(typeof privateKey === "undefined")
+ return Promise.reject("Need to provide a private key for signing");
+ //endregion
+ //endregion
+
+ //region Initial variables
+ let sequence = Promise.resolve();
+ let parameters;
+
+ const engine = getEngine();
+ //endregion
+
+ //region Get a "default parameters" for current algorithm and set correct signature algorithm
+ sequence = sequence.then(() => engine.subtle.getSignatureParameters(privateKey, hashAlgorithm));
+
+ sequence = sequence.then(result =>
+ {
+ parameters = result.parameters;
+ this.signatureAlgorithm = result.signatureAlgorithm;
+ });
+ //endregion
+
+ //region Create TBS data for signing
+ sequence = sequence.then(() =>
+ {
+ this.tbs = this.encodeTBS().toBER(false);
+ });
+ //endregion
+
+ //region Signing TBS data on provided private key
+ sequence = sequence.then(() => engine.subtle.signWithPrivateKey(this.tbs, privateKey, parameters));
+
+ sequence = sequence.then(result =>
+ {
+ this.signatureValue = new asn1js.BitString({ valueHex: result });
+ });
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ /**
+ * Verify existing certification request signature
+ * @returns {*}
+ */
+ verify()
+ {
+ return getEngine().subtle.verifyWithPublicKey(this.tbs, this.signatureValue, this.subjectPublicKeyInfo, this.signatureAlgorithm);
+ }
+ //**********************************************************************************
+ /**
+ * Importing public key for current certificate request
+ */
+ getPublicKey(parameters = null)
+ {
+ return getEngine().getPublicKey(this.subjectPublicKeyInfo, this.signatureAlgorithm, parameters);
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/ContentInfo.js b/pki.js/ContentInfo.js
new file mode 100644
index 0000000..903f12b
--- /dev/null
+++ b/pki.js/ContentInfo.js
@@ -0,0 +1,181 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class ContentInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for ContentInfo 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 {string}
+ * @desc contentType
+ */
+ this.contentType = getParametersValue(parameters, "contentType", ContentInfo.defaultValues("contentType"));
+ /**
+ * @type {Any}
+ * @desc content
+ */
+ this.content = getParametersValue(parameters, "content", ContentInfo.defaultValues("content"));
+ //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 "contentType":
+ return "";
+ case "content":
+ return new asn1js.Any();
+ default:
+ throw new Error(`Invalid member name for ContentInfo 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 "contentType":
+ return (memberValue === "");
+ case "content":
+ return (memberValue instanceof asn1js.Any);
+ default:
+ throw new Error(`Invalid member name for ContentInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * ContentInfo ::= SEQUENCE {
+ * contentType ContentType,
+ * content [0] EXPLICIT ANY DEFINED BY contentType }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [contentType]
+ * @property {string} [content]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ if(("optional" in names) === false)
+ names.optional = false;
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || "ContentInfo"),
+ optional: names.optional,
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.contentType || "contentType") }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [new asn1js.Any({ name: (names.content || "content") })] // EXPLICIT ANY value
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "contentType",
+ "content"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ ContentInfo.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for ContentInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.contentType = asn1.result.contentType.valueBlock.toString();
+ this.content = asn1.result.content;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ new asn1js.ObjectIdentifier({ value: this.contentType }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [this.content] // EXPLICIT ANY value
+ })
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const object = {
+ contentType: this.contentType
+ };
+
+ if(!(this.content instanceof asn1js.Any))
+ object.content = this.content.toJSON();
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/CryptoEngine.js b/pki.js/CryptoEngine.js
new file mode 100644
index 0000000..17761cd
--- /dev/null
+++ b/pki.js/CryptoEngine.js
@@ -0,0 +1,2565 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, stringToArrayBuffer, arrayBufferToString, utilConcatBuf } from "pvutils";
+import { createCMSECDSASignature, createECDSASignatureFromCMS } from "./common.js";
+import PublicKeyInfo from "./PublicKeyInfo.js";
+import PrivateKeyInfo from "./PrivateKeyInfo.js";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import EncryptedContentInfo from "./EncryptedContentInfo.js";
+import RSASSAPSSParams from "./RSASSAPSSParams.js";
+import PBKDF2Params from "./PBKDF2Params.js";
+import PBES2Params from "./PBES2Params.js";
+//**************************************************************************************
+/**
+ * Making MAC key using algorithm described in B.2 of PKCS#12 standard.
+ */
+function makePKCS12B2Key(cryptoEngine, hashAlgorithm, keyLength, password, salt, iterationCount)
+{
+ //region Initial variables
+ let u;
+ let v;
+
+ const result = [];
+ //endregion
+
+ //region Get "u" and "v" values
+ switch(hashAlgorithm.toUpperCase())
+ {
+ case "SHA-1":
+ u = 20; // 160
+ v = 64; // 512
+ break;
+ case "SHA-256":
+ u = 32; // 256
+ v = 64; // 512
+ break;
+ case "SHA-384":
+ u = 48; // 384
+ v = 128; // 1024
+ break;
+ case "SHA-512":
+ u = 64; // 512
+ v = 128; // 1024
+ break;
+ default:
+ throw new Error("Unsupported hashing algorithm");
+ }
+ //endregion
+
+ //region Main algorithm making key
+ //region Transform password to UTF-8 like string
+ const passwordViewInitial = new Uint8Array(password);
+
+ const passwordTransformed = new ArrayBuffer((password.byteLength * 2) + 2);
+ const passwordTransformedView = new Uint8Array(passwordTransformed);
+
+ for(let i = 0; i < passwordViewInitial.length; i++)
+ {
+ passwordTransformedView[i * 2] = 0x00;
+ passwordTransformedView[i * 2 + 1] = passwordViewInitial[i];
+ }
+
+ passwordTransformedView[passwordTransformedView.length - 2] = 0x00;
+ passwordTransformedView[passwordTransformedView.length - 1] = 0x00;
+
+ password = passwordTransformed.slice(0);
+ //endregion
+
+ //region Construct a string D (the "diversifier") by concatenating v/8 copies of ID
+ const D = new ArrayBuffer(v);
+ const dView = new Uint8Array(D);
+
+ for(let i = 0; i < D.byteLength; i++)
+ dView[i] = 3; // The ID value equal to "3" for MACing (see B.3 of standard)
+ //endregion
+
+ //region Concatenate copies of the salt together to create a string S of length v * ceil(s / v) bytes (the final copy of the salt may be trunacted to create S)
+ const saltLength = salt.byteLength;
+
+ const sLen = v * Math.ceil(saltLength / v);
+ const S = new ArrayBuffer(sLen);
+ const sView = new Uint8Array(S);
+
+ const saltView = new Uint8Array(salt);
+
+ for(let i = 0; i < sLen; i++)
+ sView[i] = saltView[i % saltLength];
+ //endregion
+
+ //region Concatenate copies of the password together to create a string P of length v * ceil(p / v) bytes (the final copy of the password may be truncated to create P)
+ const passwordLength = password.byteLength;
+
+ const pLen = v * Math.ceil(passwordLength / v);
+ const P = new ArrayBuffer(pLen);
+ const pView = new Uint8Array(P);
+
+ const passwordView = new Uint8Array(password);
+
+ for(let i = 0; i < pLen; i++)
+ pView[i] = passwordView[i % passwordLength];
+ //endregion
+
+ //region Set I=S||P to be the concatenation of S and P
+ const sPlusPLength = S.byteLength + P.byteLength;
+
+ let I = new ArrayBuffer(sPlusPLength);
+ let iView = new Uint8Array(I);
+
+ iView.set(sView);
+ iView.set(pView, sView.length);
+ //endregion
+
+ //region Set c=ceil(n / u)
+ const c = Math.ceil((keyLength >> 3) / u);
+ //endregion
+
+ //region Initial variables
+ let internalSequence = Promise.resolve(I);
+ //endregion
+
+ //region For i=1, 2, ..., c, do the following:
+ for(let i = 0; i <= c; i++)
+ {
+ internalSequence = internalSequence.then(_I =>
+ {
+ //region Create contecanetion of D and I
+ const dAndI = new ArrayBuffer(D.byteLength + _I.byteLength);
+ const dAndIView = new Uint8Array(dAndI);
+
+ dAndIView.set(dView);
+ dAndIView.set(iView, dView.length);
+ //endregion
+
+ return dAndI;
+ });
+
+ //region Make "iterationCount" rounds of hashing
+ for(let j = 0; j < iterationCount; j++)
+ internalSequence = internalSequence.then(roundBuffer => cryptoEngine.digest({ name: hashAlgorithm }, new Uint8Array(roundBuffer)));
+ //endregion
+
+ internalSequence = internalSequence.then(roundBuffer =>
+ {
+ //region Concatenate copies of Ai to create a string B of length v bits (the final copy of Ai may be truncated to create B)
+ const B = new ArrayBuffer(v);
+ const bView = new Uint8Array(B);
+
+ for(let j = 0; j < B.byteLength; j++)
+ bView[j] = roundBuffer[j % roundBuffer.length];
+ //endregion
+
+ //region Make new I value
+ const k = Math.ceil(saltLength / v) + Math.ceil(passwordLength / v);
+ const iRound = [];
+
+ let sliceStart = 0;
+ let sliceLength = v;
+
+ for(let j = 0; j < k; j++)
+ {
+ const chunk = Array.from(new Uint8Array(I.slice(sliceStart, sliceStart + sliceLength)));
+ sliceStart += v;
+ if((sliceStart + v) > I.byteLength)
+ sliceLength = I.byteLength - sliceStart;
+
+ let x = 0x1ff;
+
+ for(let l = (B.byteLength - 1); l >= 0; l--)
+ {
+ x >>= 8;
+ x += bView[l] + chunk[l];
+ chunk[l] = (x & 0xff);
+ }
+
+ iRound.push(...chunk);
+ }
+
+ I = new ArrayBuffer(iRound.length);
+ iView = new Uint8Array(I);
+
+ iView.set(iRound);
+ //endregion
+
+ result.push(...(new Uint8Array(roundBuffer)));
+
+ return I;
+ });
+ }
+ //endregion
+
+ //region Initialize final key
+ internalSequence = internalSequence.then(() =>
+ {
+ const resultBuffer = new ArrayBuffer(keyLength >> 3);
+ const resultView = new Uint8Array(resultBuffer);
+
+ resultView.set((new Uint8Array(result)).slice(0, keyLength >> 3));
+
+ return resultBuffer;
+ });
+ //endregion
+ //endregion
+
+ return internalSequence;
+}
+//**************************************************************************************
+/**
+ * Default cryptographic engine for Web Cryptography API
+ */
+export default class CryptoEngine
+{
+ //**********************************************************************************
+ /**
+ * Constructor for CryptoEngine 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 {Object}
+ * @desc Usually here we are expecting "window.crypto" or an equivalent from custom "crypto engine"
+ */
+ this.crypto = getParametersValue(parameters, "crypto", {});
+ /**
+ * @type {Object}
+ * @desc Usually here we are expecting "window.crypto.subtle" or an equivalent from custom "crypto engine"
+ */
+ this.subtle = getParametersValue(parameters, "subtle", {});
+ /**
+ * @type {string}
+ * @desc Name of the "crypto engine"
+ */
+ this.name = getParametersValue(parameters, "name", "");
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Import WebCrypto keys from different formats
+ * @param {string} format
+ * @param {ArrayBuffer|Uint8Array} keyData
+ * @param {Object} algorithm
+ * @param {boolean} extractable
+ * @param {Array} keyUsages
+ * @returns {Promise}
+ */
+ importKey(format, keyData, algorithm, extractable, keyUsages)
+ {
+ //region Initial variables
+ let jwk = {};
+ //endregion
+
+ //region Change "keyData" type if needed
+ if(keyData instanceof Uint8Array)
+ keyData = keyData.buffer;
+ //endregion
+
+ switch(format.toLowerCase())
+ {
+ case "raw":
+ return this.subtle.importKey("raw", keyData, algorithm, extractable, keyUsages);
+ case "spki":
+ {
+ const asn1 = asn1js.fromBER(keyData);
+ if(asn1.offset === (-1))
+ return Promise.reject("Incorrect keyData");
+
+ const publicKeyInfo = new PublicKeyInfo();
+ try
+ {
+ publicKeyInfo.fromSchema(asn1.result);
+ }
+ catch(ex)
+ {
+ return Promise.reject("Incorrect keyData");
+ }
+
+
+ // noinspection FallThroughInSwitchStatementJS
+ switch(algorithm.name.toUpperCase())
+ {
+ case "RSA-PSS":
+ {
+ //region Get information about used hash function
+ switch(algorithm.hash.name.toUpperCase())
+ {
+ case "SHA-1":
+ jwk.alg = "PS1";
+ break;
+ case "SHA-256":
+ jwk.alg = "PS256";
+ break;
+ case "SHA-384":
+ jwk.alg = "PS384";
+ break;
+ case "SHA-512":
+ jwk.alg = "PS512";
+ break;
+ default:
+ return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
+ }
+ //endregion
+ }
+ // break omitted
+ case "RSASSA-PKCS1-V1_5":
+ {
+ keyUsages = ["verify"]; // Override existing keyUsages value since the key is a public key
+
+ jwk.kty = "RSA";
+ jwk.ext = extractable;
+ jwk.key_ops = keyUsages;
+
+ if(publicKeyInfo.algorithm.algorithmId !== "1.2.840.113549.1.1.1")
+ return Promise.reject(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`);
+
+ //region Get information about used hash function
+ if(("alg" in jwk) === false)
+ {
+ switch(algorithm.hash.name.toUpperCase())
+ {
+ case "SHA-1":
+ jwk.alg = "RS1";
+ break;
+ case "SHA-256":
+ jwk.alg = "RS256";
+ break;
+ case "SHA-384":
+ jwk.alg = "RS384";
+ break;
+ case "SHA-512":
+ jwk.alg = "RS512";
+ break;
+ default:
+ return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
+ }
+ }
+ //endregion
+
+ //region Create RSA Public Key elements
+ const publicKeyJSON = publicKeyInfo.toJSON();
+
+ for(const key of Object.keys(publicKeyJSON))
+ jwk[key] = publicKeyJSON[key];
+ //endregion
+ }
+ break;
+ case "ECDSA":
+ keyUsages = ["verify"]; // Override existing keyUsages value since the key is a public key
+ // break omitted
+ case "ECDH":
+ {
+ //region Initial variables
+ jwk = {
+ kty: "EC",
+ ext: extractable,
+ key_ops: keyUsages
+ };
+ //endregion
+
+ //region Get information about algorithm
+ if(publicKeyInfo.algorithm.algorithmId !== "1.2.840.10045.2.1")
+ return Promise.reject(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`);
+ //endregion
+
+ //region Create ECDSA Public Key elements
+ const publicKeyJSON = publicKeyInfo.toJSON();
+
+ for(const key of Object.keys(publicKeyJSON))
+ jwk[key] = publicKeyJSON[key];
+ //endregion
+ }
+ break;
+ case "RSA-OAEP":
+ {
+ jwk.kty = "RSA";
+ jwk.ext = extractable;
+ jwk.key_ops = keyUsages;
+
+ if(this.name.toLowerCase() === "safari")
+ jwk.alg = "RSA-OAEP";
+ else
+ {
+ switch(algorithm.hash.name.toUpperCase())
+ {
+ case "SHA-1":
+ jwk.alg = "RSA-OAEP";
+ break;
+ case "SHA-256":
+ jwk.alg = "RSA-OAEP-256";
+ break;
+ case "SHA-384":
+ jwk.alg = "RSA-OAEP-384";
+ break;
+ case "SHA-512":
+ jwk.alg = "RSA-OAEP-512";
+ break;
+ default:
+ return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
+ }
+ }
+
+ //region Create ECDSA Public Key elements
+ const publicKeyJSON = publicKeyInfo.toJSON();
+
+ for(const key of Object.keys(publicKeyJSON))
+ jwk[key] = publicKeyJSON[key];
+ //endregion
+ }
+ break;
+ default:
+ return Promise.reject(`Incorrect algorithm name: ${algorithm.name.toUpperCase()}`);
+ }
+ }
+ break;
+ case "pkcs8":
+ {
+ const privateKeyInfo = new PrivateKeyInfo();
+
+ //region Parse "PrivateKeyInfo" object
+ const asn1 = asn1js.fromBER(keyData);
+ if(asn1.offset === (-1))
+ return Promise.reject("Incorrect keyData");
+
+ try
+ {
+ privateKeyInfo.fromSchema(asn1.result);
+ }
+ catch(ex)
+ {
+ return Promise.reject("Incorrect keyData");
+ }
+
+ if(("parsedKey" in privateKeyInfo) === false)
+ return Promise.reject("Incorrect keyData");
+ //endregion
+
+ // noinspection FallThroughInSwitchStatementJS
+ // noinspection FallThroughInSwitchStatementJS
+ switch(algorithm.name.toUpperCase())
+ {
+ case "RSA-PSS":
+ {
+ //region Get information about used hash function
+ switch(algorithm.hash.name.toUpperCase())
+ {
+ case "SHA-1":
+ jwk.alg = "PS1";
+ break;
+ case "SHA-256":
+ jwk.alg = "PS256";
+ break;
+ case "SHA-384":
+ jwk.alg = "PS384";
+ break;
+ case "SHA-512":
+ jwk.alg = "PS512";
+ break;
+ default:
+ return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
+ }
+ //endregion
+ }
+ // break omitted
+ case "RSASSA-PKCS1-V1_5":
+ {
+ keyUsages = ["sign"]; // Override existing keyUsages value since the key is a private key
+
+ jwk.kty = "RSA";
+ jwk.ext = extractable;
+ jwk.key_ops = keyUsages;
+
+ //region Get information about used hash function
+ if(privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.113549.1.1.1")
+ return Promise.reject(`Incorrect private key algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`);
+ //endregion
+
+ //region Get information about used hash function
+ if(("alg" in jwk) === false)
+ {
+ switch(algorithm.hash.name.toUpperCase())
+ {
+ case "SHA-1":
+ jwk.alg = "RS1";
+ break;
+ case "SHA-256":
+ jwk.alg = "RS256";
+ break;
+ case "SHA-384":
+ jwk.alg = "RS384";
+ break;
+ case "SHA-512":
+ jwk.alg = "RS512";
+ break;
+ default:
+ return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
+ }
+ }
+ //endregion
+
+ //region Create RSA Private Key elements
+ const privateKeyJSON = privateKeyInfo.toJSON();
+
+ for(const key of Object.keys(privateKeyJSON))
+ jwk[key] = privateKeyJSON[key];
+ //endregion
+ }
+ break;
+ case "ECDSA":
+ keyUsages = ["sign"]; // Override existing keyUsages value since the key is a private key
+ // break omitted
+ case "ECDH":
+ {
+ //region Initial variables
+ jwk = {
+ kty: "EC",
+ ext: extractable,
+ key_ops: keyUsages
+ };
+ //endregion
+
+ //region Get information about used hash function
+ if(privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.10045.2.1")
+ return Promise.reject(`Incorrect algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`);
+ //endregion
+
+ //region Create ECDSA Private Key elements
+ const privateKeyJSON = privateKeyInfo.toJSON();
+
+ for(const key of Object.keys(privateKeyJSON))
+ jwk[key] = privateKeyJSON[key];
+ //endregion
+ }
+ break;
+ case "RSA-OAEP":
+ {
+ jwk.kty = "RSA";
+ jwk.ext = extractable;
+ jwk.key_ops = keyUsages;
+
+ //region Get information about used hash function
+ if(this.name.toLowerCase() === "safari")
+ jwk.alg = "RSA-OAEP";
+ else
+ {
+ switch(algorithm.hash.name.toUpperCase())
+ {
+ case "SHA-1":
+ jwk.alg = "RSA-OAEP";
+ break;
+ case "SHA-256":
+ jwk.alg = "RSA-OAEP-256";
+ break;
+ case "SHA-384":
+ jwk.alg = "RSA-OAEP-384";
+ break;
+ case "SHA-512":
+ jwk.alg = "RSA-OAEP-512";
+ break;
+ default:
+ return Promise.reject(`Incorrect hash algorithm: ${algorithm.hash.name.toUpperCase()}`);
+ }
+ }
+ //endregion
+
+ //region Create RSA Private Key elements
+ const privateKeyJSON = privateKeyInfo.toJSON();
+
+ for(const key of Object.keys(privateKeyJSON))
+ jwk[key] = privateKeyJSON[key];
+ //endregion
+ }
+ break;
+ default:
+ return Promise.reject(`Incorrect algorithm name: ${algorithm.name.toUpperCase()}`);
+ }
+ }
+ break;
+ case "jwk":
+ jwk = keyData;
+ break;
+ default:
+ return Promise.reject(`Incorrect format: ${format}`);
+ }
+
+ //region Special case for Safari browser (since its acting not as WebCrypto standard describes)
+ if(this.name.toLowerCase() === "safari")
+ {
+ // Try to use both ways - import using ArrayBuffer and pure JWK (for Safari Technology Preview)
+ return Promise.resolve().then(() => this.subtle.importKey("jwk", stringToArrayBuffer(JSON.stringify(jwk)), algorithm, extractable, keyUsages))
+ .then(result => result, () => this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages));
+ }
+ //endregion
+
+ return this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages);
+ }
+ //**********************************************************************************
+ /**
+ * Export WebCrypto keys to different formats
+ * @param {string} format
+ * @param {Object} key
+ * @returns {Promise}
+ */
+ exportKey(format, key)
+ {
+ let sequence = this.subtle.exportKey("jwk", key);
+
+ //region Currently Safari returns ArrayBuffer as JWK thus we need an additional transformation
+ if(this.name.toLowerCase() === "safari")
+ {
+ sequence = sequence.then(result =>
+ {
+ // Some additional checks for Safari Technology Preview
+ if(result instanceof ArrayBuffer)
+ return JSON.parse(arrayBufferToString(result));
+
+ return result;
+ });
+ }
+ //endregion
+
+ switch(format.toLowerCase())
+ {
+ case "raw":
+ return this.subtle.exportKey("raw", key);
+ case "spki":
+ sequence = sequence.then(result =>
+ {
+ const publicKeyInfo = new PublicKeyInfo();
+
+ try
+ {
+ publicKeyInfo.fromJSON(result);
+ }
+ catch(ex)
+ {
+ return Promise.reject("Incorrect key data");
+ }
+
+ return publicKeyInfo.toSchema().toBER(false);
+ });
+ break;
+ case "pkcs8":
+ sequence = sequence.then(result =>
+ {
+ const privateKeyInfo = new PrivateKeyInfo();
+
+ try
+ {
+ privateKeyInfo.fromJSON(result);
+ }
+ catch(ex)
+ {
+ return Promise.reject("Incorrect key data");
+ }
+
+ return privateKeyInfo.toSchema().toBER(false);
+ });
+ break;
+ case "jwk":
+ break;
+ default:
+ return Promise.reject(`Incorrect format: ${format}`);
+ }
+
+ return sequence;
+ }
+ //**********************************************************************************
+ /**
+ * Convert WebCrypto keys between different export formats
+ * @param {string} inputFormat
+ * @param {string} outputFormat
+ * @param {ArrayBuffer|Object} keyData
+ * @param {Object} algorithm
+ * @param {boolean} extractable
+ * @param {Array} keyUsages
+ * @returns {Promise}
+ */
+ convert(inputFormat, outputFormat, keyData, algorithm, extractable, keyUsages)
+ {
+ switch(inputFormat.toLowerCase())
+ {
+ case "raw":
+ switch(outputFormat.toLowerCase())
+ {
+ case "raw":
+ return Promise.resolve(keyData);
+ case "spki":
+ return Promise.resolve()
+ .then(() => this.importKey("raw", keyData, algorithm, extractable, keyUsages))
+ .then(result => this.exportKey("spki", result));
+ case "pkcs8":
+ return Promise.resolve()
+ .then(() => this.importKey("raw", keyData, algorithm, extractable, keyUsages))
+ .then(result => this.exportKey("pkcs8", result));
+ case "jwk":
+ return Promise.resolve()
+ .then(() => this.importKey("raw", keyData, algorithm, extractable, keyUsages))
+ .then(result => this.exportKey("jwk", result));
+ default:
+ return Promise.reject(`Incorrect outputFormat: ${outputFormat}`);
+ }
+ case "spki":
+ switch(outputFormat.toLowerCase())
+ {
+ case "raw":
+ return Promise.resolve()
+ .then(() => this.importKey("spki", keyData, algorithm, extractable, keyUsages))
+ .then(result => this.exportKey("raw", result));
+ case "spki":
+ return Promise.resolve(keyData);
+ case "pkcs8":
+ return Promise.reject("Impossible to convert between SPKI/PKCS8");
+ case "jwk":
+ return Promise.resolve()
+ .then(() => this.importKey("spki", keyData, algorithm, extractable, keyUsages))
+ .then(result => this.exportKey("jwk", result));
+ default:
+ return Promise.reject(`Incorrect outputFormat: ${outputFormat}`);
+ }
+ case "pkcs8":
+ switch(outputFormat.toLowerCase())
+ {
+ case "raw":
+ return Promise.resolve()
+ .then(() => this.importKey("pkcs8", keyData, algorithm, extractable, keyUsages))
+ .then(result => this.exportKey("raw", result));
+ case "spki":
+ return Promise.reject("Impossible to convert between SPKI/PKCS8");
+ case "pkcs8":
+ return Promise.resolve(keyData);
+ case "jwk":
+ return Promise.resolve()
+ .then(() => this.importKey("pkcs8", keyData, algorithm, extractable, keyUsages))
+ .then(result => this.exportKey("jwk", result));
+ default:
+ return Promise.reject(`Incorrect outputFormat: ${outputFormat}`);
+ }
+ case "jwk":
+ switch(outputFormat.toLowerCase())
+ {
+ case "raw":
+ return Promise.resolve()
+ .then(() => this.importKey("jwk", keyData, algorithm, extractable, keyUsages))
+ .then(result => this.exportKey("raw", result));
+ case "spki":
+ return Promise.resolve()
+ .then(() => this.importKey("jwk", keyData, algorithm, extractable, keyUsages))
+ .then(result => this.exportKey("spki", result));
+ case "pkcs8":
+ return Promise.resolve()
+ .then(() => this.importKey("jwk", keyData, algorithm, extractable, keyUsages))
+ .then(result => this.exportKey("pkcs8", result));
+ case "jwk":
+ return Promise.resolve(keyData);
+ default:
+ return Promise.reject(`Incorrect outputFormat: ${outputFormat}`);
+ }
+ default:
+ return Promise.reject(`Incorrect inputFormat: ${inputFormat}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Wrapper for standard function "encrypt"
+ * @param args
+ * @returns {Promise}
+ */
+ encrypt(...args)
+ {
+ return this.subtle.encrypt(...args);
+ }
+ //**********************************************************************************
+ /**
+ * Wrapper for standard function "decrypt"
+ * @param args
+ * @returns {Promise}
+ */
+ decrypt(...args)
+ {
+ return this.subtle.decrypt(...args);
+ }
+ //**********************************************************************************
+ /**
+ * Wrapper for standard function "sign"
+ * @param args
+ * @returns {Promise}
+ */
+ sign(...args)
+ {
+ return this.subtle.sign(...args);
+ }
+ //**********************************************************************************
+ /**
+ * Wrapper for standard function "verify"
+ * @param args
+ * @returns {Promise}
+ */
+ verify(...args)
+ {
+ return this.subtle.verify(...args);
+ }
+ //**********************************************************************************
+ /**
+ * Wrapper for standard function "digest"
+ * @param args
+ * @returns {Promise}
+ */
+ digest(...args)
+ {
+ return this.subtle.digest(...args);
+ }
+ //**********************************************************************************
+ /**
+ * Wrapper for standard function "generateKey"
+ * @param args
+ * @returns {Promise}
+ */
+ generateKey(...args)
+ {
+ return this.subtle.generateKey(...args);
+ }
+ //**********************************************************************************
+ /**
+ * Wrapper for standard function "deriveKey"
+ * @param args
+ * @returns {Promise}
+ */
+ deriveKey(...args)
+ {
+ return this.subtle.deriveKey(...args);
+ }
+ //**********************************************************************************
+ /**
+ * Wrapper for standard function "deriveBits"
+ * @param args
+ * @returns {Promise}
+ */
+ deriveBits(...args)
+ {
+ return this.subtle.deriveBits(...args);
+ }
+ //**********************************************************************************
+ /**
+ * Wrapper for standard function "wrapKey"
+ * @param args
+ * @returns {Promise}
+ */
+ wrapKey(...args)
+ {
+ return this.subtle.wrapKey(...args);
+ }
+ //**********************************************************************************
+ /**
+ * Wrapper for standard function "unwrapKey"
+ * @param args
+ * @returns {Promise}
+ */
+ unwrapKey(...args)
+ {
+ return this.subtle.unwrapKey(...args);
+ }
+ //**********************************************************************************
+ /**
+ * Initialize input Uint8Array by random values (with help from current "crypto engine")
+ * @param {!Uint8Array} view
+ * @returns {*}
+ */
+ getRandomValues(view)
+ {
+ if(("getRandomValues" in this.crypto) === false)
+ throw new Error("No support for getRandomValues");
+
+ return this.crypto.getRandomValues(view);
+ }
+ //**********************************************************************************
+ /**
+ * Get WebCrypto algorithm by wel-known OID
+ * @param {string} oid well-known OID to search for
+ * @returns {Object}
+ */
+ getAlgorithmByOID(oid)
+ {
+ switch(oid)
+ {
+ case "1.2.840.113549.1.1.1":
+ case "1.2.840.113549.1.1.5":
+ return {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: {
+ name: "SHA-1"
+ }
+ };
+ case "1.2.840.113549.1.1.11":
+ return {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: {
+ name: "SHA-256"
+ }
+ };
+ case "1.2.840.113549.1.1.12":
+ return {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: {
+ name: "SHA-384"
+ }
+ };
+ case "1.2.840.113549.1.1.13":
+ return {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: {
+ name: "SHA-512"
+ }
+ };
+ case "1.2.840.113549.1.1.10":
+ return {
+ name: "RSA-PSS"
+ };
+ case "1.2.840.113549.1.1.7":
+ return {
+ name: "RSA-OAEP"
+ };
+ case "1.2.840.10045.2.1":
+ case "1.2.840.10045.4.1":
+ return {
+ name: "ECDSA",
+ hash: {
+ name: "SHA-1"
+ }
+ };
+ case "1.2.840.10045.4.3.2":
+ return {
+ name: "ECDSA",
+ hash: {
+ name: "SHA-256"
+ }
+ };
+ case "1.2.840.10045.4.3.3":
+ return {
+ name: "ECDSA",
+ hash: {
+ name: "SHA-384"
+ }
+ };
+ case "1.2.840.10045.4.3.4":
+ return {
+ name: "ECDSA",
+ hash: {
+ name: "SHA-512"
+ }
+ };
+ case "1.3.133.16.840.63.0.2":
+ return {
+ name: "ECDH",
+ kdf: "SHA-1"
+ };
+ case "1.3.132.1.11.1":
+ return {
+ name: "ECDH",
+ kdf: "SHA-256"
+ };
+ case "1.3.132.1.11.2":
+ return {
+ name: "ECDH",
+ kdf: "SHA-384"
+ };
+ case "1.3.132.1.11.3":
+ return {
+ name: "ECDH",
+ kdf: "SHA-512"
+ };
+ case "2.16.840.1.101.3.4.1.2":
+ return {
+ name: "AES-CBC",
+ length: 128
+ };
+ case "2.16.840.1.101.3.4.1.22":
+ return {
+ name: "AES-CBC",
+ length: 192
+ };
+ case "2.16.840.1.101.3.4.1.42":
+ return {
+ name: "AES-CBC",
+ length: 256
+ };
+ case "2.16.840.1.101.3.4.1.6":
+ return {
+ name: "AES-GCM",
+ length: 128
+ };
+ case "2.16.840.1.101.3.4.1.26":
+ return {
+ name: "AES-GCM",
+ length: 192
+ };
+ case "2.16.840.1.101.3.4.1.46":
+ return {
+ name: "AES-GCM",
+ length: 256
+ };
+ case "2.16.840.1.101.3.4.1.4":
+ return {
+ name: "AES-CFB",
+ length: 128
+ };
+ case "2.16.840.1.101.3.4.1.24":
+ return {
+ name: "AES-CFB",
+ length: 192
+ };
+ case "2.16.840.1.101.3.4.1.44":
+ return {
+ name: "AES-CFB",
+ length: 256
+ };
+ case "2.16.840.1.101.3.4.1.5":
+ return {
+ name: "AES-KW",
+ length: 128
+ };
+ case "2.16.840.1.101.3.4.1.25":
+ return {
+ name: "AES-KW",
+ length: 192
+ };
+ case "2.16.840.1.101.3.4.1.45":
+ return {
+ name: "AES-KW",
+ length: 256
+ };
+ case "1.2.840.113549.2.7":
+ return {
+ name: "HMAC",
+ hash: {
+ name: "SHA-1"
+ }
+ };
+ case "1.2.840.113549.2.9":
+ return {
+ name: "HMAC",
+ hash: {
+ name: "SHA-256"
+ }
+ };
+ case "1.2.840.113549.2.10":
+ return {
+ name: "HMAC",
+ hash: {
+ name: "SHA-384"
+ }
+ };
+ case "1.2.840.113549.2.11":
+ return {
+ name: "HMAC",
+ hash: {
+ name: "SHA-512"
+ }
+ };
+ case "1.2.840.113549.1.9.16.3.5":
+ return {
+ name: "DH"
+ };
+ case "1.3.14.3.2.26":
+ return {
+ name: "SHA-1"
+ };
+ case "2.16.840.1.101.3.4.2.1":
+ return {
+ name: "SHA-256"
+ };
+ case "2.16.840.1.101.3.4.2.2":
+ return {
+ name: "SHA-384"
+ };
+ case "2.16.840.1.101.3.4.2.3":
+ return {
+ name: "SHA-512"
+ };
+ case "1.2.840.113549.1.5.12":
+ return {
+ name: "PBKDF2"
+ };
+ //region Special case - OIDs for ECC curves
+ case "1.2.840.10045.3.1.7":
+ return {
+ name: "P-256"
+ };
+ case "1.3.132.0.34":
+ return {
+ name: "P-384"
+ };
+ case "1.3.132.0.35":
+ return {
+ name: "P-521"
+ };
+ //endregion
+ default:
+ }
+
+ return {};
+ }
+ //**********************************************************************************
+ /**
+ * Get OID for each specific algorithm
+ * @param {Object} algorithm
+ * @returns {string}
+ */
+ getOIDByAlgorithm(algorithm)
+ {
+ let result = "";
+
+ switch(algorithm.name.toUpperCase())
+ {
+ case "RSASSA-PKCS1-V1_5":
+ switch(algorithm.hash.name.toUpperCase())
+ {
+ case "SHA-1":
+ result = "1.2.840.113549.1.1.5";
+ break;
+ case "SHA-256":
+ result = "1.2.840.113549.1.1.11";
+ break;
+ case "SHA-384":
+ result = "1.2.840.113549.1.1.12";
+ break;
+ case "SHA-512":
+ result = "1.2.840.113549.1.1.13";
+ break;
+ default:
+ }
+ break;
+ case "RSA-PSS":
+ result = "1.2.840.113549.1.1.10";
+ break;
+ case "RSA-OAEP":
+ result = "1.2.840.113549.1.1.7";
+ break;
+ case "ECDSA":
+ switch(algorithm.hash.name.toUpperCase())
+ {
+ case "SHA-1":
+ result = "1.2.840.10045.4.1";
+ break;
+ case "SHA-256":
+ result = "1.2.840.10045.4.3.2";
+ break;
+ case "SHA-384":
+ result = "1.2.840.10045.4.3.3";
+ break;
+ case "SHA-512":
+ result = "1.2.840.10045.4.3.4";
+ break;
+ default:
+ }
+ break;
+ case "ECDH":
+ switch(algorithm.kdf.toUpperCase()) // Non-standard addition - hash algorithm of KDF function
+ {
+ case "SHA-1":
+ result = "1.3.133.16.840.63.0.2"; // dhSinglePass-stdDH-sha1kdf-scheme
+ break;
+ case "SHA-256":
+ result = "1.3.132.1.11.1"; // dhSinglePass-stdDH-sha256kdf-scheme
+ break;
+ case "SHA-384":
+ result = "1.3.132.1.11.2"; // dhSinglePass-stdDH-sha384kdf-scheme
+ break;
+ case "SHA-512":
+ result = "1.3.132.1.11.3"; // dhSinglePass-stdDH-sha512kdf-scheme
+ break;
+ default:
+ }
+ break;
+ case "AES-CTR":
+ break;
+ case "AES-CBC":
+ switch(algorithm.length)
+ {
+ case 128:
+ result = "2.16.840.1.101.3.4.1.2";
+ break;
+ case 192:
+ result = "2.16.840.1.101.3.4.1.22";
+ break;
+ case 256:
+ result = "2.16.840.1.101.3.4.1.42";
+ break;
+ default:
+ }
+ break;
+ case "AES-CMAC":
+ break;
+ case "AES-GCM":
+ switch(algorithm.length)
+ {
+ case 128:
+ result = "2.16.840.1.101.3.4.1.6";
+ break;
+ case 192:
+ result = "2.16.840.1.101.3.4.1.26";
+ break;
+ case 256:
+ result = "2.16.840.1.101.3.4.1.46";
+ break;
+ default:
+ }
+ break;
+ case "AES-CFB":
+ switch(algorithm.length)
+ {
+ case 128:
+ result = "2.16.840.1.101.3.4.1.4";
+ break;
+ case 192:
+ result = "2.16.840.1.101.3.4.1.24";
+ break;
+ case 256:
+ result = "2.16.840.1.101.3.4.1.44";
+ break;
+ default:
+ }
+ break;
+ case "AES-KW":
+ switch(algorithm.length)
+ {
+ case 128:
+ result = "2.16.840.1.101.3.4.1.5";
+ break;
+ case 192:
+ result = "2.16.840.1.101.3.4.1.25";
+ break;
+ case 256:
+ result = "2.16.840.1.101.3.4.1.45";
+ break;
+ default:
+ }
+ break;
+ case "HMAC":
+ switch(algorithm.hash.name.toUpperCase())
+ {
+ case "SHA-1":
+ result = "1.2.840.113549.2.7";
+ break;
+ case "SHA-256":
+ result = "1.2.840.113549.2.9";
+ break;
+ case "SHA-384":
+ result = "1.2.840.113549.2.10";
+ break;
+ case "SHA-512":
+ result = "1.2.840.113549.2.11";
+ break;
+ default:
+ }
+ break;
+ case "DH":
+ result = "1.2.840.113549.1.9.16.3.5";
+ break;
+ case "SHA-1":
+ result = "1.3.14.3.2.26";
+ break;
+ case "SHA-256":
+ result = "2.16.840.1.101.3.4.2.1";
+ break;
+ case "SHA-384":
+ result = "2.16.840.1.101.3.4.2.2";
+ break;
+ case "SHA-512":
+ result = "2.16.840.1.101.3.4.2.3";
+ break;
+ case "CONCAT":
+ break;
+ case "HKDF":
+ break;
+ case "PBKDF2":
+ result = "1.2.840.113549.1.5.12";
+ break;
+ //region Special case - OIDs for ECC curves
+ case "P-256":
+ result = "1.2.840.10045.3.1.7";
+ break;
+ case "P-384":
+ result = "1.3.132.0.34";
+ break;
+ case "P-521":
+ result = "1.3.132.0.35";
+ break;
+ //endregion
+ default:
+ }
+
+ return result;
+ }
+ //**********************************************************************************
+ /**
+ * Get default algorithm parameters for each kind of operation
+ * @param {string} algorithmName Algorithm name to get common parameters for
+ * @param {string} operation Kind of operation: "sign", "encrypt", "generatekey", "importkey", "exportkey", "verify"
+ * @returns {*}
+ */
+ getAlgorithmParameters(algorithmName, operation)
+ {
+ let result = {
+ algorithm: {},
+ usages: []
+ };
+
+ switch(algorithmName.toUpperCase())
+ {
+ case "RSASSA-PKCS1-V1_5":
+ switch(operation.toLowerCase())
+ {
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "RSASSA-PKCS1-v1_5",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
+ hash: {
+ name: "SHA-256"
+ }
+ },
+ usages: ["sign", "verify"]
+ };
+ break;
+ case "verify":
+ case "sign":
+ case "importkey":
+ result = {
+ algorithm: {
+ name: "RSASSA-PKCS1-v1_5",
+ hash: {
+ name: "SHA-256"
+ }
+ },
+ usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only
+ };
+ break;
+ case "exportkey":
+ default:
+ return {
+ algorithm: {
+ name: "RSASSA-PKCS1-v1_5"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "RSA-PSS":
+ switch(operation.toLowerCase())
+ {
+ case "sign":
+ case "verify":
+ result = {
+ algorithm: {
+ name: "RSA-PSS",
+ hash: {
+ name: "SHA-1"
+ },
+ saltLength: 20
+ },
+ usages: ["sign", "verify"]
+ };
+ break;
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "RSA-PSS",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
+ hash: {
+ name: "SHA-1"
+ }
+ },
+ usages: ["sign", "verify"]
+ };
+ break;
+ case "importkey":
+ result = {
+ algorithm: {
+ name: "RSA-PSS",
+ hash: {
+ name: "SHA-1"
+ }
+ },
+ usages: ["verify"] // For importKey("pkcs8") usage must be "sign" only
+ };
+ break;
+ case "exportkey":
+ default:
+ return {
+ algorithm: {
+ name: "RSA-PSS"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "RSA-OAEP":
+ switch(operation.toLowerCase())
+ {
+ case "encrypt":
+ case "decrypt":
+ result = {
+ algorithm: {
+ name: "RSA-OAEP"
+ },
+ usages: ["encrypt", "decrypt"]
+ };
+ break;
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "RSA-OAEP",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
+ hash: {
+ name: "SHA-256"
+ }
+ },
+ usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
+ };
+ break;
+ case "importkey":
+ result = {
+ algorithm: {
+ name: "RSA-OAEP",
+ hash: {
+ name: "SHA-256"
+ }
+ },
+ usages: ["encrypt"] // encrypt for "spki" and decrypt for "pkcs8"
+ };
+ break;
+ case "exportkey":
+ default:
+ return {
+ algorithm: {
+ name: "RSA-OAEP"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "ECDSA":
+ switch(operation.toLowerCase())
+ {
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "ECDSA",
+ namedCurve: "P-256"
+ },
+ usages: ["sign", "verify"]
+ };
+ break;
+ case "importkey":
+ result = {
+ algorithm: {
+ name: "ECDSA",
+ namedCurve: "P-256"
+ },
+ usages: ["verify"] // "sign" for "pkcs8"
+ };
+ break;
+ case "verify":
+ case "sign":
+ result = {
+ algorithm: {
+ name: "ECDSA",
+ hash: {
+ name: "SHA-256"
+ }
+ },
+ usages: ["sign"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "ECDSA"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "ECDH":
+ switch(operation.toLowerCase())
+ {
+ case "exportkey":
+ case "importkey":
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "ECDH",
+ namedCurve: "P-256"
+ },
+ usages: ["deriveKey", "deriveBits"]
+ };
+ break;
+ case "derivekey":
+ case "derivebits":
+ result = {
+ algorithm: {
+ name: "ECDH",
+ namedCurve: "P-256",
+ public: [] // Must be a "publicKey"
+ },
+ usages: ["encrypt", "decrypt"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "ECDH"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "AES-CTR":
+ switch(operation.toLowerCase())
+ {
+ case "importkey":
+ case "exportkey":
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "AES-CTR",
+ length: 256
+ },
+ usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
+ };
+ break;
+ case "decrypt":
+ case "encrypt":
+ result = {
+ algorithm: {
+ name: "AES-CTR",
+ counter: new Uint8Array(16),
+ length: 10
+ },
+ usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "AES-CTR"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "AES-CBC":
+ switch(operation.toLowerCase())
+ {
+ case "importkey":
+ case "exportkey":
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "AES-CBC",
+ length: 256
+ },
+ usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
+ };
+ break;
+ case "decrypt":
+ case "encrypt":
+ result = {
+ algorithm: {
+ name: "AES-CBC",
+ iv: this.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step
+ },
+ usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "AES-CBC"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "AES-GCM":
+ switch(operation.toLowerCase())
+ {
+ case "importkey":
+ case "exportkey":
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "AES-GCM",
+ length: 256
+ },
+ usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
+ };
+ break;
+ case "decrypt":
+ case "encrypt":
+ result = {
+ algorithm: {
+ name: "AES-GCM",
+ iv: this.getRandomValues(new Uint8Array(16)) // For "decrypt" the value should be replaced with value got on "encrypt" step
+ },
+ usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "AES-GCM"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "AES-KW":
+ switch(operation.toLowerCase())
+ {
+ case "importkey":
+ case "exportkey":
+ case "generatekey":
+ case "wrapkey":
+ case "unwrapkey":
+ result = {
+ algorithm: {
+ name: "AES-KW",
+ length: 256
+ },
+ usages: ["wrapKey", "unwrapKey"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "AES-KW"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "HMAC":
+ switch(operation.toLowerCase())
+ {
+ case "sign":
+ case "verify":
+ result = {
+ algorithm: {
+ name: "HMAC"
+ },
+ usages: ["sign", "verify"]
+ };
+ break;
+ case "importkey":
+ case "exportkey":
+ case "generatekey":
+ result = {
+ algorithm: {
+ name: "HMAC",
+ length: 32,
+ hash: {
+ name: "SHA-256"
+ }
+ },
+ usages: ["sign", "verify"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "HMAC"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "HKDF":
+ switch(operation.toLowerCase())
+ {
+ case "derivekey":
+ result = {
+ algorithm: {
+ name: "HKDF",
+ hash: "SHA-256",
+ salt: new Uint8Array([]),
+ info: new Uint8Array([])
+ },
+ usages: ["encrypt", "decrypt"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "HKDF"
+ },
+ usages: []
+ };
+ }
+ break;
+ case "PBKDF2":
+ switch(operation.toLowerCase())
+ {
+ case "derivekey":
+ result = {
+ algorithm: {
+ name: "PBKDF2",
+ hash: { name: "SHA-256" },
+ salt: new Uint8Array([]),
+ iterations: 10000
+ },
+ usages: ["encrypt", "decrypt"]
+ };
+ break;
+ default:
+ return {
+ algorithm: {
+ name: "PBKDF2"
+ },
+ usages: []
+ };
+ }
+ break;
+ default:
+ }
+
+ return result;
+ }
+ //**********************************************************************************
+ /**
+ * Getting hash algorithm by signature algorithm
+ * @param {AlgorithmIdentifier} signatureAlgorithm Signature algorithm
+ * @returns {string}
+ */
+ getHashAlgorithm(signatureAlgorithm)
+ {
+ let result = "";
+
+ switch(signatureAlgorithm.algorithmId)
+ {
+ case "1.2.840.10045.4.1": // ecdsa-with-SHA1
+ case "1.2.840.113549.1.1.5":
+ result = "SHA-1";
+ break;
+ case "1.2.840.10045.4.3.2": // ecdsa-with-SHA256
+ case "1.2.840.113549.1.1.11":
+ result = "SHA-256";
+ break;
+ case "1.2.840.10045.4.3.3": // ecdsa-with-SHA384
+ case "1.2.840.113549.1.1.12":
+ result = "SHA-384";
+ break;
+ case "1.2.840.10045.4.3.4": // ecdsa-with-SHA512
+ case "1.2.840.113549.1.1.13":
+ result = "SHA-512";
+ break;
+ case "1.2.840.113549.1.1.10": // RSA-PSS
+ {
+ try
+ {
+ const params = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams });
+ if("hashAlgorithm" in params)
+ {
+ const algorithm = this.getAlgorithmByOID(params.hashAlgorithm.algorithmId);
+ if(("name" in algorithm) === false)
+ return "";
+
+ result = algorithm.name;
+ }
+ else
+ result = "SHA-1";
+ }
+ catch(ex)
+ {
+ }
+ }
+ break;
+ default:
+ }
+
+ return result;
+ }
+ //**********************************************************************************
+ /**
+ * Specialized function encrypting "EncryptedContentInfo" object using parameters
+ * @param {Object} parameters
+ * @returns {Promise}
+ */
+ encryptEncryptedContentInfo(parameters)
+ {
+ //region Check for input parameters
+ if((parameters instanceof Object) === false)
+ return Promise.reject("Parameters must have type \"Object\"");
+
+ if(("password" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"password\"");
+
+ if(("contentEncryptionAlgorithm" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"contentEncryptionAlgorithm\"");
+
+ if(("hmacHashAlgorithm" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"hmacHashAlgorithm\"");
+
+ if(("iterationCount" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"iterationCount\"");
+
+ if(("contentToEncrypt" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"contentToEncrypt\"");
+
+ if(("contentType" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"contentType\"");
+
+ const contentEncryptionOID = this.getOIDByAlgorithm(parameters.contentEncryptionAlgorithm);
+ if(contentEncryptionOID === "")
+ return Promise.reject("Wrong \"contentEncryptionAlgorithm\" value");
+
+ const pbkdf2OID = this.getOIDByAlgorithm({
+ name: "PBKDF2"
+ });
+ if(pbkdf2OID === "")
+ return Promise.reject("Can not find OID for PBKDF2");
+
+ const hmacOID = this.getOIDByAlgorithm({
+ name: "HMAC",
+ hash: {
+ name: parameters.hmacHashAlgorithm
+ }
+ });
+ if(hmacOID === "")
+ return Promise.reject(`Incorrect value for "hmacHashAlgorithm": ${parameters.hmacHashAlgorithm}`);
+ //endregion
+
+ //region Initial variables
+ let sequence = Promise.resolve();
+
+ const ivBuffer = new ArrayBuffer(16); // For AES we need IV 16 bytes long
+ const ivView = new Uint8Array(ivBuffer);
+ this.getRandomValues(ivView);
+
+ const saltBuffer = new ArrayBuffer(64);
+ const saltView = new Uint8Array(saltBuffer);
+ this.getRandomValues(saltView);
+
+ const contentView = new Uint8Array(parameters.contentToEncrypt);
+
+ const pbkdf2Params = new PBKDF2Params({
+ salt: new asn1js.OctetString({ valueHex: saltBuffer }),
+ iterationCount: parameters.iterationCount,
+ prf: new AlgorithmIdentifier({
+ algorithmId: hmacOID,
+ algorithmParams: new asn1js.Null()
+ })
+ });
+ //endregion
+
+ //region Derive PBKDF2 key from "password" buffer
+ sequence = sequence.then(() =>
+ {
+ const passwordView = new Uint8Array(parameters.password);
+
+ return this.importKey("raw",
+ passwordView,
+ "PBKDF2",
+ false,
+ ["deriveKey"]);
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ //region Derive key for "contentEncryptionAlgorithm"
+ sequence = sequence.then(result =>
+ this.deriveKey({
+ name: "PBKDF2",
+ hash: {
+ name: parameters.hmacHashAlgorithm
+ },
+ salt: saltView,
+ iterations: parameters.iterationCount
+ },
+ result,
+ parameters.contentEncryptionAlgorithm,
+ false,
+ ["encrypt"]),
+ error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ //region Encrypt content
+ sequence = sequence.then(result =>
+ this.encrypt({
+ name: parameters.contentEncryptionAlgorithm.name,
+ iv: ivView
+ },
+ result,
+ contentView),
+ error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ //region Store all parameters in EncryptedData object
+ sequence = sequence.then(result =>
+ {
+ const pbes2Parameters = new PBES2Params({
+ keyDerivationFunc: new AlgorithmIdentifier({
+ algorithmId: pbkdf2OID,
+ algorithmParams: pbkdf2Params.toSchema()
+ }),
+ encryptionScheme: new AlgorithmIdentifier({
+ algorithmId: contentEncryptionOID,
+ algorithmParams: new asn1js.OctetString({ valueHex: ivBuffer })
+ })
+ });
+
+ return new EncryptedContentInfo({
+ contentType: parameters.contentType,
+ contentEncryptionAlgorithm: new AlgorithmIdentifier({
+ algorithmId: "1.2.840.113549.1.5.13", // pkcs5PBES2
+ algorithmParams: pbes2Parameters.toSchema()
+ }),
+ encryptedContent: new asn1js.OctetString({ valueHex: result })
+ });
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ /**
+ * Decrypt data stored in "EncryptedContentInfo" object using parameters
+ * @param parameters
+ * @return {Promise}
+ */
+ decryptEncryptedContentInfo(parameters)
+ {
+ //region Check for input parameters
+ if((parameters instanceof Object) === false)
+ return Promise.reject("Parameters must have type \"Object\"");
+
+ if(("password" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"password\"");
+
+ if(("encryptedContentInfo" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"encryptedContentInfo\"");
+
+ if(parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId !== "1.2.840.113549.1.5.13") // pkcs5PBES2
+ return Promise.reject(`Unknown "contentEncryptionAlgorithm": ${parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId}`);
+ //endregion
+
+ //region Initial variables
+ let sequence = Promise.resolve();
+
+ let pbes2Parameters;
+
+ try
+ {
+ pbes2Parameters = new PBES2Params({ schema: parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams });
+ }
+ catch(ex)
+ {
+ return Promise.reject("Incorrectly encoded \"pbes2Parameters\"");
+ }
+
+ let pbkdf2Params;
+
+ try
+ {
+ pbkdf2Params = new PBKDF2Params({ schema: pbes2Parameters.keyDerivationFunc.algorithmParams });
+ }
+ catch(ex)
+ {
+ return Promise.reject("Incorrectly encoded \"pbkdf2Params\"");
+ }
+
+ const contentEncryptionAlgorithm = this.getAlgorithmByOID(pbes2Parameters.encryptionScheme.algorithmId);
+ if(("name" in contentEncryptionAlgorithm) === false)
+ return Promise.reject(`Incorrect OID for "contentEncryptionAlgorithm": ${pbes2Parameters.encryptionScheme.algorithmId}`);
+
+ const ivBuffer = pbes2Parameters.encryptionScheme.algorithmParams.valueBlock.valueHex;
+ const ivView = new Uint8Array(ivBuffer);
+
+ const saltBuffer = pbkdf2Params.salt.valueBlock.valueHex;
+ const saltView = new Uint8Array(saltBuffer);
+
+ const iterationCount = pbkdf2Params.iterationCount;
+
+ let hmacHashAlgorithm = "SHA-1";
+
+ if("prf" in pbkdf2Params)
+ {
+ const algorithm = this.getAlgorithmByOID(pbkdf2Params.prf.algorithmId);
+ if(("name" in algorithm) === false)
+ return Promise.reject("Incorrect OID for HMAC hash algorithm");
+
+ hmacHashAlgorithm = algorithm.hash.name;
+ }
+ //endregion
+
+ //region Derive PBKDF2 key from "password" buffer
+ sequence = sequence.then(() =>
+ this.importKey("raw",
+ parameters.password,
+ "PBKDF2",
+ false,
+ ["deriveKey"]),
+ error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ //region Derive key for "contentEncryptionAlgorithm"
+ sequence = sequence.then(result =>
+ this.deriveKey({
+ name: "PBKDF2",
+ hash: {
+ name: hmacHashAlgorithm
+ },
+ salt: saltView,
+ iterations: iterationCount
+ },
+ result,
+ contentEncryptionAlgorithm,
+ false,
+ ["decrypt"]),
+ error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ //region Decrypt internal content using derived key
+ sequence = sequence.then(result =>
+ {
+ //region Create correct data block for decryption
+ let dataBuffer = new ArrayBuffer(0);
+
+ if(parameters.encryptedContentInfo.encryptedContent.idBlock.isConstructed === false)
+ dataBuffer = parameters.encryptedContentInfo.encryptedContent.valueBlock.valueHex;
+ else
+ {
+ for(const content of parameters.encryptedContentInfo.encryptedContent.valueBlock.value)
+ dataBuffer = utilConcatBuf(dataBuffer, content.valueBlock.valueHex);
+ }
+ //endregion
+
+ return this.decrypt({
+ name: contentEncryptionAlgorithm.name,
+ iv: ivView
+ },
+ result,
+ dataBuffer);
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ /**
+ * Stamping (signing) data using algorithm simular to HMAC
+ * @param {Object} parameters
+ * @return {Promise.|Promise}
+ */
+ stampDataWithPassword(parameters)
+ {
+ //region Check for input parameters
+ if((parameters instanceof Object) === false)
+ return Promise.reject("Parameters must have type \"Object\"");
+
+ if(("password" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"password\"");
+
+ if(("hashAlgorithm" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"hashAlgorithm\"");
+
+ if(("salt" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"iterationCount\"");
+
+ if(("iterationCount" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"salt\"");
+
+ if(("contentToStamp" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"contentToStamp\"");
+ //endregion
+
+ //region Choose correct length for HMAC key
+ let length;
+
+ switch(parameters.hashAlgorithm.toLowerCase())
+ {
+ case "sha-1":
+ length = 160;
+ break;
+ case "sha-256":
+ length = 256;
+ break;
+ case "sha-384":
+ length = 384;
+ break;
+ case "sha-512":
+ length = 512;
+ break;
+ default:
+ return Promise.reject(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`);
+ }
+ //endregion
+
+ //region Initial variables
+ let sequence = Promise.resolve();
+
+ const hmacAlgorithm = {
+ name: "HMAC",
+ length,
+ hash: {
+ name: parameters.hashAlgorithm
+ }
+ };
+ //endregion
+
+ //region Create PKCS#12 key for integrity checking
+ sequence = sequence.then(() => makePKCS12B2Key(this, parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount));
+ //endregion
+
+ //region Import HMAC key
+ // noinspection JSCheckFunctionSignatures
+ sequence = sequence.then(
+ result =>
+ this.importKey("raw",
+ new Uint8Array(result),
+ hmacAlgorithm,
+ false,
+ ["sign"])
+ );
+ //endregion
+
+ //region Make signed HMAC value
+ sequence = sequence.then(
+ result =>
+ this.sign(hmacAlgorithm, result, new Uint8Array(parameters.contentToStamp)),
+ error => Promise.reject(error)
+ );
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ verifyDataStampedWithPassword(parameters)
+ {
+ //region Check for input parameters
+ if((parameters instanceof Object) === false)
+ return Promise.reject("Parameters must have type \"Object\"");
+
+ if(("password" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"password\"");
+
+ if(("hashAlgorithm" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"hashAlgorithm\"");
+
+ if(("salt" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"iterationCount\"");
+
+ if(("iterationCount" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"salt\"");
+
+ if(("contentToVerify" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"contentToVerify\"");
+
+ if(("signatureToVerify" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"signatureToVerify\"");
+ //endregion
+
+ //region Choose correct length for HMAC key
+ let length;
+
+ switch(parameters.hashAlgorithm.toLowerCase())
+ {
+ case "sha-1":
+ length = 160;
+ break;
+ case "sha-256":
+ length = 256;
+ break;
+ case "sha-384":
+ length = 384;
+ break;
+ case "sha-512":
+ length = 512;
+ break;
+ default:
+ return Promise.reject(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`);
+ }
+ //endregion
+
+ //region Initial variables
+ let sequence = Promise.resolve();
+
+ const hmacAlgorithm = {
+ name: "HMAC",
+ length,
+ hash: {
+ name: parameters.hashAlgorithm
+ }
+ };
+ //endregion
+
+ //region Create PKCS#12 key for integrity checking
+ sequence = sequence.then(() => makePKCS12B2Key(this, parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount));
+ //endregion
+
+ //region Import HMAC key
+ // noinspection JSCheckFunctionSignatures
+ sequence = sequence.then(result =>
+ this.importKey("raw",
+ new Uint8Array(result),
+ hmacAlgorithm,
+ false,
+ ["verify"])
+ );
+ //endregion
+
+ //region Make signed HMAC value
+ sequence = sequence.then(
+ result =>
+ this.verify(hmacAlgorithm, result, new Uint8Array(parameters.signatureToVerify), new Uint8Array(parameters.contentToVerify)),
+ error => Promise.reject(error)
+ );
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ /**
+ * Get signature parameters by analyzing private key algorithm
+ * @param {Object} privateKey The private key user would like to use
+ * @param {string} [hashAlgorithm="SHA-1"] Hash algorithm user would like to use
+ * @return {Promise.|Promise}
+ */
+ getSignatureParameters(privateKey, hashAlgorithm = "SHA-1")
+ {
+ //region Check hashing algorithm
+ const oid = this.getOIDByAlgorithm({ name: hashAlgorithm });
+ if(oid === "")
+ return Promise.reject(`Unsupported hash algorithm: ${hashAlgorithm}`);
+ //endregion
+
+ //region Initial variables
+ const signatureAlgorithm = new AlgorithmIdentifier();
+ //endregion
+
+ //region Get a "default parameters" for current algorithm
+ const parameters = this.getAlgorithmParameters(privateKey.algorithm.name, "sign");
+ parameters.algorithm.hash.name = hashAlgorithm;
+ //endregion
+
+ //region Fill internal structures base on "privateKey" and "hashAlgorithm"
+ switch(privateKey.algorithm.name.toUpperCase())
+ {
+ case "RSASSA-PKCS1-V1_5":
+ case "ECDSA":
+ signatureAlgorithm.algorithmId = this.getOIDByAlgorithm(parameters.algorithm);
+ break;
+ case "RSA-PSS":
+ {
+ //region Set "saltLength" as a length (in octets) of hash function result
+ switch(hashAlgorithm.toUpperCase())
+ {
+ case "SHA-256":
+ parameters.algorithm.saltLength = 32;
+ break;
+ case "SHA-384":
+ parameters.algorithm.saltLength = 48;
+ break;
+ case "SHA-512":
+ parameters.algorithm.saltLength = 64;
+ break;
+ default:
+ }
+ //endregion
+
+ //region Fill "RSASSA_PSS_params" object
+ const paramsObject = {};
+
+ if(hashAlgorithm.toUpperCase() !== "SHA-1")
+ {
+ const hashAlgorithmOID = this.getOIDByAlgorithm({ name: hashAlgorithm });
+ if(hashAlgorithmOID === "")
+ return Promise.reject(`Unsupported hash algorithm: ${hashAlgorithm}`);
+
+ paramsObject.hashAlgorithm = new AlgorithmIdentifier({
+ algorithmId: hashAlgorithmOID,
+ algorithmParams: new asn1js.Null()
+ });
+
+ paramsObject.maskGenAlgorithm = new AlgorithmIdentifier({
+ algorithmId: "1.2.840.113549.1.1.8", // MGF1
+ algorithmParams: paramsObject.hashAlgorithm.toSchema()
+ });
+ }
+
+ if(parameters.algorithm.saltLength !== 20)
+ paramsObject.saltLength = parameters.algorithm.saltLength;
+
+ const pssParameters = new RSASSAPSSParams(paramsObject);
+ //endregion
+
+ //region Automatically set signature algorithm
+ signatureAlgorithm.algorithmId = "1.2.840.113549.1.1.10";
+ signatureAlgorithm.algorithmParams = pssParameters.toSchema();
+ //endregion
+ }
+ break;
+ default:
+ return Promise.reject(`Unsupported signature algorithm: ${privateKey.algorithm.name}`);
+ }
+ //endregion
+
+ return Promise.resolve().then(() => ({
+ signatureAlgorithm,
+ parameters
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Sign data with pre-defined private key
+ * @param {ArrayBuffer} data Data to be signed
+ * @param {Object} privateKey Private key to use
+ * @param {Object} parameters Parameters for used algorithm
+ * @return {Promise.|Promise}
+ */
+ signWithPrivateKey(data, privateKey, parameters)
+ {
+ return this.sign(parameters.algorithm,
+ privateKey,
+ new Uint8Array(data))
+ .then(result =>
+ {
+ //region Special case for ECDSA algorithm
+ if(parameters.algorithm.name === "ECDSA")
+ result = createCMSECDSASignature(result);
+ //endregion
+
+ return result;
+ }, error =>
+ Promise.reject(`Signing error: ${error}`)
+ );
+ }
+ //**********************************************************************************
+ fillPublicKeyParameters(publicKeyInfo, signatureAlgorithm)
+ {
+ const parameters = {};
+
+ //region Find signer's hashing algorithm
+ const shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm);
+ if(shaAlgorithm === "")
+ return Promise.reject(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`);
+ //endregion
+
+ //region Get information about public key algorithm and default parameters for import
+ let algorithmId;
+ if(signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10")
+ algorithmId = signatureAlgorithm.algorithmId;
+ else
+ algorithmId = publicKeyInfo.algorithm.algorithmId;
+
+ const algorithmObject = this.getAlgorithmByOID(algorithmId);
+ if(("name" in algorithmObject) === "")
+ return Promise.reject(`Unsupported public key algorithm: ${signatureAlgorithm.algorithmId}`);
+
+ parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importkey");
+ if("hash" in parameters.algorithm.algorithm)
+ parameters.algorithm.algorithm.hash.name = shaAlgorithm;
+
+ //region Special case for ECDSA
+ if(algorithmObject.name === "ECDSA")
+ {
+ //region Get information about named curve
+ let algorithmParamsChecked = false;
+
+ if(("algorithmParams" in publicKeyInfo.algorithm) === true)
+ {
+ if("idBlock" in publicKeyInfo.algorithm.algorithmParams)
+ {
+ if((publicKeyInfo.algorithm.algorithmParams.idBlock.tagClass === 1) && (publicKeyInfo.algorithm.algorithmParams.idBlock.tagNumber === 6))
+ algorithmParamsChecked = true;
+ }
+ }
+
+ if(algorithmParamsChecked === false)
+ return Promise.reject("Incorrect type for ECDSA public key parameters");
+
+ const curveObject = this.getAlgorithmByOID(publicKeyInfo.algorithm.algorithmParams.valueBlock.toString());
+ if(("name" in curveObject) === false)
+ return Promise.reject(`Unsupported named curve algorithm: ${publicKeyInfo.algorithm.algorithmParams.valueBlock.toString()}`);
+ //endregion
+
+ parameters.algorithm.algorithm.namedCurve = curveObject.name;
+ }
+ //endregion
+ //endregion
+
+ return parameters;
+ }
+ //**********************************************************************************
+ getPublicKey(publicKeyInfo, signatureAlgorithm, parameters = null)
+ {
+ if(parameters === null)
+ parameters = this.fillPublicKeyParameters(publicKeyInfo, signatureAlgorithm);
+
+ const publicKeyInfoSchema = publicKeyInfo.toSchema();
+ const publicKeyInfoBuffer = publicKeyInfoSchema.toBER(false);
+ const publicKeyInfoView = new Uint8Array(publicKeyInfoBuffer);
+
+ return this.importKey("spki",
+ publicKeyInfoView,
+ parameters.algorithm.algorithm,
+ true,
+ parameters.algorithm.usages
+ );
+ }
+ //**********************************************************************************
+ verifyWithPublicKey(data, signature, publicKeyInfo, signatureAlgorithm, shaAlgorithm = null)
+ {
+ //region Initial variables
+ let sequence = Promise.resolve();
+ //endregion
+
+ //region Find signer's hashing algorithm
+ if(shaAlgorithm === null)
+ {
+ shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm);
+ if(shaAlgorithm === "")
+ return Promise.reject(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`);
+
+ //region Import public key
+ sequence = sequence.then(() =>
+ this.getPublicKey(publicKeyInfo, signatureAlgorithm));
+ //endregion
+ }
+ else
+ {
+ const parameters = {};
+
+ //region Get information about public key algorithm and default parameters for import
+ let algorithmId;
+ if(signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10")
+ algorithmId = signatureAlgorithm.algorithmId;
+ else
+ algorithmId = publicKeyInfo.algorithm.algorithmId;
+
+ const algorithmObject = this.getAlgorithmByOID(algorithmId);
+ if(("name" in algorithmObject) === "")
+ return Promise.reject(`Unsupported public key algorithm: ${signatureAlgorithm.algorithmId}`);
+
+ parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importkey");
+ if("hash" in parameters.algorithm.algorithm)
+ parameters.algorithm.algorithm.hash.name = shaAlgorithm;
+
+ //region Special case for ECDSA
+ if(algorithmObject.name === "ECDSA")
+ {
+ //region Get information about named curve
+ let algorithmParamsChecked = false;
+
+ if(("algorithmParams" in publicKeyInfo.algorithm) === true)
+ {
+ if("idBlock" in publicKeyInfo.algorithm.algorithmParams)
+ {
+ if((publicKeyInfo.algorithm.algorithmParams.idBlock.tagClass === 1) && (publicKeyInfo.algorithm.algorithmParams.idBlock.tagNumber === 6))
+ algorithmParamsChecked = true;
+ }
+ }
+
+ if(algorithmParamsChecked === false)
+ return Promise.reject("Incorrect type for ECDSA public key parameters");
+
+ const curveObject = this.getAlgorithmByOID(publicKeyInfo.algorithm.algorithmParams.valueBlock.toString());
+ if(("name" in curveObject) === false)
+ return Promise.reject(`Unsupported named curve algorithm: ${publicKeyInfo.algorithm.algorithmParams.valueBlock.toString()}`);
+ //endregion
+
+ parameters.algorithm.algorithm.namedCurve = curveObject.name;
+ }
+ //endregion
+ //endregion
+
+ //region Import public key
+ sequence = sequence.then(() =>
+ this.getPublicKey(publicKeyInfo, null, parameters));
+ //endregion
+ }
+ //endregion
+
+ //region Verify signature
+ sequence = sequence.then(publicKey =>
+ {
+ //region Get default algorithm parameters for verification
+ const algorithm = this.getAlgorithmParameters(publicKey.algorithm.name, "verify");
+ if("hash" in algorithm.algorithm)
+ algorithm.algorithm.hash.name = shaAlgorithm;
+ //endregion
+
+ //region Special case for ECDSA signatures
+ let signatureValue = signature.valueBlock.valueHex;
+
+ if(publicKey.algorithm.name === "ECDSA")
+ {
+ const asn1 = asn1js.fromBER(signatureValue);
+ // noinspection JSCheckFunctionSignatures
+ signatureValue = createECDSASignatureFromCMS(asn1.result);
+ }
+ //endregion
+
+ //region Special case for RSA-PSS
+ if(publicKey.algorithm.name === "RSA-PSS")
+ {
+ let pssParameters;
+
+ try
+ {
+ pssParameters = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams });
+ }
+ catch(ex)
+ {
+ return Promise.reject(ex);
+ }
+
+ if("saltLength" in pssParameters)
+ algorithm.algorithm.saltLength = pssParameters.saltLength;
+ else
+ algorithm.algorithm.saltLength = 20;
+
+ let hashAlgo = "SHA-1";
+
+ if("hashAlgorithm" in pssParameters)
+ {
+ const hashAlgorithm = this.getAlgorithmByOID(pssParameters.hashAlgorithm.algorithmId);
+ if(("name" in hashAlgorithm) === false)
+ return Promise.reject(`Unrecognized hash algorithm: ${pssParameters.hashAlgorithm.algorithmId}`);
+
+ hashAlgo = hashAlgorithm.name;
+ }
+
+ algorithm.algorithm.hash.name = hashAlgo;
+ }
+ //endregion
+
+ return this.verify(algorithm.algorithm,
+ publicKey,
+ new Uint8Array(signatureValue),
+ new Uint8Array(data)
+ );
+ });
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/DigestInfo.js b/pki.js/DigestInfo.js
new file mode 100644
index 0000000..96571aa
--- /dev/null
+++ b/pki.js/DigestInfo.js
@@ -0,0 +1,179 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+//**************************************************************************************
+/**
+ * Class from RFC3447
+ */
+export default class DigestInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for DigestInfo 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 {AlgorithmIdentifier}
+ * @desc digestAlgorithm
+ */
+ this.digestAlgorithm = getParametersValue(parameters, "digestAlgorithm", DigestInfo.defaultValues("digestAlgorithm"));
+ /**
+ * @type {OctetString}
+ * @desc digest
+ */
+ this.digest = getParametersValue(parameters, "digest", DigestInfo.defaultValues("digest"));
+ //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 "digestAlgorithm":
+ return new AlgorithmIdentifier();
+ case "digest":
+ return new asn1js.OctetString();
+ default:
+ throw new Error(`Invalid member name for DigestInfo 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 "digestAlgorithm":
+ return ((AlgorithmIdentifier.compareWithDefault("algorithmId", memberValue.algorithmId)) &&
+ (("algorithmParams" in memberValue) === false));
+ case "digest":
+ return (memberValue.isEqual(DigestInfo.defaultValues(memberName)));
+ default:
+ throw new Error(`Invalid member name for DigestInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * DigestInfo ::= SEQUENCE {
+ * digestAlgorithm DigestAlgorithmIdentifier,
+ * digest Digest }
+ *
+ * Digest ::= OCTET STRING
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [type]
+ * @property {string} [setName]
+ * @property {string} [values]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ AlgorithmIdentifier.schema(names.digestAlgorithm || {
+ names: {
+ blockName: "digestAlgorithm"
+ }
+ }),
+ new asn1js.OctetString({ name: (names.digest || "digest") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "digestAlgorithm",
+ "digest"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ DigestInfo.schema({
+ names: {
+ digestAlgorithm: {
+ names: {
+ blockName: "digestAlgorithm"
+ }
+ },
+ digest: "digest"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for DigestInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.digestAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.digestAlgorithm });
+ this.digest = asn1.result.digest;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ this.digestAlgorithm.toSchema(),
+ this.digest
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ digestAlgorithm: this.digestAlgorithm.toJSON(),
+ digest: this.digest.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/DistributionPoint.js b/pki.js/DistributionPoint.js
new file mode 100644
index 0000000..2c0dcff
--- /dev/null
+++ b/pki.js/DistributionPoint.js
@@ -0,0 +1,334 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import GeneralName from "./GeneralName.js";
+import RelativeDistinguishedNames from "./RelativeDistinguishedNames.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class DistributionPoint
+{
+ //**********************************************************************************
+ /**
+ * Constructor for DistributionPoint class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ * @property {Object} [distributionPoint]
+ * @property {Object} [reasons]
+ * @property {Object} [cRLIssuer]
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ if("distributionPoint" in parameters)
+ /**
+ * @type {Array.}
+ * @desc distributionPoint
+ */
+ this.distributionPoint = getParametersValue(parameters, "distributionPoint", DistributionPoint.defaultValues("distributionPoint"));
+
+ if("reasons" in parameters)
+ /**
+ * @type {BitString}
+ * @desc values
+ */
+ this.reasons = getParametersValue(parameters, "reasons", DistributionPoint.defaultValues("reasons"));
+
+ if("cRLIssuer" in parameters)
+ /**
+ * @type {Array.}
+ * @desc cRLIssuer
+ */
+ this.cRLIssuer = getParametersValue(parameters, "cRLIssuer", DistributionPoint.defaultValues("cRLIssuer"));
+ //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 "distributionPoint":
+ return [];
+ case "reasons":
+ return new asn1js.BitString();
+ case "cRLIssuer":
+ return [];
+ default:
+ throw new Error(`Invalid member name for DistributionPoint class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * DistributionPoint ::= SEQUENCE {
+ * distributionPoint [0] DistributionPointName OPTIONAL,
+ * reasons [1] ReasonFlags OPTIONAL,
+ * cRLIssuer [2] GeneralNames OPTIONAL }
+ *
+ * DistributionPointName ::= CHOICE {
+ * fullName [0] GeneralNames,
+ * nameRelativeToCRLIssuer [1] RelativeDistinguishedName }
+ *
+ * ReasonFlags ::= BIT STRING {
+ * unused (0),
+ * keyCompromise (1),
+ * cACompromise (2),
+ * affiliationChanged (3),
+ * superseded (4),
+ * cessationOfOperation (5),
+ * certificateHold (6),
+ * privilegeWithdrawn (7),
+ * aACompromise (8) }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [distributionPoint]
+ * @property {string} [distributionPointNames]
+ * @property {string} [reasons]
+ * @property {string} [cRLIssuer]
+ * @property {string} [cRLIssuerNames]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Choice({
+ value: [
+ new asn1js.Constructed({
+ name: (names.distributionPoint || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Repeated({
+ name: (names.distributionPointNames || ""),
+ value: GeneralName.schema()
+ })
+ ]
+ }),
+ new asn1js.Constructed({
+ name: (names.distributionPoint || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: RelativeDistinguishedNames.schema().valueBlock.value
+ })
+ ]
+ })
+ ]
+ }),
+ new asn1js.Primitive({
+ name: (names.reasons || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ }
+ }), // IMPLICIT bitstring value
+ new asn1js.Constructed({
+ name: (names.cRLIssuer || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ value: [
+ new asn1js.Repeated({
+ name: (names.cRLIssuerNames || ""),
+ value: GeneralName.schema()
+ })
+ ]
+ }) // IMPLICIT bitstring value
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "distributionPoint",
+ "distributionPointNames",
+ "reasons",
+ "cRLIssuer",
+ "cRLIssuerNames"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ DistributionPoint.schema({
+ names: {
+ distributionPoint: "distributionPoint",
+ distributionPointNames: "distributionPointNames",
+ reasons: "reasons",
+ cRLIssuer: "cRLIssuer",
+ cRLIssuerNames: "cRLIssuerNames"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for DistributionPoint");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("distributionPoint" in asn1.result)
+ {
+ if(asn1.result.distributionPoint.idBlock.tagNumber === 0) // GENERAL_NAMES variant
+ this.distributionPoint = Array.from(asn1.result.distributionPointNames, element => new GeneralName({ schema: element }));
+
+ if(asn1.result.distributionPoint.idBlock.tagNumber === 1) // RDN variant
+ {
+ this.distributionPoint = new RelativeDistinguishedNames({
+ schema: new asn1js.Sequence({
+ value: asn1.result.distributionPoint.valueBlock.value
+ })
+ });
+ }
+ }
+
+ if("reasons" in asn1.result)
+ this.reasons = new asn1js.BitString({ valueHex: asn1.result.reasons.valueBlock.valueHex });
+
+ if("cRLIssuer" in asn1.result)
+ this.cRLIssuer = Array.from(asn1.result.cRLIssuerNames, element => new GeneralName({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ if("distributionPoint" in this)
+ {
+ let internalValue;
+
+ if(this.distributionPoint instanceof Array)
+ {
+ internalValue = new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: Array.from(this.distributionPoint, element => element.toSchema())
+ });
+ }
+ else
+ {
+ internalValue = new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [this.distributionPoint.toSchema()]
+ });
+ }
+
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [internalValue]
+ }));
+ }
+
+ if("reasons" in this)
+ {
+ outputArray.push(new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ valueHex: this.reasons.valueBlock.valueHex
+ }));
+ }
+
+ if("cRLIssuer" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ value: Array.from(this.cRLIssuer, 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 = {};
+
+ if("distributionPoint" in this)
+ {
+ if(this.distributionPoint instanceof Array)
+ object.distributionPoint = Array.from(this.distributionPoint, element => element.toJSON());
+ else
+ object.distributionPoint = this.distributionPoint.toJSON();
+ }
+
+ if("reasons" in this)
+ object.reasons = this.reasons.toJSON();
+
+ if("cRLIssuer" in this)
+ object.cRLIssuer = Array.from(this.cRLIssuer, element => element.toJSON());
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/ECCCMSSharedInfo.js b/pki.js/ECCCMSSharedInfo.js
new file mode 100644
index 0000000..38a7eb6
--- /dev/null
+++ b/pki.js/ECCCMSSharedInfo.js
@@ -0,0 +1,233 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+//**************************************************************************************
+/**
+ * Class from RFC6318
+ */
+export default class ECCCMSSharedInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for ECCCMSSharedInfo 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 {AlgorithmIdentifier}
+ * @desc keyInfo
+ */
+ this.keyInfo = getParametersValue(parameters, "keyInfo", ECCCMSSharedInfo.defaultValues("keyInfo"));
+
+ if("entityUInfo" in parameters)
+ /**
+ * @type {OctetString}
+ * @desc entityUInfo
+ */
+ this.entityUInfo = getParametersValue(parameters, "entityUInfo", ECCCMSSharedInfo.defaultValues("entityUInfo"));
+
+ /**
+ * @type {OctetString}
+ * @desc suppPubInfo
+ */
+ this.suppPubInfo = getParametersValue(parameters, "suppPubInfo", ECCCMSSharedInfo.defaultValues("suppPubInfo"));
+ //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 "keyInfo":
+ return new AlgorithmIdentifier();
+ case "entityUInfo":
+ return new asn1js.OctetString();
+ case "suppPubInfo":
+ return new asn1js.OctetString();
+ default:
+ throw new Error(`Invalid member name for ECCCMSSharedInfo 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 "keyInfo":
+ case "entityUInfo":
+ case "suppPubInfo":
+ return (memberValue.isEqual(ECCCMSSharedInfo.defaultValues(memberName)));
+ default:
+ throw new Error(`Invalid member name for ECCCMSSharedInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * ECC-CMS-SharedInfo ::= SEQUENCE {
+ * keyInfo AlgorithmIdentifier,
+ * entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL,
+ * suppPubInfo [2] EXPLICIT OCTET STRING }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [keyInfo]
+ * @property {string} [entityUInfo]
+ * @property {string} [suppPubInfo]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ AlgorithmIdentifier.schema(names.keyInfo || {}),
+ new asn1js.Constructed({
+ name: (names.entityUInfo || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ optional: true,
+ value: [new asn1js.OctetString()]
+ }),
+ new asn1js.Constructed({
+ name: (names.suppPubInfo || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ value: [new asn1js.OctetString()]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "keyInfo",
+ "entityUInfo",
+ "suppPubInfo"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ ECCCMSSharedInfo.schema({
+ names: {
+ keyInfo: {
+ names: {
+ blockName: "keyInfo"
+ }
+ },
+ entityUInfo: "entityUInfo",
+ suppPubInfo: "suppPubInfo"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for ECCCMSSharedInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.keyInfo = new AlgorithmIdentifier({ schema: asn1.result.keyInfo });
+
+ if("entityUInfo" in asn1.result)
+ this.entityUInfo = asn1.result.entityUInfo.valueBlock.value[0];
+
+ this.suppPubInfo = asn1.result.suppPubInfo.valueBlock.value[0];
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create output array for sequence
+ const outputArray = [];
+
+ outputArray.push(this.keyInfo.toSchema());
+
+ if("entityUInfo" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [this.entityUInfo]
+ }));
+ }
+
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ value: [this.suppPubInfo]
+ }));
+ //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 = {
+ keyInfo: this.keyInfo.toJSON()
+ };
+
+ if("entityUInfo" in this)
+ _object.entityUInfo = this.entityUInfo.toJSON();
+
+ _object.suppPubInfo = this.suppPubInfo.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/ECPrivateKey.js b/pki.js/ECPrivateKey.js
new file mode 100644
index 0000000..7f2c2e3
--- /dev/null
+++ b/pki.js/ECPrivateKey.js
@@ -0,0 +1,344 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, toBase64, arrayBufferToString, stringToArrayBuffer, fromBase64, clearProps } from "pvutils";
+import ECPublicKey from "./ECPublicKey.js";
+//**************************************************************************************
+/**
+ * Class from RFC5915
+ */
+export default class ECPrivateKey
+{
+ //**********************************************************************************
+ /**
+ * Constructor for ECPrivateKey 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 {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", ECPrivateKey.defaultValues("version"));
+ /**
+ * @type {OctetString}
+ * @desc privateKey
+ */
+ this.privateKey = getParametersValue(parameters, "privateKey", ECPrivateKey.defaultValues("privateKey"));
+
+ if("namedCurve" in parameters)
+ /**
+ * @type {string}
+ * @desc namedCurve
+ */
+ this.namedCurve = getParametersValue(parameters, "namedCurve", ECPrivateKey.defaultValues("namedCurve"));
+
+ if("publicKey" in parameters)
+ /**
+ * @type {ECPublicKey}
+ * @desc publicKey
+ */
+ this.publicKey = getParametersValue(parameters, "publicKey", ECPrivateKey.defaultValues("publicKey"));
+ //endregion
+
+ //region If input argument array contains "schema" for this object
+ if("schema" in parameters)
+ this.fromSchema(parameters.schema);
+ //endregion
+ //region If input argument array contains "json" for this object
+ if("json" in parameters)
+ this.fromJSON(parameters.json);
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Return default values for all class members
+ * @param {string} memberName String name for a class member
+ */
+ static defaultValues(memberName)
+ {
+ switch(memberName)
+ {
+ case "version":
+ return 1;
+ case "privateKey":
+ return new asn1js.OctetString();
+ case "namedCurve":
+ return "";
+ case "publicKey":
+ return new ECPublicKey();
+ default:
+ throw new Error(`Invalid member name for ECCPrivateKey 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 "version":
+ return (memberValue === ECPrivateKey.defaultValues(memberName));
+ case "privateKey":
+ return (memberValue.isEqual(ECPrivateKey.defaultValues(memberName)));
+ case "namedCurve":
+ return (memberValue === "");
+ case "publicKey":
+ return ((ECPublicKey.compareWithDefault("namedCurve", memberValue.namedCurve)) &&
+ (ECPublicKey.compareWithDefault("x", memberValue.x)) &&
+ (ECPublicKey.compareWithDefault("y", memberValue.y)));
+ default:
+ throw new Error(`Invalid member name for ECCPrivateKey class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * ECPrivateKey ::= SEQUENCE {
+ * version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+ * privateKey OCTET STRING,
+ * parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
+ * publicKey [1] BIT STRING OPTIONAL
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [version]
+ * @property {string} [privateKey]
+ * @property {string} [namedCurve]
+ * @property {string} [publicKey]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Integer({ name: (names.version || "") }),
+ new asn1js.OctetString({ name: (names.privateKey || "") }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.namedCurve || "") })
+ ]
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [
+ new asn1js.BitString({ name: (names.publicKey || "") })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "version",
+ "privateKey",
+ "namedCurve",
+ "publicKey"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ ECPrivateKey.schema({
+ names: {
+ version: "version",
+ privateKey: "privateKey",
+ namedCurve: "namedCurve",
+ publicKey: "publicKey"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for ECPrivateKey");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result.version.valueBlock.valueDec;
+ this.privateKey = asn1.result.privateKey;
+
+ if("namedCurve" in asn1.result)
+ this.namedCurve = asn1.result.namedCurve.valueBlock.toString();
+
+ if("publicKey" in asn1.result)
+ {
+ const publicKeyData = { schema: asn1.result.publicKey.valueBlock.valueHex };
+ if("namedCurve" in this)
+ publicKeyData.namedCurve = this.namedCurve;
+
+ this.publicKey = new ECPublicKey(publicKeyData);
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ const outputArray = [
+ new asn1js.Integer({ value: this.version }),
+ this.privateKey
+ ];
+
+ if("namedCurve" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.ObjectIdentifier({ value: this.namedCurve })
+ ]
+ }));
+ }
+
+ if("publicKey" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [
+ new asn1js.BitString({ valueHex: this.publicKey.toSchema().toBER(false) })
+ ]
+ }));
+ }
+
+ return new asn1js.Sequence({
+ value: outputArray
+ });
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ if((("namedCurve" in this) === false) || (ECPrivateKey.compareWithDefault("namedCurve", this.namedCurve)))
+ throw new Error("Not enough information for making JSON: absent \"namedCurve\" value");
+
+ let crvName = "";
+
+ switch(this.namedCurve)
+ {
+ case "1.2.840.10045.3.1.7": // P-256
+ crvName = "P-256";
+ break;
+ case "1.3.132.0.34": // P-384
+ crvName = "P-384";
+ break;
+ case "1.3.132.0.35": // P-521
+ crvName = "P-521";
+ break;
+ default:
+ }
+
+ const privateKeyJSON = {
+ crv: crvName,
+ d: toBase64(arrayBufferToString(this.privateKey.valueBlock.valueHex), true, true, false)
+ };
+
+ if("publicKey" in this)
+ {
+ const publicKeyJSON = this.publicKey.toJSON();
+
+ privateKeyJSON.x = publicKeyJSON.x;
+ privateKeyJSON.y = publicKeyJSON.y;
+ }
+
+ return privateKeyJSON;
+ }
+ //**********************************************************************************
+ /**
+ * Convert JSON value into current object
+ * @param {Object} json
+ */
+ fromJSON(json)
+ {
+ let coodinateLength = 0;
+
+ if("crv" in json)
+ {
+ switch(json.crv.toUpperCase())
+ {
+ case "P-256":
+ this.namedCurve = "1.2.840.10045.3.1.7";
+ coodinateLength = 32;
+ break;
+ case "P-384":
+ this.namedCurve = "1.3.132.0.34";
+ coodinateLength = 48;
+ break;
+ case "P-521":
+ this.namedCurve = "1.3.132.0.35";
+ coodinateLength = 66;
+ break;
+ default:
+ }
+ }
+ else
+ throw new Error("Absent mandatory parameter \"crv\"");
+
+ if("d" in json)
+ {
+ const convertBuffer = stringToArrayBuffer(fromBase64(json.d, true));
+
+ if(convertBuffer.byteLength < coodinateLength)
+ {
+ const buffer = new ArrayBuffer(coodinateLength);
+ const view = new Uint8Array(buffer);
+ const convertBufferView = new Uint8Array(convertBuffer);
+ view.set(convertBufferView, 1);
+
+ this.privateKey = new asn1js.OctetString({ valueHex: buffer });
+ }
+ else
+ this.privateKey = new asn1js.OctetString({ valueHex: convertBuffer.slice(0, coodinateLength) });
+ }
+ else
+ throw new Error("Absent mandatory parameter \"d\"");
+
+ if(("x" in json) && ("y" in json))
+ this.publicKey = new ECPublicKey({ json });
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/ECPublicKey.js b/pki.js/ECPublicKey.js
new file mode 100644
index 0000000..7504b46
--- /dev/null
+++ b/pki.js/ECPublicKey.js
@@ -0,0 +1,242 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, utilConcatBuf, isEqualBuffer, toBase64, fromBase64, arrayBufferToString, stringToArrayBuffer } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC5480
+ */
+export default class ECPublicKey
+{
+ //**********************************************************************************
+ /**
+ * Constructor for ECCPublicKey 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 {ArrayBuffer}
+ * @desc type
+ */
+ this.x = getParametersValue(parameters, "x", ECPublicKey.defaultValues("x"));
+ /**
+ * @type {ArrayBuffer}
+ * @desc values
+ */
+ this.y = getParametersValue(parameters, "y", ECPublicKey.defaultValues("y"));
+ /**
+ * @type {string}
+ * @desc namedCurve
+ */
+ this.namedCurve = getParametersValue(parameters, "namedCurve", ECPublicKey.defaultValues("namedCurve"));
+ //endregion
+
+ //region If input argument array contains "schema" for this object
+ if("schema" in parameters)
+ this.fromSchema(parameters.schema);
+ //endregion
+ //region If input argument array contains "json" for this object
+ if("json" in parameters)
+ this.fromJSON(parameters.json);
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Return default values for all class members
+ * @param {string} memberName String name for a class member
+ */
+ static defaultValues(memberName)
+ {
+ switch(memberName)
+ {
+ case "x":
+ case "y":
+ return new ArrayBuffer(0);
+ case "namedCurve":
+ return "";
+ default:
+ throw new Error(`Invalid member name for ECCPublicKey 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 "x":
+ case "y":
+ return (isEqualBuffer(memberValue, ECPublicKey.defaultValues(memberName)));
+ case "namedCurve":
+ return (memberValue === "");
+ default:
+ throw new Error(`Invalid member name for ECCPublicKey class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ return new asn1js.RawData();
+ }
+ //**********************************************************************************
+ /**
+ * Convert ArrayBuffer into current class
+ * @param {!ArrayBuffer} schema Special case: schema is an ArrayBuffer
+ */
+ fromSchema(schema)
+ {
+ //region Check the schema is valid
+ if((schema instanceof ArrayBuffer) === false)
+ throw new Error("Object's schema was not verified against input data for ECPublicKey");
+
+ const view = new Uint8Array(schema);
+ if(view[0] !== 0x04)
+ throw new Error("Object's schema was not verified against input data for ECPublicKey");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ let coordinateLength;
+
+ switch(this.namedCurve)
+ {
+ case "1.2.840.10045.3.1.7": // P-256
+ coordinateLength = 32;
+ break;
+ case "1.3.132.0.34": // P-384
+ coordinateLength = 48;
+ break;
+ case "1.3.132.0.35": // P-521
+ coordinateLength = 66;
+ break;
+ default:
+ throw new Error(`Incorrect curve OID: ${this.namedCurve}`);
+ }
+
+ if(schema.byteLength !== (coordinateLength * 2 + 1))
+ throw new Error("Object's schema was not verified against input data for ECPublicKey");
+
+ this.x = schema.slice(1, coordinateLength + 1);
+ this.y = schema.slice(1 + coordinateLength, coordinateLength * 2 + 1);
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ return new asn1js.RawData({ data: utilConcatBuf(
+ (new Uint8Array([0x04])).buffer,
+ this.x,
+ this.y
+ )
+ });
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ let crvName = "";
+
+ switch(this.namedCurve)
+ {
+ case "1.2.840.10045.3.1.7": // P-256
+ crvName = "P-256";
+ break;
+ case "1.3.132.0.34": // P-384
+ crvName = "P-384";
+ break;
+ case "1.3.132.0.35": // P-521
+ crvName = "P-521";
+ break;
+ default:
+ }
+
+ return {
+ crv: crvName,
+ x: toBase64(arrayBufferToString(this.x), true, true, false),
+ y: toBase64(arrayBufferToString(this.y), true, true, false)
+ };
+ }
+ //**********************************************************************************
+ /**
+ * Convert JSON value into current object
+ * @param {Object} json
+ */
+ fromJSON(json)
+ {
+ let coodinateLength = 0;
+
+ if("crv" in json)
+ {
+ switch(json.crv.toUpperCase())
+ {
+ case "P-256":
+ this.namedCurve = "1.2.840.10045.3.1.7";
+ coodinateLength = 32;
+ break;
+ case "P-384":
+ this.namedCurve = "1.3.132.0.34";
+ coodinateLength = 48;
+ break;
+ case "P-521":
+ this.namedCurve = "1.3.132.0.35";
+ coodinateLength = 66;
+ break;
+ default:
+ }
+ }
+ else
+ throw new Error("Absent mandatory parameter \"crv\"");
+
+ if("x" in json)
+ {
+ const convertBuffer = stringToArrayBuffer(fromBase64(json.x, true));
+
+ if(convertBuffer.byteLength < coodinateLength)
+ {
+ this.x = new ArrayBuffer(coodinateLength);
+ const view = new Uint8Array(this.x);
+ const convertBufferView = new Uint8Array(convertBuffer);
+ view.set(convertBufferView, 1);
+ }
+ else
+ this.x = convertBuffer.slice(0, coodinateLength);
+ }
+ else
+ throw new Error("Absent mandatory parameter \"x\"");
+
+ if("y" in json)
+ {
+ const convertBuffer = stringToArrayBuffer(fromBase64(json.y, true));
+
+ if(convertBuffer.byteLength < coodinateLength)
+ {
+ this.y = new ArrayBuffer(coodinateLength);
+ const view = new Uint8Array(this.y);
+ const convertBufferView = new Uint8Array(convertBuffer);
+ view.set(convertBufferView, 1);
+ }
+ else
+ this.y = convertBuffer.slice(0, coodinateLength);
+ }
+ else
+ throw new Error("Absent mandatory parameter \"y\"");
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/EncapsulatedContentInfo.js b/pki.js/EncapsulatedContentInfo.js
new file mode 100644
index 0000000..de91cc7
--- /dev/null
+++ b/pki.js/EncapsulatedContentInfo.js
@@ -0,0 +1,243 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class EncapsulatedContentInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for EncapsulatedContentInfo 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 {string}
+ * @desc eContentType
+ */
+ this.eContentType = getParametersValue(parameters, "eContentType", EncapsulatedContentInfo.defaultValues("eContentType"));
+
+ if("eContent" in parameters)
+ {
+ /**
+ * @type {OctetString}
+ * @desc eContent
+ */
+ this.eContent = getParametersValue(parameters, "eContent", EncapsulatedContentInfo.defaultValues("eContent"));
+ if((this.eContent.idBlock.tagClass === 1) &&
+ (this.eContent.idBlock.tagNumber === 4))
+ {
+ //region Divide OCTETSTRING value down to small pieces
+ if(this.eContent.idBlock.isConstructed === false)
+ {
+ const constrString = new asn1js.OctetString({
+ idBlock: { isConstructed: true },
+ isConstructed: true
+ });
+
+ let offset = 0;
+ let length = this.eContent.valueBlock.valueHex.byteLength;
+
+ while(length > 0)
+ {
+ const pieceView = new Uint8Array(this.eContent.valueBlock.valueHex, offset, ((offset + 65536) > this.eContent.valueBlock.valueHex.byteLength) ? (this.eContent.valueBlock.valueHex.byteLength - offset) : 65536);
+ const _array = new ArrayBuffer(pieceView.length);
+ const _view = new Uint8Array(_array);
+
+ for(let i = 0; i < _view.length; i++)
+ _view[i] = pieceView[i];
+
+ constrString.valueBlock.value.push(new asn1js.OctetString({ valueHex: _array }));
+
+ length -= pieceView.length;
+ offset += pieceView.length;
+ }
+
+ this.eContent = constrString;
+ }
+ //endregion
+ }
+ }
+ //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 "eContentType":
+ return "";
+ case "eContent":
+ return new asn1js.OctetString();
+ default:
+ throw new Error(`Invalid member name for EncapsulatedContentInfo 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 "eContentType":
+ return (memberValue === "");
+ case "eContent":
+ {
+ if((memberValue.idBlock.tagClass === 1) && (memberValue.idBlock.tagNumber === 4))
+ return (memberValue.isEqual(EncapsulatedContentInfo.defaultValues("eContent")));
+
+ return false;
+ }
+ default:
+ throw new Error(`Invalid member name for EncapsulatedContentInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * EncapsulatedContentInfo ::= SEQUENCE {
+ * eContentType ContentType,
+ * eContent [0] EXPLICIT OCTET STRING OPTIONAL } * Changed it to ANY, as in PKCS#7
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [type]
+ * @property {string} [setName]
+ * @property {string} [values]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.eContentType || "") }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Any({ name: (names.eContent || "") }) // In order to aling this with PKCS#7 and CMS as well
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "eContentType",
+ "eContent"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ EncapsulatedContentInfo.schema({
+ names: {
+ eContentType: "eContentType",
+ eContent: "eContent"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for EncapsulatedContentInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.eContentType = asn1.result.eContentType.valueBlock.toString();
+ if("eContent" in asn1.result)
+ this.eContent = asn1.result.eContent;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.ObjectIdentifier({ value: this.eContentType }));
+ if("eContent" in this)
+ {
+ if(EncapsulatedContentInfo.compareWithDefault("eContent", this.eContent) === false)
+ {
+ outputArray.push(new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [this.eContent]
+ }));
+ }
+ }
+ //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 = {
+ eContentType: this.eContentType
+ };
+
+ if("eContent" in this)
+ {
+ if(EncapsulatedContentInfo.compareWithDefault("eContent", this.eContent) === false)
+ _object.eContent = this.eContent.toJSON();
+ }
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/EncryptedContentInfo.js b/pki.js/EncryptedContentInfo.js
new file mode 100644
index 0000000..2e87e11
--- /dev/null
+++ b/pki.js/EncryptedContentInfo.js
@@ -0,0 +1,287 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class EncryptedContentInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for EncryptedContentInfo 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 {string}
+ * @desc contentType
+ */
+ this.contentType = getParametersValue(parameters, "contentType", EncryptedContentInfo.defaultValues("contentType"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc contentEncryptionAlgorithm
+ */
+ this.contentEncryptionAlgorithm = getParametersValue(parameters, "contentEncryptionAlgorithm", EncryptedContentInfo.defaultValues("contentEncryptionAlgorithm"));
+
+ if("encryptedContent" in parameters)
+ {
+ /**
+ * @type {OctetString}
+ * @desc encryptedContent (!!!) could be contructive or primitive value (!!!)
+ */
+ this.encryptedContent = parameters.encryptedContent;
+
+ if((this.encryptedContent.idBlock.tagClass === 1) &&
+ (this.encryptedContent.idBlock.tagNumber === 4))
+ {
+ //region Divide OCTETSTRING value down to small pieces
+ if(this.encryptedContent.idBlock.isConstructed === false)
+ {
+ const constrString = new asn1js.OctetString({
+ idBlock: { isConstructed: true },
+ isConstructed: true
+ });
+
+ let offset = 0;
+ let length = this.encryptedContent.valueBlock.valueHex.byteLength;
+
+ while(length > 0)
+ {
+ const pieceView = new Uint8Array(this.encryptedContent.valueBlock.valueHex, offset, ((offset + 1024) > this.encryptedContent.valueBlock.valueHex.byteLength) ? (this.encryptedContent.valueBlock.valueHex.byteLength - offset) : 1024);
+ const _array = new ArrayBuffer(pieceView.length);
+ const _view = new Uint8Array(_array);
+
+ for(let i = 0; i < _view.length; i++)
+ _view[i] = pieceView[i];
+
+ constrString.valueBlock.value.push(new asn1js.OctetString({ valueHex: _array }));
+
+ length -= pieceView.length;
+ offset += pieceView.length;
+ }
+
+ this.encryptedContent = constrString;
+ }
+ //endregion
+ }
+ }
+ //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 "contentType":
+ return "";
+ case "contentEncryptionAlgorithm":
+ return new AlgorithmIdentifier();
+ case "encryptedContent":
+ return new asn1js.OctetString();
+ default:
+ throw new Error(`Invalid member name for EncryptedContentInfo 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 "contentType":
+ return (memberValue === "");
+ case "contentEncryptionAlgorithm":
+ return ((memberValue.algorithmId === "") && (("algorithmParams" in memberValue) === false));
+ case "encryptedContent":
+ return (memberValue.isEqual(EncryptedContentInfo.defaultValues(memberName)));
+ default:
+ throw new Error(`Invalid member name for EncryptedContentInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * EncryptedContentInfo ::= SEQUENCE {
+ * contentType ContentType,
+ * contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
+ * encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL }
+ *
+ * Comment: Strange, but modern crypto engines create "encryptedContent" as "[0] EXPLICIT EncryptedContent"
+ *
+ * EncryptedContent ::= OCTET STRING
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [contentType]
+ * @property {string} [contentEncryptionAlgorithm]
+ * @property {string} [encryptedContent]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.contentType || "") }),
+ AlgorithmIdentifier.schema(names.contentEncryptionAlgorithm || {}),
+ // The CHOICE we need because "EncryptedContent" could have either "constructive"
+ // or "primitive" form of encoding and we need to handle both variants
+ new asn1js.Choice({
+ value: [
+ new asn1js.Constructed({
+ name: (names.encryptedContent || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Repeated({
+ value: new asn1js.OctetString()
+ })
+ ]
+ }),
+ new asn1js.Primitive({
+ name: (names.encryptedContent || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ }
+ })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "contentType",
+ "contentEncryptionAlgorithm",
+ "encryptedContent"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ EncryptedContentInfo.schema({
+ names: {
+ contentType: "contentType",
+ contentEncryptionAlgorithm: {
+ names: {
+ blockName: "contentEncryptionAlgorithm"
+ }
+ },
+ encryptedContent: "encryptedContent"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for EncryptedContentInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.contentType = asn1.result.contentType.valueBlock.toString();
+ this.contentEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.contentEncryptionAlgorithm });
+
+ if("encryptedContent" in asn1.result)
+ {
+ this.encryptedContent = asn1.result.encryptedContent;
+
+ this.encryptedContent.idBlock.tagClass = 1; // UNIVERSAL
+ this.encryptedContent.idBlock.tagNumber = 4; // OCTETSTRING (!!!) The value still has instance of "in_window.org.pkijs.asn1.ASN1_CONSTRUCTED / ASN1_PRIMITIVE"
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const sequenceLengthBlock = {
+ isIndefiniteForm: false
+ };
+
+ const outputArray = [];
+
+ outputArray.push(new asn1js.ObjectIdentifier({ value: this.contentType }));
+ outputArray.push(this.contentEncryptionAlgorithm.toSchema());
+
+ if("encryptedContent" in this)
+ {
+ sequenceLengthBlock.isIndefiniteForm = this.encryptedContent.idBlock.isConstructed;
+
+ const encryptedValue = this.encryptedContent;
+
+ encryptedValue.idBlock.tagClass = 3; // CONTEXT-SPECIFIC
+ encryptedValue.idBlock.tagNumber = 0; // [0]
+
+ encryptedValue.lenBlock.isIndefiniteForm = this.encryptedContent.idBlock.isConstructed;
+
+ outputArray.push(encryptedValue);
+ }
+ //endregion
+
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ lenBlock: sequenceLengthBlock,
+ value: outputArray
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const _object = {
+ contentType: this.contentType,
+ contentEncryptionAlgorithm: this.contentEncryptionAlgorithm.toJSON()
+ };
+
+ if("encryptedContent" in this)
+ _object.encryptedContent = this.encryptedContent.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/EncryptedData.js b/pki.js/EncryptedData.js
new file mode 100644
index 0000000..fe9d2d6
--- /dev/null
+++ b/pki.js/EncryptedData.js
@@ -0,0 +1,287 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import { getEngine } from "./common.js";
+import EncryptedContentInfo from "./EncryptedContentInfo.js";
+import Attribute from "./Attribute.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class EncryptedData
+{
+ //**********************************************************************************
+ /**
+ * Constructor for EncryptedData 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 {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", EncryptedData.defaultValues("version"));
+ /**
+ * @type {EncryptedContentInfo}
+ * @desc encryptedContentInfo
+ */
+ this.encryptedContentInfo = getParametersValue(parameters, "encryptedContentInfo", EncryptedData.defaultValues("encryptedContentInfo"));
+
+ if("unprotectedAttrs" in parameters)
+ /**
+ * @type {Array.}
+ * @desc unprotectedAttrs
+ */
+ this.unprotectedAttrs = getParametersValue(parameters, "unprotectedAttrs", EncryptedData.defaultValues("unprotectedAttrs"));
+ //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 "version":
+ return 0;
+ case "encryptedContentInfo":
+ return new EncryptedContentInfo();
+ case "unprotectedAttrs":
+ return [];
+ default:
+ throw new Error(`Invalid member name for EncryptedData 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 "version":
+ return (memberValue === 0);
+ case "encryptedContentInfo":
+ return ((EncryptedContentInfo.compareWithDefault("contentType", memberValue.contentType)) &&
+ (EncryptedContentInfo.compareWithDefault("contentEncryptionAlgorithm", memberValue.contentEncryptionAlgorithm)) &&
+ (EncryptedContentInfo.compareWithDefault("encryptedContent", memberValue.encryptedContent)));
+ case "unprotectedAttrs":
+ return (memberValue.length === 0);
+ default:
+ throw new Error(`Invalid member name for EncryptedData class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * EncryptedData ::= SEQUENCE {
+ * version CMSVersion,
+ * encryptedContentInfo EncryptedContentInfo,
+ * unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [version]
+ * @property {string} [encryptedContentInfo]
+ * @property {string} [unprotectedAttrs]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Integer({ name: (names.version || "") }),
+ EncryptedContentInfo.schema(names.encryptedContentInfo || {}),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [
+ new asn1js.Repeated({
+ name: (names.unprotectedAttrs || ""),
+ value: Attribute.schema()
+ })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "version",
+ "encryptedContentInfo",
+ "unprotectedAttrs"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ EncryptedData.schema({
+ names: {
+ version: "version",
+ encryptedContentInfo: {
+ names: {
+ blockName: "encryptedContentInfo"
+ }
+ },
+ unprotectedAttrs: "unprotectedAttrs"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for EncryptedData");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result.version.valueBlock.valueDec;
+ this.encryptedContentInfo = new EncryptedContentInfo({ schema: asn1.result.encryptedContentInfo });
+
+ if("unprotectedAttrs" in asn1.result)
+ this.unprotectedAttrs = Array.from(asn1.result.unprotectedAttrs, element => new Attribute({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.Integer({ value: this.version }));
+ outputArray.push(this.encryptedContentInfo.toSchema());
+
+ if("unprotectedAttrs" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: Array.from(this.unprotectedAttrs, 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 = {
+ version: this.version,
+ encryptedContentInfo: this.encryptedContentInfo.toJSON()
+ };
+
+ if("unprotectedAttrs" in this)
+ _object.unprotectedAttrs = Array.from(this.unprotectedAttrs, element => element.toJSON());
+
+ return _object;
+ }
+ //**********************************************************************************
+ /**
+ * Create a new CMS Encrypted Data content
+ * @param {Object} parameters Parameters neccessary for encryption
+ * @returns {Promise}
+ */
+ encrypt(parameters)
+ {
+ //region Check for input parameters
+ if((parameters instanceof Object) === false)
+ return Promise.reject("Parameters must have type \"Object\"");
+ //endregion
+
+ //region Get cryptographic engine
+ const engine = getEngine();
+ if(typeof engine === "undefined")
+ return Promise.reject("Unable to initialize cryptographic engine");
+ //endregion
+
+ //region Set "contentType" parameter
+ parameters.contentType = "1.2.840.113549.1.7.1"; // "data"
+ //endregion
+
+ if("encryptEncryptedContentInfo" in engine.subtle)
+ {
+ return engine.subtle.encryptEncryptedContentInfo(parameters).then(result =>
+ {
+ this.encryptedContentInfo = result;
+ });
+ }
+
+ return Promise.reject(`No support for "encryptEncryptedContentInfo" in current crypto engine ${engine.name}`);
+ }
+ //**********************************************************************************
+ /**
+ * Create a new CMS Encrypted Data content
+ * @param {Object} parameters Parameters neccessary for encryption
+ */
+ decrypt(parameters)
+ {
+ //region Check for input parameters
+ if((parameters instanceof Object) === false)
+ return Promise.reject("Parameters must have type \"Object\"");
+ //endregion
+
+ //region Get cryptographic engine
+ const engine = getEngine();
+ if(typeof engine === "undefined")
+ return Promise.reject("Unable to initialize cryptographic engine");
+ //endregion
+
+ //region Set "encryptedContentInfo" value
+ parameters.encryptedContentInfo = this.encryptedContentInfo;
+ //endregion
+
+ if("decryptEncryptedContentInfo" in engine.subtle)
+ return engine.subtle.decryptEncryptedContentInfo(parameters);
+
+ return Promise.reject(`No support for "decryptEncryptedContentInfo" in current crypto engine ${engine.name}`);
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/EnvelopedData.js b/pki.js/EnvelopedData.js
new file mode 100644
index 0000000..98b067d
--- /dev/null
+++ b/pki.js/EnvelopedData.js
@@ -0,0 +1,1729 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, utilConcatBuf, clearProps } from "pvutils";
+import { getOIDByAlgorithm, getRandomValues, getCrypto, getAlgorithmByOID, kdf } from "./common.js";
+import OriginatorInfo from "./OriginatorInfo.js";
+import RecipientInfo from "./RecipientInfo.js";
+import EncryptedContentInfo from "./EncryptedContentInfo.js";
+import Attribute from "./Attribute.js";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import RSAESOAEPParams from "./RSAESOAEPParams.js";
+import KeyTransRecipientInfo from "./KeyTransRecipientInfo.js";
+import IssuerAndSerialNumber from "./IssuerAndSerialNumber.js";
+import RecipientEncryptedKey from "./RecipientEncryptedKey.js";
+import KeyAgreeRecipientIdentifier from "./KeyAgreeRecipientIdentifier.js";
+import KeyAgreeRecipientInfo from "./KeyAgreeRecipientInfo.js";
+import RecipientEncryptedKeys from "./RecipientEncryptedKeys.js";
+import KEKRecipientInfo from "./KEKRecipientInfo.js";
+import KEKIdentifier from "./KEKIdentifier.js";
+import PBKDF2Params from "./PBKDF2Params.js";
+import PasswordRecipientinfo from "./PasswordRecipientinfo.js";
+import ECCCMSSharedInfo from "./ECCCMSSharedInfo.js";
+import OriginatorIdentifierOrKey from "./OriginatorIdentifierOrKey.js";
+import OriginatorPublicKey from "./OriginatorPublicKey.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class EnvelopedData
+{
+ //**********************************************************************************
+ /**
+ * Constructor for EnvelopedData 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 {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", EnvelopedData.defaultValues("version"));
+
+ if("originatorInfo" in parameters)
+ /**
+ * @type {OriginatorInfo}
+ * @desc originatorInfo
+ */
+ this.originatorInfo = getParametersValue(parameters, "originatorInfo", EnvelopedData.defaultValues("originatorInfo"));
+
+ /**
+ * @type {Array.}
+ * @desc recipientInfos
+ */
+ this.recipientInfos = getParametersValue(parameters, "recipientInfos", EnvelopedData.defaultValues("recipientInfos"));
+ /**
+ * @type {EncryptedContentInfo}
+ * @desc encryptedContentInfo
+ */
+ this.encryptedContentInfo = getParametersValue(parameters, "encryptedContentInfo", EnvelopedData.defaultValues("encryptedContentInfo"));
+
+ if("unprotectedAttrs" in parameters)
+ /**
+ * @type {Array.}
+ * @desc unprotectedAttrs
+ */
+ this.unprotectedAttrs = getParametersValue(parameters, "unprotectedAttrs", EnvelopedData.defaultValues("unprotectedAttrs"));
+ //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 "version":
+ return 0;
+ case "originatorInfo":
+ return new OriginatorInfo();
+ case "recipientInfos":
+ return [];
+ case "encryptedContentInfo":
+ return new EncryptedContentInfo();
+ case "unprotectedAttrs":
+ return [];
+ default:
+ throw new Error(`Invalid member name for EnvelopedData 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 "version":
+ return (memberValue === EnvelopedData.defaultValues(memberName));
+ case "originatorInfo":
+ return ((memberValue.certs.certificates.length === 0) && (memberValue.crls.crls.length === 0));
+ case "recipientInfos":
+ case "unprotectedAttrs":
+ return (memberValue.length === 0);
+ case "encryptedContentInfo":
+ return ((EncryptedContentInfo.compareWithDefault("contentType", memberValue.contentType)) &&
+ (EncryptedContentInfo.compareWithDefault("contentEncryptionAlgorithm", memberValue.contentEncryptionAlgorithm) &&
+ (EncryptedContentInfo.compareWithDefault("encryptedContent", memberValue.encryptedContent))));
+ default:
+ throw new Error(`Invalid member name for EnvelopedData class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * EnvelopedData ::= SEQUENCE {
+ * version CMSVersion,
+ * originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
+ * recipientInfos RecipientInfos,
+ * encryptedContentInfo EncryptedContentInfo,
+ * unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [version]
+ * @property {string} [originatorInfo]
+ * @property {string} [recipientInfos]
+ * @property {string} [encryptedContentInfo]
+ * @property {string} [unprotectedAttrs]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Integer({ name: (names.version || "") }),
+ new asn1js.Constructed({
+ name: (names.originatorInfo || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: OriginatorInfo.schema().valueBlock.value
+ }),
+ new asn1js.Set({
+ value: [
+ new asn1js.Repeated({
+ name: (names.recipientInfos || ""),
+ value: RecipientInfo.schema()
+ })
+ ]
+ }),
+ EncryptedContentInfo.schema(names.encryptedContentInfo || {}),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [
+ new asn1js.Repeated({
+ name: (names.unprotectedAttrs || ""),
+ value: Attribute.schema()
+ })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "version",
+ "originatorInfo",
+ "recipientInfos",
+ "encryptedContentInfo",
+ "unprotectedAttrs"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ EnvelopedData.schema({
+ names: {
+ version: "version",
+ originatorInfo: "originatorInfo",
+ recipientInfos: "recipientInfos",
+ encryptedContentInfo: {
+ names: {
+ blockName: "encryptedContentInfo"
+ }
+ },
+ unprotectedAttrs: "unprotectedAttrs"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for EnvelopedData");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result.version.valueBlock.valueDec;
+
+ if("originatorInfo" in asn1.result)
+ {
+ this.originatorInfo = new OriginatorInfo({
+ schema: new asn1js.Sequence({
+ value: asn1.result.originatorInfo.valueBlock.value
+ })
+ });
+ }
+
+ this.recipientInfos = Array.from(asn1.result.recipientInfos, element => new RecipientInfo({ schema: element }));
+ this.encryptedContentInfo = new EncryptedContentInfo({ schema: asn1.result.encryptedContentInfo });
+
+ if("unprotectedAttrs" in asn1.result)
+ this.unprotectedAttrs = Array.from(asn1.result.unprotectedAttrs, element => new Attribute({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.Integer({ value: this.version }));
+
+ if("originatorInfo" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: this.originatorInfo.toSchema().valueBlock.value
+ }));
+ }
+
+ outputArray.push(new asn1js.Set({
+ value: Array.from(this.recipientInfos, element => element.toSchema())
+ }));
+
+ outputArray.push(this.encryptedContentInfo.toSchema());
+
+ if("unprotectedAttrs" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: Array.from(this.unprotectedAttrs, 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 = {
+ version: this.version
+ };
+
+ if("originatorInfo" in this)
+ _object.originatorInfo = this.originatorInfo.toJSON();
+
+ _object.recipientInfos = Array.from(this.recipientInfos, element => element.toJSON());
+ _object.encryptedContentInfo = this.encryptedContentInfo.toJSON();
+
+ if("unprotectedAttrs" in this)
+ _object.unprotectedAttrs = Array.from(this.unprotectedAttrs, element => element.toJSON());
+
+ return _object;
+ }
+ //**********************************************************************************
+ /**
+ * Helpers function for filling "RecipientInfo" based on recipient's certificate.
+ * Problem with WebCrypto is that for RSA certificates we have only one option - "key transport" and
+ * for ECC certificates we also have one option - "key agreement". As soon as Google will implement
+ * DH algorithm it would be possible to use "key agreement" also for RSA certificates.
+ * @param {Certificate} [certificate] Recipient's certificate
+ * @param {Object} [parameters] Additional parameters neccessary for "fine tunning" of encryption process
+ * @param {number} [variant] Variant = 1 is for "key transport", variant = 2 is for "key agreement". In fact the "variant" is unneccessary now because Google has no DH algorithm implementation. Thus key encryption scheme would be choosen by certificate type only: "key transport" for RSA and "key agreement" for ECC certificates.
+ */
+ addRecipientByCertificate(certificate, parameters, variant)
+ {
+ //region Initial variables
+ const encryptionParameters = parameters || {};
+ //endregion
+
+ //region Check type of certificate
+ if(certificate.subjectPublicKeyInfo.algorithm.algorithmId.indexOf("1.2.840.113549") !== (-1))
+ variant = 1; // For the moment it is the only variant for RSA-based certificates
+ else
+ {
+ if(certificate.subjectPublicKeyInfo.algorithm.algorithmId.indexOf("1.2.840.10045") !== (-1))
+ variant = 2; // For the moment it is the only variant for ECC-based certificates
+ else
+ throw new Error(`Unknown type of certificate's public key: ${certificate.subjectPublicKeyInfo.algorithm.algorithmId}`);
+ }
+ //endregion
+
+ //region Initialize encryption parameters
+ if(("oaepHashAlgorithm" in encryptionParameters) === false)
+ encryptionParameters.oaepHashAlgorithm = "SHA-512";
+
+ if(("kdfAlgorithm" in encryptionParameters) === false)
+ encryptionParameters.kdfAlgorithm = "SHA-512";
+
+ if(("kekEncryptionLength" in encryptionParameters) === false)
+ encryptionParameters.kekEncryptionLength = 256;
+ //endregion
+
+ //region Add new "recipient" depends on "variant" and certificate type
+ switch(variant)
+ {
+ case 1: // Key transport scheme
+ {
+ //region keyEncryptionAlgorithm
+ const oaepOID = getOIDByAlgorithm({
+ name: "RSA-OAEP"
+ });
+ if(oaepOID === "")
+ throw new Error("Can not find OID for OAEP");
+ //endregion
+
+ //region RSAES-OAEP-params
+ const hashOID = getOIDByAlgorithm({
+ name: encryptionParameters.oaepHashAlgorithm
+ });
+ if(hashOID === "")
+ throw new Error(`Unknown OAEP hash algorithm: ${encryptionParameters.oaepHashAlgorithm}`);
+
+ const hashAlgorithm = new AlgorithmIdentifier({
+ algorithmId: hashOID,
+ algorithmParams: new asn1js.Null()
+ });
+
+ const rsaOAEPParams = new RSAESOAEPParams({
+ hashAlgorithm,
+ maskGenAlgorithm: new AlgorithmIdentifier({
+ algorithmId: "1.2.840.113549.1.1.8", // id-mgf1
+ algorithmParams: hashAlgorithm.toSchema()
+ })
+ });
+ //endregion
+
+ //region KeyTransRecipientInfo
+ const keyInfo = new KeyTransRecipientInfo({
+ version: 0,
+ rid: new IssuerAndSerialNumber({
+ issuer: certificate.issuer,
+ serialNumber: certificate.serialNumber
+ }),
+ keyEncryptionAlgorithm: new AlgorithmIdentifier({
+ algorithmId: oaepOID,
+ algorithmParams: rsaOAEPParams.toSchema()
+ }),
+ recipientCertificate: certificate
+ // "encryptedKey" will be calculated in "encrypt" function
+ });
+ //endregion
+
+ //region Final values for "CMS_ENVELOPED_DATA"
+ this.recipientInfos.push(new RecipientInfo({
+ variant: 1,
+ value: keyInfo
+ }));
+ //endregion
+ }
+ break;
+ case 2: // Key agreement scheme
+ {
+ //region RecipientEncryptedKey
+ const encryptedKey = new RecipientEncryptedKey({
+ rid: new KeyAgreeRecipientIdentifier({
+ variant: 1,
+ value: new IssuerAndSerialNumber({
+ issuer: certificate.issuer,
+ serialNumber: certificate.serialNumber
+ })
+ })
+ // "encryptedKey" will be calculated in "encrypt" function
+ });
+ //endregion
+
+ //region keyEncryptionAlgorithm
+ const aesKWoid = getOIDByAlgorithm({
+ name: "AES-KW",
+ length: encryptionParameters.kekEncryptionLength
+ });
+ if(aesKWoid === "")
+ throw new Error(`Unknown length for key encryption algorithm: ${encryptionParameters.kekEncryptionLength}`);
+
+ const aesKW = new AlgorithmIdentifier({
+ algorithmId: aesKWoid,
+ algorithmParams: new asn1js.Null()
+ });
+ //endregion
+
+ //region KeyAgreeRecipientInfo
+ const ecdhOID = getOIDByAlgorithm({
+ name: "ECDH",
+ kdf: encryptionParameters.kdfAlgorithm
+ });
+ if(ecdhOID === "")
+ throw new Error(`Unknown KDF algorithm: ${encryptionParameters.kdfAlgorithm}`);
+
+ // In fact there is no need in so long UKM, but RFC2631
+ // has requirement that "UserKeyMaterial" must be 512 bits long
+ const ukmBuffer = new ArrayBuffer(64);
+ const ukmView = new Uint8Array(ukmBuffer);
+ getRandomValues(ukmView); // Generate random values in 64 bytes long buffer
+
+ const keyInfo = new KeyAgreeRecipientInfo({
+ version: 3,
+ // "originator" will be calculated in "encrypt" function because ephemeral key would be generated there
+ ukm: new asn1js.OctetString({ valueHex: ukmBuffer }),
+ keyEncryptionAlgorithm: new AlgorithmIdentifier({
+ algorithmId: ecdhOID,
+ algorithmParams: aesKW.toSchema()
+ }),
+ recipientEncryptedKeys: new RecipientEncryptedKeys({
+ encryptedKeys: [encryptedKey]
+ }),
+ recipientCertificate: certificate
+ });
+ //endregion
+
+ //region Final values for "CMS_ENVELOPED_DATA"
+ this.recipientInfos.push(new RecipientInfo({
+ variant: 2,
+ value: keyInfo
+ }));
+ //endregion
+ }
+ break;
+ default:
+ throw new Error(`Unknown "variant" value: ${variant}`);
+ }
+ //endregion
+
+ return true;
+ }
+ //**********************************************************************************
+ /**
+ * Add recipient based on pre-defined data like password or KEK
+ * @param {ArrayBuffer} preDefinedData ArrayBuffer with pre-defined data
+ * @param {Object} parameters Additional parameters neccessary for "fine tunning" of encryption process
+ * @param {number} variant Variant = 1 for pre-defined "key encryption key" (KEK). Variant = 2 for password-based encryption.
+ */
+ addRecipientByPreDefinedData(preDefinedData, parameters, variant)
+ {
+ //region Initial variables
+ const encryptionParameters = parameters || {};
+ //endregion
+
+ //region Check initial parameters
+ if((preDefinedData instanceof ArrayBuffer) === false)
+ throw new Error("Please pass \"preDefinedData\" in ArrayBuffer type");
+
+ if(preDefinedData.byteLength === 0)
+ throw new Error("Pre-defined data could have zero length");
+ //endregion
+
+ //region Initialize encryption parameters
+ if(("keyIdentifier" in encryptionParameters) === false)
+ {
+ const keyIdentifierBuffer = new ArrayBuffer(16);
+ const keyIdentifierView = new Uint8Array(keyIdentifierBuffer);
+ getRandomValues(keyIdentifierView);
+
+ encryptionParameters.keyIdentifier = keyIdentifierBuffer;
+ }
+
+ if(("hmacHashAlgorithm" in encryptionParameters) === false)
+ encryptionParameters.hmacHashAlgorithm = "SHA-512";
+
+ if(("iterationCount" in encryptionParameters) === false)
+ encryptionParameters.iterationCount = 2048;
+
+ if(("keyEncryptionAlgorithm" in encryptionParameters) === false)
+ {
+ encryptionParameters.keyEncryptionAlgorithm = {
+ name: "AES-KW",
+ length: 256
+ };
+ }
+
+ if(("keyEncryptionAlgorithmParams" in encryptionParameters) === false)
+ encryptionParameters.keyEncryptionAlgorithmParams = new asn1js.Null();
+ //endregion
+
+ //region Add new recipient based on passed variant
+ switch(variant)
+ {
+ case 1: // KEKRecipientInfo
+ {
+ //region keyEncryptionAlgorithm
+ const kekOID = getOIDByAlgorithm(encryptionParameters.keyEncryptionAlgorithm);
+ if(kekOID === "")
+ throw new Error("Incorrect value for \"keyEncryptionAlgorithm\"");
+ //endregion
+
+ //region KEKRecipientInfo
+ const keyInfo = new KEKRecipientInfo({
+ version: 4,
+ kekid: new KEKIdentifier({
+ keyIdentifier: new asn1js.OctetString({ valueHex: encryptionParameters.keyIdentifier })
+ }),
+ keyEncryptionAlgorithm: new AlgorithmIdentifier({
+ algorithmId: kekOID,
+ /*
+ For AES-KW params are NULL, but for other algorithm could another situation.
+ */
+ algorithmParams: encryptionParameters.keyEncryptionAlgorithmParams
+ }),
+ preDefinedKEK: preDefinedData
+ // "encryptedKey" would be set in "ecrypt" function
+ });
+ //endregion
+
+ //region Final values for "CMS_ENVELOPED_DATA"
+ this.recipientInfos.push(new RecipientInfo({
+ variant: 3,
+ value: keyInfo
+ }));
+ //endregion
+ }
+ break;
+ case 2: // PasswordRecipientinfo
+ {
+ //region keyDerivationAlgorithm
+ const pbkdf2OID = getOIDByAlgorithm({
+ name: "PBKDF2"
+ });
+ if(pbkdf2OID === "")
+ throw new Error("Can not find OID for PBKDF2");
+ //endregion
+
+ //region Salt
+ const saltBuffer = new ArrayBuffer(64);
+ const saltView = new Uint8Array(saltBuffer);
+ getRandomValues(saltView);
+ //endregion
+
+ //region HMAC-based algorithm
+ const hmacOID = getOIDByAlgorithm({
+ name: "HMAC",
+ hash: {
+ name: encryptionParameters.hmacHashAlgorithm
+ }
+ });
+ if(hmacOID === "")
+ throw new Error(`Incorrect value for "hmacHashAlgorithm": ${encryptionParameters.hmacHashAlgorithm}`);
+ //endregion
+
+ //region PBKDF2-params
+ const pbkdf2Params = new PBKDF2Params({
+ salt: new asn1js.OctetString({ valueHex: saltBuffer }),
+ iterationCount: encryptionParameters.iterationCount,
+ prf: new AlgorithmIdentifier({
+ algorithmId: hmacOID,
+ algorithmParams: new asn1js.Null()
+ })
+ });
+ //endregion
+
+ //region keyEncryptionAlgorithm
+ const kekOID = getOIDByAlgorithm(encryptionParameters.keyEncryptionAlgorithm);
+ if(kekOID === "")
+ throw new Error("Incorrect value for \"keyEncryptionAlgorithm\"");
+ //endregion
+
+ //region PasswordRecipientinfo
+ const keyInfo = new PasswordRecipientinfo({
+ version: 0,
+ keyDerivationAlgorithm: new AlgorithmIdentifier({
+ algorithmId: pbkdf2OID,
+ algorithmParams: pbkdf2Params.toSchema()
+ }),
+ keyEncryptionAlgorithm: new AlgorithmIdentifier({
+ algorithmId: kekOID,
+ /*
+ For AES-KW params are NULL, but for other algorithm could be another situation.
+ */
+ algorithmParams: encryptionParameters.keyEncryptionAlgorithmParams
+ }),
+ password: preDefinedData
+ // "encryptedKey" would be set in "ecrypt" function
+ });
+ //endregion
+
+ //region Final values for "CMS_ENVELOPED_DATA"
+ this.recipientInfos.push(new RecipientInfo({
+ variant: 4,
+ value: keyInfo
+ }));
+ //endregion
+ }
+ break;
+ default:
+ throw new Error(`Unknown value for "variant": ${variant}`);
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Create a new CMS Enveloped Data content with encrypted data
+ * @param {Object} contentEncryptionAlgorithm WebCrypto algorithm. For the moment here could be only "AES-CBC" or "AES-GCM" algorithms.
+ * @param {ArrayBuffer} contentToEncrypt Content to encrypt
+ * @returns {Promise}
+ */
+ encrypt(contentEncryptionAlgorithm, contentToEncrypt)
+ {
+ //region Initial variables
+ let sequence = Promise.resolve();
+
+ const ivBuffer = new ArrayBuffer(16); // For AES we need IV 16 bytes long
+ const ivView = new Uint8Array(ivBuffer);
+ getRandomValues(ivView);
+
+ const contentView = new Uint8Array(contentToEncrypt);
+
+ let sessionKey;
+ let encryptedContent;
+ let exportedSessionKey;
+
+ const recipientsPromises = [];
+
+ const _this = this;
+ //endregion
+
+ //region Check for input parameters
+ const contentEncryptionOID = getOIDByAlgorithm(contentEncryptionAlgorithm);
+ if(contentEncryptionOID === "")
+ return Promise.reject("Wrong \"contentEncryptionAlgorithm\" value");
+ //endregion
+
+ //region Get a "crypto" extension
+ const crypto = getCrypto();
+ if(typeof crypto === "undefined")
+ return Promise.reject("Unable to create WebCrypto object");
+ //endregion
+
+ //region Generate new content encryption key
+ sequence = sequence.then(() =>
+ crypto.generateKey(contentEncryptionAlgorithm, true, ["encrypt"]));
+ //endregion
+ //region Encrypt content
+ sequence = sequence.then(result =>
+ {
+ sessionKey = result;
+
+ return crypto.encrypt({
+ name: contentEncryptionAlgorithm.name,
+ iv: ivView
+ },
+ sessionKey,
+ contentView);
+ }, error =>
+ Promise.reject(error));
+ //endregion
+ //region Export raw content of content encryption key
+ sequence = sequence.then(result =>
+ {
+ //region Create output OCTETSTRING with encrypted content
+ encryptedContent = result;
+ //endregion
+
+ return crypto.exportKey("raw", sessionKey);
+ }, error =>
+ Promise.reject(error)
+ ).then(result =>
+ {
+ exportedSessionKey = result;
+
+ return true;
+ }, error =>
+ Promise.reject(error));
+ //endregion
+ //region Append common information to CMS_ENVELOPED_DATA
+ sequence = sequence.then(() =>
+ {
+ this.version = 2;
+ this.encryptedContentInfo = new EncryptedContentInfo({
+ contentType: "1.2.840.113549.1.7.1", // "data"
+ contentEncryptionAlgorithm: new AlgorithmIdentifier({
+ algorithmId: contentEncryptionOID,
+ algorithmParams: new asn1js.OctetString({ valueHex: ivBuffer })
+ }),
+ encryptedContent: new asn1js.OctetString({ valueHex: encryptedContent })
+ });
+ }, error =>
+ Promise.reject(error));
+ //endregion
+
+ //region Special sub-functions to work with each recipient's type
+ function SubKeyAgreeRecipientInfo(index)
+ {
+ //region Initial variables
+ let currentSequence = Promise.resolve();
+
+ let ecdhPublicKey;
+ let ecdhPrivateKey;
+
+ let recipientCurve;
+ let recipientCurveLength;
+
+ let exportedECDHPublicKey;
+ //endregion
+
+ //region Get "namedCurve" parameter from recipient's certificate
+ currentSequence = currentSequence.then(() =>
+ {
+ const curveObject = _this.recipientInfos[index].value.recipientCertificate.subjectPublicKeyInfo.algorithm.algorithmParams;
+
+ if(curveObject.constructor.blockName() !== asn1js.ObjectIdentifier.blockName())
+ return Promise.reject(`Incorrect "recipientCertificate" for index ${index}`);
+
+ const curveOID = curveObject.valueBlock.toString();
+
+ switch(curveOID)
+ {
+ case "1.2.840.10045.3.1.7":
+ recipientCurve = "P-256";
+ recipientCurveLength = 256;
+ break;
+ case "1.3.132.0.34":
+ recipientCurve = "P-384";
+ recipientCurveLength = 384;
+ break;
+ case "1.3.132.0.35":
+ recipientCurve = "P-521";
+ recipientCurveLength = 528;
+ break;
+ default:
+ return Promise.reject(`Incorrect curve OID for index ${index}`);
+ }
+
+ return recipientCurve;
+ }, error =>
+ Promise.reject(error));
+ //endregion
+
+ //region Generate ephemeral ECDH key
+ currentSequence = currentSequence.then(result =>
+ crypto.generateKey({
+ name: "ECDH",
+ namedCurve: result
+ },
+ true,
+ ["deriveBits"]),
+ error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Export public key of ephemeral ECDH key pair
+ currentSequence = currentSequence.then(result =>
+ {
+ ecdhPublicKey = result.publicKey;
+ ecdhPrivateKey = result.privateKey;
+
+ return crypto.exportKey("spki", ecdhPublicKey);
+ },
+ error =>
+ Promise.reject(error));
+ //endregion
+
+ //region Import recipient's public key
+ currentSequence = currentSequence.then(result =>
+ {
+ exportedECDHPublicKey = result;
+
+ return _this.recipientInfos[index].value.recipientCertificate.getPublicKey({
+ algorithm: {
+ algorithm: {
+ name: "ECDH",
+ namedCurve: recipientCurve
+ },
+ usages: []
+ }
+ });
+ }, error =>
+ Promise.reject(error));
+ //endregion
+ //region Create shared secret
+ currentSequence = currentSequence.then(result => crypto.deriveBits({
+ name: "ECDH",
+ public: result
+ },
+ ecdhPrivateKey,
+ recipientCurveLength),
+ error =>
+ Promise.reject(error));
+ //endregion
+
+ //region Apply KDF function to shared secret
+ currentSequence = currentSequence.then(
+ /**
+ * @param {ArrayBuffer} result
+ */
+ result =>
+ {
+ //region Get length of used AES-KW algorithm
+ const aesKWAlgorithm = new AlgorithmIdentifier({ schema: _this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmParams });
+
+ const KWalgorithm = getAlgorithmByOID(aesKWAlgorithm.algorithmId);
+ if(("name" in KWalgorithm) === false)
+ return Promise.reject(`Incorrect OID for key encryption algorithm: ${aesKWAlgorithm.algorithmId}`);
+ //endregion
+
+ //region Translate AES-KW length to ArrayBuffer
+ let kwLength = KWalgorithm.length;
+
+ const kwLengthBuffer = new ArrayBuffer(4);
+ const kwLengthView = new Uint8Array(kwLengthBuffer);
+
+ for(let j = 3; j >= 0; j--)
+ {
+ kwLengthView[j] = kwLength;
+ kwLength >>= 8;
+ }
+ //endregion
+
+ //region Create and encode "ECC-CMS-SharedInfo" structure
+ const eccInfo = new ECCCMSSharedInfo({
+ keyInfo: new AlgorithmIdentifier({
+ algorithmId: aesKWAlgorithm.algorithmId,
+ /*
+ Initially RFC5753 says that AES algorithms have absent parameters.
+ But since early implementations all put NULL here. Thus, in order to be
+ "backward compatible", index also put NULL here.
+ */
+ algorithmParams: new asn1js.Null()
+ }),
+ entityUInfo: _this.recipientInfos[index].value.ukm,
+ suppPubInfo: new asn1js.OctetString({ valueHex: kwLengthBuffer })
+ });
+
+ const encodedInfo = eccInfo.toSchema().toBER(false);
+ //endregion
+
+ //region Get SHA algorithm used together with ECDH
+ const ecdhAlgorithm = getAlgorithmByOID(_this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmId);
+ if(("name" in ecdhAlgorithm) === false)
+ return Promise.reject(`Incorrect OID for key encryption algorithm: ${_this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmId}`);
+ //endregion
+
+ return kdf(ecdhAlgorithm.kdf, result, KWalgorithm.length, encodedInfo);
+ },
+ error =>
+ Promise.reject(error));
+ //endregion
+ //region Import AES-KW key from result of KDF function
+ currentSequence = currentSequence.then(result =>
+ crypto.importKey("raw", result, { name: "AES-KW" }, true, ["wrapKey"]),
+ error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Finally wrap session key by using AES-KW algorithm
+ currentSequence = currentSequence.then(result => crypto.wrapKey("raw", sessionKey, result, { name: "AES-KW" }),
+ error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Append all neccessary data to current CMS_RECIPIENT_INFO object
+ currentSequence = currentSequence.then(result =>
+ {
+ //region OriginatorIdentifierOrKey
+ const asn1 = asn1js.fromBER(exportedECDHPublicKey);
+
+ const originator = new OriginatorIdentifierOrKey();
+ originator.variant = 3;
+ originator.value = new OriginatorPublicKey({ schema: asn1.result });
+ // There is option when we can stay with ECParameters, but here index prefer to avoid the params
+ if("algorithmParams" in originator.value.algorithm)
+ delete originator.value.algorithm.algorithmParams;
+
+ _this.recipientInfos[index].value.originator = originator;
+ //endregion
+
+ //region RecipientEncryptedKey
+ /*
+ We will not support using of same ephemeral key for many recipients
+ */
+ _this.recipientInfos[index].value.recipientEncryptedKeys.encryptedKeys[0].encryptedKey = new asn1js.OctetString({ valueHex: result });
+ //endregion
+
+ return {ecdhPrivateKey};
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ return currentSequence;
+ }
+
+ function SubKeyTransRecipientInfo(index)
+ {
+ //region Initial variables
+ let currentSequence = Promise.resolve();
+ //endregion
+
+ //region Get recipient's public key
+ currentSequence = currentSequence.then(() =>
+ {
+ //region Check we have a correct algorithm here
+ const oaepOID = getOIDByAlgorithm({
+ name: "RSA-OAEP"
+ });
+ if(oaepOID === "")
+ throw new Error("Can not find OID for OAEP");
+
+ if(_this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmId !== oaepOID)
+ throw new Error("Not supported encryption scheme, only RSA-OAEP is supported for key transport encryption scheme");
+ //endregion
+
+ //region Get current used SHA algorithm
+ const schema = _this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmParams;
+ const rsaOAEPParams = new RSAESOAEPParams({ schema });
+
+ const hashAlgorithm = getAlgorithmByOID(rsaOAEPParams.hashAlgorithm.algorithmId);
+ if(("name" in hashAlgorithm) === false)
+ return Promise.reject(`Incorrect OID for hash algorithm: ${rsaOAEPParams.hashAlgorithm.algorithmId}`);
+ //endregion
+
+ return _this.recipientInfos[index].value.recipientCertificate.getPublicKey({
+ algorithm: {
+ algorithm: {
+ name: "RSA-OAEP",
+ hash: {
+ name: hashAlgorithm.name
+ }
+ },
+ usages: ["encrypt", "wrapKey"]
+ }
+ });
+ }, error =>
+ Promise.reject(error));
+ //endregion
+ //region Encrypt early exported session key on recipient's public key
+ currentSequence = currentSequence.then(result =>
+ crypto.encrypt(result.algorithm, result, exportedSessionKey),
+ error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ //region Append all neccessary data to current CMS_RECIPIENT_INFO object
+ currentSequence = currentSequence.then(result =>
+ {
+ //region RecipientEncryptedKey
+ _this.recipientInfos[index].value.encryptedKey = new asn1js.OctetString({ valueHex: result });
+ //endregion
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ return currentSequence;
+ }
+
+ function SubKEKRecipientInfo(index)
+ {
+ //region Initial variables
+ let currentSequence = Promise.resolve();
+ let kekAlgorithm;
+ //endregion
+
+ //region Import KEK from pre-defined data
+ currentSequence = currentSequence.then(() =>
+ {
+ //region Get WebCrypto form of "keyEncryptionAlgorithm"
+ kekAlgorithm = getAlgorithmByOID(_this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmId);
+ if(("name" in kekAlgorithm) === false)
+ return Promise.reject(`Incorrect OID for "keyEncryptionAlgorithm": ${_this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmId}`);
+ //endregion
+
+ return crypto.importKey("raw",
+ new Uint8Array(_this.recipientInfos[index].value.preDefinedKEK),
+ kekAlgorithm,
+ true,
+ ["wrapKey"]); // Too specific for AES-KW
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ //region Wrap previously exported session key
+ currentSequence = currentSequence.then(result =>
+ crypto.wrapKey("raw", sessionKey, result, kekAlgorithm),
+ error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Append all neccessary data to current CMS_RECIPIENT_INFO object
+ currentSequence = currentSequence.then(result =>
+ {
+ //region RecipientEncryptedKey
+ _this.recipientInfos[index].value.encryptedKey = new asn1js.OctetString({ valueHex: result });
+ //endregion
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ return currentSequence;
+ }
+
+ function SubPasswordRecipientinfo(index)
+ {
+ //region Initial variables
+ let currentSequence = Promise.resolve();
+ let pbkdf2Params;
+ let kekAlgorithm;
+ //endregion
+
+ //region Check that we have encoded "keyDerivationAlgorithm" plus "PBKDF2_params" in there
+ currentSequence = currentSequence.then(() =>
+ {
+ if(("keyDerivationAlgorithm" in _this.recipientInfos[index].value) === false)
+ return Promise.reject("Please append encoded \"keyDerivationAlgorithm\"");
+
+ if(("algorithmParams" in _this.recipientInfos[index].value.keyDerivationAlgorithm) === false)
+ return Promise.reject("Incorrectly encoded \"keyDerivationAlgorithm\"");
+
+ try
+ {
+ pbkdf2Params = new PBKDF2Params({ schema: _this.recipientInfos[index].value.keyDerivationAlgorithm.algorithmParams });
+ }
+ catch(ex)
+ {
+ return Promise.reject("Incorrectly encoded \"keyDerivationAlgorithm\"");
+ }
+
+ return Promise.resolve();
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Derive PBKDF2 key from "password" buffer
+ currentSequence = currentSequence.then(() =>
+ {
+ const passwordView = new Uint8Array(_this.recipientInfos[index].value.password);
+
+ return crypto.importKey("raw",
+ passwordView,
+ "PBKDF2",
+ false,
+ ["deriveKey"]);
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Derive key for "keyEncryptionAlgorithm"
+ currentSequence = currentSequence.then(result =>
+ {
+ //region Get WebCrypto form of "keyEncryptionAlgorithm"
+ kekAlgorithm = getAlgorithmByOID(_this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmId);
+ if(("name" in kekAlgorithm) === false)
+ return Promise.reject(`Incorrect OID for "keyEncryptionAlgorithm": ${_this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmId}`);
+ //endregion
+
+ //region Get HMAC hash algorithm
+ let hmacHashAlgorithm = "SHA-1";
+
+ if("prf" in pbkdf2Params)
+ {
+ const algorithm = getAlgorithmByOID(pbkdf2Params.prf.algorithmId);
+ if(("name" in algorithm) === false)
+ return Promise.reject("Incorrect OID for HMAC hash algorithm");
+
+ hmacHashAlgorithm = algorithm.hash.name;
+ }
+ //endregion
+
+ //region Get PBKDF2 "salt" value
+ const saltView = new Uint8Array(pbkdf2Params.salt.valueBlock.valueHex);
+ //endregion
+
+ //region Get PBKDF2 iterations count
+ const iterations = pbkdf2Params.iterationCount;
+ //endregion
+
+ return crypto.deriveKey({
+ name: "PBKDF2",
+ hash: {
+ name: hmacHashAlgorithm
+ },
+ salt: saltView,
+ iterations
+ },
+ result,
+ kekAlgorithm,
+ true,
+ ["wrapKey"]); // Usages are too specific for KEK algorithm
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Wrap previously exported session key (Also too specific for KEK algorithm)
+ currentSequence = currentSequence.then(result =>
+ crypto.wrapKey("raw", sessionKey, result, kekAlgorithm),
+ error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Append all neccessary data to current CMS_RECIPIENT_INFO object
+ currentSequence = currentSequence.then(result =>
+ {
+ //region RecipientEncryptedKey
+ _this.recipientInfos[index].value.encryptedKey = new asn1js.OctetString({ valueHex: result });
+ //endregion
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ return currentSequence;
+ }
+
+ //endregion
+
+ //region Create special routines for each "recipient"
+ sequence = sequence.then(() =>
+ {
+ for(let i = 0; i < this.recipientInfos.length; i++)
+ {
+ //region Initial variables
+ let currentSequence = Promise.resolve();
+ //endregion
+
+ switch(this.recipientInfos[i].variant)
+ {
+ case 1: // KeyTransRecipientInfo
+ currentSequence = SubKeyTransRecipientInfo(i);
+ break;
+ case 2: // KeyAgreeRecipientInfo
+ currentSequence = SubKeyAgreeRecipientInfo(i);
+ break;
+ case 3: // KEKRecipientInfo
+ currentSequence = SubKEKRecipientInfo(i);
+ break;
+ case 4: // PasswordRecipientinfo
+ currentSequence = SubPasswordRecipientinfo(i);
+ break;
+ default:
+ return Promise.reject(`Uknown recipient type in array with index ${i}`);
+ }
+
+ recipientsPromises.push(currentSequence);
+ }
+
+ return Promise.all(recipientsPromises);
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ /**
+ * Decrypt existing CMS Enveloped Data content
+ * @param {number} recipientIndex Index of recipient
+ * @param {Object} parameters Additional parameters
+ * @returns {Promise}
+ */
+ decrypt(recipientIndex, parameters)
+ {
+ //region Initial variables
+ let sequence = Promise.resolve();
+
+ const decryptionParameters = parameters || {};
+
+ const _this = this;
+ //endregion
+
+ //region Check for input parameters
+ if((recipientIndex + 1) > this.recipientInfos.length)
+ return Promise.reject(`Maximum value for "index" is: ${this.recipientInfos.length - 1}`);
+ //endregion
+
+ //region Get a "crypto" extension
+ const crypto = getCrypto();
+ if(typeof crypto === "undefined")
+ return Promise.reject("Unable to create WebCrypto object");
+ //endregion
+
+ //region Special sub-functions to work with each recipient's type
+ function SubKeyAgreeRecipientInfo(index)
+ {
+ //region Initial variables
+ let currentSequence = Promise.resolve();
+
+ let recipientCurve;
+ let recipientCurveLength;
+
+ let curveOID;
+
+ let ecdhPrivateKey;
+ //endregion
+
+ //region Get "namedCurve" parameter from recipient's certificate
+ currentSequence = currentSequence.then(() =>
+ {
+ if(("recipientCertificate" in decryptionParameters) === false)
+ return Promise.reject("Parameter \"recipientCertificate\" is mandatory for \"KeyAgreeRecipientInfo\"");
+
+ if(("recipientPrivateKey" in decryptionParameters) === false)
+ return Promise.reject("Parameter \"recipientPrivateKey\" is mandatory for \"KeyAgreeRecipientInfo\"");
+
+ const curveObject = decryptionParameters.recipientCertificate.subjectPublicKeyInfo.algorithm.algorithmParams;
+
+
+ if(curveObject.constructor.blockName() !== asn1js.ObjectIdentifier.blockName())
+ return Promise.reject(`Incorrect "recipientCertificate" for index ${index}`);
+ curveOID = curveObject.valueBlock.toString();
+
+ switch(curveOID)
+ {
+ case "1.2.840.10045.3.1.7":
+ recipientCurve = "P-256";
+ recipientCurveLength = 256;
+ break;
+ case "1.3.132.0.34":
+ recipientCurve = "P-384";
+ recipientCurveLength = 384;
+ break;
+ case "1.3.132.0.35":
+ recipientCurve = "P-521";
+ recipientCurveLength = 528;
+ break;
+ default:
+ return Promise.reject(`Incorrect curve OID for index ${index}`);
+ }
+
+ return crypto.importKey("pkcs8",
+ decryptionParameters.recipientPrivateKey,
+ {
+ name: "ECDH",
+ namedCurve: recipientCurve
+ },
+ true,
+ ["deriveBits"]
+ );
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Import sender's ephemeral public key
+ currentSequence = currentSequence.then(result =>
+ {
+ ecdhPrivateKey = result;
+
+ //region Change "OriginatorPublicKey" if "curve" parameter absent
+ if(("algorithmParams" in _this.recipientInfos[index].value.originator.value.algorithm) === false)
+ _this.recipientInfos[index].value.originator.value.algorithm.algorithmParams = new asn1js.ObjectIdentifier({ value: curveOID });
+ //endregion
+
+ //region Create ArrayBuffer with sender's public key
+ const buffer = _this.recipientInfos[index].value.originator.value.toSchema().toBER(false);
+ //endregion
+
+ return crypto.importKey("spki",
+ buffer,
+ {
+ name: "ECDH",
+ namedCurve: recipientCurve
+ },
+ true,
+ []);
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Create shared secret
+ currentSequence = currentSequence.then(result =>
+ crypto.deriveBits({
+ name: "ECDH",
+ public: result
+ },
+ ecdhPrivateKey,
+ recipientCurveLength),
+ error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Apply KDF function to shared secret
+ currentSequence = currentSequence.then(
+ /**
+ * @param {ArrayBuffer} result
+ */
+ result =>
+ {
+ //region Get length of used AES-KW algorithm
+ const aesKWAlgorithm = new AlgorithmIdentifier({ schema: _this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmParams });
+
+ const KWalgorithm = getAlgorithmByOID(aesKWAlgorithm.algorithmId);
+ if(("name" in KWalgorithm) === false)
+ return Promise.reject(`Incorrect OID for key encryption algorithm: ${aesKWAlgorithm.algorithmId}`);
+ //endregion
+
+ //region Translate AES-KW length to ArrayBuffer
+ let kwLength = KWalgorithm.length;
+
+ const kwLengthBuffer = new ArrayBuffer(4);
+ const kwLengthView = new Uint8Array(kwLengthBuffer);
+
+ for(let j = 3; j >= 0; j--)
+ {
+ kwLengthView[j] = kwLength;
+ kwLength >>= 8;
+ }
+ //endregion
+
+ //region Create and encode "ECC-CMS-SharedInfo" structure
+ const eccInfo = new ECCCMSSharedInfo({
+ keyInfo: new AlgorithmIdentifier({
+ algorithmId: aesKWAlgorithm.algorithmId,
+ /*
+ Initially RFC5753 says that AES algorithms have absent parameters.
+ But since early implementations all put NULL here. Thus, in order to be
+ "backward compatible", index also put NULL here.
+ */
+ algorithmParams: new asn1js.Null()
+ }),
+ entityUInfo: _this.recipientInfos[index].value.ukm,
+ suppPubInfo: new asn1js.OctetString({ valueHex: kwLengthBuffer })
+ });
+
+ const encodedInfo = eccInfo.toSchema().toBER(false);
+ //endregion
+
+ //region Get SHA algorithm used together with ECDH
+ const ecdhAlgorithm = getAlgorithmByOID(_this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmId);
+ if(("name" in ecdhAlgorithm) === false)
+ return Promise.reject(`Incorrect OID for key encryption algorithm: ${_this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmId}`);
+ //endregion
+
+ return kdf(ecdhAlgorithm.kdf, result, KWalgorithm.length, encodedInfo);
+ },
+ error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Import AES-KW key from result of KDF function
+ currentSequence = currentSequence.then(result =>
+ crypto.importKey("raw",
+ result,
+ { name: "AES-KW" },
+ true,
+ ["unwrapKey"]),
+ error => Promise.reject(error)
+ );
+ //endregion
+ //region Finally unwrap session key
+ currentSequence = currentSequence.then(result =>
+ {
+ //region Get WebCrypto form of content encryption algorithm
+ const contentEncryptionAlgorithm = getAlgorithmByOID(_this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId);
+ if(("name" in contentEncryptionAlgorithm) === false)
+ return Promise.reject(`Incorrect "contentEncryptionAlgorithm": ${_this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId}`);
+ //endregion
+
+ return crypto.unwrapKey("raw",
+ _this.recipientInfos[index].value.recipientEncryptedKeys.encryptedKeys[0].encryptedKey.valueBlock.valueHex,
+ result,
+ { name: "AES-KW" },
+ contentEncryptionAlgorithm,
+ true,
+ ["decrypt"]);
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ return currentSequence;
+ }
+
+ function SubKeyTransRecipientInfo(index)
+ {
+ //region Initial variables
+ let currentSequence = Promise.resolve();
+ //endregion
+
+ //region Import recipient's private key
+ currentSequence = currentSequence.then(() =>
+ {
+ if(("recipientPrivateKey" in decryptionParameters) === false)
+ return Promise.reject("Parameter \"recipientPrivateKey\" is mandatory for \"KeyTransRecipientInfo\"");
+
+ //region Get current used SHA algorithm
+ const schema = _this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmParams;
+ const rsaOAEPParams = new RSAESOAEPParams({ schema });
+
+ const hashAlgorithm = getAlgorithmByOID(rsaOAEPParams.hashAlgorithm.algorithmId);
+ if(("name" in hashAlgorithm) === false)
+ return Promise.reject(`Incorrect OID for hash algorithm: ${rsaOAEPParams.hashAlgorithm.algorithmId}`);
+ //endregion
+
+ return crypto.importKey("pkcs8",
+ decryptionParameters.recipientPrivateKey,
+ {
+ name: "RSA-OAEP",
+ hash: {
+ name: hashAlgorithm.name
+ }
+ },
+ true,
+ ["decrypt"]);
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Decrypt encrypted session key
+ currentSequence = currentSequence.then(result =>
+ crypto.decrypt(result.algorithm,
+ result,
+ _this.recipientInfos[index].value.encryptedKey.valueBlock.valueHex
+ ), error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Import decrypted session key
+ currentSequence = currentSequence.then(result =>
+ {
+ //region Get WebCrypto form of content encryption algorithm
+ const contentEncryptionAlgorithm = getAlgorithmByOID(_this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId);
+ if(("name" in contentEncryptionAlgorithm) === false)
+ return Promise.reject(`Incorrect "contentEncryptionAlgorithm": ${_this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId}`);
+ //endregion
+
+ return crypto.importKey("raw",
+ result,
+ contentEncryptionAlgorithm,
+ true,
+ ["decrypt"]
+ );
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ return currentSequence;
+ }
+
+ function SubKEKRecipientInfo(index)
+ {
+ //region Initial variables
+ let currentSequence = Promise.resolve();
+ let kekAlgorithm;
+ //endregion
+
+ //region Import KEK from pre-defined data
+ currentSequence = currentSequence.then(() =>
+ {
+ if(("preDefinedData" in decryptionParameters) === false)
+ return Promise.reject("Parameter \"preDefinedData\" is mandatory for \"KEKRecipientInfo\"");
+
+ //region Get WebCrypto form of "keyEncryptionAlgorithm"
+ kekAlgorithm = getAlgorithmByOID(_this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmId);
+ if(("name" in kekAlgorithm) === false)
+ return Promise.reject(`Incorrect OID for "keyEncryptionAlgorithm": ${_this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmId}`);
+ //endregion
+
+ return crypto.importKey("raw",
+ decryptionParameters.preDefinedData,
+ kekAlgorithm,
+ true,
+ ["unwrapKey"]); // Too specific for AES-KW
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Unwrap previously exported session key
+ currentSequence = currentSequence.then(result =>
+ {
+ //region Get WebCrypto form of content encryption algorithm
+ const contentEncryptionAlgorithm = getAlgorithmByOID(_this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId);
+ if(("name" in contentEncryptionAlgorithm) === false)
+ return Promise.reject(`Incorrect "contentEncryptionAlgorithm": ${_this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId}`);
+ //endregion
+
+ return crypto.unwrapKey("raw",
+ _this.recipientInfos[index].value.encryptedKey.valueBlock.valueHex,
+ result,
+ kekAlgorithm,
+ contentEncryptionAlgorithm,
+ true,
+ ["decrypt"]);
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ return currentSequence;
+ }
+
+ function SubPasswordRecipientinfo(index)
+ {
+ //region Initial variables
+ let currentSequence = Promise.resolve();
+ let pbkdf2Params;
+ let kekAlgorithm;
+ //endregion
+
+ //region Derive PBKDF2 key from "password" buffer
+ currentSequence = currentSequence.then(() =>
+ {
+ if(("preDefinedData" in decryptionParameters) === false)
+ return Promise.reject("Parameter \"preDefinedData\" is mandatory for \"KEKRecipientInfo\"");
+
+ if(("keyDerivationAlgorithm" in _this.recipientInfos[index].value) === false)
+ return Promise.reject("Please append encoded \"keyDerivationAlgorithm\"");
+
+ if(("algorithmParams" in _this.recipientInfos[index].value.keyDerivationAlgorithm) === false)
+ return Promise.reject("Incorrectly encoded \"keyDerivationAlgorithm\"");
+
+ try
+ {
+ pbkdf2Params = new PBKDF2Params({ schema: _this.recipientInfos[index].value.keyDerivationAlgorithm.algorithmParams });
+ }
+ catch(ex)
+ {
+ return Promise.reject("Incorrectly encoded \"keyDerivationAlgorithm\"");
+ }
+
+ return crypto.importKey("raw",
+ decryptionParameters.preDefinedData,
+ "PBKDF2",
+ false,
+ ["deriveKey"]);
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Derive key for "keyEncryptionAlgorithm"
+ currentSequence = currentSequence.then(result =>
+ {
+ //region Get WebCrypto form of "keyEncryptionAlgorithm"
+ kekAlgorithm = getAlgorithmByOID(_this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmId);
+ if(("name" in kekAlgorithm) === false)
+ return Promise.reject(`Incorrect OID for "keyEncryptionAlgorithm": ${_this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmId}`);
+ //endregion
+
+ //region Get HMAC hash algorithm
+ let hmacHashAlgorithm = "SHA-1";
+
+ if("prf" in pbkdf2Params)
+ {
+ const algorithm = getAlgorithmByOID(pbkdf2Params.prf.algorithmId);
+ if(("name" in algorithm) === false)
+ return Promise.reject("Incorrect OID for HMAC hash algorithm");
+
+ hmacHashAlgorithm = algorithm.hash.name;
+ }
+ //endregion
+
+ //region Get PBKDF2 "salt" value
+ const saltView = new Uint8Array(pbkdf2Params.salt.valueBlock.valueHex);
+ //endregion
+
+ //region Get PBKDF2 iterations count
+ const iterations = pbkdf2Params.iterationCount;
+ //endregion
+
+ return crypto.deriveKey({
+ name: "PBKDF2",
+ hash: {
+ name: hmacHashAlgorithm
+ },
+ salt: saltView,
+ iterations
+ },
+ result,
+ kekAlgorithm,
+ true,
+ ["unwrapKey"]); // Usages are too specific for KEK algorithm
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+ //region Unwrap previously exported session key
+ currentSequence = currentSequence.then(result =>
+ {
+ //region Get WebCrypto form of content encryption algorithm
+ const contentEncryptionAlgorithm = getAlgorithmByOID(_this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId);
+ if(("name" in contentEncryptionAlgorithm) === false)
+ return Promise.reject(`Incorrect "contentEncryptionAlgorithm": ${_this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId}`);
+ //endregion
+
+ return crypto.unwrapKey("raw",
+ _this.recipientInfos[index].value.encryptedKey.valueBlock.valueHex,
+ result,
+ kekAlgorithm,
+ contentEncryptionAlgorithm,
+ true,
+ ["decrypt"]);
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ return currentSequence;
+ }
+
+ //endregion
+
+ //region Perform steps, specific to each type of session key encryption
+ sequence = sequence.then(() =>
+ {
+ //region Initial variables
+ let currentSequence = Promise.resolve();
+ //endregion
+
+ switch(this.recipientInfos[recipientIndex].variant)
+ {
+ case 1: // KeyTransRecipientInfo
+ currentSequence = SubKeyTransRecipientInfo(recipientIndex);
+ break;
+ case 2: // KeyAgreeRecipientInfo
+ currentSequence = SubKeyAgreeRecipientInfo(recipientIndex);
+ break;
+ case 3: // KEKRecipientInfo
+ currentSequence = SubKEKRecipientInfo(recipientIndex);
+ break;
+ case 4: // PasswordRecipientinfo
+ currentSequence = SubPasswordRecipientinfo(recipientIndex);
+ break;
+ default:
+ return Promise.reject(`Uknown recipient type in array with index ${recipientIndex}`);
+ }
+
+ return currentSequence;
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ //region Finally decrypt data by session key
+ sequence = sequence.then(result =>
+ {
+ //region Get WebCrypto form of content encryption algorithm
+ const contentEncryptionAlgorithm = getAlgorithmByOID(this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId);
+ if(("name" in contentEncryptionAlgorithm) === false)
+ return Promise.reject(`Incorrect "contentEncryptionAlgorithm": ${this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId}`);
+ //endregion
+
+ //region Get "intialization vector" for content encryption algorithm
+ const ivBuffer = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams.valueBlock.valueHex;
+ const ivView = new Uint8Array(ivBuffer);
+ //endregion
+
+ //region Create correct data block for decryption
+ let dataBuffer = new ArrayBuffer(0);
+
+ if(this.encryptedContentInfo.encryptedContent.idBlock.isConstructed === false)
+ dataBuffer = this.encryptedContentInfo.encryptedContent.valueBlock.valueHex;
+ else
+ {
+ for(const content of this.encryptedContentInfo.encryptedContent.valueBlock.value)
+ dataBuffer = utilConcatBuf(dataBuffer, content.valueBlock.valueHex);
+ }
+ //endregion
+
+ return crypto.decrypt({
+ name: contentEncryptionAlgorithm.name,
+ iv: ivView
+ },
+ result,
+ dataBuffer);
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/ExtKeyUsage.js b/pki.js/ExtKeyUsage.js
new file mode 100644
index 0000000..01ea8ee
--- /dev/null
+++ b/pki.js/ExtKeyUsage.js
@@ -0,0 +1,135 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class ExtKeyUsage
+{
+ //**********************************************************************************
+ /**
+ * Constructor for ExtKeyUsage 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 {Array.}
+ * @desc keyPurposes
+ */
+ this.keyPurposes = getParametersValue(parameters, "keyPurposes", ExtKeyUsage.defaultValues("keyPurposes"));
+ //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 "keyPurposes":
+ return [];
+ default:
+ throw new Error(`Invalid member name for ExtKeyUsage class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * ExtKeyUsage ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
+ *
+ * KeyPurposeId ::= OBJECT IDENTIFIER
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [keyPurposes]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.keyPurposes || ""),
+ value: new asn1js.ObjectIdentifier()
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "keyPurposes"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ ExtKeyUsage.schema({
+ names: {
+ keyPurposes: "keyPurposes"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for ExtKeyUsage");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.keyPurposes = Array.from(asn1.result.keyPurposes, element => element.valueBlock.toString());
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: Array.from(this.keyPurposes, element => new asn1js.ObjectIdentifier({ value: element }))
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ keyPurposes: Array.from(this.keyPurposes)
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/Extension.js b/pki.js/Extension.js
new file mode 100644
index 0000000..ff59e45
--- /dev/null
+++ b/pki.js/Extension.js
@@ -0,0 +1,453 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import SubjectDirectoryAttributes from "./SubjectDirectoryAttributes.js";
+import PrivateKeyUsagePeriod from "./PrivateKeyUsagePeriod.js";
+import AltName from "./AltName.js";
+import BasicConstraints from "./BasicConstraints.js";
+import IssuingDistributionPoint from "./IssuingDistributionPoint.js";
+import GeneralNames from "./GeneralNames.js";
+import NameConstraints from "./NameConstraints.js";
+import CRLDistributionPoints from "./CRLDistributionPoints.js";
+import CertificatePolicies from "./CertificatePolicies.js";
+import PolicyMappings from "./PolicyMappings.js";
+import AuthorityKeyIdentifier from "./AuthorityKeyIdentifier.js";
+import PolicyConstraints from "./PolicyConstraints.js";
+import ExtKeyUsage from "./ExtKeyUsage.js";
+import InfoAccess from "./InfoAccess.js";
+import SignedCertificateTimestampList from "./SignedCertificateTimestampList.js";
+import CertificateTemplate from "./CertificateTemplate.js";
+import CAVersion from "./CAVersion.js";
+import QCStatements from "./QCStatements.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class Extension
+{
+ //**********************************************************************************
+ /**
+ * Constructor for Extension 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 {string}
+ * @desc extnID
+ */
+ this.extnID = getParametersValue(parameters, "extnID", Extension.defaultValues("extnID"));
+ /**
+ * @type {boolean}
+ * @desc critical
+ */
+ this.critical = getParametersValue(parameters, "critical", Extension.defaultValues("critical"));
+ /**
+ * @type {OctetString}
+ * @desc extnValue
+ */
+ if("extnValue" in parameters)
+ this.extnValue = new asn1js.OctetString({ valueHex: parameters.extnValue });
+ else
+ this.extnValue = Extension.defaultValues("extnValue");
+
+ if("parsedValue" in parameters)
+ /**
+ * @type {Object}
+ * @desc parsedValue
+ */
+ this.parsedValue = getParametersValue(parameters, "parsedValue", Extension.defaultValues("parsedValue"));
+ //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 "extnID":
+ return "";
+ case "critical":
+ return false;
+ case "extnValue":
+ return new asn1js.OctetString();
+ case "parsedValue":
+ return {};
+ default:
+ throw new Error(`Invalid member name for Extension class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * Extension ::= SEQUENCE {
+ * extnID OBJECT IDENTIFIER,
+ * critical BOOLEAN DEFAULT FALSE,
+ * extnValue OCTET STRING
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [extnID]
+ * @property {string} [critical]
+ * @property {string} [extnValue]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.extnID || "") }),
+ new asn1js.Boolean({
+ name: (names.critical || ""),
+ optional: true
+ }),
+ new asn1js.OctetString({ name: (names.extnValue || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "extnID",
+ "critical",
+ "extnValue"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ let asn1 = asn1js.compareSchema(schema,
+ schema,
+ Extension.schema({
+ names: {
+ extnID: "extnID",
+ critical: "critical",
+ extnValue: "extnValue"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for Extension");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.extnID = asn1.result.extnID.valueBlock.toString();
+ if("critical" in asn1.result)
+ this.critical = asn1.result.critical.valueBlock.value;
+ this.extnValue = asn1.result.extnValue;
+
+ //region Get "parsedValue" for well-known extensions
+ asn1 = asn1js.fromBER(this.extnValue.valueBlock.valueHex);
+ if(asn1.offset === (-1))
+ return;
+
+ switch(this.extnID)
+ {
+ case "2.5.29.9": // SubjectDirectoryAttributes
+ try
+ {
+ this.parsedValue = new SubjectDirectoryAttributes({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new SubjectDirectoryAttributes();
+ this.parsedValue.parsingError = "Incorrectly formated SubjectDirectoryAttributes";
+ }
+ break;
+ case "2.5.29.14": // SubjectKeyIdentifier
+ this.parsedValue = asn1.result; // Should be just a simple OCTETSTRING
+ break;
+ case "2.5.29.15": // KeyUsage
+ this.parsedValue = asn1.result; // Should be just a simple BITSTRING
+ break;
+ case "2.5.29.16": // PrivateKeyUsagePeriod
+ try
+ {
+ this.parsedValue = new PrivateKeyUsagePeriod({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new PrivateKeyUsagePeriod();
+ this.parsedValue.parsingError = "Incorrectly formated PrivateKeyUsagePeriod";
+ }
+ break;
+ case "2.5.29.17": // SubjectAltName
+ case "2.5.29.18": // IssuerAltName
+ try
+ {
+ this.parsedValue = new AltName({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new AltName();
+ this.parsedValue.parsingError = "Incorrectly formated AltName";
+ }
+ break;
+ case "2.5.29.19": // BasicConstraints
+ try
+ {
+ this.parsedValue = new BasicConstraints({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new BasicConstraints();
+ this.parsedValue.parsingError = "Incorrectly formated BasicConstraints";
+ }
+ break;
+ case "2.5.29.20": // CRLNumber
+ case "2.5.29.27": // BaseCRLNumber (delta CRL indicator)
+ this.parsedValue = asn1.result; // Should be just a simple INTEGER
+ break;
+ case "2.5.29.21": // CRLReason
+ this.parsedValue = asn1.result; // Should be just a simple ENUMERATED
+ break;
+ case "2.5.29.24": // InvalidityDate
+ this.parsedValue = asn1.result; // Should be just a simple GeneralizedTime
+ break;
+ case "2.5.29.28": // IssuingDistributionPoint
+ try
+ {
+ this.parsedValue = new IssuingDistributionPoint({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new IssuingDistributionPoint();
+ this.parsedValue.parsingError = "Incorrectly formated IssuingDistributionPoint";
+ }
+ break;
+ case "2.5.29.29": // CertificateIssuer
+ try
+ {
+ this.parsedValue = new GeneralNames({ schema: asn1.result }); // Should be just a simple
+ }
+ catch(ex)
+ {
+ this.parsedValue = new GeneralNames();
+ this.parsedValue.parsingError = "Incorrectly formated GeneralNames";
+ }
+ break;
+ case "2.5.29.30": // NameConstraints
+ try
+ {
+ this.parsedValue = new NameConstraints({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new NameConstraints();
+ this.parsedValue.parsingError = "Incorrectly formated NameConstraints";
+ }
+ break;
+ case "2.5.29.31": // CRLDistributionPoints
+ case "2.5.29.46": // FreshestCRL
+ try
+ {
+ this.parsedValue = new CRLDistributionPoints({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new CRLDistributionPoints();
+ this.parsedValue.parsingError = "Incorrectly formated CRLDistributionPoints";
+ }
+ break;
+ case "2.5.29.32": // CertificatePolicies
+ case "1.3.6.1.4.1.311.21.10": // szOID_APPLICATION_CERT_POLICIES - Microsoft-specific OID
+ try
+ {
+ this.parsedValue = new CertificatePolicies({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new CertificatePolicies();
+ this.parsedValue.parsingError = "Incorrectly formated CertificatePolicies";
+ }
+ break;
+ case "2.5.29.33": // PolicyMappings
+ try
+ {
+ this.parsedValue = new PolicyMappings({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new PolicyMappings();
+ this.parsedValue.parsingError = "Incorrectly formated CertificatePolicies";
+ }
+ break;
+ case "2.5.29.35": // AuthorityKeyIdentifier
+ try
+ {
+ this.parsedValue = new AuthorityKeyIdentifier({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new AuthorityKeyIdentifier();
+ this.parsedValue.parsingError = "Incorrectly formated AuthorityKeyIdentifier";
+ }
+ break;
+ case "2.5.29.36": // PolicyConstraints
+ try
+ {
+ this.parsedValue = new PolicyConstraints({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new PolicyConstraints();
+ this.parsedValue.parsingError = "Incorrectly formated PolicyConstraints";
+ }
+ break;
+ case "2.5.29.37": // ExtKeyUsage
+ try
+ {
+ this.parsedValue = new ExtKeyUsage({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new ExtKeyUsage();
+ this.parsedValue.parsingError = "Incorrectly formated ExtKeyUsage";
+ }
+ break;
+ case "2.5.29.54": // InhibitAnyPolicy
+ this.parsedValue = asn1.result; // Should be just a simple INTEGER
+ break;
+ case "1.3.6.1.5.5.7.1.1": // AuthorityInfoAccess
+ case "1.3.6.1.5.5.7.1.11": // SubjectInfoAccess
+ try
+ {
+ this.parsedValue = new InfoAccess({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new InfoAccess();
+ this.parsedValue.parsingError = "Incorrectly formated InfoAccess";
+ }
+ break;
+ case "1.3.6.1.4.1.11129.2.4.2": // SignedCertificateTimestampList
+ try
+ {
+ this.parsedValue = new SignedCertificateTimestampList({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new SignedCertificateTimestampList();
+ this.parsedValue.parsingError = "Incorrectly formated SignedCertificateTimestampList";
+ }
+ break;
+ case "1.3.6.1.4.1.311.20.2": // szOID_ENROLL_CERTTYPE_EXTENSION - Microsoft-specific extension
+ this.parsedValue = asn1.result; // Used to be simple Unicode string
+ break;
+ case "1.3.6.1.4.1.311.21.2": // szOID_CERTSRV_PREVIOUS_CERT_HASH - Microsoft-specific extension
+ this.parsedValue = asn1.result; // Used to be simple OctetString
+ break;
+ case "1.3.6.1.4.1.311.21.7": // szOID_CERTIFICATE_TEMPLATE - Microsoft-specific extension
+ try
+ {
+ this.parsedValue = new CertificateTemplate({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new CertificateTemplate();
+ this.parsedValue.parsingError = "Incorrectly formated CertificateTemplate";
+ }
+ break;
+ case "1.3.6.1.4.1.311.21.1": // szOID_CERTSRV_CA_VERSION - Microsoft-specific extension
+ try
+ {
+ this.parsedValue = new CAVersion({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new CAVersion();
+ this.parsedValue.parsingError = "Incorrectly formated CAVersion";
+ }
+ break;
+ case "1.3.6.1.5.5.7.1.3": // QCStatements
+ try
+ {
+ this.parsedValue = new QCStatements({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ this.parsedValue = new QCStatements();
+ this.parsedValue.parsingError = "Incorrectly formated QCStatements";
+ }
+ break;
+ default:
+ }
+ //endregion
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.ObjectIdentifier({ value: this.extnID }));
+
+ if(this.critical !== Extension.defaultValues("critical"))
+ outputArray.push(new asn1js.Boolean({ value: this.critical }));
+
+ outputArray.push(this.extnValue);
+ //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 = {
+ extnID: this.extnID,
+ extnValue: this.extnValue.toJSON()
+ };
+
+ if(this.critical !== Extension.defaultValues("critical"))
+ object.critical = this.critical;
+
+ if("parsedValue" in this)
+ {
+ if("toJSON" in this.parsedValue)
+ object.parsedValue = this.parsedValue.toJSON();
+ }
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/Extensions.js b/pki.js/Extensions.js
new file mode 100644
index 0000000..11bc998
--- /dev/null
+++ b/pki.js/Extensions.js
@@ -0,0 +1,137 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import Extension from "./Extension.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class Extensions
+{
+ //**********************************************************************************
+ /**
+ * Constructor for Extensions 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 {Array.}
+ * @desc type
+ */
+ this.extensions = getParametersValue(parameters, "extensions", Extensions.defaultValues("extensions"));
+ //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 "extensions":
+ return [];
+ default:
+ throw new Error(`Invalid member name for Extensions class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @param {boolean} optional Flag that current schema should be optional
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {}, optional = false)
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [extensions]
+ * @property {string} [extension]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ optional,
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.extensions || ""),
+ value: Extension.schema(names.extension || {})
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "extensions"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ Extensions.schema({
+ names: {
+ extensions: "extensions"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for Extensions");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.extensions = Array.from(asn1.result.extensions, element => new Extension({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: Array.from(this.extensions, element => element.toSchema())
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ extensions: Array.from(this.extensions, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/GeneralName.js b/pki.js/GeneralName.js
new file mode 100644
index 0000000..09a2ba8
--- /dev/null
+++ b/pki.js/GeneralName.js
@@ -0,0 +1,644 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import RelativeDistinguishedNames from "./RelativeDistinguishedNames.js";
+//**************************************************************************************
+//region Additional asn1js schema elements existing inside GeneralName schema
+//**************************************************************************************
+/**
+ * Schema for "builtInStandardAttributes" of "ORAddress"
+ * @param {Object} parameters
+ * @property {Object} [names]
+ * @param {boolean} optional
+ * @returns {Sequence}
+ */
+function builtInStandardAttributes(parameters = {}, optional = false)
+{
+ //builtInStandardAttributes ::= Sequence {
+ // country-name CountryName OPTIONAL,
+ // administration-domain-name AdministrationDomainName OPTIONAL,
+ // network-address [0] IMPLICIT NetworkAddress OPTIONAL,
+ // terminal-identifier [1] IMPLICIT TerminalIdentifier OPTIONAL,
+ // private-domain-name [2] PrivateDomainName OPTIONAL,
+ // organization-name [3] IMPLICIT OrganizationName OPTIONAL,
+ // numeric-user-identifier [4] IMPLICIT NumericUserIdentifier OPTIONAL,
+ // personal-name [5] IMPLICIT PersonalName OPTIONAL,
+ // organizational-unit-names [6] IMPLICIT OrganizationalUnitNames OPTIONAL }
+
+ /**
+ * @type {Object}
+ * @property {string} [country_name]
+ * @property {string} [administration_domain_name]
+ * @property {string} [network_address]
+ * @property {string} [terminal_identifier]
+ * @property {string} [private_domain_name]
+ * @property {string} [organization_name]
+ * @property {string} [numeric_user_identifier]
+ * @property {string} [personal_name]
+ * @property {string} [organizational_unit_names]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ optional,
+ value: [
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 2, // APPLICATION-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ name: (names.country_name || ""),
+ value: [
+ new asn1js.Choice({
+ value: [
+ new asn1js.NumericString(),
+ new asn1js.PrintableString()
+ ]
+ })
+ ]
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 2, // APPLICATION-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ name: (names.administration_domain_name || ""),
+ value: [
+ new asn1js.Choice({
+ value: [
+ new asn1js.NumericString(),
+ new asn1js.PrintableString()
+ ]
+ })
+ ]
+ }),
+ new asn1js.Primitive({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ name: (names.network_address || ""),
+ isHexOnly: true
+ }),
+ new asn1js.Primitive({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ name: (names.terminal_identifier || ""),
+ isHexOnly: true
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ name: (names.private_domain_name || ""),
+ value: [
+ new asn1js.Choice({
+ value: [
+ new asn1js.NumericString(),
+ new asn1js.PrintableString()
+ ]
+ })
+ ]
+ }),
+ new asn1js.Primitive({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 3 // [3]
+ },
+ name: (names.organization_name || ""),
+ isHexOnly: true
+ }),
+ new asn1js.Primitive({
+ optional: true,
+ name: (names.numeric_user_identifier || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 4 // [4]
+ },
+ isHexOnly: true
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ name: (names.personal_name || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 5 // [5]
+ },
+ value: [
+ new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ isHexOnly: true
+ }),
+ new asn1js.Primitive({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ isHexOnly: true
+ }),
+ new asn1js.Primitive({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ isHexOnly: true
+ }),
+ new asn1js.Primitive({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 3 // [3]
+ },
+ isHexOnly: true
+ })
+ ]
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ name: (names.organizational_unit_names || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 6 // [6]
+ },
+ value: [
+ new asn1js.Repeated({
+ value: new asn1js.PrintableString()
+ })
+ ]
+ })
+ ]
+ }));
+}
+//**************************************************************************************
+/**
+ * Schema for "builtInDomainDefinedAttributes" of "ORAddress"
+ * @param {boolean} optional
+ * @returns {Sequence}
+ */
+function builtInDomainDefinedAttributes(optional = false)
+{
+ return (new asn1js.Sequence({
+ optional,
+ value: [
+ new asn1js.PrintableString(),
+ new asn1js.PrintableString()
+ ]
+ }));
+}
+//**************************************************************************************
+/**
+ * Schema for "builtInDomainDefinedAttributes" of "ORAddress"
+ * @param {boolean} optional
+ * @returns {Set}
+ */
+function extensionAttributes(optional = false)
+{
+ return (new asn1js.Set({
+ optional,
+ value: [
+ new asn1js.Primitive({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ isHexOnly: true
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [new asn1js.Any()]
+ })
+ ]
+ }));
+}
+//**************************************************************************************
+//endregion
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class GeneralName
+{
+ //**********************************************************************************
+ /**
+ * Constructor for GeneralName class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ * @property {number} [type] value type - from a tagged value (0 for "otherName", 1 for "rfc822Name" etc.)
+ * @property {Object} [value] asn1js object having GeneralName value (type depends on "type" value)
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ /**
+ * @type {number}
+ * @desc value type - from a tagged value (0 for "otherName", 1 for "rfc822Name" etc.)
+ */
+ this.type = getParametersValue(parameters, "type", GeneralName.defaultValues("type"));
+ /**
+ * @type {Object}
+ * @desc asn1js object having GeneralName value (type depends on "type" value)
+ */
+ this.value = getParametersValue(parameters, "value", GeneralName.defaultValues("value"));
+ //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 "type":
+ return 9;
+ case "value":
+ return {};
+ default:
+ throw new Error(`Invalid member name for GeneralName 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 "type":
+ return (memberValue === GeneralName.defaultValues(memberName));
+ case "value":
+ return (Object.keys(memberValue).length === 0);
+ default:
+ throw new Error(`Invalid member name for GeneralName class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * GeneralName ::= Choice {
+ * otherName [0] OtherName,
+ * rfc822Name [1] IA5String,
+ * dNSName [2] IA5String,
+ * x400Address [3] ORAddress,
+ * directoryName [4] value,
+ * ediPartyName [5] EDIPartyName,
+ * uniformResourceIdentifier [6] IA5String,
+ * iPAddress [7] OCTET STRING,
+ * registeredID [8] OBJECT IDENTIFIER }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {Object} [directoryName]
+ * @property {Object} [builtInStandardAttributes]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Choice({
+ value: [
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier(),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [new asn1js.Any()]
+ })
+ ]
+ }),
+ new asn1js.Primitive({
+ name: (names.blockName || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ }
+ }),
+ new asn1js.Primitive({
+ name: (names.blockName || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ }
+ }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 3 // [3]
+ },
+ name: (names.blockName || ""),
+ value: [
+ builtInStandardAttributes((names.builtInStandardAttributes || {}), false),
+ builtInDomainDefinedAttributes(true),
+ extensionAttributes(true)
+ ]
+ }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 4 // [4]
+ },
+ name: (names.blockName || ""),
+ value: [RelativeDistinguishedNames.schema(names.directoryName || {})]
+ }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 5 // [5]
+ },
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Choice({
+ value: [
+ new asn1js.TeletexString(),
+ new asn1js.PrintableString(),
+ new asn1js.UniversalString(),
+ new asn1js.Utf8String(),
+ new asn1js.BmpString()
+ ]
+ })
+ ]
+ }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [
+ new asn1js.Choice({
+ value: [
+ new asn1js.TeletexString(),
+ new asn1js.PrintableString(),
+ new asn1js.UniversalString(),
+ new asn1js.Utf8String(),
+ new asn1js.BmpString()
+ ]
+ })
+ ]
+ })
+ ]
+ }),
+ new asn1js.Primitive({
+ name: (names.blockName || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 6 // [6]
+ }
+ }),
+ new asn1js.Primitive({
+ name: (names.blockName || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 7 // [7]
+ }
+ }),
+ new asn1js.Primitive({
+ name: (names.blockName || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 8 // [8]
+ }
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "blockName",
+ "otherName",
+ "rfc822Name",
+ "dNSName",
+ "x400Address",
+ "directoryName",
+ "ediPartyName",
+ "uniformResourceIdentifier",
+ "iPAddress",
+ "registeredID"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ GeneralName.schema({
+ names: {
+ blockName: "blockName",
+ otherName: "otherName",
+ rfc822Name: "rfc822Name",
+ dNSName: "dNSName",
+ x400Address: "x400Address",
+ directoryName: {
+ names: {
+ blockName: "directoryName"
+ }
+ },
+ ediPartyName: "ediPartyName",
+ uniformResourceIdentifier: "uniformResourceIdentifier",
+ iPAddress: "iPAddress",
+ registeredID: "registeredID"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for GeneralName");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.type = asn1.result.blockName.idBlock.tagNumber;
+
+ switch(this.type)
+ {
+ case 0: // otherName
+ this.value = asn1.result.blockName;
+ break;
+ case 1: // rfc822Name + dNSName + uniformResourceIdentifier
+ case 2:
+ case 6:
+ {
+ const value = asn1.result.blockName;
+
+ value.idBlock.tagClass = 1; // UNIVERSAL
+ value.idBlock.tagNumber = 22; // IA5STRING
+
+ const valueBER = value.toBER(false);
+
+ this.value = asn1js.fromBER(valueBER).result.valueBlock.value;
+ }
+ break;
+ case 3: // x400Address
+ this.value = asn1.result.blockName;
+ break;
+ case 4: // directoryName
+ this.value = new RelativeDistinguishedNames({ schema: asn1.result.directoryName });
+ break;
+ case 5: // ediPartyName
+ this.value = asn1.result.ediPartyName;
+ break;
+ case 7: // iPAddress
+ this.value = new asn1js.OctetString({ valueHex: asn1.result.blockName.valueBlock.valueHex });
+ break;
+ case 8: // registeredID
+ {
+ const value = asn1.result.blockName;
+
+ value.idBlock.tagClass = 1; // UNIVERSAL
+ value.idBlock.tagNumber = 6; // ObjectIdentifier
+
+ const valueBER = value.toBER(false);
+
+ this.value = asn1js.fromBER(valueBER).result.valueBlock.toString(); // Getting a string representation of the ObjectIdentifier
+ }
+ break;
+ default:
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ switch(this.type)
+ {
+ case 0:
+ case 3:
+ case 5:
+ return new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: this.type
+ },
+ value: [
+ this.value
+ ]
+ });
+ case 1:
+ case 2:
+ case 6:
+ {
+ const value = new asn1js.IA5String({ value: this.value });
+
+ value.idBlock.tagClass = 3;
+ value.idBlock.tagNumber = this.type;
+
+ return value;
+ }
+ case 4:
+ return new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 4
+ },
+ value: [this.value.toSchema()]
+ });
+ case 7:
+ {
+ const value = this.value;
+
+ value.idBlock.tagClass = 3;
+ value.idBlock.tagNumber = this.type;
+
+ return value;
+ }
+ case 8:
+ {
+ const value = new asn1js.ObjectIdentifier({ value: this.value });
+
+ value.idBlock.tagClass = 3;
+ value.idBlock.tagNumber = this.type;
+
+ return value;
+ }
+ default:
+ return GeneralName.schema();
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const _object = {
+ type: this.type,
+ value: ""
+ };
+
+ if((typeof this.value) === "string")
+ _object.value = this.value;
+ else
+ {
+ try
+ {
+ _object.value = this.value.toJSON();
+ }
+ catch(ex){}
+ }
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/GeneralNames.js b/pki.js/GeneralNames.js
new file mode 100644
index 0000000..68d59be
--- /dev/null
+++ b/pki.js/GeneralNames.js
@@ -0,0 +1,138 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import GeneralName from "./GeneralName.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class GeneralNames
+{
+ //**********************************************************************************
+ /**
+ * Constructor for GeneralNames 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 {Array.}
+ * @desc Array of "general names"
+ */
+ this.names = getParametersValue(parameters, "names", GeneralNames.defaultValues("names"));
+ //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 "names":
+ return [];
+ default:
+ throw new Error(`Invalid member name for GeneralNames class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @param {boolean} [optional=false] Flag would be element optional or not
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {}, optional = false)
+ {
+ /**
+ * @type {Object}
+ * @property {string} utcTimeName Name for "utcTimeName" choice
+ * @property {string} generalTimeName Name for "generalTimeName" choice
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ optional,
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.generalNames || ""),
+ value: GeneralName.schema()
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "names",
+ "generalNames"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ GeneralNames.schema({
+ names: {
+ blockName: "names",
+ generalNames: "generalNames"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for GeneralNames");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.names = Array.from(asn1.result.generalNames, element => new GeneralName({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: Array.from(this.names, element => element.toSchema())
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ names: Array.from(this.names, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/GeneralSubtree.js b/pki.js/GeneralSubtree.js
new file mode 100644
index 0000000..e71fb51
--- /dev/null
+++ b/pki.js/GeneralSubtree.js
@@ -0,0 +1,257 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import GeneralName from "./GeneralName.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class GeneralSubtree
+{
+ //**********************************************************************************
+ /**
+ * Constructor for GeneralSubtree 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 {GeneralName}
+ * @desc base
+ */
+ this.base = getParametersValue(parameters, "base", GeneralSubtree.defaultValues("base"));
+
+ /**
+ * @type {number|Integer}
+ * @desc base
+ */
+ this.minimum = getParametersValue(parameters, "minimum", GeneralSubtree.defaultValues("minimum"));
+
+ if("maximum" in parameters)
+ /**
+ * @type {number|Integer}
+ * @desc minimum
+ */
+ this.maximum = getParametersValue(parameters, "maximum", GeneralSubtree.defaultValues("maximum"));
+ //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 "base":
+ return new GeneralName();
+ case "minimum":
+ return 0;
+ case "maximum":
+ return 0;
+ default:
+ throw new Error(`Invalid member name for GeneralSubtree class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * GeneralSubtree ::= SEQUENCE {
+ * base GeneralName,
+ * minimum [0] BaseDistance DEFAULT 0,
+ * maximum [1] BaseDistance OPTIONAL }
+ *
+ * BaseDistance ::= INTEGER (0..MAX)
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [base]
+ * @property {string} [minimum]
+ * @property {string} [maximum]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ GeneralName.schema(names.base || {}),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [new asn1js.Integer({ name: (names.minimum || "") })]
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [new asn1js.Integer({ name: (names.maximum || "") })]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "base",
+ "minimum",
+ "maximum"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ GeneralSubtree.schema({
+ names: {
+ base: {
+ names: {
+ blockName: "base"
+ }
+ },
+ minimum: "minimum",
+ maximum: "maximum"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for GeneralSubtree");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.base = new GeneralName({ schema: asn1.result.base });
+
+ if("minimum" in asn1.result)
+ {
+ if(asn1.result.minimum.valueBlock.isHexOnly)
+ this.minimum = asn1.result.minimum;
+ else
+ this.minimum = asn1.result.minimum.valueBlock.valueDec;
+ }
+
+ if("maximum" in asn1.result)
+ {
+ if(asn1.result.maximum.valueBlock.isHexOnly)
+ this.maximum = asn1.result.maximum;
+ else
+ this.maximum = asn1.result.maximum.valueBlock.valueDec;
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(this.base.toSchema());
+
+ if(this.minimum !== 0)
+ {
+ let valueMinimum = 0;
+
+ if(this.minimum instanceof asn1js.Integer)
+ valueMinimum = this.minimum;
+ else
+ valueMinimum = new asn1js.Integer({ value: this.minimum });
+
+ outputArray.push(new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [valueMinimum]
+ }));
+ }
+
+ if("maximum" in this)
+ {
+ let valueMaximum = 0;
+
+ if(this.maximum instanceof asn1js.Integer)
+ valueMaximum = this.maximum;
+ else
+ valueMaximum = new asn1js.Integer({ value: this.maximum });
+
+ outputArray.push(new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [valueMaximum]
+ }));
+ }
+ //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 = {
+ base: this.base.toJSON()
+ };
+
+ if(this.minimum !== 0)
+ {
+ if((typeof this.minimum) === "number")
+ object.minimum = this.minimum;
+ else
+ object.minimum = this.minimum.toJSON();
+ }
+
+ if("maximum" in this)
+ {
+ if((typeof this.maximum) === "number")
+ object.maximum = this.maximum;
+ else
+ object.maximum = this.maximum.toJSON();
+ }
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/InfoAccess.js b/pki.js/InfoAccess.js
new file mode 100644
index 0000000..1cb1ead
--- /dev/null
+++ b/pki.js/InfoAccess.js
@@ -0,0 +1,135 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AccessDescription from "./AccessDescription.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class InfoAccess
+{
+ //**********************************************************************************
+ /**
+ * Constructor for InfoAccess 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 {Array.}
+ * @desc accessDescriptions
+ */
+ this.accessDescriptions = getParametersValue(parameters, "accessDescriptions", InfoAccess.defaultValues("accessDescriptions"));
+ //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 "accessDescriptions":
+ return [];
+ default:
+ throw new Error(`Invalid member name for InfoAccess class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * AuthorityInfoAccessSyntax ::=
+ * SEQUENCE SIZE (1..MAX) OF AccessDescription
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [accessDescriptions]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.accessDescriptions || ""),
+ value: AccessDescription.schema()
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "accessDescriptions"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ InfoAccess.schema({
+ names: {
+ accessDescriptions: "accessDescriptions"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for InfoAccess");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.accessDescriptions = Array.from(asn1.result.accessDescriptions, element => new AccessDescription({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: Array.from(this.accessDescriptions, element => element.toSchema())
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ accessDescriptions: Array.from(this.accessDescriptions, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/IssuerAndSerialNumber.js b/pki.js/IssuerAndSerialNumber.js
new file mode 100644
index 0000000..579dcce
--- /dev/null
+++ b/pki.js/IssuerAndSerialNumber.js
@@ -0,0 +1,155 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import RelativeDistinguishedNames from "./RelativeDistinguishedNames.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class IssuerAndSerialNumber
+{
+ //**********************************************************************************
+ /**
+ * Constructor for IssuerAndSerialNumber 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 {RelativeDistinguishedNames}
+ * @desc issuer
+ */
+ this.issuer = getParametersValue(parameters, "issuer", IssuerAndSerialNumber.defaultValues("issuer"));
+ /**
+ * @type {Integer}
+ * @desc serialNumber
+ */
+ this.serialNumber = getParametersValue(parameters, "serialNumber", IssuerAndSerialNumber.defaultValues("serialNumber"));
+ //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 "issuer":
+ return new RelativeDistinguishedNames();
+ case "serialNumber":
+ return new asn1js.Integer();
+ default:
+ throw new Error(`Invalid member name for IssuerAndSerialNumber class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * IssuerAndSerialNumber ::= SEQUENCE {
+ * issuer Name,
+ * serialNumber CertificateSerialNumber }
+ *
+ * CertificateSerialNumber ::= INTEGER
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [issuer]
+ * @property {string} [serialNumber]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ RelativeDistinguishedNames.schema(names.issuer || {}),
+ new asn1js.Integer({ name: (names.serialNumber || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "issuer",
+ "serialNumber"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ IssuerAndSerialNumber.schema({
+ names: {
+ issuer: {
+ names: {
+ blockName: "issuer"
+ }
+ },
+ serialNumber: "serialNumber"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for IssuerAndSerialNumber");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.issuer = new RelativeDistinguishedNames({ schema: asn1.result.issuer });
+ this.serialNumber = asn1.result.serialNumber;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ this.issuer.toSchema(),
+ this.serialNumber
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ issuer: this.issuer.toJSON(),
+ serialNumber: this.serialNumber.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/IssuingDistributionPoint.js b/pki.js/IssuingDistributionPoint.js
new file mode 100644
index 0000000..df1e50d
--- /dev/null
+++ b/pki.js/IssuingDistributionPoint.js
@@ -0,0 +1,449 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import GeneralName from "./GeneralName.js";
+import RelativeDistinguishedNames from "./RelativeDistinguishedNames.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class IssuingDistributionPoint
+{
+ //**********************************************************************************
+ /**
+ * Constructor for IssuingDistributionPoint class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ if("distributionPoint" in parameters)
+ /**
+ * @type {Array.|RelativeDistinguishedNames}
+ * @desc distributionPoint
+ */
+ this.distributionPoint = getParametersValue(parameters, "distributionPoint", IssuingDistributionPoint.defaultValues("distributionPoint"));
+
+ /**
+ * @type {boolean}
+ * @desc onlyContainsUserCerts
+ */
+ this.onlyContainsUserCerts = getParametersValue(parameters, "onlyContainsUserCerts", IssuingDistributionPoint.defaultValues("onlyContainsUserCerts"));
+
+ /**
+ * @type {boolean}
+ * @desc onlyContainsCACerts
+ */
+ this.onlyContainsCACerts = getParametersValue(parameters, "onlyContainsCACerts", IssuingDistributionPoint.defaultValues("onlyContainsCACerts"));
+
+ if("onlySomeReasons" in parameters)
+ /**
+ * @type {number}
+ * @desc onlySomeReasons
+ */
+ this.onlySomeReasons = getParametersValue(parameters, "onlySomeReasons", IssuingDistributionPoint.defaultValues("onlySomeReasons"));
+
+ /**
+ * @type {boolean}
+ * @desc indirectCRL
+ */
+ this.indirectCRL = getParametersValue(parameters, "indirectCRL", IssuingDistributionPoint.defaultValues("indirectCRL"));
+
+ /**
+ * @type {boolean}
+ * @desc onlyContainsAttributeCerts
+ */
+ this.onlyContainsAttributeCerts = getParametersValue(parameters, "onlyContainsAttributeCerts", IssuingDistributionPoint.defaultValues("onlyContainsAttributeCerts"));
+ //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 "distributionPoint":
+ return [];
+ case "onlyContainsUserCerts":
+ return false;
+ case "onlyContainsCACerts":
+ return false;
+ case "onlySomeReasons":
+ return 0;
+ case "indirectCRL":
+ return false;
+ case "onlyContainsAttributeCerts":
+ return false;
+ default:
+ throw new Error(`Invalid member name for IssuingDistributionPoint class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * IssuingDistributionPoint ::= SEQUENCE {
+ * distributionPoint [0] DistributionPointName OPTIONAL,
+ * onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE,
+ * onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE,
+ * onlySomeReasons [3] ReasonFlags OPTIONAL,
+ * indirectCRL [4] BOOLEAN DEFAULT FALSE,
+ * onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE }
+ *
+ * ReasonFlags ::= BIT STRING {
+ * unused (0),
+ * keyCompromise (1),
+ * cACompromise (2),
+ * affiliationChanged (3),
+ * superseded (4),
+ * cessationOfOperation (5),
+ * certificateHold (6),
+ * privilegeWithdrawn (7),
+ * aACompromise (8) }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [distributionPoint]
+ * @property {string} [distributionPointNames]
+ * @property {string} [onlyContainsUserCerts]
+ * @property {string} [onlyContainsCACerts]
+ * @property {string} [onlySomeReasons]
+ * @property {string} [indirectCRL]
+ * @property {string} [onlyContainsAttributeCerts]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Choice({
+ value: [
+ new asn1js.Constructed({
+ name: (names.distributionPoint || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Repeated({
+ name: (names.distributionPointNames || ""),
+ value: GeneralName.schema()
+ })
+ ]
+ }),
+ new asn1js.Constructed({
+ name: (names.distributionPoint || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: RelativeDistinguishedNames.schema().valueBlock.value
+ })
+ ]
+ })
+ ]
+ }),
+ new asn1js.Primitive({
+ name: (names.onlyContainsUserCerts || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ }
+ }), // IMPLICIT boolean value
+ new asn1js.Primitive({
+ name: (names.onlyContainsCACerts || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ }
+ }), // IMPLICIT boolean value
+ new asn1js.Primitive({
+ name: (names.onlySomeReasons || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 3 // [3]
+ }
+ }), // IMPLICIT bitstring value
+ new asn1js.Primitive({
+ name: (names.indirectCRL || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 4 // [4]
+ }
+ }), // IMPLICIT boolean value
+ new asn1js.Primitive({
+ name: (names.onlyContainsAttributeCerts || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 5 // [5]
+ }
+ }) // IMPLICIT boolean value
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "distributionPoint",
+ "distributionPointNames",
+ "onlyContainsUserCerts",
+ "onlyContainsCACerts",
+ "onlySomeReasons",
+ "indirectCRL",
+ "onlyContainsAttributeCerts"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ IssuingDistributionPoint.schema({
+ names: {
+ distributionPoint: "distributionPoint",
+ distributionPointNames: "distributionPointNames",
+ onlyContainsUserCerts: "onlyContainsUserCerts",
+ onlyContainsCACerts: "onlyContainsCACerts",
+ onlySomeReasons: "onlySomeReasons",
+ indirectCRL: "indirectCRL",
+ onlyContainsAttributeCerts: "onlyContainsAttributeCerts"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for IssuingDistributionPoint");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("distributionPoint" in asn1.result)
+ {
+ switch(true)
+ {
+ case (asn1.result.distributionPoint.idBlock.tagNumber === 0): // GENERAL_NAMES variant
+ this.distributionPoint = Array.from(asn1.result.distributionPointNames, element => new GeneralName({ schema: element }));
+ break;
+ case (asn1.result.distributionPoint.idBlock.tagNumber === 1): // RDN variant
+ {
+ this.distributionPoint = new RelativeDistinguishedNames({
+ schema: new asn1js.Sequence({
+ value: asn1.result.distributionPoint.valueBlock.value
+ })
+ });
+ }
+ break;
+ default:
+ throw new Error("Unknown tagNumber for distributionPoint: {$asn1.result.distributionPoint.idBlock.tagNumber}");
+ }
+ }
+
+ if("onlyContainsUserCerts" in asn1.result)
+ {
+ const view = new Uint8Array(asn1.result.onlyContainsUserCerts.valueBlock.valueHex);
+ this.onlyContainsUserCerts = (view[0] !== 0x00);
+ }
+
+ if("onlyContainsCACerts" in asn1.result)
+ {
+ const view = new Uint8Array(asn1.result.onlyContainsCACerts.valueBlock.valueHex);
+ this.onlyContainsCACerts = (view[0] !== 0x00);
+ }
+
+ if("onlySomeReasons" in asn1.result)
+ {
+ const view = new Uint8Array(asn1.result.onlySomeReasons.valueBlock.valueHex);
+ this.onlySomeReasons = view[0];
+ }
+
+ if("indirectCRL" in asn1.result)
+ {
+ const view = new Uint8Array(asn1.result.indirectCRL.valueBlock.valueHex);
+ this.indirectCRL = (view[0] !== 0x00);
+ }
+
+ if("onlyContainsAttributeCerts" in asn1.result)
+ {
+ const view = new Uint8Array(asn1.result.onlyContainsAttributeCerts.valueBlock.valueHex);
+ this.onlyContainsAttributeCerts = (view[0] !== 0x00);
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ if("distributionPoint" in this)
+ {
+ let value;
+
+ if(this.distributionPoint instanceof Array)
+ {
+ value = new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: Array.from(this.distributionPoint, element => element.toSchema())
+ });
+ }
+ else
+ {
+ value = this.distributionPoint.toSchema();
+
+ value.idBlock.tagClass = 3; // CONTEXT - SPECIFIC
+ value.idBlock.tagNumber = 1; // [1]
+ }
+
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [value]
+ }));
+ }
+
+ if(this.onlyContainsUserCerts !== IssuingDistributionPoint.defaultValues("onlyContainsUserCerts"))
+ {
+ outputArray.push(new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ valueHex: (new Uint8Array([0xFF])).buffer
+ }));
+ }
+
+ if(this.onlyContainsCACerts !== IssuingDistributionPoint.defaultValues("onlyContainsCACerts"))
+ {
+ outputArray.push(new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ valueHex: (new Uint8Array([0xFF])).buffer
+ }));
+ }
+
+ if("onlySomeReasons" in this)
+ {
+ const buffer = new ArrayBuffer(1);
+ const view = new Uint8Array(buffer);
+
+ view[0] = this.onlySomeReasons;
+
+ outputArray.push(new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 3 // [3]
+ },
+ valueHex: buffer
+ }));
+ }
+
+ if(this.indirectCRL !== IssuingDistributionPoint.defaultValues("indirectCRL"))
+ {
+ outputArray.push(new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 4 // [4]
+ },
+ valueHex: (new Uint8Array([0xFF])).buffer
+ }));
+ }
+
+ if(this.onlyContainsAttributeCerts !== IssuingDistributionPoint.defaultValues("onlyContainsAttributeCerts"))
+ {
+ outputArray.push(new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 5 // [5]
+ },
+ valueHex: (new Uint8Array([0xFF])).buffer
+ }));
+ }
+ //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 = {};
+
+ if("distributionPoint" in this)
+ {
+ if(this.distributionPoint instanceof Array)
+ object.distributionPoint = Array.from(this.distributionPoint, element => element.toJSON());
+ else
+ object.distributionPoint = this.distributionPoint.toJSON();
+ }
+
+ if(this.onlyContainsUserCerts !== IssuingDistributionPoint.defaultValues("onlyContainsUserCerts"))
+ object.onlyContainsUserCerts = this.onlyContainsUserCerts;
+
+ if(this.onlyContainsCACerts !== IssuingDistributionPoint.defaultValues("onlyContainsCACerts"))
+ object.onlyContainsCACerts = this.onlyContainsCACerts;
+
+ if("onlySomeReasons" in this)
+ object.onlySomeReasons = this.onlySomeReasons;
+
+ if(this.indirectCRL !== IssuingDistributionPoint.defaultValues("indirectCRL"))
+ object.indirectCRL = this.indirectCRL;
+
+ if(this.onlyContainsAttributeCerts !== IssuingDistributionPoint.defaultValues("onlyContainsAttributeCerts"))
+ object.onlyContainsAttributeCerts = this.onlyContainsAttributeCerts;
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/KEKIdentifier.js b/pki.js/KEKIdentifier.js
new file mode 100644
index 0000000..ddc4c13
--- /dev/null
+++ b/pki.js/KEKIdentifier.js
@@ -0,0 +1,220 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import OtherKeyAttribute from "./OtherKeyAttribute.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class KEKIdentifier
+{
+ //**********************************************************************************
+ /**
+ * Constructor for KEKIdentifier 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 {OctetString}
+ * @desc keyIdentifier
+ */
+ this.keyIdentifier = getParametersValue(parameters, "keyIdentifier", KEKIdentifier.defaultValues("keyIdentifier"));
+
+ if("date" in parameters)
+ /**
+ * @type {GeneralizedTime}
+ * @desc date
+ */
+ this.date = getParametersValue(parameters, "date", KEKIdentifier.defaultValues("date"));
+ if("other" in parameters)
+ /**
+ * @type {OtherKeyAttribute}
+ * @desc other
+ */
+ this.other = getParametersValue(parameters, "other", KEKIdentifier.defaultValues("other"));
+ //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 "keyIdentifier":
+ return new asn1js.OctetString();
+ case "date":
+ return new asn1js.GeneralizedTime();
+ case "other":
+ return new OtherKeyAttribute();
+ default:
+ throw new Error(`Invalid member name for KEKIdentifier 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 "keyIdentifier":
+ return (memberValue.isEqual(KEKIdentifier.defaultValues("keyIdentifier")));
+ case "date":
+ // noinspection OverlyComplexBooleanExpressionJS
+ return ((memberValue.year === 0) &&
+ (memberValue.month === 0) &&
+ (memberValue.day === 0) &&
+ (memberValue.hour === 0) &&
+ (memberValue.minute === 0) &&
+ (memberValue.second === 0) &&
+ (memberValue.millisecond === 0));
+ case "other":
+ return ((memberValue.compareWithDefault("keyAttrId", memberValue.keyAttrId)) &&
+ (("keyAttr" in memberValue) === false));
+ default:
+ throw new Error(`Invalid member name for KEKIdentifier class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * KEKIdentifier ::= SEQUENCE {
+ * keyIdentifier OCTET STRING,
+ * date GeneralizedTime OPTIONAL,
+ * other OtherKeyAttribute OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [keyIdentifier]
+ * @property {string} [date]
+ * @property {string} [other]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.OctetString({ name: (names.keyIdentifier || "") }),
+ new asn1js.GeneralizedTime({
+ optional: true,
+ name: (names.date || "")
+ }),
+ OtherKeyAttribute.schema(names.other || {})
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "keyIdentifier",
+ "date",
+ "other"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ KEKIdentifier.schema({
+ names: {
+ keyIdentifier: "keyIdentifier",
+ date: "date",
+ other: {
+ names: {
+ blockName: "other"
+ }
+ }
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for KEKIdentifier");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.keyIdentifier = asn1.result.keyIdentifier;
+
+ if("date" in asn1.result)
+ this.date = asn1.result.date;
+
+ if("other" in asn1.result)
+ this.other = new OtherKeyAttribute({ schema: asn1.result.other });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(this.keyIdentifier);
+
+ if("date" in this)
+ outputArray.push(this.date);
+
+ if("other" in this)
+ outputArray.push(this.other.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 = {
+ keyIdentifier: this.keyIdentifier.toJSON()
+ };
+
+ if("date" in this)
+ _object.date = this.date;
+
+ if("other" in this)
+ _object.other = this.other.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/KEKRecipientInfo.js b/pki.js/KEKRecipientInfo.js
new file mode 100644
index 0000000..4d47f20
--- /dev/null
+++ b/pki.js/KEKRecipientInfo.js
@@ -0,0 +1,221 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import KEKIdentifier from "./KEKIdentifier.js";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class KEKRecipientInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for KEKRecipientInfo 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 {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", KEKRecipientInfo.defaultValues("version"));
+ /**
+ * @type {KEKIdentifier}
+ * @desc kekid
+ */
+ this.kekid = getParametersValue(parameters, "kekid", KEKRecipientInfo.defaultValues("kekid"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc keyEncryptionAlgorithm
+ */
+ this.keyEncryptionAlgorithm = getParametersValue(parameters, "keyEncryptionAlgorithm", KEKRecipientInfo.defaultValues("keyEncryptionAlgorithm"));
+ /**
+ * @type {OctetString}
+ * @desc encryptedKey
+ */
+ this.encryptedKey = getParametersValue(parameters, "encryptedKey", KEKRecipientInfo.defaultValues("encryptedKey"));
+ /**
+ * @type {ArrayBuffer}
+ * @desc preDefinedKEK KEK using to encrypt CEK
+ */
+ this.preDefinedKEK = getParametersValue(parameters, "preDefinedKEK", KEKRecipientInfo.defaultValues("preDefinedKEK"));
+ //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 "version":
+ return 0;
+ case "kekid":
+ return new KEKIdentifier();
+ case "keyEncryptionAlgorithm":
+ return new AlgorithmIdentifier();
+ case "encryptedKey":
+ return new asn1js.OctetString();
+ case "preDefinedKEK":
+ return new ArrayBuffer(0);
+ default:
+ throw new Error(`Invalid member name for KEKRecipientInfo 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 "KEKRecipientInfo":
+ return (memberValue === KEKRecipientInfo.defaultValues("version"));
+ case "kekid":
+ return ((memberValue.compareWithDefault("keyIdentifier", memberValue.keyIdentifier)) &&
+ (("date" in memberValue) === false) &&
+ (("other" in memberValue) === false));
+ case "keyEncryptionAlgorithm":
+ return ((memberValue.algorithmId === "") && (("algorithmParams" in memberValue) === false));
+ case "encryptedKey":
+ return (memberValue.isEqual(KEKRecipientInfo.defaultValues("encryptedKey")));
+ case "preDefinedKEK":
+ return (memberValue.byteLength === 0);
+ default:
+ throw new Error(`Invalid member name for KEKRecipientInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * KEKRecipientInfo ::= SEQUENCE {
+ * version CMSVersion, -- always set to 4
+ * kekid KEKIdentifier,
+ * keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
+ * encryptedKey EncryptedKey }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [version]
+ * @property {string} [kekid]
+ * @property {string} [keyEncryptionAlgorithm]
+ * @property {string} [encryptedKey]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Integer({ name: (names.version || "") }),
+ KEKIdentifier.schema(names.kekid || {}),
+ AlgorithmIdentifier.schema(names.keyEncryptionAlgorithm || {}),
+ new asn1js.OctetString({ name: (names.encryptedKey || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "version",
+ "kekid",
+ "keyEncryptionAlgorithm",
+ "encryptedKey"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ KEKRecipientInfo.schema({
+ names: {
+ version: "version",
+ kekid: {
+ names: {
+ blockName: "kekid"
+ }
+ },
+ keyEncryptionAlgorithm: {
+ names: {
+ blockName: "keyEncryptionAlgorithm"
+ }
+ },
+ encryptedKey: "encryptedKey"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for KEKRecipientInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result.version.valueBlock.valueDec;
+ this.kekid = new KEKIdentifier({ schema: asn1.result.kekid });
+ this.keyEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.keyEncryptionAlgorithm });
+ this.encryptedKey = asn1.result.encryptedKey;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ new asn1js.Integer({ value: this.version }),
+ this.kekid.toSchema(),
+ this.keyEncryptionAlgorithm.toSchema(),
+ this.encryptedKey
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ version: this.version,
+ kekid: this.kekid.toJSON(),
+ keyEncryptionAlgorithm: this.keyEncryptionAlgorithm.toJSON(),
+ encryptedKey: this.encryptedKey.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/KeyAgreeRecipientIdentifier.js b/pki.js/KeyAgreeRecipientIdentifier.js
new file mode 100644
index 0000000..2be16f3
--- /dev/null
+++ b/pki.js/KeyAgreeRecipientIdentifier.js
@@ -0,0 +1,206 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import IssuerAndSerialNumber from "./IssuerAndSerialNumber.js";
+import RecipientKeyIdentifier from "./RecipientKeyIdentifier.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class KeyAgreeRecipientIdentifier
+{
+ //**********************************************************************************
+ /**
+ * Constructor for KeyAgreeRecipientIdentifier 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 {number}
+ * @desc variant
+ */
+ this.variant = getParametersValue(parameters, "variant", KeyAgreeRecipientIdentifier.defaultValues("variant"));
+ /**
+ * @type {*}
+ * @desc values
+ */
+ this.value = getParametersValue(parameters, "value", KeyAgreeRecipientIdentifier.defaultValues("value"));
+ //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 "variant":
+ return (-1);
+ case "value":
+ return {};
+ default:
+ throw new Error(`Invalid member name for KeyAgreeRecipientIdentifier 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 "variant":
+ return (memberValue === (-1));
+ case "value":
+ return (Object.keys(memberValue).length === 0);
+ default:
+ throw new Error(`Invalid member name for KeyAgreeRecipientIdentifier class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * KeyAgreeRecipientIdentifier ::= CHOICE {
+ * issuerAndSerialNumber IssuerAndSerialNumber,
+ * rKeyId [0] IMPLICIT RecipientKeyIdentifier }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [issuerAndSerialNumber]
+ * @property {string} [rKeyId]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Choice({
+ value: [
+ IssuerAndSerialNumber.schema(names.issuerAndSerialNumber || {
+ names: {
+ blockName: (names.blockName || "")
+ }
+ }),
+ new asn1js.Constructed({
+ name: (names.blockName || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: RecipientKeyIdentifier.schema(names.rKeyId || {
+ names: {
+ blockName: (names.blockName || "")
+ }
+ }).valueBlock.value
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "blockName"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ KeyAgreeRecipientIdentifier.schema({
+ names: {
+ blockName: "blockName"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for KeyAgreeRecipientIdentifier");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if(asn1.result.blockName.idBlock.tagClass === 1)
+ {
+ this.variant = 1;
+ this.value = new IssuerAndSerialNumber({ schema: asn1.result.blockName });
+ }
+ else
+ {
+ this.variant = 2;
+
+ this.value = new RecipientKeyIdentifier({
+ schema: new asn1js.Sequence({
+ value: asn1.result.blockName.valueBlock.value
+ })
+ });
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ switch(this.variant)
+ {
+ case 1:
+ return this.value.toSchema();
+ case 2:
+ return new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: this.value.toSchema().valueBlock.value
+ });
+ default:
+ return new asn1js.Any();
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const _object = {
+ variant: this.variant
+ };
+
+ if((this.variant === 1) || (this.variant === 2))
+ _object.value = this.value.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/KeyAgreeRecipientInfo.js b/pki.js/KeyAgreeRecipientInfo.js
new file mode 100644
index 0000000..638a988
--- /dev/null
+++ b/pki.js/KeyAgreeRecipientInfo.js
@@ -0,0 +1,290 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import OriginatorIdentifierOrKey from "./OriginatorIdentifierOrKey.js";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import RecipientEncryptedKeys from "./RecipientEncryptedKeys.js";
+import Certificate from "./Certificate.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class KeyAgreeRecipientInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for KeyAgreeRecipientInfo 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 {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", KeyAgreeRecipientInfo.defaultValues("version"));
+ /**
+ * @type {OriginatorIdentifierOrKey}
+ * @desc originator
+ */
+ this.originator = getParametersValue(parameters, "originator", KeyAgreeRecipientInfo.defaultValues("originator"));
+
+ if("ukm" in parameters)
+ /**
+ * @type {OctetString}
+ * @desc ukm
+ */
+ this.ukm = getParametersValue(parameters, "ukm", KeyAgreeRecipientInfo.defaultValues("ukm"));
+
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc keyEncryptionAlgorithm
+ */
+ this.keyEncryptionAlgorithm = getParametersValue(parameters, "keyEncryptionAlgorithm", KeyAgreeRecipientInfo.defaultValues("keyEncryptionAlgorithm"));
+ /**
+ * @type {RecipientEncryptedKeys}
+ * @desc recipientEncryptedKeys
+ */
+ this.recipientEncryptedKeys = getParametersValue(parameters, "recipientEncryptedKeys", KeyAgreeRecipientInfo.defaultValues("recipientEncryptedKeys"));
+ /**
+ * @type {Certificate}
+ * @desc recipientCertificate For some reasons we need to store recipient's certificate here
+ */
+ this.recipientCertificate = getParametersValue(parameters, "recipientCertificate", KeyAgreeRecipientInfo.defaultValues("recipientCertificate"));
+ //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 "version":
+ return 0;
+ case "originator":
+ return new OriginatorIdentifierOrKey();
+ case "ukm":
+ return new asn1js.OctetString();
+ case "keyEncryptionAlgorithm":
+ return new AlgorithmIdentifier();
+ case "recipientEncryptedKeys":
+ return new RecipientEncryptedKeys();
+ case "recipientCertificate":
+ return new Certificate();
+ default:
+ throw new Error(`Invalid member name for KeyAgreeRecipientInfo 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 "version":
+ return (memberValue === 0);
+ case "originator":
+ return ((memberValue.variant === (-1)) && (("value" in memberValue) === false));
+ case "ukm":
+ return (memberValue.isEqual(KeyAgreeRecipientInfo.defaultValues("ukm")));
+ case "keyEncryptionAlgorithm":
+ return ((memberValue.algorithmId === "") && (("algorithmParams" in memberValue) === false));
+ case "recipientEncryptedKeys":
+ return (memberValue.encryptedKeys.length === 0);
+ case "recipientCertificate":
+ return false; // For now leave it as is
+ default:
+ throw new Error(`Invalid member name for KeyAgreeRecipientInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * KeyAgreeRecipientInfo ::= SEQUENCE {
+ * version CMSVersion, -- always set to 3
+ * originator [0] EXPLICIT OriginatorIdentifierOrKey,
+ * ukm [1] EXPLICIT UserKeyingMaterial OPTIONAL,
+ * keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
+ * recipientEncryptedKeys RecipientEncryptedKeys }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [version]
+ * @property {string} [originator]
+ * @property {string} [ukm]
+ * @property {string} [keyEncryptionAlgorithm]
+ * @property {string} [recipientEncryptedKeys]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: names.blockName || "",
+ value: [
+ new asn1js.Integer({ name: names.version || "" }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ OriginatorIdentifierOrKey.schema(names.originator || {})
+ ]
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [new asn1js.OctetString({ name: names.ukm || "" })]
+ }),
+ AlgorithmIdentifier.schema(names.keyEncryptionAlgorithm || {}),
+ RecipientEncryptedKeys.schema(names.recipientEncryptedKeys || {})
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "version",
+ "originator",
+ "ukm",
+ "keyEncryptionAlgorithm",
+ "recipientEncryptedKeys"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ KeyAgreeRecipientInfo.schema({
+ names: {
+ version: "version",
+ originator: {
+ names: {
+ blockName: "originator"
+ }
+ },
+ ukm: "ukm",
+ keyEncryptionAlgorithm: {
+ names: {
+ blockName: "keyEncryptionAlgorithm"
+ }
+ },
+ recipientEncryptedKeys: {
+ names: {
+ blockName: "recipientEncryptedKeys"
+ }
+ }
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for KeyAgreeRecipientInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result.version.valueBlock.valueDec;
+ this.originator = new OriginatorIdentifierOrKey({ schema: asn1.result.originator });
+
+ if("ukm" in asn1.result)
+ this.ukm = asn1.result.ukm;
+
+ this.keyEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.keyEncryptionAlgorithm });
+ this.recipientEncryptedKeys = new RecipientEncryptedKeys({ schema: asn1.result.recipientEncryptedKeys });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for final sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.Integer({ value: this.version }));
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [this.originator.toSchema()]
+ }));
+
+ if("ukm" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [this.ukm]
+ }));
+ }
+
+ outputArray.push(this.keyEncryptionAlgorithm.toSchema());
+ outputArray.push(this.recipientEncryptedKeys.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 = {
+ version: this.version,
+ originator: this.originator.toJSON()
+ };
+
+ if("ukm" in this)
+ _object.ukm = this.ukm.toJSON();
+
+ _object.keyEncryptionAlgorithm = this.keyEncryptionAlgorithm.toJSON();
+ _object.recipientEncryptedKeys = this.recipientEncryptedKeys.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/KeyBag.js b/pki.js/KeyBag.js
new file mode 100644
index 0000000..62fa53b
--- /dev/null
+++ b/pki.js/KeyBag.js
@@ -0,0 +1,20 @@
+import PrivateKeyInfo from "./PrivateKeyInfo.js";
+//**************************************************************************************
+/**
+ * Class from RFC5208
+ */
+export default class KeyBag extends PrivateKeyInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for Attribute class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ */
+ constructor(parameters = {})
+ {
+ super(parameters);
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/KeyTransRecipientInfo.js b/pki.js/KeyTransRecipientInfo.js
new file mode 100644
index 0000000..812df3b
--- /dev/null
+++ b/pki.js/KeyTransRecipientInfo.js
@@ -0,0 +1,248 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import Certificate from "./Certificate.js";
+import RecipientIdentifier from "./RecipientIdentifier.js";
+import IssuerAndSerialNumber from "./IssuerAndSerialNumber.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class KeyTransRecipientInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for KeyTransRecipientInfo 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 {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", KeyTransRecipientInfo.defaultValues("version"));
+ /**
+ * @type {RecipientIdentifier}
+ * @desc rid
+ */
+ this.rid = getParametersValue(parameters, "rid", KeyTransRecipientInfo.defaultValues("rid"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc keyEncryptionAlgorithm
+ */
+ this.keyEncryptionAlgorithm = getParametersValue(parameters, "keyEncryptionAlgorithm", KeyTransRecipientInfo.defaultValues("keyEncryptionAlgorithm"));
+ /**
+ * @type {OctetString}
+ * @desc encryptedKey
+ */
+ this.encryptedKey = getParametersValue(parameters, "encryptedKey", KeyTransRecipientInfo.defaultValues("encryptedKey"));
+ /**
+ * @type {Certificate}
+ * @desc recipientCertificate For some reasons we need to store recipient's certificate here
+ */
+ this.recipientCertificate = getParametersValue(parameters, "recipientCertificate", KeyTransRecipientInfo.defaultValues("recipientCertificate"));
+ //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 "version":
+ return (-1);
+ case "rid":
+ return {};
+ case "keyEncryptionAlgorithm":
+ return new AlgorithmIdentifier();
+ case "encryptedKey":
+ return new asn1js.OctetString();
+ case "recipientCertificate":
+ return new Certificate();
+ default:
+ throw new Error(`Invalid member name for KeyTransRecipientInfo 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 "version":
+ return (memberValue === KeyTransRecipientInfo.defaultValues("version"));
+ case "rid":
+ return (Object.keys(memberValue).length === 0);
+ case "keyEncryptionAlgorithm":
+ case "encryptedKey":
+ return memberValue.isEqual(KeyTransRecipientInfo.defaultValues(memberName));
+ case "recipientCertificate":
+ return false; // For now we do not need to compare any values with the "recipientCertificate"
+ default:
+ throw new Error(`Invalid member name for KeyTransRecipientInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * KeyTransRecipientInfo ::= SEQUENCE {
+ * version CMSVersion, -- always set to 0 or 2
+ * rid RecipientIdentifier,
+ * keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
+ * encryptedKey EncryptedKey }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [version]
+ * @property {string} [rid]
+ * @property {string} [keyEncryptionAlgorithm]
+ * @property {string} [encryptedKey]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Integer({ name: (names.version || "") }),
+ RecipientIdentifier.schema(names.rid || {}),
+ AlgorithmIdentifier.schema(names.keyEncryptionAlgorithm || {}),
+ new asn1js.OctetString({ name: (names.encryptedKey || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "version",
+ "rid",
+ "keyEncryptionAlgorithm",
+ "encryptedKey"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ KeyTransRecipientInfo.schema({
+ names: {
+ version: "version",
+ rid: {
+ names: {
+ blockName: "rid"
+ }
+ },
+ keyEncryptionAlgorithm: {
+ names: {
+ blockName: "keyEncryptionAlgorithm"
+ }
+ },
+ encryptedKey: "encryptedKey"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for KeyTransRecipientInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result.version.valueBlock.valueDec;
+
+ if(asn1.result.rid.idBlock.tagClass === 3)
+ this.rid = new asn1js.OctetString({ valueHex: asn1.result.rid.valueBlock.valueHex }); // SubjectKeyIdentifier
+ else
+ this.rid = new IssuerAndSerialNumber({ schema: asn1.result.rid });
+
+ this.keyEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.keyEncryptionAlgorithm });
+ this.encryptedKey = asn1.result.encryptedKey;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ if(this.rid instanceof IssuerAndSerialNumber)
+ {
+ this.version = 0;
+
+ outputArray.push(new asn1js.Integer({ value: this.version }));
+ outputArray.push(this.rid.toSchema());
+ }
+ else
+ {
+ this.version = 2;
+
+ outputArray.push(new asn1js.Integer({ value: this.version }));
+ outputArray.push(new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ valueHex: this.rid.valueBlock.valueHex
+ }));
+ }
+
+ outputArray.push(this.keyEncryptionAlgorithm.toSchema());
+ outputArray.push(this.encryptedKey);
+ //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()
+ {
+ return {
+ version: this.version,
+ rid: this.rid.toJSON(),
+ keyEncryptionAlgorithm: this.keyEncryptionAlgorithm.toJSON(),
+ encryptedKey: this.encryptedKey.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/MacData.js b/pki.js/MacData.js
new file mode 100644
index 0000000..89dc601
--- /dev/null
+++ b/pki.js/MacData.js
@@ -0,0 +1,214 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import DigestInfo from "./DigestInfo.js";
+//**************************************************************************************
+/**
+ * Class from RFC7292
+ */
+export default class MacData
+{
+ //**********************************************************************************
+ /**
+ * Constructor for MacData 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 {DigestInfo}
+ * @desc mac
+ */
+ this.mac = getParametersValue(parameters, "mac", MacData.defaultValues("mac"));
+ /**
+ * @type {OctetString}
+ * @desc macSalt
+ */
+ this.macSalt = getParametersValue(parameters, "macSalt", MacData.defaultValues("macSalt"));
+
+ if("iterations" in parameters)
+ /**
+ * @type {number}
+ * @desc iterations
+ */
+ this.iterations = getParametersValue(parameters, "iterations", MacData.defaultValues("iterations"));
+ //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 "mac":
+ return new DigestInfo();
+ case "macSalt":
+ return new asn1js.OctetString();
+ case "iterations":
+ return 1;
+ default:
+ throw new Error(`Invalid member name for MacData 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 "mac":
+ return ((DigestInfo.compareWithDefault("digestAlgorithm", memberValue.digestAlgorithm)) &&
+ (DigestInfo.compareWithDefault("digest", memberValue.digest)));
+ case "macSalt":
+ return (memberValue.isEqual(MacData.defaultValues(memberName)));
+ case "iterations":
+ return (memberValue === MacData.defaultValues(memberName));
+ default:
+ throw new Error(`Invalid member name for MacData class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * MacData ::= SEQUENCE {
+ * mac DigestInfo,
+ * macSalt OCTET STRING,
+ * iterations INTEGER DEFAULT 1
+ * -- Note: The default is for historical reasons and its use is
+ * -- deprecated. A higher value, like 1024 is recommended.
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [optional]
+ * @property {string} [mac]
+ * @property {string} [macSalt]
+ * @property {string} [iterations]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ optional: (names.optional || true),
+ value: [
+ DigestInfo.schema(names.mac || {
+ names: {
+ blockName: "mac"
+ }
+ }),
+ new asn1js.OctetString({ name: (names.macSalt || "macSalt") }),
+ new asn1js.Integer({
+ optional: true,
+ name: (names.iterations || "iterations")
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "mac",
+ "macSalt",
+ "iterations"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ MacData.schema({
+ names: {
+ mac: {
+ names: {
+ blockName: "mac"
+ }
+ },
+ macSalt: "macSalt",
+ iterations: "iterations"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for MacData");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.mac = new DigestInfo({ schema: asn1.result.mac });
+ this.macSalt = asn1.result.macSalt;
+
+ if("iterations" in asn1.result)
+ this.iterations = asn1.result.iterations.valueBlock.valueDec;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ const outputArray = [
+ this.mac.toSchema(),
+ this.macSalt
+ ];
+
+ if("iterations" in this)
+ outputArray.push(new asn1js.Integer({ value: this.iterations }));
+
+ return (new asn1js.Sequence({
+ value: outputArray
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const output = {
+ mac: this.mac.toJSON(),
+ macSalt: this.macSalt.toJSON()
+ };
+
+ if("iterations" in this)
+ output.iterations = this.iterations.toJSON();
+
+ return output;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/MessageImprint.js b/pki.js/MessageImprint.js
new file mode 100644
index 0000000..ea70b74
--- /dev/null
+++ b/pki.js/MessageImprint.js
@@ -0,0 +1,171 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+//**************************************************************************************
+/**
+ * Class from RFC3161
+ */
+export default class MessageImprint
+{
+ //**********************************************************************************
+ /**
+ * Constructor for MessageImprint 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 {AlgorithmIdentifier}
+ * @desc hashAlgorithm
+ */
+ this.hashAlgorithm = getParametersValue(parameters, "hashAlgorithm", MessageImprint.defaultValues("hashAlgorithm"));
+ /**
+ * @type {OctetString}
+ * @desc hashedMessage
+ */
+ this.hashedMessage = getParametersValue(parameters, "hashedMessage", MessageImprint.defaultValues("hashedMessage"));
+ //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 "hashAlgorithm":
+ return new AlgorithmIdentifier();
+ case "hashedMessage":
+ return new asn1js.OctetString();
+ default:
+ throw new Error(`Invalid member name for MessageImprint 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 "hashAlgorithm":
+ return ((memberValue.algorithmId === "") && (("algorithmParams" in memberValue) === false));
+ case "hashedMessage":
+ return (memberValue.isEqual(MessageImprint.defaultValues(memberName)) === 0);
+ default:
+ throw new Error(`Invalid member name for MessageImprint class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * MessageImprint ::= SEQUENCE {
+ * hashAlgorithm AlgorithmIdentifier,
+ * hashedMessage OCTET STRING }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [hashAlgorithm]
+ * @property {string} [hashedMessage]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ AlgorithmIdentifier.schema(names.hashAlgorithm || {}),
+ new asn1js.OctetString({ name: (names.hashedMessage || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "hashAlgorithm",
+ "hashedMessage"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ MessageImprint.schema({
+ names: {
+ hashAlgorithm: {
+ names: {
+ blockName: "hashAlgorithm"
+ }
+ },
+ hashedMessage: "hashedMessage"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for MessageImprint");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.hashAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.hashAlgorithm });
+ this.hashedMessage = asn1.result.hashedMessage;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ this.hashAlgorithm.toSchema(),
+ this.hashedMessage
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ hashAlgorithm: this.hashAlgorithm.toJSON(),
+ hashedMessage: this.hashedMessage.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/NameConstraints.js b/pki.js/NameConstraints.js
new file mode 100644
index 0000000..7b1e4c1
--- /dev/null
+++ b/pki.js/NameConstraints.js
@@ -0,0 +1,207 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import GeneralSubtree from "./GeneralSubtree.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class NameConstraints
+{
+ //**********************************************************************************
+ /**
+ * Constructor for NameConstraints class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ if("permittedSubtrees" in parameters)
+ /**
+ * @type {Array.}
+ * @desc permittedSubtrees
+ */
+ this.permittedSubtrees = getParametersValue(parameters, "permittedSubtrees", NameConstraints.defaultValues("permittedSubtrees"));
+
+ if("excludedSubtrees" in parameters)
+ /**
+ * @type {Array.}
+ * @desc excludedSubtrees
+ */
+ this.excludedSubtrees = getParametersValue(parameters, "excludedSubtrees", NameConstraints.defaultValues("excludedSubtrees"));
+ //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 "permittedSubtrees":
+ return [];
+ case "excludedSubtrees":
+ return [];
+ default:
+ throw new Error(`Invalid member name for NameConstraints class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * NameConstraints ::= SEQUENCE {
+ * permittedSubtrees [0] GeneralSubtrees OPTIONAL,
+ * excludedSubtrees [1] GeneralSubtrees OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [permittedSubtrees]
+ * @property {string} [excludedSubtrees]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Repeated({
+ name: (names.permittedSubtrees || ""),
+ value: GeneralSubtree.schema()
+ })
+ ]
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [
+ new asn1js.Repeated({
+ name: (names.excludedSubtrees || ""),
+ value: GeneralSubtree.schema()
+ })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "permittedSubtrees",
+ "excludedSubtrees"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ NameConstraints.schema({
+ names: {
+ permittedSubtrees: "permittedSubtrees",
+ excludedSubtrees: "excludedSubtrees"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for NameConstraints");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("permittedSubtrees" in asn1.result)
+ this.permittedSubtrees = Array.from(asn1.result.permittedSubtrees, element => new GeneralSubtree({ schema: element }));
+
+ if("excludedSubtrees" in asn1.result)
+ this.excludedSubtrees = Array.from(asn1.result.excludedSubtrees, element => new GeneralSubtree({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ if("permittedSubtrees" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: Array.from(this.permittedSubtrees, element => element.toSchema())
+ }));
+ }
+
+ if("excludedSubtrees" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: Array.from(this.excludedSubtrees, 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 = {};
+
+ if("permittedSubtrees" in this)
+ object.permittedSubtrees = Array.from(this.permittedSubtrees, element => element.toJSON());
+
+ if("excludedSubtrees" in this)
+ object.excludedSubtrees = Array.from(this.excludedSubtrees, element => element.toJSON());
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/OCSPRequest.js b/pki.js/OCSPRequest.js
new file mode 100644
index 0000000..4ed6fa8
--- /dev/null
+++ b/pki.js/OCSPRequest.js
@@ -0,0 +1,304 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import { getEngine } from "./common.js";
+import TBSRequest from "./TBSRequest.js";
+import Signature from "./Signature.js";
+import Request from "./Request.js";
+import CertID from "./CertID.js";
+//**************************************************************************************
+/**
+ * Class from RFC6960
+ */
+export default class OCSPRequest
+{
+ //**********************************************************************************
+ /**
+ * Constructor for OCSPRequest 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 {TBSRequest}
+ * @desc tbsRequest
+ */
+ this.tbsRequest = getParametersValue(parameters, "tbsRequest", OCSPRequest.defaultValues("tbsRequest"));
+
+ if("optionalSignature" in parameters)
+ /**
+ * @type {Signature}
+ * @desc optionalSignature
+ */
+ this.optionalSignature = getParametersValue(parameters, "optionalSignature", OCSPRequest.defaultValues("optionalSignature"));
+ //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 "tbsRequest":
+ return new TBSRequest();
+ case "optionalSignature":
+ return new Signature();
+ default:
+ throw new Error(`Invalid member name for OCSPRequest 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 "tbsRequest":
+ // noinspection OverlyComplexBooleanExpressionJS
+ return ((TBSRequest.compareWithDefault("tbs", memberValue.tbs)) &&
+ (TBSRequest.compareWithDefault("version", memberValue.version)) &&
+ (TBSRequest.compareWithDefault("requestorName", memberValue.requestorName)) &&
+ (TBSRequest.compareWithDefault("requestList", memberValue.requestList)) &&
+ (TBSRequest.compareWithDefault("requestExtensions", memberValue.requestExtensions)));
+ case "optionalSignature":
+ return ((Signature.compareWithDefault("signatureAlgorithm", memberValue.signatureAlgorithm)) &&
+ (Signature.compareWithDefault("signature", memberValue.signature)) &&
+ (Signature.compareWithDefault("certs", memberValue.certs)));
+ default:
+ throw new Error(`Invalid member name for OCSPRequest class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * OCSPRequest ::= SEQUENCE {
+ * tbsRequest TBSRequest,
+ * optionalSignature [0] EXPLICIT Signature OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [tbsRequest]
+ * @property {string} [optionalSignature]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: names.blockName || "OCSPRequest",
+ value: [
+ TBSRequest.schema(names.tbsRequest || {
+ names: {
+ blockName: "tbsRequest"
+ }
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ Signature.schema(names.optionalSignature || {
+ names: {
+ blockName: "optionalSignature"
+ }
+ })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "tbsRequest",
+ "optionalSignature"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ OCSPRequest.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for OCSPRequest");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.tbsRequest = new TBSRequest({ schema: asn1.result.tbsRequest });
+ if("optionalSignature" in asn1.result)
+ this.optionalSignature = new Signature({ schema: asn1.result.optionalSignature });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @param {boolean} encodeFlag If param equal to false then create TBS schema via decoding stored value. In othe case create TBS schema via assembling from TBS parts.
+ * @returns {Object} asn1js object
+ */
+ toSchema(encodeFlag = false)
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(this.tbsRequest.toSchema(encodeFlag));
+ if("optionalSignature" in this)
+ outputArray.push(this.optionalSignature.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 = {
+ tbsRequest: this.tbsRequest.toJSON()
+ };
+
+ if("optionalSignature" in this)
+ _object.optionalSignature = this.optionalSignature.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+ /**
+ * Making OCSP Request for specific certificate
+ * @param {Certificate} certificate Certificate making OCSP Request for
+ * @param {Object} parameters Additional parameters
+ * @returns {Promise}
+ */
+ createForCertificate(certificate, parameters)
+ {
+ //region Initial variables
+ let sequence = Promise.resolve();
+
+ const certID = new CertID();
+ //endregion
+
+ //region Create OCSP certificate identifier for the certificate
+ sequence = sequence.then(() =>
+ certID.createForCertificate(certificate, parameters)
+ );
+ //endregion
+
+ //region Make final request data
+ sequence = sequence.then(() =>
+ {
+ this.tbsRequest = new TBSRequest({
+ requestList: [
+ new Request({
+ reqCert: certID
+ })
+ ]
+ });
+ }, error =>
+ Promise.reject(error)
+ );
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ /**
+ * Make signature for current OCSP Request
+ * @param {Object} privateKey Private key for "subjectPublicKeyInfo" structure
+ * @param {string} [hashAlgorithm] Hashing algorithm. Default SHA-1
+ * @returns {Promise}
+ */
+ sign(privateKey, hashAlgorithm = "SHA-1")
+ {
+ //region Initial checking
+ //region Check private key
+ if(typeof privateKey === "undefined")
+ return Promise.reject("Need to provide a private key for signing");
+ //endregion
+
+ //region Check that "optionalSignature" exists in the current request
+ if(("optionalSignature" in this) === false)
+ return Promise.reject("Need to create \"optionalSignature\" field before signing");
+ //endregion
+ //endregion
+
+ //region Initial variables
+ let sequence = Promise.resolve();
+ let parameters;
+
+ let tbs;
+
+ const engine = getEngine();
+ //endregion
+
+ //region Get a "default parameters" for current algorithm and set correct signature algorithm
+ sequence = sequence.then(() => engine.subtle.getSignatureParameters(privateKey, hashAlgorithm));
+
+ sequence = sequence.then(result =>
+ {
+ parameters = result.parameters;
+ this.optionalSignature.signatureAlgorithm = result.signatureAlgorithm;
+ });
+ //endregion
+
+ //region Create TBS data for signing
+ sequence = sequence.then(() =>
+ {
+ tbs = this.tbsRequest.toSchema(true).toBER(false);
+ });
+ //endregion
+
+ //region Signing TBS data on provided private key
+ sequence = sequence.then(() => engine.subtle.signWithPrivateKey(tbs, privateKey, parameters));
+
+ sequence = sequence.then(result =>
+ {
+ this.optionalSignature.signature = new asn1js.BitString({ valueHex: result });
+ });
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ verify()
+ {
+ // TODO: Create the function
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/OCSPResponse.js b/pki.js/OCSPResponse.js
new file mode 100644
index 0000000..782010b
--- /dev/null
+++ b/pki.js/OCSPResponse.js
@@ -0,0 +1,299 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import ResponseBytes from "./ResponseBytes.js";
+import BasicOCSPResponse from "./BasicOCSPResponse.js";
+//**************************************************************************************
+/**
+ * Class from RFC6960
+ */
+export default class OCSPResponse
+{
+ //**********************************************************************************
+ /**
+ * Constructor for OCSPResponse 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 {Enumerated}
+ * @desc responseStatus
+ */
+ this.responseStatus = getParametersValue(parameters, "responseStatus", OCSPResponse.defaultValues("responseStatus"));
+
+ if("responseBytes" in parameters)
+ /**
+ * @type {ResponseBytes}
+ * @desc responseBytes
+ */
+ this.responseBytes = getParametersValue(parameters, "responseBytes", OCSPResponse.defaultValues("responseBytes"));
+ //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 "responseStatus":
+ return new asn1js.Enumerated();
+ case "responseBytes":
+ return new ResponseBytes();
+ default:
+ throw new Error(`Invalid member name for OCSPResponse 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 "responseStatus":
+ return (memberValue.isEqual(OCSPResponse.defaultValues(memberName)));
+ case "responseBytes":
+ return ((ResponseBytes.compareWithDefault("responseType", memberValue.responseType)) &&
+ (ResponseBytes.compareWithDefault("response", memberValue.response)));
+ default:
+ throw new Error(`Invalid member name for OCSPResponse class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * OCSPResponse ::= SEQUENCE {
+ * responseStatus OCSPResponseStatus,
+ * responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
+ *
+ * OCSPResponseStatus ::= ENUMERATED {
+ * successful (0), -- Response has valid confirmations
+ * malformedRequest (1), -- Illegal confirmation request
+ * internalError (2), -- Internal error in issuer
+ * tryLater (3), -- Try again later
+ * -- (4) is not used
+ * sigRequired (5), -- Must sign the request
+ * unauthorized (6) -- Request unauthorized
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [responseStatus]
+ * @property {string} [responseBytes]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || "OCSPResponse"),
+ value: [
+ new asn1js.Enumerated({ name: (names.responseStatus || "responseStatus") }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ ResponseBytes.schema(names.responseBytes || {
+ names: {
+ blockName: "responseBytes"
+ }
+ })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "responseStatus",
+ "responseBytes"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ OCSPResponse.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for OCSPResponse");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.responseStatus = asn1.result.responseStatus;
+ if("responseBytes" in asn1.result)
+ this.responseBytes = new ResponseBytes({ schema: asn1.result.responseBytes });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(this.responseStatus);
+ if("responseBytes" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [this.responseBytes.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 = {
+ responseStatus: this.responseStatus.toJSON()
+ };
+
+ if("responseBytes" in this)
+ _object.responseBytes = this.responseBytes.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+ /**
+ * Get OCSP response status for specific certificate
+ * @param {Certificate} certificate
+ * @param {Certificate} issuerCertificate
+ * @returns {*}
+ */
+ getCertificateStatus(certificate, issuerCertificate)
+ {
+ //region Initial variables
+ let basicResponse;
+
+ const result = {
+ isForCertificate: false,
+ status: 2 // 0 = good, 1 = revoked, 2 = unknown
+ };
+ //endregion
+
+ //region Check that "ResponseBytes" contain "OCSP_BASIC_RESPONSE"
+ if(("responseBytes" in this) === false)
+ return result;
+
+ if(this.responseBytes.responseType !== "1.3.6.1.5.5.7.48.1.1") // id-pkix-ocsp-basic
+ return result;
+
+ try
+ {
+ const asn1Basic = asn1js.fromBER(this.responseBytes.response.valueBlock.valueHex);
+ basicResponse = new BasicOCSPResponse({ schema: asn1Basic.result });
+ }
+ catch(ex)
+ {
+ return result;
+ }
+ //endregion
+
+ return basicResponse.getCertificateStatus(certificate, issuerCertificate);
+ }
+ //**********************************************************************************
+ /**
+ * Make a signature for current OCSP Response
+ * @param {Object} privateKey Private key for "subjectPublicKeyInfo" structure
+ * @param {string} [hashAlgorithm] Hashing algorithm. Default SHA-1
+ * @returns {Promise}
+ */
+ sign(privateKey, hashAlgorithm)
+ {
+ //region Check that ResponseData has type BasicOCSPResponse and sign it
+ if(this.responseBytes.responseType === "1.3.6.1.5.5.7.48.1.1")
+ {
+ const asn1 = asn1js.fromBER(this.responseBytes.response.valueBlock.valueHex);
+ const basicResponse = new BasicOCSPResponse({ schema: asn1.result });
+
+ return basicResponse.sign(privateKey, hashAlgorithm);
+ }
+
+ return Promise.reject(`Unknown ResponseBytes type: ${this.responseBytes.responseType}`);
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Verify current OCSP Response
+ * @param {Certificate|null} issuerCertificate In order to decrease size of resp issuer cert could be ommited. In such case you need manually provide it.
+ * @returns {Promise}
+ */
+ verify(issuerCertificate = null)
+ {
+ //region Check that ResponseBytes exists in the object
+ if(("responseBytes" in this) === false)
+ return Promise.reject("Empty ResponseBytes field");
+ //endregion
+
+ //region Check that ResponceData has type BasicOCSPResponse and verify it
+ if(this.responseBytes.responseType === "1.3.6.1.5.5.7.48.1.1")
+ {
+ const asn1 = asn1js.fromBER(this.responseBytes.response.valueBlock.valueHex);
+ const basicResponse = new BasicOCSPResponse({ schema: asn1.result });
+
+ if(issuerCertificate !== null)
+ {
+ if(("certs" in basicResponse) === false)
+ basicResponse.certs = [];
+
+ basicResponse.certs.push(issuerCertificate);
+ }
+
+ return basicResponse.verify();
+ }
+
+ return Promise.reject(`Unknown ResponseBytes type: ${this.responseBytes.responseType}`);
+ //endregion
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/OriginatorIdentifierOrKey.js b/pki.js/OriginatorIdentifierOrKey.js
new file mode 100644
index 0000000..b1f8760
--- /dev/null
+++ b/pki.js/OriginatorIdentifierOrKey.js
@@ -0,0 +1,228 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import IssuerAndSerialNumber from "./IssuerAndSerialNumber.js";
+import OriginatorPublicKey from "./OriginatorPublicKey.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class OriginatorIdentifierOrKey
+{
+ //**********************************************************************************
+ /**
+ * Constructor for OriginatorIdentifierOrKey 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 {number}
+ * @desc variant
+ */
+ this.variant = getParametersValue(parameters, "variant", OriginatorIdentifierOrKey.defaultValues("variant"));
+
+ if("value" in parameters)
+ /**
+ * @type {IssuerAndSerialNumber|OctetString|OriginatorPublicKey}
+ * @desc value
+ */
+ this.value = getParametersValue(parameters, "value", OriginatorIdentifierOrKey.defaultValues("value"));
+ //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 "variant":
+ return (-1);
+ case "value":
+ return {};
+ default:
+ throw new Error(`Invalid member name for OriginatorIdentifierOrKey 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 "variant":
+ return (memberValue === (-1));
+ case "value":
+ return (Object.keys(memberValue).length === 0);
+ default:
+ throw new Error(`Invalid member name for OriginatorIdentifierOrKey class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * OriginatorIdentifierOrKey ::= CHOICE {
+ * issuerAndSerialNumber IssuerAndSerialNumber,
+ * subjectKeyIdentifier [0] SubjectKeyIdentifier,
+ * originatorKey [1] OriginatorPublicKey }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Choice({
+ value: [
+ IssuerAndSerialNumber.schema({
+ names: {
+ blockName: (names.blockName || "")
+ }
+ }),
+ new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ name: (names.blockName || "")
+ }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ name: (names.blockName || ""),
+ value: OriginatorPublicKey.schema().valueBlock.value
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "blockName"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ OriginatorIdentifierOrKey.schema({
+ names: {
+ blockName: "blockName"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for OriginatorIdentifierOrKey");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if(asn1.result.blockName.idBlock.tagClass === 1)
+ {
+ this.variant = 1;
+ this.value = new IssuerAndSerialNumber({ schema: asn1.result.blockName });
+ }
+ else
+ {
+ if(asn1.result.blockName.idBlock.tagNumber === 0)
+ {
+ //region Create "OCTETSTRING" from "ASN1_PRIMITIVE"
+ asn1.result.blockName.idBlock.tagClass = 1; // UNIVERSAL
+ asn1.result.blockName.idBlock.tagNumber = 4; // OCTETSTRING
+ //endregion
+
+ this.variant = 2;
+ this.value = asn1.result.blockName;
+ }
+ else
+ {
+ this.variant = 3;
+ this.value = new OriginatorPublicKey({
+ schema: new asn1js.Sequence({
+ value: asn1.result.blockName.valueBlock.value
+ })
+ });
+ }
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ switch(this.variant)
+ {
+ case 1:
+ return this.value.toSchema();
+ case 2:
+ this.value.idBlock.tagClass = 3; // CONTEXT-SPECIFIC
+ this.value.idBlock.tagNumber = 0; // [0]
+
+ return this.value;
+ case 3:
+ {
+ const _schema = this.value.toSchema();
+
+ _schema.idBlock.tagClass = 3; // CONTEXT-SPECIFIC
+ _schema.idBlock.tagNumber = 1; // [1]
+
+ return _schema;
+ }
+ default:
+ return new asn1js.Any();
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const _object = {
+ variant: this.variant
+ };
+
+ if((this.variant === 1) || (this.variant === 2) || (this.variant === 3))
+ _object.value = this.value.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/OriginatorInfo.js b/pki.js/OriginatorInfo.js
new file mode 100644
index 0000000..28e7345
--- /dev/null
+++ b/pki.js/OriginatorInfo.js
@@ -0,0 +1,228 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import CertificateSet from "./CertificateSet.js";
+import RevocationInfoChoices from "./RevocationInfoChoices.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class OriginatorInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for OriginatorInfo class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ if("certs" in parameters)
+ /**
+ * @type {CertificateSet}
+ * @desc certs
+ */
+ this.certs = getParametersValue(parameters, "certs", OriginatorInfo.defaultValues("certs"));
+
+ if("crls" in parameters)
+ /**
+ * @type {RevocationInfoChoices}
+ * @desc crls
+ */
+ this.crls = getParametersValue(parameters, "crls", OriginatorInfo.defaultValues("crls"));
+ //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 "certs":
+ return new CertificateSet();
+ case "crls":
+ return new RevocationInfoChoices();
+ default:
+ throw new Error(`Invalid member name for OriginatorInfo 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 "certs":
+ return (memberValue.certificates.length === 0);
+ case "crls":
+ return ((memberValue.crls.length === 0) && (memberValue.otherRevocationInfos.length === 0));
+ default:
+ throw new Error(`Invalid member name for OriginatorInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * OriginatorInfo ::= SEQUENCE {
+ * certs [0] IMPLICIT CertificateSet OPTIONAL,
+ * crls [1] IMPLICIT RevocationInfoChoices OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [certs]
+ * @property {string} [crls]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Constructed({
+ name: (names.certs || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: CertificateSet.schema().valueBlock.value
+ }),
+ new asn1js.Constructed({
+ name: (names.crls || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: RevocationInfoChoices.schema().valueBlock.value
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "certs",
+ "crls"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ OriginatorInfo.schema({
+ names: {
+ certs: "certs",
+ crls: "crls"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for OriginatorInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("certs" in asn1.result)
+ {
+ this.certs = new CertificateSet({
+ schema: new asn1js.Set({
+ value: asn1.result.certs.valueBlock.value
+ })
+ });
+ }
+
+ if("crls" in asn1.result)
+ {
+ this.crls = new RevocationInfoChoices({
+ schema: new asn1js.Set({
+ value: asn1.result.crls.valueBlock.value
+ })
+ });
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ const sequenceValue = [];
+
+ if("certs" in this)
+ {
+ sequenceValue.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: this.certs.toSchema().valueBlock.value
+ }));
+ }
+
+ if("crls" in this)
+ {
+ sequenceValue.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: this.crls.toSchema().valueBlock.value
+ }));
+ }
+
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: sequenceValue
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const object = {};
+
+ if("certs" in this)
+ object.certs = this.certs.toJSON();
+
+ if("crls" in this)
+ object.crls = this.crls.toJSON();
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/OriginatorPublicKey.js b/pki.js/OriginatorPublicKey.js
new file mode 100644
index 0000000..3f2ea81
--- /dev/null
+++ b/pki.js/OriginatorPublicKey.js
@@ -0,0 +1,170 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class OriginatorPublicKey
+{
+ //**********************************************************************************
+ /**
+ * Constructor for OriginatorPublicKey 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 {AlgorithmIdentifier}
+ * @desc algorithm
+ */
+ this.algorithm = getParametersValue(parameters, "algorithm", OriginatorPublicKey.defaultValues("algorithm"));
+ /**
+ * @type {BitString}
+ * @desc publicKey
+ */
+ this.publicKey = getParametersValue(parameters, "publicKey", OriginatorPublicKey.defaultValues("publicKey"));
+ //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 "algorithm":
+ return new AlgorithmIdentifier();
+ case "publicKey":
+ return new asn1js.BitString();
+ default:
+ throw new Error(`Invalid member name for OriginatorPublicKey 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 "algorithm":
+ case "publicKey":
+ return (memberValue.isEqual(OriginatorPublicKey.defaultValues(memberName)));
+ default:
+ throw new Error(`Invalid member name for OriginatorPublicKey class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * OriginatorPublicKey ::= SEQUENCE {
+ * algorithm AlgorithmIdentifier,
+ * publicKey BIT STRING }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [algorithm]
+ * @property {string} [publicKey]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ AlgorithmIdentifier.schema(names.algorithm || {}),
+ new asn1js.BitString({ name: (names.publicKey || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "algorithm",
+ "publicKey"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ OriginatorPublicKey.schema({
+ names: {
+ algorithm: {
+ names: {
+ blockName: "algorithm"
+ }
+ },
+ publicKey: "publicKey"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for OriginatorPublicKey");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.algorithm = new AlgorithmIdentifier({ schema: asn1.result.algorithm });
+ this.publicKey = asn1.result.publicKey;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ this.algorithm.toSchema(),
+ this.publicKey
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ algorithm: this.algorithm.toJSON(),
+ publicKey: this.publicKey.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/OtherCertificateFormat.js b/pki.js/OtherCertificateFormat.js
new file mode 100644
index 0000000..b3ab792
--- /dev/null
+++ b/pki.js/OtherCertificateFormat.js
@@ -0,0 +1,147 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class OtherCertificateFormat
+{
+ //**********************************************************************************
+ /**
+ * Constructor for OtherCertificateFormat 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 {string}
+ * @desc otherCertFormat
+ */
+ this.otherCertFormat = getParametersValue(parameters, "otherCertFormat", OtherCertificateFormat.defaultValues("otherCertFormat"));
+ /**
+ * @type {Any}
+ * @desc otherCert
+ */
+ this.otherCert = getParametersValue(parameters, "otherCert", OtherCertificateFormat.defaultValues("otherCert"));
+ //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 "otherCertFormat":
+ return "";
+ case "otherCert":
+ return new asn1js.Any();
+ default:
+ throw new Error(`Invalid member name for OtherCertificateFormat class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * OtherCertificateFormat ::= SEQUENCE {
+ * otherCertFormat OBJECT IDENTIFIER,
+ * otherCert ANY DEFINED BY otherCertFormat }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [otherCertFormat]
+ * @property {string} [otherCert]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.otherCertFormat || "otherCertFormat") }),
+ new asn1js.Any({ name: (names.otherCert || "otherCert") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "otherCertFormat",
+ "otherCert"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ OtherCertificateFormat.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for OtherCertificateFormat");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.otherCertFormat = asn1.result.otherCertFormat.valueBlock.toString();
+ this.otherCert = asn1.result.otherCert;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ new asn1js.ObjectIdentifier({ value: this.otherCertFormat }),
+ this.otherCert
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const object = {
+ otherCertFormat: this.otherCertFormat
+ };
+
+ if(!(this.otherCert instanceof asn1js.Any))
+ object.otherCert = this.otherCert.toJSON();
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/OtherKeyAttribute.js b/pki.js/OtherKeyAttribute.js
new file mode 100644
index 0000000..5642c84
--- /dev/null
+++ b/pki.js/OtherKeyAttribute.js
@@ -0,0 +1,185 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class OtherKeyAttribute
+{
+ //**********************************************************************************
+ /**
+ * Constructor for OtherKeyAttribute 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 {string}
+ * @desc keyAttrId
+ */
+ this.keyAttrId = getParametersValue(parameters, "keyAttrId", OtherKeyAttribute.defaultValues("keyAttrId"));
+
+ if("keyAttr" in parameters)
+ /**
+ * @type {*}
+ * @desc keyAttr
+ */
+ this.keyAttr = getParametersValue(parameters, "keyAttr", OtherKeyAttribute.defaultValues("keyAttr"));
+ //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 "keyAttrId":
+ return "";
+ case "keyAttr":
+ return {};
+ default:
+ throw new Error(`Invalid member name for OtherKeyAttribute 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 "keyAttrId":
+ return (memberValue === "");
+ case "keyAttr":
+ return (Object.keys(memberValue).length === 0);
+ default:
+ throw new Error(`Invalid member name for OtherKeyAttribute class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * OtherKeyAttribute ::= SEQUENCE {
+ * keyAttrId OBJECT IDENTIFIER,
+ * keyAttr ANY DEFINED BY keyAttrId OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [optional]
+ * @property {string} [keyAttrId]
+ * @property {string} [keyAttr]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ optional: (names.optional || true),
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.keyAttrId || "") }),
+ new asn1js.Any({
+ optional: true,
+ name: (names.keyAttr || "")
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "keyAttrId",
+ "keyAttr"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ OtherKeyAttribute.schema({
+ names: {
+ keyAttrId: "keyAttrId",
+ keyAttr: "keyAttr"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for OtherKeyAttribute");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.keyAttrId = asn1.result.keyAttrId.valueBlock.toString();
+
+ if("keyAttr" in asn1.result)
+ this.keyAttr = asn1.result.keyAttr;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.ObjectIdentifier({ value: this.keyAttrId }));
+
+ if("keyAttr" in this)
+ outputArray.push(this.keyAttr);
+ //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 = {
+ keyAttrId: this.keyAttrId
+ };
+
+ if("keyAttr" in this)
+ _object.keyAttr = this.keyAttr.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/OtherPrimeInfo.js b/pki.js/OtherPrimeInfo.js
new file mode 100644
index 0000000..cd97a08
--- /dev/null
+++ b/pki.js/OtherPrimeInfo.js
@@ -0,0 +1,190 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, toBase64, arrayBufferToString, stringToArrayBuffer, fromBase64, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC3447
+ */
+export default class OtherPrimeInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for OtherPrimeInfo 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 {Integer}
+ * @desc prime
+ */
+ this.prime = getParametersValue(parameters, "prime", OtherPrimeInfo.defaultValues("prime"));
+ /**
+ * @type {Integer}
+ * @desc exponent
+ */
+ this.exponent = getParametersValue(parameters, "exponent", OtherPrimeInfo.defaultValues("exponent"));
+ /**
+ * @type {Integer}
+ * @desc coefficient
+ */
+ this.coefficient = getParametersValue(parameters, "coefficient", OtherPrimeInfo.defaultValues("coefficient"));
+ //endregion
+
+ //region If input argument array contains "schema" for this object
+ if("schema" in parameters)
+ this.fromSchema(parameters.schema);
+ //endregion
+ //region If input argument array contains "json" for this object
+ if("json" in parameters)
+ this.fromJSON(parameters.json);
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Return default values for all class members
+ * @param {string} memberName String name for a class member
+ */
+ static defaultValues(memberName)
+ {
+ switch(memberName)
+ {
+ case "prime":
+ return new asn1js.Integer();
+ case "exponent":
+ return new asn1js.Integer();
+ case "coefficient":
+ return new asn1js.Integer();
+ default:
+ throw new Error(`Invalid member name for OtherPrimeInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * OtherPrimeInfo ::= Sequence {
+ * prime Integer, -- ri
+ * exponent Integer, -- di
+ * coefficient Integer -- ti
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+
+ /**
+ * @type {Object}
+ * @property {string} prime
+ * @property {string} exponent
+ * @property {string} coefficient
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Integer({ name: (names.prime || "") }),
+ new asn1js.Integer({ name: (names.exponent || "") }),
+ new asn1js.Integer({ name: (names.coefficient || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "prime",
+ "exponent",
+ "coefficient"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ OtherPrimeInfo.schema({
+ names: {
+ prime: "prime",
+ exponent: "exponent",
+ coefficient: "coefficient"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for OtherPrimeInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.prime = asn1.result.prime.convertFromDER();
+ this.exponent = asn1.result.exponent.convertFromDER();
+ this.coefficient = asn1.result.coefficient.convertFromDER();
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ this.prime.convertToDER(),
+ this.exponent.convertToDER(),
+ this.coefficient.convertToDER()
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ r: toBase64(arrayBufferToString(this.prime.valueBlock.valueHex), true, true),
+ d: toBase64(arrayBufferToString(this.exponent.valueBlock.valueHex), true, true),
+ t: toBase64(arrayBufferToString(this.coefficient.valueBlock.valueHex), true, true)
+ };
+ }
+ //**********************************************************************************
+ /**
+ * Convert JSON value into current object
+ * @param {Object} json
+ */
+ fromJSON(json)
+ {
+ if("r" in json)
+ this.prime = new asn1js.Integer({ valueHex: stringToArrayBuffer(fromBase64(json.r, true)) });
+ else
+ throw new Error("Absent mandatory parameter \"r\"");
+
+ if("d" in json)
+ this.exponent = new asn1js.Integer({ valueHex: stringToArrayBuffer(fromBase64(json.d, true)) });
+ else
+ throw new Error("Absent mandatory parameter \"d\"");
+
+ if("t" in json)
+ this.coefficient = new asn1js.Integer({ valueHex: stringToArrayBuffer(fromBase64(json.t, true)) });
+ else
+ throw new Error("Absent mandatory parameter \"t\"");
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/OtherRecipientInfo.js b/pki.js/OtherRecipientInfo.js
new file mode 100644
index 0000000..e45d1fa
--- /dev/null
+++ b/pki.js/OtherRecipientInfo.js
@@ -0,0 +1,170 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class OtherRecipientInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for OtherRecipientInfo 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 {string}
+ * @desc oriType
+ */
+ this.oriType = getParametersValue(parameters, "oriType", OtherRecipientInfo.defaultValues("oriType"));
+ /**
+ * @type {*}
+ * @desc oriValue
+ */
+ this.oriValue = getParametersValue(parameters, "oriValue", OtherRecipientInfo.defaultValues("oriValue"));
+ //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 "oriType":
+ return "";
+ case "oriValue":
+ return {};
+ default:
+ throw new Error(`Invalid member name for OtherRecipientInfo 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 "oriType":
+ return (memberValue === "");
+ case "oriValue":
+ return (Object.keys(memberValue).length === 0);
+ default:
+ throw new Error(`Invalid member name for OtherRecipientInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * OtherRecipientInfo ::= SEQUENCE {
+ * oriType OBJECT IDENTIFIER,
+ * oriValue ANY DEFINED BY oriType }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [oriType]
+ * @property {string} [oriValue]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.oriType || "") }),
+ new asn1js.Any({ name: (names.oriValue || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "oriType",
+ "oriValue"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ OtherRecipientInfo.schema({
+ names: {
+ oriType: "oriType",
+ oriValue: "oriValue"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for OtherRecipientInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.oriType = asn1.result.oriType.valueBlock.toString();
+ this.oriValue = asn1.result.oriValue;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ new asn1js.ObjectIdentifier({ value: this.oriType }),
+ this.oriValue
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const _object = {
+ oriType: this.oriType
+ };
+
+ if(OtherRecipientInfo.compareWithDefault("oriValue", this.oriValue) === false)
+ _object.oriValue = this.oriValue.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/OtherRevocationInfoFormat.js b/pki.js/OtherRevocationInfoFormat.js
new file mode 100644
index 0000000..acd471f
--- /dev/null
+++ b/pki.js/OtherRevocationInfoFormat.js
@@ -0,0 +1,147 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class OtherRevocationInfoFormat
+{
+ //**********************************************************************************
+ /**
+ * Constructor for OtherRevocationInfoFormat 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 {string}
+ * @desc otherRevInfoFormat
+ */
+ this.otherRevInfoFormat = getParametersValue(parameters, "otherRevInfoFormat", OtherRevocationInfoFormat.defaultValues("otherRevInfoFormat"));
+ /**
+ * @type {Any}
+ * @desc otherRevInfo
+ */
+ this.otherRevInfo = getParametersValue(parameters, "otherRevInfo", OtherRevocationInfoFormat.defaultValues("otherRevInfo"));
+ //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 "otherRevInfoFormat":
+ return "";
+ case "otherRevInfo":
+ return new asn1js.Any();
+ default:
+ throw new Error(`Invalid member name for OtherRevocationInfoFormat class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * OtherCertificateFormat ::= SEQUENCE {
+ * otherRevInfoFormat OBJECT IDENTIFIER,
+ * otherRevInfo ANY DEFINED BY otherCertFormat }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [otherRevInfoFormat]
+ * @property {string} [otherRevInfo]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.otherRevInfoFormat || "otherRevInfoFormat") }),
+ new asn1js.Any({ name: (names.otherRevInfo || "otherRevInfo") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "otherRevInfoFormat",
+ "otherRevInfo"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ OtherRevocationInfoFormat.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for OtherRevocationInfoFormat");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.otherRevInfoFormat = asn1.result.otherRevInfoFormat.valueBlock.toString();
+ this.otherRevInfo = asn1.result.otherRevInfo;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ new asn1js.ObjectIdentifier({ value: this.otherRevInfoFormat }),
+ this.otherRevInfo
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const object = {
+ otherRevInfoFormat: this.otherRevInfoFormat
+ };
+
+ if(!(this.otherRevInfo instanceof asn1js.Any))
+ object.otherRevInfo = this.otherRevInfo.toJSON();
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/PBES2Params.js b/pki.js/PBES2Params.js
new file mode 100644
index 0000000..54bc202
--- /dev/null
+++ b/pki.js/PBES2Params.js
@@ -0,0 +1,157 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+//**************************************************************************************
+/**
+ * Class from RFC2898
+ */
+export default class PBES2Params
+{
+ //**********************************************************************************
+ /**
+ * Constructor for PBES2Params 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 {AlgorithmIdentifier}
+ * @desc keyDerivationFunc
+ */
+ this.keyDerivationFunc = getParametersValue(parameters, "keyDerivationFunc", PBES2Params.defaultValues("keyDerivationFunc"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc encryptionScheme
+ */
+ this.encryptionScheme = getParametersValue(parameters, "encryptionScheme", PBES2Params.defaultValues("encryptionScheme"));
+ //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 "keyDerivationFunc":
+ return new AlgorithmIdentifier();
+ case "encryptionScheme":
+ return new AlgorithmIdentifier();
+ default:
+ throw new Error(`Invalid member name for PBES2Params class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * PBES2-params ::= SEQUENCE {
+ * keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}},
+ * encryptionScheme AlgorithmIdentifier {{PBES2-Encs}} }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [keyDerivationFunc]
+ * @property {string} [encryptionScheme]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ AlgorithmIdentifier.schema(names.keyDerivationFunc || {}),
+ AlgorithmIdentifier.schema(names.encryptionScheme || {})
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "keyDerivationFunc",
+ "encryptionScheme"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ PBES2Params.schema({
+ names: {
+ keyDerivationFunc: {
+ names: {
+ blockName: "keyDerivationFunc"
+ }
+ },
+ encryptionScheme: {
+ names: {
+ blockName: "encryptionScheme"
+ }
+ }
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PBES2Params");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.keyDerivationFunc = new AlgorithmIdentifier({ schema: asn1.result.keyDerivationFunc });
+ this.encryptionScheme = new AlgorithmIdentifier({ schema: asn1.result.encryptionScheme });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ this.keyDerivationFunc.toSchema(),
+ this.encryptionScheme.toSchema()
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ keyDerivationFunc: this.keyDerivationFunc.toJSON(),
+ encryptionScheme: this.encryptionScheme.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/PBKDF2Params.js b/pki.js/PBKDF2Params.js
new file mode 100644
index 0000000..6eb6684
--- /dev/null
+++ b/pki.js/PBKDF2Params.js
@@ -0,0 +1,242 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+//**************************************************************************************
+/**
+ * Class from RFC2898
+ */
+export default class PBKDF2Params
+{
+ //**********************************************************************************
+ /**
+ * Constructor for PBKDF2Params 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 {Object}
+ * @desc salt
+ */
+ this.salt = getParametersValue(parameters, "salt", PBKDF2Params.defaultValues("salt"));
+ /**
+ * @type {number}
+ * @desc iterationCount
+ */
+ this.iterationCount = getParametersValue(parameters, "iterationCount", PBKDF2Params.defaultValues("iterationCount"));
+
+ if("keyLength" in parameters)
+ /**
+ * @type {number}
+ * @desc keyLength
+ */
+ this.keyLength = getParametersValue(parameters, "keyLength", PBKDF2Params.defaultValues("keyLength"));
+
+ if("prf" in parameters)
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc prf
+ */
+ this.prf = getParametersValue(parameters, "prf", PBKDF2Params.defaultValues("prf"));
+ //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 "salt":
+ return {};
+ case "iterationCount":
+ return (-1);
+ case "keyLength":
+ return 0;
+ case "prf":
+ return new AlgorithmIdentifier({
+ algorithmId: "1.3.14.3.2.26", // SHA-1
+ algorithmParams: new asn1js.Null()
+ });
+ default:
+ throw new Error(`Invalid member name for PBKDF2Params class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * PBKDF2-params ::= SEQUENCE {
+ * salt CHOICE {
+ * specified OCTET STRING,
+ * otherSource AlgorithmIdentifier },
+ * iterationCount INTEGER (1..MAX),
+ * keyLength INTEGER (1..MAX) OPTIONAL,
+ * prf AlgorithmIdentifier
+ * DEFAULT { algorithm hMAC-SHA1, parameters NULL } }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [saltPrimitive]
+ * @property {string} [saltConstructed]
+ * @property {string} [iterationCount]
+ * @property {string} [keyLength]
+ * @property {string} [prf]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Choice({
+ value: [
+ new asn1js.OctetString({ name: (names.saltPrimitive || "") }),
+ AlgorithmIdentifier.schema(names.saltConstructed || {})
+ ]
+ }),
+ new asn1js.Integer({ name: (names.iterationCount || "") }),
+ new asn1js.Integer({
+ name: (names.keyLength || ""),
+ optional: true
+ }),
+ AlgorithmIdentifier.schema(names.prf || {
+ names: {
+ optional: true
+ }
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "salt",
+ "iterationCount",
+ "keyLength",
+ "prf"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ PBKDF2Params.schema({
+ names: {
+ saltPrimitive: "salt",
+ saltConstructed: {
+ names: {
+ blockName: "salt"
+ }
+ },
+ iterationCount: "iterationCount",
+ keyLength: "keyLength",
+ prf: {
+ names: {
+ blockName: "prf",
+ optional: true
+ }
+ }
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PBKDF2Params");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.salt = asn1.result.salt;
+ this.iterationCount = asn1.result.iterationCount.valueBlock.valueDec;
+
+ if("keyLength" in asn1.result)
+ this.keyLength = asn1.result.keyLength.valueBlock.valueDec;
+
+ if("prf" in asn1.result)
+ this.prf = new AlgorithmIdentifier({ schema: asn1.result.prf });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(this.salt);
+ outputArray.push(new asn1js.Integer({ value: this.iterationCount }));
+
+ if("keyLength" in this)
+ {
+ if(PBKDF2Params.defaultValues("keyLength") !== this.keyLength)
+ outputArray.push(new asn1js.Integer({ value: this.keyLength }));
+ }
+
+ if("prf" in this)
+ {
+ if(PBKDF2Params.defaultValues("prf").isEqual(this.prf) === false)
+ outputArray.push(this.prf.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 = {
+ salt: this.salt.toJSON(),
+ iterationCount: this.iterationCount
+ };
+
+ if("keyLength" in this)
+ {
+ if(PBKDF2Params.defaultValues("keyLength") !== this.keyLength)
+ _object.keyLength = this.keyLength;
+ }
+
+ if("prf" in this)
+ {
+ if(PBKDF2Params.defaultValues("prf").isEqual(this.prf) === false)
+ _object.prf = this.prf.toJSON();
+ }
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/PFX.js b/pki.js/PFX.js
new file mode 100644
index 0000000..3da1a77
--- /dev/null
+++ b/pki.js/PFX.js
@@ -0,0 +1,650 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, utilConcatBuf, clearProps } from "pvutils";
+import { getCrypto, getEngine, getRandomValues, getOIDByAlgorithm, getAlgorithmByOID } from "./common.js";
+import ContentInfo from "./ContentInfo.js";
+import MacData from "./MacData.js";
+import DigestInfo from "./DigestInfo.js";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import SignedData from "./SignedData.js";
+import EncapsulatedContentInfo from "./EncapsulatedContentInfo.js";
+import Attribute from "./Attribute.js";
+import SignerInfo from "./SignerInfo.js";
+import IssuerAndSerialNumber from "./IssuerAndSerialNumber.js";
+import SignedAndUnsignedAttributes from "./SignedAndUnsignedAttributes.js";
+import AuthenticatedSafe from "./AuthenticatedSafe.js";
+//**************************************************************************************
+/**
+ * Class from RFC7292
+ */
+export default class PFX
+{
+ //**********************************************************************************
+ /**
+ * Constructor for PFX 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 {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", PFX.defaultValues("version"));
+ /**
+ * @type {ContentInfo}
+ * @desc authSafe
+ */
+ this.authSafe = getParametersValue(parameters, "authSafe", PFX.defaultValues("authSafe"));
+
+ if("macData" in parameters)
+ /**
+ * @type {MacData}
+ * @desc macData
+ */
+ this.macData = getParametersValue(parameters, "macData", PFX.defaultValues("macData"));
+
+ if("parsedValue" in parameters)
+ /**
+ * @type {*}
+ * @desc parsedValue
+ */
+ this.parsedValue = getParametersValue(parameters, "parsedValue", PFX.defaultValues("parsedValue"));
+ //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 "version":
+ return 3;
+ case "authSafe":
+ return (new ContentInfo());
+ case "macData":
+ return (new MacData());
+ case "parsedValue":
+ return {};
+ default:
+ throw new Error(`Invalid member name for PFX 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 "version":
+ return (memberValue === PFX.defaultValues(memberName));
+ case "authSafe":
+ return ((ContentInfo.compareWithDefault("contentType", memberValue.contentType)) &&
+ (ContentInfo.compareWithDefault("content", memberValue.content)));
+ case "macData":
+ return ((MacData.compareWithDefault("mac", memberValue.mac)) &&
+ (MacData.compareWithDefault("macSalt", memberValue.macSalt)) &&
+ (MacData.compareWithDefault("iterations", memberValue.iterations)));
+ case "parsedValue":
+ return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0));
+ default:
+ throw new Error(`Invalid member name for PFX class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * PFX ::= SEQUENCE {
+ * version INTEGER {v3(3)}(v3,...),
+ * authSafe ContentInfo,
+ * macData MacData OPTIONAL
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [version]
+ * @property {string} [authSafe]
+ * @property {string} [macData]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Integer({ name: (names.version || "version") }),
+ ContentInfo.schema(names.authSafe || {
+ names: {
+ blockName: "authSafe"
+ }
+ }),
+ MacData.schema(names.macData || {
+ names: {
+ blockName: "macData",
+ optional: true
+ }
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "version",
+ "authSafe",
+ "macData"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ PFX.schema({
+ names: {
+ version: "version",
+ authSafe: {
+ names: {
+ blockName: "authSafe"
+ }
+ },
+ macData: {
+ names: {
+ blockName: "macData"
+ }
+ }
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PFX");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result.version.valueBlock.valueDec;
+ this.authSafe = new ContentInfo({ schema: asn1.result.authSafe });
+
+ if("macData" in asn1.result)
+ this.macData = new MacData({ schema: asn1.result.macData });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ const outputArray = [
+ new asn1js.Integer({ value: this.version }),
+ this.authSafe.toSchema()
+ ];
+
+ if("macData" in this)
+ outputArray.push(this.macData.toSchema());
+
+ return (new asn1js.Sequence({
+ value: outputArray
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const output = {
+ version: this.version,
+ authSafe: this.authSafe.toJSON()
+ };
+
+ if("macData" in this)
+ output.macData = this.macData.toJSON();
+
+ return output;
+ }
+ //**********************************************************************************
+ /**
+ * Making ContentInfo from "parsedValue" object
+ * @param {Object} parameters Parameters, specific to each "integrity mode"
+ */
+ makeInternalValues(parameters = {})
+ {
+ //region Check mandatory parameter
+ if((parameters instanceof Object) === false)
+ return Promise.reject("The \"parameters\" must has \"Object\" type");
+
+ if(("parsedValue" in this) === false)
+ return Promise.reject("Please call \"parseValues\" function first in order to make \"parsedValue\" data");
+
+ if(("integrityMode" in this.parsedValue) === false)
+ return Promise.reject("Absent mandatory parameter \"integrityMode\" inside \"parsedValue\"");
+ //endregion
+
+ //region Initial variables
+ let sequence = Promise.resolve();
+ //endregion
+
+ //region Get a "crypto" extension
+ const crypto = getCrypto();
+ if(typeof crypto === "undefined")
+ return Promise.reject("Unable to create WebCrypto object");
+ //endregion
+
+ //region Makes values for each particular integrity mode
+ //region Check that we do have neccessary fields in "parsedValue" object
+ if(("authenticatedSafe" in this.parsedValue) === false)
+ return Promise.reject("Absent mandatory parameter \"authenticatedSafe\" in \"parsedValue\"");
+ //endregion
+
+ switch(this.parsedValue.integrityMode)
+ {
+ //region HMAC-based integrity
+ case 0:
+ {
+ //region Check additional mandatory parameters
+ if(("iterations" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"iterations\"");
+
+ if(("pbkdf2HashAlgorithm" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"pbkdf2HashAlgorithm\"");
+
+ if(("hmacHashAlgorithm" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"hmacHashAlgorithm\"");
+
+ if(("password" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"password\"");
+ //endregion
+
+ //region Initial variables
+ const saltBuffer = new ArrayBuffer(64);
+ const saltView = new Uint8Array(saltBuffer);
+
+ getRandomValues(saltView);
+
+ const data = this.parsedValue.authenticatedSafe.toSchema().toBER(false);
+
+ this.authSafe = new ContentInfo({
+ contentType: "1.2.840.113549.1.7.1",
+ content: new asn1js.OctetString({ valueHex: data })
+ });
+ //endregion
+
+ //region Call current crypto engine for making HMAC-based data stamp
+ const engine = getEngine();
+
+ if(("stampDataWithPassword" in engine.subtle) === false)
+ return Promise.reject(`No support for "stampDataWithPassword" in current engine "${engine.name}"`);
+
+ sequence = sequence.then(() =>
+ engine.subtle.stampDataWithPassword({
+ password: parameters.password,
+ hashAlgorithm: parameters.hmacHashAlgorithm,
+ salt: saltBuffer,
+ iterationCount: parameters.iterations,
+ contentToStamp: data
+ })
+ );
+ //endregion
+
+ //region Make "MacData" values
+ sequence = sequence.then(
+ result =>
+ {
+ this.macData = new MacData({
+ mac: new DigestInfo({
+ digestAlgorithm: new AlgorithmIdentifier({
+ algorithmId: getOIDByAlgorithm({ name: parameters.hmacHashAlgorithm })
+ }),
+ digest: new asn1js.OctetString({ valueHex: result })
+ }),
+ macSalt: new asn1js.OctetString({ valueHex: saltBuffer }),
+ iterations: parameters.iterations
+ });
+ },
+ error => Promise.reject(error)
+ );
+ //endregion
+ //endregion
+ }
+ break;
+ //endregion
+ //region publicKey-based integrity
+ case 1:
+ {
+ //region Check additional mandatory parameters
+ if(("signingCertificate" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"signingCertificate\"");
+
+ if(("privateKey" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"privateKey\"");
+
+ if(("hashAlgorithm" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"hashAlgorithm\"");
+ //endregion
+
+ //region Making data to be signed
+ // NOTE: all internal data for "authenticatedSafe" must be already prepared.
+ // Thus user must call "makeValues" for all internal "SafeContent" value with appropriate parameters.
+ // Or user can choose to use values from initial parsing of existing PKCS#12 data.
+
+ const toBeSigned = this.parsedValue.authenticatedSafe.toSchema().toBER(false);
+ //endregion
+
+ //region Initial variables
+ const cmsSigned = new SignedData({
+ version: 1,
+ encapContentInfo: new EncapsulatedContentInfo({
+ eContentType: "1.2.840.113549.1.7.1", // "data" content type
+ eContent: new asn1js.OctetString({ valueHex: toBeSigned })
+ }),
+ certificates: [parameters.signingCertificate]
+ });
+ //endregion
+
+ //region Making additional attributes for CMS Signed Data
+ //region Create a message digest
+ sequence = sequence.then(
+ () => crypto.digest({ name: parameters.hashAlgorithm }, new Uint8Array(toBeSigned))
+ );
+ //endregion
+
+ //region Combine all signed extensions
+ sequence = sequence.then(
+ result =>
+ {
+ //region Initial variables
+ const signedAttr = [];
+ //endregion
+
+ //region contentType
+ signedAttr.push(new Attribute({
+ type: "1.2.840.113549.1.9.3",
+ values: [
+ new asn1js.ObjectIdentifier({ value: "1.2.840.113549.1.7.1" })
+ ]
+ }));
+ //endregion
+ //region signingTime
+ signedAttr.push(new Attribute({
+ type: "1.2.840.113549.1.9.5",
+ values: [
+ new asn1js.UTCTime({ valueDate: new Date() })
+ ]
+ }));
+ //endregion
+ //region messageDigest
+ signedAttr.push(new Attribute({
+ type: "1.2.840.113549.1.9.4",
+ values: [
+ new asn1js.OctetString({ valueHex: result })
+ ]
+ }));
+ //endregion
+
+ //region Making final value for "SignerInfo" type
+ cmsSigned.signerInfos.push(new SignerInfo({
+ version: 1,
+ sid: new IssuerAndSerialNumber({
+ issuer: parameters.signingCertificate.issuer,
+ serialNumber: parameters.signingCertificate.serialNumber
+ }),
+ signedAttrs: new SignedAndUnsignedAttributes({
+ type: 0,
+ attributes: signedAttr
+ })
+ }));
+ //endregion
+ },
+ error => Promise.reject(`Error during making digest for message: ${error}`)
+ );
+ //endregion
+ //endregion
+
+ //region Signing CMS Signed Data
+ sequence = sequence.then(
+ () => cmsSigned.sign(parameters.privateKey, 0, parameters.hashAlgorithm)
+ );
+ //endregion
+
+ //region Making final CMS_CONTENT_INFO type
+ sequence = sequence.then(
+ () =>
+ {
+ this.authSafe = new ContentInfo({
+ contentType: "1.2.840.113549.1.7.2",
+ content: cmsSigned.toSchema(true)
+ });
+ },
+ error => Promise.reject(`Error during making signature: ${error}`)
+ );
+ //endregion
+ }
+ break;
+ //endregion
+ //region default
+ default:
+ return Promise.reject(`Parameter "integrityMode" has unknown value: ${parameters.integrityMode}`);
+ //endregion
+ }
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ parseInternalValues(parameters)
+ {
+ //region Check input data from "parameters"
+ if((parameters instanceof Object) === false)
+ return Promise.reject("The \"parameters\" must has \"Object\" type");
+
+ if(("checkIntegrity" in parameters) === false)
+ parameters.checkIntegrity = true;
+ //endregion
+
+ //region Initial variables
+ let sequence = Promise.resolve();
+ //endregion
+
+ //region Get a "crypto" extension
+ const crypto = getCrypto();
+ if(typeof crypto === "undefined")
+ return Promise.reject("Unable to create WebCrypto object");
+ //endregion
+
+ //region Create value for "this.parsedValue.authenticatedSafe" and check integrity
+ this.parsedValue = {};
+
+ switch(this.authSafe.contentType)
+ {
+ //region data
+ case "1.2.840.113549.1.7.1":
+ {
+ //region Check additional mandatory parameters
+ if(("password" in parameters) === false)
+ return Promise.reject("Absent mandatory parameter \"password\"");
+ //endregion
+
+ //region Integrity based on HMAC
+ this.parsedValue.integrityMode = 0;
+ //endregion
+
+ //region Check that we do have OCTETSTRING as "content"
+ if((this.authSafe.content instanceof asn1js.OctetString) === false)
+ return Promise.reject("Wrong type of \"this.authSafe.content\"");
+ //endregion
+
+ //region Check we have "constructive encoding" for AuthSafe content
+ let authSafeContent = new ArrayBuffer(0);
+
+ if(this.authSafe.content.valueBlock.isConstructed)
+ {
+ for(const contentValue of this.authSafe.content.valueBlock.value)
+ authSafeContent = utilConcatBuf(authSafeContent, contentValue.valueBlock.valueHex);
+ }
+ else
+ authSafeContent = this.authSafe.content.valueBlock.valueHex;
+ //endregion
+
+ //region Parse internal ASN.1 data
+ const asn1 = asn1js.fromBER(authSafeContent);
+ if(asn1.offset === (-1))
+ return Promise.reject("Error during parsing of ASN.1 data inside \"this.authSafe.content\"");
+ //endregion
+
+ //region Set "authenticatedSafe" value
+ this.parsedValue.authenticatedSafe = new AuthenticatedSafe({ schema: asn1.result });
+ //endregion
+
+ //region Check integrity
+ if(parameters.checkIntegrity)
+ {
+ //region Check that "MacData" exists
+ if(("macData" in this) === false)
+ return Promise.reject("Absent \"macData\" value, can not check PKCS#12 data integrity");
+ //endregion
+
+ //region Initial variables
+ const hashAlgorithm = getAlgorithmByOID(this.macData.mac.digestAlgorithm.algorithmId);
+ if(("name" in hashAlgorithm) === false)
+ return Promise.reject(`Unsupported digest algorithm: ${this.macData.mac.digestAlgorithm.algorithmId}`);
+ //endregion
+
+ //region Call current crypto engine for verifying HMAC-based data stamp
+ const engine = getEngine();
+
+ sequence = sequence.then(() =>
+ engine.subtle.verifyDataStampedWithPassword({
+ password: parameters.password,
+ hashAlgorithm: hashAlgorithm.name,
+ salt: this.macData.macSalt.valueBlock.valueHex,
+ iterationCount: this.macData.iterations,
+ contentToVerify: authSafeContent,
+ signatureToVerify: this.macData.mac.digest.valueBlock.valueHex
+ })
+ );
+ //endregion
+
+ //region Verify HMAC signature
+ sequence = sequence.then(
+ result =>
+ {
+ if(result === false)
+ return Promise.reject("Integrity for the PKCS#12 data is broken!");
+
+ return Promise.resolve();
+ },
+ error => Promise.reject(error)
+ );
+ //endregion
+ }
+ //endregion
+ }
+ break;
+ //endregion
+ //region signedData
+ case "1.2.840.113549.1.7.2":
+ {
+ //region Integrity based on signature using public key
+ this.parsedValue.integrityMode = 1;
+ //endregion
+
+ //region Parse CMS Signed Data
+ const cmsSigned = new SignedData({ schema: this.authSafe.content });
+ //endregion
+
+ //region Check that we do have OCTETSTRING as "content"
+ if(("eContent" in cmsSigned.encapContentInfo) === false)
+ return Promise.reject("Absent of attached data in \"cmsSigned.encapContentInfo\"");
+
+ if((cmsSigned.encapContentInfo.eContent instanceof asn1js.OctetString) === false)
+ return Promise.reject("Wrong type of \"cmsSigned.encapContentInfo.eContent\"");
+ //endregion
+
+ //region Create correct data block for verification
+ let data = new ArrayBuffer(0);
+
+ if(cmsSigned.encapContentInfo.eContent.idBlock.isConstructed === false)
+ data = cmsSigned.encapContentInfo.eContent.valueBlock.valueHex;
+ else
+ {
+ for(let i = 0; i < cmsSigned.encapContentInfo.eContent.valueBlock.value.length; i++)
+ data = utilConcatBuf(data, cmsSigned.encapContentInfo.eContent.valueBlock.value[i].valueBlock.valueHex);
+ }
+ //endregion
+
+ //region Parse internal ASN.1 data
+ const asn1 = asn1js.fromBER(data);
+ if(asn1.offset === (-1))
+ return Promise.reject("Error during parsing of ASN.1 data inside \"this.authSafe.content\"");
+ //endregion
+
+ //region Set "authenticatedSafe" value
+ this.parsedValue.authenticatedSafe = new AuthenticatedSafe({ schema: asn1.result });
+ //endregion
+
+ //region Check integrity
+ sequence = sequence.then(
+ () => cmsSigned.verify({ signer: 0, checkChain: false })
+ ).then(
+ result =>
+ {
+ if(result === false)
+ return Promise.reject("Integrity for the PKCS#12 data is broken!");
+
+ return Promise.resolve();
+ },
+ error => Promise.reject(`Error during integrity verification: ${error}`)
+ );
+ //endregion
+ }
+ break;
+ //endregion
+ //region default
+ default:
+ return Promise.reject(`Incorrect value for "this.authSafe.contentType": ${this.authSafe.contentType}`);
+ //endregion
+ }
+ //endregion
+
+ //region Return result of the function
+ return sequence.then(
+ () => this,
+ error => Promise.reject(`Error during parsing: ${error}`)
+ );
+ //endregion
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/PKCS8ShroudedKeyBag.js b/pki.js/PKCS8ShroudedKeyBag.js
new file mode 100644
index 0000000..d29b7a9
--- /dev/null
+++ b/pki.js/PKCS8ShroudedKeyBag.js
@@ -0,0 +1,285 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import EncryptedData from "./EncryptedData.js";
+import EncryptedContentInfo from "./EncryptedContentInfo.js";
+import PrivateKeyInfo from "./PrivateKeyInfo.js";
+//**************************************************************************************
+/**
+ * Class from RFC7292
+ */
+export default class PKCS8ShroudedKeyBag
+{
+ //**********************************************************************************
+ /**
+ * Constructor for PKCS8ShroudedKeyBag 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 {AlgorithmIdentifier}
+ * @desc encryptionAlgorithm
+ */
+ this.encryptionAlgorithm = getParametersValue(parameters, "encryptionAlgorithm", PKCS8ShroudedKeyBag.defaultValues("encryptionAlgorithm"));
+ /**
+ * @type {OctetString}
+ * @desc encryptedData
+ */
+ this.encryptedData = getParametersValue(parameters, "encryptedData", PKCS8ShroudedKeyBag.defaultValues("encryptedData"));
+
+ if("parsedValue" in parameters)
+ /**
+ * @type {*}
+ * @desc parsedValue
+ */
+ this.parsedValue = getParametersValue(parameters, "parsedValue", PKCS8ShroudedKeyBag.defaultValues("parsedValue"));
+ //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 "encryptionAlgorithm":
+ return (new AlgorithmIdentifier());
+ case "encryptedData":
+ return (new asn1js.OctetString());
+ case "parsedValue":
+ return {};
+ default:
+ throw new Error(`Invalid member name for PKCS8ShroudedKeyBag 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 "encryptionAlgorithm":
+ return ((AlgorithmIdentifier.compareWithDefault("algorithmId", memberValue.algorithmId)) &&
+ (("algorithmParams" in memberValue) === false));
+ case "encryptedData":
+ return (memberValue.isEqual(PKCS8ShroudedKeyBag.defaultValues(memberName)));
+ case "parsedValue":
+ return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0));
+ default:
+ throw new Error(`Invalid member name for PKCS8ShroudedKeyBag class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * PKCS8ShroudedKeyBag ::= EncryptedPrivateKeyInfo
+ *
+ * EncryptedPrivateKeyInfo ::= SEQUENCE {
+ * encryptionAlgorithm AlgorithmIdentifier {{KeyEncryptionAlgorithms}},
+ * encryptedData EncryptedData
+ * }
+ *
+ * EncryptedData ::= OCTET STRING
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [encryptionAlgorithm]
+ * @property {string} [encryptedData]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ AlgorithmIdentifier.schema(names.encryptionAlgorithm || {
+ names: {
+ blockName: "encryptionAlgorithm"
+ }
+ }),
+ new asn1js.Choice({
+ value: [
+ new asn1js.OctetString({ name: (names.encryptedData || "encryptedData") }),
+ new asn1js.OctetString({
+ idBlock: {
+ isConstructed: true
+ },
+ name: (names.encryptedData || "encryptedData")
+ })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "encryptionAlgorithm",
+ "encryptedData"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ PKCS8ShroudedKeyBag.schema({
+ names: {
+ encryptionAlgorithm: {
+ names: {
+ blockName: "encryptionAlgorithm"
+ }
+ },
+ encryptedData: "encryptedData"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PKCS8ShroudedKeyBag");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.encryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.encryptionAlgorithm });
+ this.encryptedData = asn1.result.encryptedData;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ this.encryptionAlgorithm.toSchema(),
+ this.encryptedData
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ encryptionAlgorithm: this.encryptionAlgorithm.toJSON(),
+ encryptedData: this.encryptedData.toJSON()
+ };
+ }
+ //**********************************************************************************
+ parseInternalValues(parameters)
+ {
+ //region Initial variables
+ let sequence = Promise.resolve();
+
+ const cmsEncrypted = new EncryptedData({
+ encryptedContentInfo: new EncryptedContentInfo({
+ contentEncryptionAlgorithm: this.encryptionAlgorithm,
+ encryptedContent: this.encryptedData
+ })
+ });
+ //endregion
+
+ //region Decrypt internal data
+ sequence = sequence.then(
+ () => cmsEncrypted.decrypt(parameters),
+ error => Promise.reject(error)
+ );
+ //endregion
+
+ //region Initialize "parsedValue" with decrypted PKCS#8 private key
+ sequence = sequence.then(
+ /**
+ * @param {ArrayBuffer} result
+ */
+ result =>
+ {
+ const asn1 = asn1js.fromBER(result);
+ if(asn1.offset === (-1))
+ return Promise.reject("Error during parsing ASN.1 data");
+
+ this.parsedValue = new PrivateKeyInfo({ schema: asn1.result });
+
+ return Promise.resolve();
+ },
+ error => Promise.reject(error)
+ );
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ makeInternalValues(parameters)
+ {
+ //region Check that we do have "parsedValue"
+ if(("parsedValue" in this) === false)
+ return Promise.reject("Please initialize \"parsedValue\" first");
+ //endregion
+
+ //region Initial variables
+ let sequence = Promise.resolve();
+
+ const cmsEncrypted = new EncryptedData();
+ //endregion
+
+ //region Encrypt internal data
+ sequence = sequence.then(
+ () =>
+ {
+ parameters.contentToEncrypt = this.parsedValue.toSchema().toBER(false);
+
+ return cmsEncrypted.encrypt(parameters);
+ },
+ error => Promise.reject(error)
+ );
+ //endregion
+
+ //region Initialize internal values
+ sequence = sequence.then(
+ () =>
+ {
+ this.encryptionAlgorithm = cmsEncrypted.encryptedContentInfo.contentEncryptionAlgorithm;
+ this.encryptedData = cmsEncrypted.encryptedContentInfo.encryptedContent;
+ }
+ );
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/PKIStatusInfo.js b/pki.js/PKIStatusInfo.js
new file mode 100644
index 0000000..dee8552
--- /dev/null
+++ b/pki.js/PKIStatusInfo.js
@@ -0,0 +1,227 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC3161
+ */
+export default class PKIStatusInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for PKIStatusInfo 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 {number}
+ * @desc status
+ */
+ this.status = getParametersValue(parameters, "status", PKIStatusInfo.defaultValues("status"));
+
+ if("statusStrings" in parameters)
+ /**
+ * @type {Array.}
+ * @desc statusStrings
+ */
+ this.statusStrings = getParametersValue(parameters, "statusStrings", PKIStatusInfo.defaultValues("statusStrings"));
+
+ if("failInfo" in parameters)
+ /**
+ * @type {BitString}
+ * @desc failInfo
+ */
+ this.failInfo = getParametersValue(parameters, "failInfo", PKIStatusInfo.defaultValues("failInfo"));
+ //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 "status":
+ return 2;
+ case "statusStrings":
+ return [];
+ case "failInfo":
+ return new asn1js.BitString();
+ default:
+ throw new Error(`Invalid member name for PKIStatusInfo 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 "status":
+ return (memberValue === PKIStatusInfo.defaultValues(memberName));
+ case "statusStrings":
+ return (memberValue.length === 0);
+ case "failInfo":
+ return (memberValue.isEqual(PKIStatusInfo.defaultValues(memberName)));
+ default:
+ throw new Error(`Invalid member name for PKIStatusInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * PKIStatusInfo ::= SEQUENCE {
+ * status PKIStatus,
+ * statusString PKIFreeText OPTIONAL,
+ * failInfo PKIFailureInfo OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [status]
+ * @property {string} [statusStrings]
+ * @property {string} [failInfo]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Integer({ name: (names.status || "") }),
+ new asn1js.Sequence({
+ optional: true,
+ value: [
+ new asn1js.Repeated({
+ name: (names.statusStrings || ""),
+ value: new asn1js.Utf8String()
+ })
+ ]
+ }),
+ new asn1js.BitString({
+ name: (names.failInfo || ""),
+ optional: true
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "status",
+ "statusStrings",
+ "failInfo"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ PKIStatusInfo.schema({
+ names: {
+ status: "status",
+ statusStrings: "statusStrings",
+ failInfo: "failInfo"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PKIStatusInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ const _status = asn1.result.status;
+
+ if((_status.valueBlock.isHexOnly === true) ||
+ (_status.valueBlock.valueDec < 0) ||
+ (_status.valueBlock.valueDec > 5))
+ throw new Error("PKIStatusInfo \"status\" has invalid value");
+
+ this.status = _status.valueBlock.valueDec;
+
+ if("statusStrings" in asn1.result)
+ this.statusStrings = asn1.result.statusStrings;
+ if("failInfo" in asn1.result)
+ this.failInfo = asn1.result.failInfo;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array of output sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.Integer({ value: this.status }));
+
+ if("statusStrings" in this)
+ {
+ outputArray.push(new asn1js.Sequence({
+ optional: true,
+ value: this.statusStrings
+ }));
+ }
+
+ if("failInfo" in this)
+ outputArray.push(this.failInfo);
+ //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 = {
+ status: this.status
+ };
+
+ if("statusStrings" in this)
+ _object.statusStrings = Array.from(this.statusStrings, element => element.toJSON());
+
+ if("failInfo" in this)
+ _object.failInfo = this.failInfo.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/PasswordRecipientinfo.js b/pki.js/PasswordRecipientinfo.js
new file mode 100644
index 0000000..4647d89
--- /dev/null
+++ b/pki.js/PasswordRecipientinfo.js
@@ -0,0 +1,247 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class PasswordRecipientinfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for PasswordRecipientinfo 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 {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", PasswordRecipientinfo.defaultValues("version"));
+
+ if("keyDerivationAlgorithm" in parameters)
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc keyDerivationAlgorithm
+ */
+ this.keyDerivationAlgorithm = getParametersValue(parameters, "keyDerivationAlgorithm", PasswordRecipientinfo.defaultValues("keyDerivationAlgorithm"));
+
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc keyEncryptionAlgorithm
+ */
+ this.keyEncryptionAlgorithm = getParametersValue(parameters, "keyEncryptionAlgorithm", PasswordRecipientinfo.defaultValues("keyEncryptionAlgorithm"));
+ /**
+ * @type {OctetString}
+ * @desc encryptedKey
+ */
+ this.encryptedKey = getParametersValue(parameters, "encryptedKey", PasswordRecipientinfo.defaultValues("encryptedKey"));
+ /**
+ * @type {ArrayBuffer}
+ * @desc password Password to derive key from
+ */
+ this.password = getParametersValue(parameters, "password", PasswordRecipientinfo.defaultValues("password"));
+ //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 "version":
+ return (-1);
+ case "keyDerivationAlgorithm":
+ return new AlgorithmIdentifier();
+ case "keyEncryptionAlgorithm":
+ return new AlgorithmIdentifier();
+ case "encryptedKey":
+ return new asn1js.OctetString();
+ case "password":
+ return new ArrayBuffer(0);
+ default:
+ throw new Error(`Invalid member name for PasswordRecipientinfo 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 "version":
+ return (memberValue === (-1));
+ case "keyDerivationAlgorithm":
+ case "keyEncryptionAlgorithm":
+ return ((memberValue.algorithmId === "") && (("algorithmParams" in memberValue) === false));
+ case "encryptedKey":
+ return (memberValue.isEqual(PasswordRecipientinfo.defaultValues("encryptedKey")));
+ case "password":
+ return (memberValue.byteLength === 0);
+ default:
+ throw new Error(`Invalid member name for PasswordRecipientinfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * PasswordRecipientInfo ::= SEQUENCE {
+ * version CMSVersion, -- Always set to 0
+ * keyDerivationAlgorithm [0] KeyDerivationAlgorithmIdentifier OPTIONAL,
+ * keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
+ * encryptedKey EncryptedKey }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [keyDerivationAlgorithm]
+ * @property {string} [keyEncryptionAlgorithm]
+ * @property {string} [encryptedKey]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Integer({ name: (names.version || "") }),
+ new asn1js.Constructed({
+ name: (names.keyDerivationAlgorithm || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: AlgorithmIdentifier.schema().valueBlock.value
+ }),
+ AlgorithmIdentifier.schema(names.keyEncryptionAlgorithm || {}),
+ new asn1js.OctetString({ name: (names.encryptedKey || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "version",
+ "keyDerivationAlgorithm",
+ "keyEncryptionAlgorithm",
+ "encryptedKey"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ PasswordRecipientinfo.schema({
+ names: {
+ version: "version",
+ keyDerivationAlgorithm: "keyDerivationAlgorithm",
+ keyEncryptionAlgorithm: {
+ names: {
+ blockName: "keyEncryptionAlgorithm"
+ }
+ },
+ encryptedKey: "encryptedKey"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PasswordRecipientinfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result.version.valueBlock.valueDec;
+
+ if("keyDerivationAlgorithm" in asn1.result)
+ {
+ this.keyDerivationAlgorithm = new AlgorithmIdentifier({
+ schema: new asn1js.Sequence({
+ value: asn1.result.keyDerivationAlgorithm.valueBlock.value
+ })
+ });
+ }
+
+ this.keyEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.keyEncryptionAlgorithm });
+ this.encryptedKey = asn1.result.encryptedKey;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create output array for sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.Integer({ value: this.version }));
+
+ if("keyDerivationAlgorithm" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: this.keyDerivationAlgorithm.toSchema().valueBlock.value
+ }));
+ }
+
+ outputArray.push(this.keyEncryptionAlgorithm.toSchema());
+ outputArray.push(this.encryptedKey);
+ //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()
+ {
+ return {
+ version: this.version,
+ keyDerivationAlgorithm: this.keyDerivationAlgorithm.toJSON(),
+ keyEncryptionAlgorithm: this.keyEncryptionAlgorithm.toJSON(),
+ encryptedKey: this.encryptedKey.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/PolicyConstraints.js b/pki.js/PolicyConstraints.js
new file mode 100644
index 0000000..8f6968f
--- /dev/null
+++ b/pki.js/PolicyConstraints.js
@@ -0,0 +1,216 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class PolicyConstraints
+{
+ //**********************************************************************************
+ /**
+ * Constructor for PolicyConstraints class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ if("requireExplicitPolicy" in parameters)
+ /**
+ * @type {number}
+ * @desc requireExplicitPolicy
+ */
+ this.requireExplicitPolicy = getParametersValue(parameters, "requireExplicitPolicy", PolicyConstraints.defaultValues("requireExplicitPolicy"));
+
+ if("inhibitPolicyMapping" in parameters)
+ /**
+ * @type {number}
+ * @desc Value of the TIME class
+ */
+ this.inhibitPolicyMapping = getParametersValue(parameters, "inhibitPolicyMapping", PolicyConstraints.defaultValues("inhibitPolicyMapping"));
+ //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 "requireExplicitPolicy":
+ return 0;
+ case "inhibitPolicyMapping":
+ return 0;
+ default:
+ throw new Error(`Invalid member name for PolicyConstraints class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * PolicyConstraints ::= SEQUENCE {
+ * requireExplicitPolicy [0] SkipCerts OPTIONAL,
+ * inhibitPolicyMapping [1] SkipCerts OPTIONAL }
+ *
+ * SkipCerts ::= INTEGER (0..MAX)
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [requireExplicitPolicy]
+ * @property {string} [inhibitPolicyMapping]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Primitive({
+ name: (names.requireExplicitPolicy || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ }
+ }), // IMPLICIT integer value
+ new asn1js.Primitive({
+ name: (names.inhibitPolicyMapping || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ }
+ }) // IMPLICIT integer value
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "requireExplicitPolicy",
+ "inhibitPolicyMapping"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ PolicyConstraints.schema({
+ names: {
+ requireExplicitPolicy: "requireExplicitPolicy",
+ inhibitPolicyMapping: "inhibitPolicyMapping"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PolicyConstraints");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("requireExplicitPolicy" in asn1.result)
+ {
+ const field1 = asn1.result.requireExplicitPolicy;
+
+ field1.idBlock.tagClass = 1; // UNIVERSAL
+ field1.idBlock.tagNumber = 2; // INTEGER
+
+ const ber1 = field1.toBER(false);
+ const int1 = asn1js.fromBER(ber1);
+
+ this.requireExplicitPolicy = int1.result.valueBlock.valueDec;
+ }
+
+ if("inhibitPolicyMapping" in asn1.result)
+ {
+ const field2 = asn1.result.inhibitPolicyMapping;
+
+ field2.idBlock.tagClass = 1; // UNIVERSAL
+ field2.idBlock.tagNumber = 2; // INTEGER
+
+ const ber2 = field2.toBER(false);
+ const int2 = asn1js.fromBER(ber2);
+
+ this.inhibitPolicyMapping = int2.result.valueBlock.valueDec;
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create correct values for output sequence
+ const outputArray = [];
+
+ if("requireExplicitPolicy" in this)
+ {
+ const int1 = new asn1js.Integer({ value: this.requireExplicitPolicy });
+
+ int1.idBlock.tagClass = 3; // CONTEXT-SPECIFIC
+ int1.idBlock.tagNumber = 0; // [0]
+
+ outputArray.push(int1);
+ }
+
+ if("inhibitPolicyMapping" in this)
+ {
+ const int2 = new asn1js.Integer({ value: this.inhibitPolicyMapping });
+
+ int2.idBlock.tagClass = 3; // CONTEXT-SPECIFIC
+ int2.idBlock.tagNumber = 1; // [1]
+
+ outputArray.push(int2);
+ }
+ //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 = {};
+
+ if("requireExplicitPolicy" in this)
+ object.requireExplicitPolicy = this.requireExplicitPolicy;
+
+ if("inhibitPolicyMapping" in this)
+ object.inhibitPolicyMapping = this.inhibitPolicyMapping;
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/PolicyInformation.js b/pki.js/PolicyInformation.js
new file mode 100644
index 0000000..6c455ce
--- /dev/null
+++ b/pki.js/PolicyInformation.js
@@ -0,0 +1,178 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import PolicyQualifierInfo from "./PolicyQualifierInfo.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class PolicyInformation
+{
+ //**********************************************************************************
+ /**
+ * Constructor for PolicyInformation 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 {string}
+ * @desc policyIdentifier
+ */
+ this.policyIdentifier = getParametersValue(parameters, "policyIdentifier", PolicyInformation.defaultValues("policyIdentifier"));
+
+ if("policyQualifiers" in parameters)
+ /**
+ * @type {Array.}
+ * @desc Value of the TIME class
+ */
+ this.policyQualifiers = getParametersValue(parameters, "policyQualifiers", PolicyInformation.defaultValues("policyQualifiers"));
+ //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 "policyIdentifier":
+ return "";
+ case "policyQualifiers":
+ return [];
+ default:
+ throw new Error(`Invalid member name for PolicyInformation class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * PolicyInformation ::= SEQUENCE {
+ * policyIdentifier CertPolicyId,
+ * policyQualifiers SEQUENCE SIZE (1..MAX) OF
+ * PolicyQualifierInfo OPTIONAL }
+ *
+ * CertPolicyId ::= OBJECT IDENTIFIER
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [policyIdentifier]
+ * @property {string} [policyQualifiers]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.policyIdentifier || "") }),
+ new asn1js.Sequence({
+ optional: true,
+ value: [
+ new asn1js.Repeated({
+ name: (names.policyQualifiers || ""),
+ value: PolicyQualifierInfo.schema()
+ })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "policyIdentifier",
+ "policyQualifiers"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ PolicyInformation.schema({
+ names: {
+ policyIdentifier: "policyIdentifier",
+ policyQualifiers: "policyQualifiers"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PolicyInformation");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.policyIdentifier = asn1.result.policyIdentifier.valueBlock.toString();
+
+ if("policyQualifiers" in asn1.result)
+ this.policyQualifiers = Array.from(asn1.result.policyQualifiers, element => new PolicyQualifierInfo({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.ObjectIdentifier({ value: this.policyIdentifier }));
+
+ if("policyQualifiers" in this)
+ {
+ outputArray.push(new asn1js.Sequence({
+ value: Array.from(this.policyQualifiers, 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 = {
+ policyIdentifier: this.policyIdentifier
+ };
+
+ if("policyQualifiers" in this)
+ object.policyQualifiers = Array.from(this.policyQualifiers, element => element.toJSON());
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/PolicyMapping.js b/pki.js/PolicyMapping.js
new file mode 100644
index 0000000..b17bfe4
--- /dev/null
+++ b/pki.js/PolicyMapping.js
@@ -0,0 +1,148 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class PolicyMapping
+{
+ //**********************************************************************************
+ /**
+ * Constructor for PolicyMapping 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 {string}
+ * @desc issuerDomainPolicy
+ */
+ this.issuerDomainPolicy = getParametersValue(parameters, "issuerDomainPolicy", PolicyMapping.defaultValues("issuerDomainPolicy"));
+ /**
+ * @type {string}
+ * @desc subjectDomainPolicy
+ */
+ this.subjectDomainPolicy = getParametersValue(parameters, "subjectDomainPolicy", PolicyMapping.defaultValues("subjectDomainPolicy"));
+ //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 "issuerDomainPolicy":
+ return "";
+ case "subjectDomainPolicy":
+ return "";
+ default:
+ throw new Error(`Invalid member name for PolicyMapping class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * PolicyMapping ::= SEQUENCE {
+ * issuerDomainPolicy CertPolicyId,
+ * subjectDomainPolicy CertPolicyId }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [issuerDomainPolicy]
+ * @property {string} [subjectDomainPolicy]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.issuerDomainPolicy || "") }),
+ new asn1js.ObjectIdentifier({ name: (names.subjectDomainPolicy || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "issuerDomainPolicy",
+ "subjectDomainPolicy"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ PolicyMapping.schema({
+ names: {
+ issuerDomainPolicy: "issuerDomainPolicy",
+ subjectDomainPolicy: "subjectDomainPolicy"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PolicyMapping");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.issuerDomainPolicy = asn1.result.issuerDomainPolicy.valueBlock.toString();
+ this.subjectDomainPolicy = asn1.result.subjectDomainPolicy.valueBlock.toString();
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ new asn1js.ObjectIdentifier({ value: this.issuerDomainPolicy }),
+ new asn1js.ObjectIdentifier({ value: this.subjectDomainPolicy })
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ issuerDomainPolicy: this.issuerDomainPolicy,
+ subjectDomainPolicy: this.subjectDomainPolicy
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/PolicyMappings.js b/pki.js/PolicyMappings.js
new file mode 100644
index 0000000..2e55ef9
--- /dev/null
+++ b/pki.js/PolicyMappings.js
@@ -0,0 +1,135 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import PolicyMapping from "./PolicyMapping.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class PolicyMappings
+{
+ //**********************************************************************************
+ /**
+ * Constructor for PolicyMappings 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 {Array.}
+ * @desc mappings
+ */
+ this.mappings = getParametersValue(parameters, "mappings", PolicyMappings.defaultValues("mappings"));
+ //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 "mappings":
+ return [];
+ default:
+ throw new Error(`Invalid member name for PolicyMappings class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF PolicyMapping
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [utcTimeName] Name for "utcTimeName" choice
+ * @property {string} [generalTimeName] Name for "generalTimeName" choice
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.mappings || ""),
+ value: PolicyMapping.schema()
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "mappings"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ PolicyMappings.schema({
+ names: {
+ mappings: "mappings"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PolicyMappings");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.mappings = Array.from(asn1.result.mappings, element => new PolicyMapping({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: Array.from(this.mappings, element => element.toSchema())
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ mappings: Array.from(this.mappings, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/PolicyQualifierInfo.js b/pki.js/PolicyQualifierInfo.js
new file mode 100644
index 0000000..9ba45e7
--- /dev/null
+++ b/pki.js/PolicyQualifierInfo.js
@@ -0,0 +1,154 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class PolicyQualifierInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for PolicyQualifierInfo 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 {string}
+ * @desc policyQualifierId
+ */
+ this.policyQualifierId = getParametersValue(parameters, "policyQualifierId", PolicyQualifierInfo.defaultValues("policyQualifierId"));
+ /**
+ * @type {Object}
+ * @desc qualifier
+ */
+ this.qualifier = getParametersValue(parameters, "qualifier", PolicyQualifierInfo.defaultValues("qualifier"));
+ //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 "policyQualifierId":
+ return "";
+ case "qualifier":
+ return new asn1js.Any();
+ default:
+ throw new Error(`Invalid member name for PolicyQualifierInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * PolicyQualifierInfo ::= SEQUENCE {
+ * policyQualifierId PolicyQualifierId,
+ * qualifier ANY DEFINED BY policyQualifierId }
+ *
+ * id-qt OBJECT IDENTIFIER ::= { id-pkix 2 }
+ * id-qt-cps OBJECT IDENTIFIER ::= { id-qt 1 }
+ * id-qt-unotice OBJECT IDENTIFIER ::= { id-qt 2 }
+ *
+ * PolicyQualifierId ::= OBJECT IDENTIFIER ( id-qt-cps | id-qt-unotice )
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [policyQualifierId]
+ * @property {string} [qualifier]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.policyQualifierId || "") }),
+ new asn1js.Any({ name: (names.qualifier || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "policyQualifierId",
+ "qualifier"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ PolicyQualifierInfo.schema({
+ names: {
+ policyQualifierId: "policyQualifierId",
+ qualifier: "qualifier"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PolicyQualifierInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.policyQualifierId = asn1.result.policyQualifierId.valueBlock.toString();
+ this.qualifier = asn1.result.qualifier;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ new asn1js.ObjectIdentifier({ value: this.policyQualifierId }),
+ this.qualifier
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ policyQualifierId: this.policyQualifierId,
+ qualifier: this.qualifier.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/PrivateKeyInfo.js b/pki.js/PrivateKeyInfo.js
new file mode 100644
index 0000000..7e9eabb
--- /dev/null
+++ b/pki.js/PrivateKeyInfo.js
@@ -0,0 +1,329 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import Attribute from "./Attribute.js";
+import ECPrivateKey from "./ECPrivateKey.js";
+import RSAPrivateKey from "./RSAPrivateKey.js";
+//**************************************************************************************
+/**
+ * Class from RFC5208
+ */
+export default class PrivateKeyInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for PrivateKeyInfo 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 {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", PrivateKeyInfo.defaultValues("version"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc privateKeyAlgorithm
+ */
+ this.privateKeyAlgorithm = getParametersValue(parameters, "privateKeyAlgorithm", PrivateKeyInfo.defaultValues("privateKeyAlgorithm"));
+ /**
+ * @type {OctetString}
+ * @desc privateKey
+ */
+ this.privateKey = getParametersValue(parameters, "privateKey", PrivateKeyInfo.defaultValues("privateKey"));
+
+ if("attributes" in parameters)
+ /**
+ * @type {Array.}
+ * @desc attributes
+ */
+ this.attributes = getParametersValue(parameters, "attributes", PrivateKeyInfo.defaultValues("attributes"));
+
+ if("parsedKey" in parameters)
+ /**
+ * @type {ECPrivateKey|RSAPrivateKey}
+ * @desc Parsed public key value
+ */
+ this.parsedKey = getParametersValue(parameters, "parsedKey", PrivateKeyInfo.defaultValues("parsedKey"));
+ //endregion
+
+ //region If input argument array contains "schema" for this object
+ if("schema" in parameters)
+ this.fromSchema(parameters.schema);
+ //endregion
+ //region If input argument array contains "json" for this object
+ if("json" in parameters)
+ this.fromJSON(parameters.json);
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Return default values for all class members
+ * @param {string} memberName String name for a class member
+ */
+ static defaultValues(memberName)
+ {
+ switch(memberName)
+ {
+ case "version":
+ return 0;
+ case "privateKeyAlgorithm":
+ return new AlgorithmIdentifier();
+ case "privateKey":
+ return new asn1js.OctetString();
+ case "attributes":
+ return [];
+ case "parsedKey":
+ return {};
+ default:
+ throw new Error(`Invalid member name for PrivateKeyInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * PrivateKeyInfo ::= SEQUENCE {
+ * version Version,
+ * privateKeyAlgorithm AlgorithmIdentifier {{PrivateKeyAlgorithms}},
+ * privateKey PrivateKey,
+ * attributes [0] Attributes OPTIONAL }
+ *
+ * Version ::= INTEGER {v1(0)} (v1,...)
+ *
+ * PrivateKey ::= OCTET STRING
+ *
+ * Attributes ::= SET OF Attribute
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [version]
+ * @property {string} [privateKeyAlgorithm]
+ * @property {string} [privateKey]
+ * @property {string} [attributes]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Integer({ name: (names.version || "") }),
+ AlgorithmIdentifier.schema(names.privateKeyAlgorithm || {}),
+ new asn1js.OctetString({ name: (names.privateKey || "") }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Repeated({
+ name: (names.attributes || ""),
+ value: Attribute.schema()
+ })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "version",
+ "privateKeyAlgorithm",
+ "privateKey",
+ "attributes"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ PrivateKeyInfo.schema({
+ names: {
+ version: "version",
+ privateKeyAlgorithm: {
+ names: {
+ blockName: "privateKeyAlgorithm"
+ }
+ },
+ privateKey: "privateKey",
+ attributes: "attributes"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PrivateKeyInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result.version.valueBlock.valueDec;
+ this.privateKeyAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.privateKeyAlgorithm });
+ this.privateKey = asn1.result.privateKey;
+
+ if("attributes" in asn1.result)
+ this.attributes = Array.from(asn1.result.attributes, element => new Attribute({ schema: element }));
+
+ switch(this.privateKeyAlgorithm.algorithmId)
+ {
+ case "1.2.840.113549.1.1.1": // RSA
+ {
+ const privateKeyASN1 = asn1js.fromBER(this.privateKey.valueBlock.valueHex);
+ if(privateKeyASN1.offset !== (-1))
+ this.parsedKey = new RSAPrivateKey({ schema: privateKeyASN1.result });
+ }
+ break;
+ case "1.2.840.10045.2.1": // ECDSA
+ if("algorithmParams" in this.privateKeyAlgorithm)
+ {
+ if(this.privateKeyAlgorithm.algorithmParams instanceof asn1js.ObjectIdentifier)
+ {
+ const privateKeyASN1 = asn1js.fromBER(this.privateKey.valueBlock.valueHex);
+ if(privateKeyASN1.offset !== (-1))
+ {
+ this.parsedKey = new ECPrivateKey({
+ namedCurve: this.privateKeyAlgorithm.algorithmParams.valueBlock.toString(),
+ schema: privateKeyASN1.result
+ });
+ }
+ }
+ }
+ break;
+ default:
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [
+ new asn1js.Integer({ value: this.version }),
+ this.privateKeyAlgorithm.toSchema(),
+ this.privateKey
+ ];
+
+ if("attributes" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: Array.from(this.attributes, 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()
+ {
+ //region Return common value in case we do not have enough info fo making JWK
+ if(("parsedKey" in this) === false)
+ {
+ const object = {
+ version: this.version,
+ privateKeyAlgorithm: this.privateKeyAlgorithm.toJSON(),
+ privateKey: this.privateKey.toJSON()
+ };
+
+ if("attributes" in this)
+ object.attributes = Array.from(this.attributes, element => element.toJSON());
+
+ return object;
+ }
+ //endregion
+
+ //region Making JWK
+ const jwk = {};
+
+ switch(this.privateKeyAlgorithm.algorithmId)
+ {
+ case "1.2.840.10045.2.1": // ECDSA
+ jwk.kty = "EC";
+ break;
+ case "1.2.840.113549.1.1.1": // RSA
+ jwk.kty = "RSA";
+ break;
+ default:
+ }
+
+ const publicKeyJWK = this.parsedKey.toJSON();
+
+ for(const key of Object.keys(publicKeyJWK))
+ jwk[key] = publicKeyJWK[key];
+
+ return jwk;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert JSON value into current object
+ * @param {Object} json
+ */
+ fromJSON(json)
+ {
+ if("kty" in json)
+ {
+ switch(json.kty.toUpperCase())
+ {
+ case "EC":
+ this.parsedKey = new ECPrivateKey({ json });
+
+ this.privateKeyAlgorithm = new AlgorithmIdentifier({
+ algorithmId: "1.2.840.10045.2.1",
+ algorithmParams: new asn1js.ObjectIdentifier({ value: this.parsedKey.namedCurve })
+ });
+ break;
+ case "RSA":
+ this.parsedKey = new RSAPrivateKey({ json });
+
+ this.privateKeyAlgorithm = new AlgorithmIdentifier({
+ algorithmId: "1.2.840.113549.1.1.1",
+ algorithmParams: new asn1js.Null()
+ });
+ break;
+ default:
+ throw new Error(`Invalid value for "kty" parameter: ${json.kty}`);
+ }
+
+ this.privateKey = new asn1js.OctetString({ valueHex: this.parsedKey.toSchema().toBER(false) });
+ }
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/PrivateKeyUsagePeriod.js b/pki.js/PrivateKeyUsagePeriod.js
new file mode 100644
index 0000000..e0c8536
--- /dev/null
+++ b/pki.js/PrivateKeyUsagePeriod.js
@@ -0,0 +1,207 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class PrivateKeyUsagePeriod
+{
+ //**********************************************************************************
+ /**
+ * Constructor for PrivateKeyUsagePeriod class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ if("notBefore" in parameters)
+ /**
+ * @type {Date}
+ * @desc notBefore
+ */
+ this.notBefore = getParametersValue(parameters, "notBefore", PrivateKeyUsagePeriod.defaultValues("notBefore"));
+
+ if("notAfter" in parameters)
+ /**
+ * @type {Date}
+ * @desc notAfter
+ */
+ this.notAfter = getParametersValue(parameters, "notAfter", PrivateKeyUsagePeriod.defaultValues("notAfter"));
+ //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 "notBefore":
+ return new Date();
+ case "notAfter":
+ return new Date();
+ default:
+ throw new Error(`Invalid member name for PrivateKeyUsagePeriod class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * PrivateKeyUsagePeriod OID ::= 2.5.29.16
+ *
+ * PrivateKeyUsagePeriod ::= SEQUENCE {
+ * notBefore [0] GeneralizedTime OPTIONAL,
+ * notAfter [1] GeneralizedTime OPTIONAL }
+ * -- either notBefore or notAfter MUST be present
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [notBefore]
+ * @property {string} [notAfter]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Primitive({
+ name: (names.notBefore || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ }
+ }),
+ new asn1js.Primitive({
+ name: (names.notAfter || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ }
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "notBefore",
+ "notAfter"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ PrivateKeyUsagePeriod.schema({
+ names: {
+ notBefore: "notBefore",
+ notAfter: "notAfter"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PrivateKeyUsagePeriod");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("notBefore" in asn1.result)
+ {
+ const localNotBefore = new asn1js.GeneralizedTime();
+ localNotBefore.fromBuffer(asn1.result.notBefore.valueBlock.valueHex);
+ this.notBefore = localNotBefore.toDate();
+ }
+
+ if("notAfter" in asn1.result)
+ {
+ const localNotAfter = new asn1js.GeneralizedTime({ valueHex: asn1.result.notAfter.valueBlock.valueHex });
+ localNotAfter.fromBuffer(asn1.result.notAfter.valueBlock.valueHex);
+ this.notAfter = localNotAfter.toDate();
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ if("notBefore" in this)
+ {
+ outputArray.push(new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ valueHex: (new asn1js.GeneralizedTime({ valueDate: this.notBefore })).valueBlock.valueHex
+ }));
+ }
+
+ if("notAfter" in this)
+ {
+ outputArray.push(new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ valueHex: (new asn1js.GeneralizedTime({ valueDate: this.notAfter })).valueBlock.valueHex
+ }));
+ }
+ //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 = {};
+
+ if("notBefore" in this)
+ object.notBefore = this.notBefore;
+
+ if("notAfter" in this)
+ object.notAfter = this.notAfter;
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/PublicKeyInfo.js b/pki.js/PublicKeyInfo.js
new file mode 100644
index 0000000..576710b
--- /dev/null
+++ b/pki.js/PublicKeyInfo.js
@@ -0,0 +1,311 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import { getCrypto } from "./common.js";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import ECPublicKey from "./ECPublicKey.js";
+import RSAPublicKey from "./RSAPublicKey.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class PublicKeyInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for PublicKeyInfo 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 {AlgorithmIdentifier}
+ * @desc Algorithm identifier
+ */
+ this.algorithm = getParametersValue(parameters, "algorithm", PublicKeyInfo.defaultValues("algorithm"));
+ /**
+ * @type {BitString}
+ * @desc Subject public key value
+ */
+ this.subjectPublicKey = getParametersValue(parameters, "subjectPublicKey", PublicKeyInfo.defaultValues("subjectPublicKey"));
+
+ if("parsedKey" in parameters)
+ /**
+ * @type {ECPublicKey|RSAPublicKey}
+ * @desc Parsed public key value
+ */
+ this.parsedKey = getParametersValue(parameters, "parsedKey", PublicKeyInfo.defaultValues("parsedKey"));
+ //endregion
+
+ //region If input argument array contains "schema" for this object
+ if("schema" in parameters)
+ this.fromSchema(parameters.schema);
+ //endregion
+ //region If input argument array contains "json" for this object
+ if("json" in parameters)
+ this.fromJSON(parameters.json);
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Return default values for all class members
+ * @param {string} memberName String name for a class member
+ */
+ static defaultValues(memberName)
+ {
+ switch(memberName)
+ {
+ case "algorithm":
+ return new AlgorithmIdentifier();
+ case "subjectPublicKey":
+ return new asn1js.BitString();
+ default:
+ throw new Error(`Invalid member name for PublicKeyInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * SubjectPublicKeyInfo ::= Sequence {
+ * algorithm AlgorithmIdentifier,
+ * subjectPublicKey BIT STRING }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [algorithm]
+ * @property {string} [subjectPublicKey]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ AlgorithmIdentifier.schema(names.algorithm || {}),
+ new asn1js.BitString({ name: (names.subjectPublicKey || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "algorithm",
+ "subjectPublicKey"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ PublicKeyInfo.schema({
+ names: {
+ algorithm: {
+ names: {
+ blockName: "algorithm"
+ }
+ },
+ subjectPublicKey: "subjectPublicKey"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for PublicKeyInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.algorithm = new AlgorithmIdentifier({ schema: asn1.result.algorithm });
+ this.subjectPublicKey = asn1.result.subjectPublicKey;
+
+ switch(this.algorithm.algorithmId)
+ {
+ case "1.2.840.10045.2.1": // ECDSA
+ if("algorithmParams" in this.algorithm)
+ {
+ if(this.algorithm.algorithmParams.constructor.blockName() === asn1js.ObjectIdentifier.blockName())
+ {
+ try
+ {
+ this.parsedKey = new ECPublicKey({
+ namedCurve: this.algorithm.algorithmParams.valueBlock.toString(),
+ schema: this.subjectPublicKey.valueBlock.valueHex
+ });
+ }
+ catch(ex){} // Could be a problems during recognision of internal public key data here. Let's ignore them.
+ }
+ }
+ break;
+ case "1.2.840.113549.1.1.1": // RSA
+ {
+ const publicKeyASN1 = asn1js.fromBER(this.subjectPublicKey.valueBlock.valueHex);
+ if(publicKeyASN1.offset !== (-1))
+ {
+ try
+ {
+ this.parsedKey = new RSAPublicKey({ schema: publicKeyASN1.result });
+ }
+ catch(ex){} // Could be a problems during recognision of internal public key data here. Let's ignore them.
+ }
+ }
+ break;
+ default:
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ this.algorithm.toSchema(),
+ this.subjectPublicKey
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ //region Return common value in case we do not have enough info fo making JWK
+ if(("parsedKey" in this) === false)
+ {
+ return {
+ algorithm: this.algorithm.toJSON(),
+ subjectPublicKey: this.subjectPublicKey.toJSON()
+ };
+ }
+ //endregion
+
+ //region Making JWK
+ const jwk = {};
+
+ switch(this.algorithm.algorithmId)
+ {
+ case "1.2.840.10045.2.1": // ECDSA
+ jwk.kty = "EC";
+ break;
+ case "1.2.840.113549.1.1.1": // RSA
+ jwk.kty = "RSA";
+ break;
+ default:
+ }
+
+ const publicKeyJWK = this.parsedKey.toJSON();
+
+ for(const key of Object.keys(publicKeyJWK))
+ jwk[key] = publicKeyJWK[key];
+
+ return jwk;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert JSON value into current object
+ * @param {Object} json
+ */
+ fromJSON(json)
+ {
+ if("kty" in json)
+ {
+ switch(json.kty.toUpperCase())
+ {
+ case "EC":
+ this.parsedKey = new ECPublicKey({ json });
+
+ this.algorithm = new AlgorithmIdentifier({
+ algorithmId: "1.2.840.10045.2.1",
+ algorithmParams: new asn1js.ObjectIdentifier({ value: this.parsedKey.namedCurve })
+ });
+ break;
+ case "RSA":
+ this.parsedKey = new RSAPublicKey({ json });
+
+ this.algorithm = new AlgorithmIdentifier({
+ algorithmId: "1.2.840.113549.1.1.1",
+ algorithmParams: new asn1js.Null()
+ });
+ break;
+ default:
+ throw new Error(`Invalid value for "kty" parameter: ${json.kty}`);
+ }
+
+ this.subjectPublicKey = new asn1js.BitString({ valueHex: this.parsedKey.toSchema().toBER(false) });
+ }
+ }
+ //**********************************************************************************
+ importKey(publicKey)
+ {
+ //region Initial variables
+ let sequence = Promise.resolve();
+ const _this = this;
+ //endregion
+
+ //region Initial check
+ if(typeof publicKey === "undefined")
+ return Promise.reject("Need to provide publicKey input parameter");
+ //endregion
+
+ //region Get a "crypto" extension
+ const crypto = getCrypto();
+ if(typeof crypto === "undefined")
+ return Promise.reject("Unable to create WebCrypto object");
+ //endregion
+
+ //region Export public key
+ sequence = sequence.then(() =>
+ crypto.exportKey("spki", publicKey));
+ //endregion
+
+ //region Initialize internal variables by parsing exported value
+ sequence = sequence.then(
+ /**
+ * @param {ArrayBuffer} exportedKey
+ */
+ exportedKey =>
+ {
+ const asn1 = asn1js.fromBER(exportedKey);
+ try
+ {
+ _this.fromSchema(asn1.result);
+ }
+ catch(exception)
+ {
+ return Promise.reject("Error during initializing object from schema");
+ }
+
+ return undefined;
+ },
+ error => Promise.reject(`Error during exporting public key: ${error}`)
+ );
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/QCStatements.js b/pki.js/QCStatements.js
new file mode 100644
index 0000000..5251ffe
--- /dev/null
+++ b/pki.js/QCStatements.js
@@ -0,0 +1,327 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC3739
+ */
+export class QCStatement
+{
+ //**********************************************************************************
+ /**
+ * Constructor for QCStatement 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 {string}
+ */
+ this.id = getParametersValue(parameters, "id", QCStatement.defaultValues("id"));
+
+ if("type" in parameters)
+ {
+ /**
+ * @type {*} Any data described by "id"
+ */
+ this.type = getParametersValue(parameters, "type", QCStatement.defaultValues("type"));
+ }
+ //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 "id":
+ return "";
+ case "type":
+ return new asn1js.Null();
+ default:
+ throw new Error(`Invalid member name for QCStatement 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 "id":
+ return (memberValue === "");
+ case "type":
+ return (memberValue instanceof asn1js.Null);
+ default:
+ throw new Error(`Invalid member name for QCStatement class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * QCStatement ::= SEQUENCE {
+ * statementId QC-STATEMENT.&id({SupportedStatements}),
+ * statementInfo QC-STATEMENT.&Type({SupportedStatements}{@statementId}) OPTIONAL
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [id]
+ * @property {string} [type]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.id || "") }),
+ new asn1js.Any({
+ name: (names.type || ""),
+ optional: true
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "id",
+ "type"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ QCStatement.schema({
+ names: {
+ id: "id",
+ type: "type"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for QCStatement");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.id = asn1.result.id.valueBlock.toString();
+
+ if("type" in asn1.result)
+ this.type = asn1.result.type;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ const value = [
+ new asn1js.ObjectIdentifier({ value: this.id })
+ ];
+
+ if("type" in this)
+ value.push(this.type);
+
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const object = {
+ id: this.id
+ };
+
+ if("type" in this)
+ object.type = this.type.toJSON();
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
+/**
+ * Class from RFC3739
+ */
+export default class QCStatements
+{
+ //**********************************************************************************
+ /**
+ * Constructor for QCStatements 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 {Array}
+ */
+ this.values = getParametersValue(parameters, "values", QCStatements.defaultValues("values"));
+ //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 "values":
+ return [];
+ default:
+ throw new Error(`Invalid member name for QCStatements 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 "values":
+ return (memberValue.length === 0);
+ default:
+ throw new Error(`Invalid member name for QCStatements class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * QCStatements ::= SEQUENCE OF QCStatement
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [values]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.values || ""),
+ value: QCStatement.schema(names.value || {})
+ }),
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "values"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ QCStatements.schema({
+ names: {
+ values: "values"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for QCStatements");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.values = Array.from(asn1.result.values, element => new QCStatement({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: Array.from(this.values, element => element.toSchema())
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ extensions: Array.from(this.values, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/README.MD b/pki.js/README.MD
new file mode 100644
index 0000000..b8e48f9
--- /dev/null
+++ b/pki.js/README.MD
@@ -0,0 +1,130 @@
+## DESCRIPTION OF THE PROJECT
+
+PKIjs designed to be a helper for everyone making any PKI-related applications.
+Currently PKI defined by a set of documents, and usually these documents have form in RFC (Requst For Comments) managed by IETF.
+PKIjs respects this situation and provide to user a flexible environment based on existing set of RFCs, related to PKI.
+
+| RFC number | RFC name |
+|----------------|----------------|
+|RFC5280|Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile|
+|RFC3161|Internet X.509 Public Key Infrastructure Time-Stamp Protocol (TSP)|
+|RFC5652|Cryptographic Message Syntax (CMS)|
+|RFC3447|Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography Specifications Version 2.1|
+|RFC5753|Use of Elliptic Curve Cryptography (ECC) Algorithms in Cryptographic Message Syntax (CMS)|
+|RFC2898|PKCS #5: Password-Based Cryptography Specification|
+|RFC6960|X.509 Internet Public Key Infrastructure Online Certificate Status Protocol - OCSP|
+|RFC2986|PKCS #10: Certification Request Syntax Specification Version 1.7|
+|RFC7292|PKCS #12: Personal Information Exchange Syntax v1.1|
+|RFC6318|Suite B in Secure/Multipurpose Internet Mail Extensions (S/MIME)|
+|RFC5915|Elliptic Curve Private Key Structure|
+|RFC5480|Elliptic Curve Cryptography Subject Public Key Information|
+|RFC5208|Public-Key Cryptography Standards (PKCS) #8: Private-Key Information Syntax Specification Version 1.2|
+|RFC4055|Additional Algorithms and Identifiers for RSA Cryptography for use in the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile|
+
+PKI.js made of many specialized classes, each of them is responsible for handling one structure from specific RFC. For example, in order to handle X.509 certificate, described in RFC5280, PKI.js has class "Certificate". Each class inside PKI.js is inside separate file.
+Name of each class equals to name from RFC document. Here is a table with PKI.js class names and related RFCs:
+
+| Class Name | RFC number |
+|----------------|----------------|
+|AccessDescription|RFC5280|
+|Accuracy|RFC3161|
+|AlgorithmIdentifier|RFC5280|
+|AltName|RFC5280|
+|Attribute|RFC2986|
+|AttributeTypeAndValue|RFC5280|
+|AuthenticatedSafe|RFC7292|
+|AuthorityKeyIdentifier|RFC5280|
+|BasicConstraints|RFC5280|
+|BasicOCSPResponse|RFC6960|
+|CRLBag|RFC7292|
+|CRLDistributionPoints|RFC5280|
+|CertBag|RFC7292|
+|CertID|RFC6960|
+|Certificate|RFC5280|
+|CertificatePolicies|RFC5280|
+|CertificateRevocationList|RFC5280|
+|CertificateSet|RFC5652|
+|CertificationRequest|RFC2986|
+|ContentInfo|RFC5652|
+|DigestInfo|RFC3447|
+|DistributionPoint|RFC5280|
+|ECCCMSSharedInfo|RFC6318|
+|ECPrivateKey|RFC5915|
+|ECPublicKey|RFC5480|
+|EncapsulatedContentInfo|RFC5652|
+|EncryptedContentInfo|RFC5652|
+|EncryptedData|RFC5652|
+|EnvelopedData|RFC5652|
+|ExtKeyUsage|RFC5280|
+|Extension|RFC5280|
+|Extensions|RFC5280|
+|GeneralName|RFC5280|
+|GeneralNames|RFC5280|
+|GeneralSubtree|RFC5280|
+|InfoAccess|RFC5280|
+|IssuerAndSerialNumber|RFC5652|
+|IssuingDistributionPoint|RFC5280|
+|KEKIdentifier|RFC5652|
+|KEKRecipientInfo|RFC5652|
+|KeyAgreeRecipientIdentifier|RFC5652|
+|KeyAgreeRecipientInfo|RFC5652|
+|KeyBag|RFC5208|
+|KeyTransRecipientInfo|RFC5652|
+|MacData|RFC7292|
+|MessageImprint|RFC3161|
+|NameConstraints|RFC5280|
+|OCSPRequest|RFC6960|
+|OCSPResponse|RFC6960|
+|OriginatorIdentifierOrKey|RFC5652|
+|OriginatorInfo|RFC5652|
+|OriginatorPublicKey|RFC5652|
+|OtherCertificateFormat|RFC5652|
+|OtherKeyAttribute|RFC5652|
+|OtherPrimeInfo|RFC3447|
+|OtherRecipientInfo|RFC5652|
+|OtherRevocationInfoFormat|RFC5652|
+|PBES2Params|RFC2898|
+|PBKDF2Params|RFC2898|
+|PFX|RFC7292|
+|PKCS8ShroudedKeyBag|RFC7292|
+|PKIStatusInfo|RFC3161|
+|PasswordRecipientinfo|RFC5652|
+|PolicyConstraints|RFC5280|
+|PolicyInformation|RFC5280|
+|PolicyMapping|RFC5280|
+|PolicyMappings|RFC5280|
+|PolicyQualifierInfo|RFC5280|
+|PrivateKeyInfo|RFC5208|
+|PrivateKeyUsagePeriod|RFC5280|
+|PublicKeyInfo|RFC5280|
+|RSAESOAEPParams|RFC3447|
+|RSAPrivateKey|RFC3447|
+|RSAPublicKey|RFC3447|
+|RSASSAPSSParams|RFC4055|
+|RecipientEncryptedKey|RFC5652|
+|RecipientEncryptedKeys|RFC5652|
+|RecipientIdentifier|RFC5652|
+|RecipientInfo|RFC5652|
+|RecipientKeyIdentifier|RFC5652|
+|RelativeDistinguishedNames|RFC5280|
+|Request|RFC6960|
+|ResponseBytes|RFC6960|
+|ResponseData|RFC6960|
+|RevocationInfoChoices|RFC5652|
+|RevokedCertificate|RFC5280|
+|SafeBag|RFC7292|
+|SafeContents|RFC7292|
+|SecretBag|RFC7292|
+|Signature|RFC6960|
+|SignedAndUnsignedAttributes|RFC5652|
+|SignedData|RFC5652|
+|SignerInfo|RFC5652|
+|SingleResponse|RFC6960|
+|SubjectDirectoryAttributes|RFC5280|
+|TBSRequest|RFC6960|
+|TSTInfo|RFC3161|
+|Time|RFC5280
+|TimeStampReq|RFC3161|
+|TimeStampResp|RFC3161|
+
+PKI.js library could be extended very easily to handle additional types from any RFC. If you have a special need for any RFC's new types please create issue on GitHub.
diff --git a/pki.js/RSAESOAEPParams.js b/pki.js/RSAESOAEPParams.js
new file mode 100644
index 0000000..7885209
--- /dev/null
+++ b/pki.js/RSAESOAEPParams.js
@@ -0,0 +1,255 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+//**************************************************************************************
+/**
+ * Class from RFC3447
+ */
+export default class RSAESOAEPParams
+{
+ //**********************************************************************************
+ /**
+ * Constructor for RSAESOAEPParams 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 {AlgorithmIdentifier}
+ * @desc hashAlgorithm
+ */
+ this.hashAlgorithm = getParametersValue(parameters, "hashAlgorithm", RSAESOAEPParams.defaultValues("hashAlgorithm"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc maskGenAlgorithm
+ */
+ this.maskGenAlgorithm = getParametersValue(parameters, "maskGenAlgorithm", RSAESOAEPParams.defaultValues("maskGenAlgorithm"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc pSourceAlgorithm
+ */
+ this.pSourceAlgorithm = getParametersValue(parameters, "pSourceAlgorithm", RSAESOAEPParams.defaultValues("pSourceAlgorithm"));
+ //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 "hashAlgorithm":
+ return new AlgorithmIdentifier({
+ algorithmId: "1.3.14.3.2.26", // SHA-1
+ algorithmParams: new asn1js.Null()
+ });
+ case "maskGenAlgorithm":
+ return new AlgorithmIdentifier({
+ algorithmId: "1.2.840.113549.1.1.8", // MGF1
+ algorithmParams: (new AlgorithmIdentifier({
+ algorithmId: "1.3.14.3.2.26", // SHA-1
+ algorithmParams: new asn1js.Null()
+ })).toSchema()
+ });
+ case "pSourceAlgorithm":
+ return new AlgorithmIdentifier({
+ algorithmId: "1.2.840.113549.1.1.9", // id-pSpecified
+ algorithmParams: new asn1js.OctetString({ valueHex: (new Uint8Array([0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09])).buffer }) // SHA-1 hash of empty string
+ });
+ default:
+ throw new Error(`Invalid member name for RSAESOAEPParams class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * RSAES-OAEP-params ::= SEQUENCE {
+ * hashAlgorithm [0] HashAlgorithm DEFAULT sha1,
+ * maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1,
+ * pSourceAlgorithm [2] PSourceAlgorithm DEFAULT pSpecifiedEmpty
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [hashAlgorithm]
+ * @property {string} [maskGenAlgorithm]
+ * @property {string} [pSourceAlgorithm]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ optional: true,
+ value: [AlgorithmIdentifier.schema(names.hashAlgorithm || {})]
+ }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ optional: true,
+ value: [AlgorithmIdentifier.schema(names.maskGenAlgorithm || {})]
+ }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ optional: true,
+ value: [AlgorithmIdentifier.schema(names.pSourceAlgorithm || {})]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "hashAlgorithm",
+ "maskGenAlgorithm",
+ "pSourceAlgorithm"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ RSAESOAEPParams.schema({
+ names: {
+ hashAlgorithm: {
+ names: {
+ blockName: "hashAlgorithm"
+ }
+ },
+ maskGenAlgorithm: {
+ names: {
+ blockName: "maskGenAlgorithm"
+ }
+ },
+ pSourceAlgorithm: {
+ names: {
+ blockName: "pSourceAlgorithm"
+ }
+ }
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RSAESOAEPParams");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("hashAlgorithm" in asn1.result)
+ this.hashAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.hashAlgorithm });
+
+ if("maskGenAlgorithm" in asn1.result)
+ this.maskGenAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.maskGenAlgorithm });
+
+ if("pSourceAlgorithm" in asn1.result)
+ this.pSourceAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.pSourceAlgorithm });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ if(!this.hashAlgorithm.isEqual(RSAESOAEPParams.defaultValues("hashAlgorithm")))
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [this.hashAlgorithm.toSchema()]
+ }));
+ }
+
+ if(!this.maskGenAlgorithm.isEqual(RSAESOAEPParams.defaultValues("maskGenAlgorithm")))
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [this.maskGenAlgorithm.toSchema()]
+ }));
+ }
+
+ if(!this.pSourceAlgorithm.isEqual(RSAESOAEPParams.defaultValues("pSourceAlgorithm")))
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ value: [this.pSourceAlgorithm.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 = {};
+
+ if(!this.hashAlgorithm.isEqual(RSAESOAEPParams.defaultValues("hashAlgorithm")))
+ object.hashAlgorithm = this.hashAlgorithm.toJSON();
+
+ if(!this.maskGenAlgorithm.isEqual(RSAESOAEPParams.defaultValues("maskGenAlgorithm")))
+ object.maskGenAlgorithm = this.maskGenAlgorithm.toJSON();
+
+ if(!this.pSourceAlgorithm.isEqual(RSAESOAEPParams.defaultValues("pSourceAlgorithm")))
+ object.pSourceAlgorithm = this.pSourceAlgorithm.toJSON();
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/RSAPrivateKey.js b/pki.js/RSAPrivateKey.js
new file mode 100644
index 0000000..767a18a
--- /dev/null
+++ b/pki.js/RSAPrivateKey.js
@@ -0,0 +1,356 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, toBase64, arrayBufferToString, stringToArrayBuffer, fromBase64, clearProps } from "pvutils";
+import OtherPrimeInfo from "./OtherPrimeInfo.js";
+//**************************************************************************************
+/**
+ * Class from RFC3447
+ */
+export default class RSAPrivateKey
+{
+ //**********************************************************************************
+ /**
+ * Constructor for RSAPrivateKey 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 {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", RSAPrivateKey.defaultValues("version"));
+ /**
+ * @type {Integer}
+ * @desc modulus
+ */
+ this.modulus = getParametersValue(parameters, "modulus", RSAPrivateKey.defaultValues("modulus"));
+ /**
+ * @type {Integer}
+ * @desc publicExponent
+ */
+ this.publicExponent = getParametersValue(parameters, "publicExponent", RSAPrivateKey.defaultValues("publicExponent"));
+ /**
+ * @type {Integer}
+ * @desc privateExponent
+ */
+ this.privateExponent = getParametersValue(parameters, "privateExponent", RSAPrivateKey.defaultValues("privateExponent"));
+ /**
+ * @type {Integer}
+ * @desc prime1
+ */
+ this.prime1 = getParametersValue(parameters, "prime1", RSAPrivateKey.defaultValues("prime1"));
+ /**
+ * @type {Integer}
+ * @desc prime2
+ */
+ this.prime2 = getParametersValue(parameters, "prime2", RSAPrivateKey.defaultValues("prime2"));
+ /**
+ * @type {Integer}
+ * @desc exponent1
+ */
+ this.exponent1 = getParametersValue(parameters, "exponent1", RSAPrivateKey.defaultValues("exponent1"));
+ /**
+ * @type {Integer}
+ * @desc exponent2
+ */
+ this.exponent2 = getParametersValue(parameters, "exponent2", RSAPrivateKey.defaultValues("exponent2"));
+ /**
+ * @type {Integer}
+ * @desc coefficient
+ */
+ this.coefficient = getParametersValue(parameters, "coefficient", RSAPrivateKey.defaultValues("coefficient"));
+
+ if("otherPrimeInfos" in parameters)
+ /**
+ * @type {Array.}
+ * @desc otherPrimeInfos
+ */
+ this.otherPrimeInfos = getParametersValue(parameters, "otherPrimeInfos", RSAPrivateKey.defaultValues("otherPrimeInfos"));
+ //endregion
+
+ //region If input argument array contains "schema" for this object
+ if("schema" in parameters)
+ this.fromSchema(parameters.schema);
+ //endregion
+ //region If input argument array contains "json" for this object
+ if("json" in parameters)
+ this.fromJSON(parameters.json);
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Return default values for all class members
+ * @param {string} memberName String name for a class member
+ */
+ static defaultValues(memberName)
+ {
+ switch(memberName)
+ {
+ case "version":
+ return 0;
+ case "modulus":
+ return new asn1js.Integer();
+ case "publicExponent":
+ return new asn1js.Integer();
+ case "privateExponent":
+ return new asn1js.Integer();
+ case "prime1":
+ return new asn1js.Integer();
+ case "prime2":
+ return new asn1js.Integer();
+ case "exponent1":
+ return new asn1js.Integer();
+ case "exponent2":
+ return new asn1js.Integer();
+ case "coefficient":
+ return new asn1js.Integer();
+ case "otherPrimeInfos":
+ return [];
+ default:
+ throw new Error(`Invalid member name for RSAPrivateKey class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * RSAPrivateKey ::= Sequence {
+ * version Version,
+ * modulus Integer, -- n
+ * publicExponent Integer, -- e
+ * privateExponent Integer, -- d
+ * prime1 Integer, -- p
+ * prime2 Integer, -- q
+ * exponent1 Integer, -- d mod (p-1)
+ * exponent2 Integer, -- d mod (q-1)
+ * coefficient Integer, -- (inverse of q) mod p
+ * otherPrimeInfos OtherPrimeInfos OPTIONAL
+ * }
+ *
+ * OtherPrimeInfos ::= Sequence SIZE(1..MAX) OF OtherPrimeInfo
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [version]
+ * @property {string} [modulus]
+ * @property {string} [publicExponent]
+ * @property {string} [privateExponent]
+ * @property {string} [prime1]
+ * @property {string} [prime2]
+ * @property {string} [exponent1]
+ * @property {string} [exponent2]
+ * @property {string} [coefficient]
+ * @property {string} [otherPrimeInfosName]
+ * @property {Object} [otherPrimeInfo]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Integer({ name: (names.version || "") }),
+ new asn1js.Integer({ name: (names.modulus || "") }),
+ new asn1js.Integer({ name: (names.publicExponent || "") }),
+ new asn1js.Integer({ name: (names.privateExponent || "") }),
+ new asn1js.Integer({ name: (names.prime1 || "") }),
+ new asn1js.Integer({ name: (names.prime2 || "") }),
+ new asn1js.Integer({ name: (names.exponent1 || "") }),
+ new asn1js.Integer({ name: (names.exponent2 || "") }),
+ new asn1js.Integer({ name: (names.coefficient || "") }),
+ new asn1js.Sequence({
+ optional: true,
+ value: [
+ new asn1js.Repeated({
+ name: (names.otherPrimeInfosName || ""),
+ value: OtherPrimeInfo.schema(names.otherPrimeInfo || {})
+ })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "version",
+ "modulus",
+ "publicExponent",
+ "privateExponent",
+ "prime1",
+ "prime2",
+ "exponent1",
+ "exponent2",
+ "coefficient",
+ "otherPrimeInfos"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ RSAPrivateKey.schema({
+ names: {
+ version: "version",
+ modulus: "modulus",
+ publicExponent: "publicExponent",
+ privateExponent: "privateExponent",
+ prime1: "prime1",
+ prime2: "prime2",
+ exponent1: "exponent1",
+ exponent2: "exponent2",
+ coefficient: "coefficient",
+ otherPrimeInfo: {
+ names: {
+ blockName: "otherPrimeInfos"
+ }
+ }
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RSAPrivateKey");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result.version.valueBlock.valueDec;
+ this.modulus = asn1.result.modulus.convertFromDER(256);
+ this.publicExponent = asn1.result.publicExponent;
+ this.privateExponent = asn1.result.privateExponent.convertFromDER(256);
+ this.prime1 = asn1.result.prime1.convertFromDER(128);
+ this.prime2 = asn1.result.prime2.convertFromDER(128);
+ this.exponent1 = asn1.result.exponent1.convertFromDER(128);
+ this.exponent2 = asn1.result.exponent2.convertFromDER(128);
+ this.coefficient = asn1.result.coefficient.convertFromDER(128);
+
+ if("otherPrimeInfos" in asn1.result)
+ this.otherPrimeInfos = Array.from(asn1.result.otherPrimeInfos, element => new OtherPrimeInfo({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.Integer({ value: this.version }));
+ outputArray.push(this.modulus.convertToDER());
+ outputArray.push(this.publicExponent);
+ outputArray.push(this.privateExponent.convertToDER());
+ outputArray.push(this.prime1.convertToDER());
+ outputArray.push(this.prime2.convertToDER());
+ outputArray.push(this.exponent1.convertToDER());
+ outputArray.push(this.exponent2.convertToDER());
+ outputArray.push(this.coefficient.convertToDER());
+
+ if("otherPrimeInfos" in this)
+ {
+ outputArray.push(new asn1js.Sequence({
+ value: Array.from(this.otherPrimeInfos, 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 jwk = {
+ n: toBase64(arrayBufferToString(this.modulus.valueBlock.valueHex), true, true, true),
+ e: toBase64(arrayBufferToString(this.publicExponent.valueBlock.valueHex), true, true, true),
+ d: toBase64(arrayBufferToString(this.privateExponent.valueBlock.valueHex), true, true, true),
+ p: toBase64(arrayBufferToString(this.prime1.valueBlock.valueHex), true, true, true),
+ q: toBase64(arrayBufferToString(this.prime2.valueBlock.valueHex), true, true, true),
+ dp: toBase64(arrayBufferToString(this.exponent1.valueBlock.valueHex), true, true, true),
+ dq: toBase64(arrayBufferToString(this.exponent2.valueBlock.valueHex), true, true, true),
+ qi: toBase64(arrayBufferToString(this.coefficient.valueBlock.valueHex), true, true, true)
+ };
+
+ if("otherPrimeInfos" in this)
+ jwk.oth = Array.from(this.otherPrimeInfos, element => element.toJSON());
+
+ return jwk;
+ }
+ //**********************************************************************************
+ /**
+ * Convert JSON value into current object
+ * @param {Object} json
+ */
+ fromJSON(json)
+ {
+ if("n" in json)
+ this.modulus = new asn1js.Integer({ valueHex: stringToArrayBuffer(fromBase64(json.n, true, true)) });
+ else
+ throw new Error("Absent mandatory parameter \"n\"");
+
+ if("e" in json)
+ this.publicExponent = new asn1js.Integer({ valueHex: stringToArrayBuffer(fromBase64(json.e, true, true)) });
+ else
+ throw new Error("Absent mandatory parameter \"e\"");
+
+ if("d" in json)
+ this.privateExponent = new asn1js.Integer({ valueHex: stringToArrayBuffer(fromBase64(json.d, true, true)) });
+ else
+ throw new Error("Absent mandatory parameter \"d\"");
+
+ if("p" in json)
+ this.prime1 = new asn1js.Integer({ valueHex: stringToArrayBuffer(fromBase64(json.p, true, true)) });
+ else
+ throw new Error("Absent mandatory parameter \"p\"");
+
+ if("q" in json)
+ this.prime2 = new asn1js.Integer({ valueHex: stringToArrayBuffer(fromBase64(json.q, true, true)) });
+ else
+ throw new Error("Absent mandatory parameter \"q\"");
+
+ if("dp" in json)
+ this.exponent1 = new asn1js.Integer({ valueHex: stringToArrayBuffer(fromBase64(json.dp, true, true)) });
+ else
+ throw new Error("Absent mandatory parameter \"dp\"");
+
+ if("dq" in json)
+ this.exponent2 = new asn1js.Integer({ valueHex: stringToArrayBuffer(fromBase64(json.dq, true, true)) });
+ else
+ throw new Error("Absent mandatory parameter \"dq\"");
+
+ if("qi" in json)
+ this.coefficient = new asn1js.Integer({ valueHex: stringToArrayBuffer(fromBase64(json.qi, true, true)) });
+ else
+ throw new Error("Absent mandatory parameter \"qi\"");
+
+ if("oth" in json)
+ this.otherPrimeInfos = Array.from(json.oth, element => new OtherPrimeInfo({ json: element }));
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/RSAPublicKey.js b/pki.js/RSAPublicKey.js
new file mode 100644
index 0000000..b45ca65
--- /dev/null
+++ b/pki.js/RSAPublicKey.js
@@ -0,0 +1,174 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, toBase64, arrayBufferToString, stringToArrayBuffer, fromBase64, nearestPowerOf2, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC3447
+ */
+export default class RSAPublicKey
+{
+ //**********************************************************************************
+ /**
+ * Constructor for RSAPublicKey class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ * @property {Integer} [modulus]
+ * @property {Integer} [publicExponent]
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ /**
+ * @type {Integer}
+ * @desc Modulus part of RSA public key
+ */
+ this.modulus = getParametersValue(parameters, "modulus", RSAPublicKey.defaultValues("modulus"));
+ /**
+ * @type {Integer}
+ * @desc Public exponent of RSA public key
+ */
+ this.publicExponent = getParametersValue(parameters, "publicExponent", RSAPublicKey.defaultValues("publicExponent"));
+ //endregion
+
+ //region If input argument array contains "schema" for this object
+ if("schema" in parameters)
+ this.fromSchema(parameters.schema);
+ //endregion
+ //region If input argument array contains "json" for this object
+ if("json" in parameters)
+ this.fromJSON(parameters.json);
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Return default values for all class members
+ * @param {string} memberName String name for a class member
+ */
+ static defaultValues(memberName)
+ {
+ switch(memberName)
+ {
+ case "modulus":
+ return new asn1js.Integer();
+ case "publicExponent":
+ return new asn1js.Integer();
+ default:
+ throw new Error(`Invalid member name for RSAPublicKey class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * RSAPublicKey ::= Sequence {
+ * modulus Integer, -- n
+ * publicExponent Integer -- e
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} utcTimeName Name for "utcTimeName" choice
+ * @property {string} generalTimeName Name for "generalTimeName" choice
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Integer({ name: (names.modulus || "") }),
+ new asn1js.Integer({ name: (names.publicExponent || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "modulus",
+ "publicExponent"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ RSAPublicKey.schema({
+ names: {
+ modulus: "modulus",
+ publicExponent: "publicExponent"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RSAPublicKey");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.modulus = asn1.result.modulus.convertFromDER(256);
+ this.publicExponent = asn1.result.publicExponent;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ this.modulus.convertToDER(),
+ this.publicExponent
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ n: toBase64(arrayBufferToString(this.modulus.valueBlock.valueHex), true, true, true),
+ e: toBase64(arrayBufferToString(this.publicExponent.valueBlock.valueHex), true, true, true)
+ };
+ }
+ //**********************************************************************************
+ /**
+ * Convert JSON value into current object
+ * @param {Object} json
+ */
+ fromJSON(json)
+ {
+ if("n" in json)
+ {
+ const array = stringToArrayBuffer(fromBase64(json.n, true));
+ this.modulus = new asn1js.Integer({ valueHex: array.slice(0, Math.pow(2, nearestPowerOf2(array.byteLength))) });
+ }
+ else
+ throw new Error("Absent mandatory parameter \"n\"");
+
+ if("e" in json)
+ this.publicExponent = new asn1js.Integer({ valueHex: stringToArrayBuffer(fromBase64(json.e, true)).slice(0, 3) });
+ else
+ throw new Error("Absent mandatory parameter \"e\"");
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/RSASSAPSSParams.js b/pki.js/RSASSAPSSParams.js
new file mode 100644
index 0000000..4b174f9
--- /dev/null
+++ b/pki.js/RSASSAPSSParams.js
@@ -0,0 +1,283 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+//**************************************************************************************
+/**
+ * Class from RFC4055
+ */
+export default class RSASSAPSSParams
+{
+ //**********************************************************************************
+ /**
+ * Constructor for RSASSAPSSParams 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 {AlgorithmIdentifier}
+ * @desc Algorithms of hashing (DEFAULT sha1)
+ */
+ this.hashAlgorithm = getParametersValue(parameters, "hashAlgorithm", RSASSAPSSParams.defaultValues("hashAlgorithm"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc Algorithm of "mask generaion function (MGF)" (DEFAULT mgf1SHA1)
+ */
+ this.maskGenAlgorithm = getParametersValue(parameters, "maskGenAlgorithm", RSASSAPSSParams.defaultValues("maskGenAlgorithm"));
+ /**
+ * @type {number}
+ * @desc Salt length (DEFAULT 20)
+ */
+ this.saltLength = getParametersValue(parameters, "saltLength", RSASSAPSSParams.defaultValues("saltLength"));
+ /**
+ * @type {number}
+ * @desc (DEFAULT 1)
+ */
+ this.trailerField = getParametersValue(parameters, "trailerField", RSASSAPSSParams.defaultValues("trailerField"));
+ //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 "hashAlgorithm":
+ return new AlgorithmIdentifier({
+ algorithmId: "1.3.14.3.2.26", // SHA-1
+ algorithmParams: new asn1js.Null()
+ });
+ case "maskGenAlgorithm":
+ return new AlgorithmIdentifier({
+ algorithmId: "1.2.840.113549.1.1.8", // MGF1
+ algorithmParams: (new AlgorithmIdentifier({
+ algorithmId: "1.3.14.3.2.26", // SHA-1
+ algorithmParams: new asn1js.Null()
+ })).toSchema()
+ });
+ case "saltLength":
+ return 20;
+ case "trailerField":
+ return 1;
+ default:
+ throw new Error(`Invalid member name for RSASSAPSSParams class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * RSASSA-PSS-params ::= Sequence {
+ * hashAlgorithm [0] HashAlgorithm DEFAULT sha1Identifier,
+ * maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1Identifier,
+ * saltLength [2] Integer DEFAULT 20,
+ * trailerField [3] Integer DEFAULT 1 }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [hashAlgorithm]
+ * @property {string} [maskGenAlgorithm]
+ * @property {string} [saltLength]
+ * @property {string} [trailerField]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ optional: true,
+ value: [AlgorithmIdentifier.schema(names.hashAlgorithm || {})]
+ }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ optional: true,
+ value: [AlgorithmIdentifier.schema(names.maskGenAlgorithm || {})]
+ }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ optional: true,
+ value: [new asn1js.Integer({ name: (names.saltLength || "") })]
+ }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 3 // [3]
+ },
+ optional: true,
+ value: [new asn1js.Integer({ name: (names.trailerField || "") })]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "hashAlgorithm",
+ "maskGenAlgorithm",
+ "saltLength",
+ "trailerField"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ RSASSAPSSParams.schema({
+ names: {
+ hashAlgorithm: {
+ names: {
+ blockName: "hashAlgorithm"
+ }
+ },
+ maskGenAlgorithm: {
+ names: {
+ blockName: "maskGenAlgorithm"
+ }
+ },
+ saltLength: "saltLength",
+ trailerField: "trailerField"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RSASSAPSSParams");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("hashAlgorithm" in asn1.result)
+ this.hashAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.hashAlgorithm });
+
+ if("maskGenAlgorithm" in asn1.result)
+ this.maskGenAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.maskGenAlgorithm });
+
+ if("saltLength" in asn1.result)
+ this.saltLength = asn1.result.saltLength.valueBlock.valueDec;
+
+ if("trailerField" in asn1.result)
+ this.trailerField = asn1.result.trailerField.valueBlock.valueDec;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ if(!this.hashAlgorithm.isEqual(RSASSAPSSParams.defaultValues("hashAlgorithm")))
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [this.hashAlgorithm.toSchema()]
+ }));
+ }
+
+ if(!this.maskGenAlgorithm.isEqual(RSASSAPSSParams.defaultValues("maskGenAlgorithm")))
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [this.maskGenAlgorithm.toSchema()]
+ }));
+ }
+
+ if(this.saltLength !== RSASSAPSSParams.defaultValues("saltLength"))
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ value: [new asn1js.Integer({ value: this.saltLength })]
+ }));
+ }
+
+ if(this.trailerField !== RSASSAPSSParams.defaultValues("trailerField"))
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 3 // [3]
+ },
+ value: [new asn1js.Integer({ value: this.trailerField })]
+ }));
+ }
+ //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 = {};
+
+ if(!this.hashAlgorithm.isEqual(RSASSAPSSParams.defaultValues("hashAlgorithm")))
+ object.hashAlgorithm = this.hashAlgorithm.toJSON();
+
+ if(!this.maskGenAlgorithm.isEqual(RSASSAPSSParams.defaultValues("maskGenAlgorithm")))
+ object.maskGenAlgorithm = this.maskGenAlgorithm.toJSON();
+
+ if(this.saltLength !== RSASSAPSSParams.defaultValues("saltLength"))
+ object.saltLength = this.saltLength;
+
+ if(this.trailerField !== RSASSAPSSParams.defaultValues("trailerField"))
+ object.trailerField = this.trailerField;
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/RecipientEncryptedKey.js b/pki.js/RecipientEncryptedKey.js
new file mode 100644
index 0000000..d75c30b
--- /dev/null
+++ b/pki.js/RecipientEncryptedKey.js
@@ -0,0 +1,173 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import KeyAgreeRecipientIdentifier from "./KeyAgreeRecipientIdentifier.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class RecipientEncryptedKey
+{
+ //**********************************************************************************
+ /**
+ * Constructor for RecipientEncryptedKey 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 {KeyAgreeRecipientIdentifier}
+ * @desc rid
+ */
+ this.rid = getParametersValue(parameters, "rid", RecipientEncryptedKey.defaultValues("rid"));
+ /**
+ * @type {OctetString}
+ * @desc encryptedKey
+ */
+ this.encryptedKey = getParametersValue(parameters, "encryptedKey", RecipientEncryptedKey.defaultValues("encryptedKey"));
+ //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 "rid":
+ return new KeyAgreeRecipientIdentifier();
+ case "encryptedKey":
+ return new asn1js.OctetString();
+ default:
+ throw new Error(`Invalid member name for RecipientEncryptedKey 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 "rid":
+ return ((memberValue.variant === (-1)) && (("value" in memberValue) === false));
+ case "encryptedKey":
+ return (memberValue.isEqual(RecipientEncryptedKey.defaultValues("encryptedKey")));
+ default:
+ throw new Error(`Invalid member name for RecipientEncryptedKey class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * RecipientEncryptedKey ::= SEQUENCE {
+ * rid KeyAgreeRecipientIdentifier,
+ * encryptedKey EncryptedKey }
+ *
+ * EncryptedKey ::= OCTET STRING
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [rid]
+ * @property {string} [encryptedKey]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ KeyAgreeRecipientIdentifier.schema(names.rid || {}),
+ new asn1js.OctetString({ name: (names.encryptedKey || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "rid",
+ "encryptedKey"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ RecipientEncryptedKey.schema({
+ names: {
+ rid: {
+ names: {
+ blockName: "rid"
+ }
+ },
+ encryptedKey: "encryptedKey"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RecipientEncryptedKey");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.rid = new KeyAgreeRecipientIdentifier({ schema: asn1.result.rid });
+ this.encryptedKey = asn1.result.encryptedKey;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ this.rid.toSchema(),
+ this.encryptedKey
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ rid: this.rid.toJSON(),
+ encryptedKey: this.encryptedKey.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/RecipientEncryptedKeys.js b/pki.js/RecipientEncryptedKeys.js
new file mode 100644
index 0000000..01ba31e
--- /dev/null
+++ b/pki.js/RecipientEncryptedKeys.js
@@ -0,0 +1,150 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import RecipientEncryptedKey from "./RecipientEncryptedKey.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class RecipientEncryptedKeys
+{
+ //**********************************************************************************
+ /**
+ * Constructor for RecipientEncryptedKeys 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 {Array.}
+ * @desc encryptedKeys
+ */
+ this.encryptedKeys = getParametersValue(parameters, "encryptedKeys", RecipientEncryptedKeys.defaultValues("encryptedKeys"));
+ //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 "encryptedKeys":
+ return [];
+ default:
+ throw new Error(`Invalid member name for RecipientEncryptedKeys 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 "encryptedKeys":
+ return (memberValue.length === 0);
+ default:
+ throw new Error(`Invalid member name for RecipientEncryptedKeys class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * RecipientEncryptedKeys ::= SEQUENCE OF RecipientEncryptedKey
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [RecipientEncryptedKeys]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.RecipientEncryptedKeys || ""),
+ value: RecipientEncryptedKey.schema()
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "RecipientEncryptedKeys"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ RecipientEncryptedKeys.schema({
+ names: {
+ RecipientEncryptedKeys: "RecipientEncryptedKeys"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RecipientEncryptedKeys");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.encryptedKeys = Array.from(asn1.result.RecipientEncryptedKeys, element => new RecipientEncryptedKey({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: Array.from(this.encryptedKeys, element => element.toSchema())
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ encryptedKeys: Array.from(this.encryptedKeys, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/RecipientIdentifier.js b/pki.js/RecipientIdentifier.js
new file mode 100644
index 0000000..3c7f710
--- /dev/null
+++ b/pki.js/RecipientIdentifier.js
@@ -0,0 +1,197 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import IssuerAndSerialNumber from "./IssuerAndSerialNumber.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class RecipientIdentifier
+{
+ //**********************************************************************************
+ /**
+ * Constructor for RecipientIdentifier 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 {number}
+ * @desc variant
+ */
+ this.variant = getParametersValue(parameters, "variant", RecipientIdentifier.defaultValues("variant"));
+
+ if("value" in parameters)
+ /**
+ * @type {*}
+ * @desc value
+ */
+ this.value = getParametersValue(parameters, "value", RecipientIdentifier.defaultValues("value"));
+ //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 "variant":
+ return (-1);
+ case "value":
+ return {};
+ default:
+ throw new Error(`Invalid member name for RecipientIdentifier 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 "variant":
+ return (memberValue === (-1));
+ case "values":
+ return (Object.keys(memberValue).length === 0);
+ default:
+ throw new Error(`Invalid member name for RecipientIdentifier class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * RecipientIdentifier ::= CHOICE {
+ * issuerAndSerialNumber IssuerAndSerialNumber,
+ * subjectKeyIdentifier [0] SubjectKeyIdentifier }
+ *
+ * SubjectKeyIdentifier ::= OCTET STRING
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Choice({
+ value: [
+ IssuerAndSerialNumber.schema({
+ names: {
+ blockName: (names.blockName || "")
+ }
+ }),
+ new asn1js.Primitive({
+ name: (names.blockName || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ }
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "blockName"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ RecipientIdentifier.schema({
+ names: {
+ blockName: "blockName"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RecipientIdentifier");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if(asn1.result.blockName.idBlock.tagClass === 1)
+ {
+ this.variant = 1;
+ this.value = new IssuerAndSerialNumber({ schema: asn1.result.blockName });
+ }
+ else
+ {
+ this.variant = 2;
+ this.value = new asn1js.OctetString({ valueHex: asn1.result.blockName.valueBlock.valueHex }) ;
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ switch(this.variant)
+ {
+ case 1:
+ return this.value.toSchema();
+ case 2:
+ return new asn1js.Primitive({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ valueHex: this.value.valueBlock.valueHex
+ });
+ default:
+ return new asn1js.Any();
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const _object = {
+ variant: this.variant
+ };
+
+ if((this.variant === 1) || (this.variant === 2))
+ _object.value = this.value.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/RecipientInfo.js b/pki.js/RecipientInfo.js
new file mode 100644
index 0000000..085721a
--- /dev/null
+++ b/pki.js/RecipientInfo.js
@@ -0,0 +1,258 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import KeyTransRecipientInfo from "./KeyTransRecipientInfo.js";
+import KeyAgreeRecipientInfo from "./KeyAgreeRecipientInfo.js";
+import KEKRecipientInfo from "./KEKRecipientInfo.js";
+import PasswordRecipientinfo from "./PasswordRecipientinfo.js";
+import OtherRecipientInfo from "./OtherRecipientInfo.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class RecipientInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for RecipientInfo 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 {string}
+ * @desc variant
+ */
+ this.variant = getParametersValue(parameters, "variant", RecipientInfo.defaultValues("variant"));
+
+ if("value" in parameters)
+ /**
+ * @type {*}
+ * @desc value
+ */
+ this.value = getParametersValue(parameters, "value", RecipientInfo.defaultValues("value"));
+ //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 "variant":
+ return (-1);
+ case "value":
+ return {};
+ default:
+ throw new Error(`Invalid member name for RecipientInfo 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 "variant":
+ return (memberValue === RecipientInfo.defaultValues(memberName));
+ case "value":
+ return (Object.keys(memberValue).length === 0);
+ default:
+ throw new Error(`Invalid member name for RecipientInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * RecipientInfo ::= CHOICE {
+ * ktri KeyTransRecipientInfo,
+ * kari [1] KeyAgreeRecipientInfo,
+ * kekri [2] KEKRecipientInfo,
+ * pwri [3] PasswordRecipientinfo,
+ * ori [4] OtherRecipientInfo }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [type]
+ * @property {string} [setName]
+ * @property {string} [values]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Choice({
+ value: [
+ KeyTransRecipientInfo.schema({
+ names: {
+ blockName: (names.blockName || "")
+ }
+ }),
+ new asn1js.Constructed({
+ name: (names.blockName || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: KeyAgreeRecipientInfo.schema().valueBlock.value
+ }),
+ new asn1js.Constructed({
+ name: (names.blockName || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ value: KEKRecipientInfo.schema().valueBlock.value
+ }),
+ new asn1js.Constructed({
+ name: (names.blockName || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 3 // [3]
+ },
+ value: PasswordRecipientinfo.schema().valueBlock.value
+ }),
+ new asn1js.Constructed({
+ name: (names.blockName || ""),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 4 // [4]
+ },
+ value: OtherRecipientInfo.schema().valueBlock.value
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "blockName"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ RecipientInfo.schema({
+ names: {
+ blockName: "blockName"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RecipientInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if(asn1.result.blockName.idBlock.tagClass === 1)
+ {
+ this.variant = 1;
+ this.value = new KeyTransRecipientInfo({ schema: asn1.result.blockName });
+ }
+ else
+ {
+ //region Create "SEQUENCE" from "ASN1_CONSTRUCTED"
+ const blockSequence = new asn1js.Sequence({
+ value: asn1.result.blockName.valueBlock.value
+ });
+ //endregion
+
+ switch(asn1.result.blockName.idBlock.tagNumber)
+ {
+ case 1:
+ this.variant = 2;
+ this.value = new KeyAgreeRecipientInfo({ schema: blockSequence });
+ break;
+ case 2:
+ this.variant = 3;
+ this.value = new KEKRecipientInfo({ schema: blockSequence });
+ break;
+ case 3:
+ this.variant = 4;
+ this.value = new PasswordRecipientinfo({ schema: blockSequence });
+ break;
+ case 4:
+ this.variant = 5;
+ this.value = new OtherRecipientInfo({ schema: blockSequence });
+ break;
+ default:
+ throw new Error("Incorrect structure of RecipientInfo block");
+ }
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ const _schema = this.value.toSchema();
+
+ switch(this.variant)
+ {
+ case 1:
+ return _schema;
+ case 2:
+ case 3:
+ case 4:
+ //region Create "ASN1_CONSTRUCTED" from "SEQUENCE"
+ _schema.idBlock.tagClass = 3; // CONTEXT-SPECIFIC
+ _schema.idBlock.tagNumber = (this.variant - 1);
+ //endregion
+
+ return _schema;
+ default:
+ return new asn1js.Any();
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const _object = {
+ variant: this.variant
+ };
+
+ if((this.variant >= 1) && (this.variant <= 4))
+ _object.value = this.value.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/RecipientKeyIdentifier.js b/pki.js/RecipientKeyIdentifier.js
new file mode 100644
index 0000000..d27b404
--- /dev/null
+++ b/pki.js/RecipientKeyIdentifier.js
@@ -0,0 +1,220 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import OtherKeyAttribute from "./OtherKeyAttribute.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class RecipientKeyIdentifier
+{
+ //**********************************************************************************
+ /**
+ * Constructor for RecipientKeyIdentifier 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 {OctetString}
+ * @desc subjectKeyIdentifier
+ */
+ this.subjectKeyIdentifier = getParametersValue(parameters, "subjectKeyIdentifier", RecipientKeyIdentifier.defaultValues("subjectKeyIdentifier"));
+
+ if("date" in parameters)
+ /**
+ * @type {GeneralizedTime}
+ * @desc date
+ */
+ this.date = getParametersValue(parameters, "date", RecipientKeyIdentifier.defaultValues("date"));
+
+ if("other" in parameters)
+ /**
+ * @type {OtherKeyAttribute}
+ * @desc other
+ */
+ this.other = getParametersValue(parameters, "other", RecipientKeyIdentifier.defaultValues("other"));
+ //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 "subjectKeyIdentifier":
+ return new asn1js.OctetString();
+ case "date":
+ return new asn1js.GeneralizedTime();
+ case "other":
+ return new OtherKeyAttribute();
+ default:
+ throw new Error(`Invalid member name for RecipientKeyIdentifier 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 "subjectKeyIdentifier":
+ return (memberValue.isEqual(RecipientKeyIdentifier.defaultValues("subjectKeyIdentifier")));
+ case "date":
+ // noinspection OverlyComplexBooleanExpressionJS
+ return ((memberValue.year === 0) &&
+ (memberValue.month === 0) &&
+ (memberValue.day === 0) &&
+ (memberValue.hour === 0) &&
+ (memberValue.minute === 0) &&
+ (memberValue.second === 0) &&
+ (memberValue.millisecond === 0));
+ case "other":
+ return ((memberValue.keyAttrId === "") && (("keyAttr" in memberValue) === false));
+ default:
+ throw new Error(`Invalid member name for RecipientKeyIdentifier class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * RecipientKeyIdentifier ::= SEQUENCE {
+ * subjectKeyIdentifier SubjectKeyIdentifier,
+ * date GeneralizedTime OPTIONAL,
+ * other OtherKeyAttribute OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [type]
+ * @property {string} [setName]
+ * @property {string} [values]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.OctetString({ name: (names.subjectKeyIdentifier || "") }),
+ new asn1js.GeneralizedTime({
+ optional: true,
+ name: (names.date || "")
+ }),
+ OtherKeyAttribute.schema(names.other || {})
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "subjectKeyIdentifier",
+ "date",
+ "other"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ RecipientKeyIdentifier.schema({
+ names: {
+ subjectKeyIdentifier: "subjectKeyIdentifier",
+ date: "date",
+ other: {
+ names: {
+ blockName: "other"
+ }
+ }
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RecipientKeyIdentifier");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.subjectKeyIdentifier = asn1.result.subjectKeyIdentifier;
+
+ if("date" in asn1.result)
+ this.date = asn1.result.date;
+
+ if("other" in asn1.result)
+ this.other = new OtherKeyAttribute({ schema: asn1.result.other });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(this.subjectKeyIdentifier);
+
+ if("date" in this)
+ outputArray.push(this.date);
+
+ if("other" in this)
+ outputArray.push(this.other.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 = {
+ subjectKeyIdentifier: this.subjectKeyIdentifier.toJSON()
+ };
+
+ if("date" in this)
+ _object.date = this.date;
+
+ if("other" in this)
+ _object.other = this.other.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/RelativeDistinguishedNames.js b/pki.js/RelativeDistinguishedNames.js
new file mode 100644
index 0000000..e8c579d
--- /dev/null
+++ b/pki.js/RelativeDistinguishedNames.js
@@ -0,0 +1,217 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, isEqualBuffer, clearProps } from "pvutils";
+import AttributeTypeAndValue from "./AttributeTypeAndValue.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class RelativeDistinguishedNames
+{
+ //**********************************************************************************
+ /**
+ * Constructor for RelativeDistinguishedNames class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ * @property {Array.} [typesAndValues] Array of "type and value" objects
+ * @property {ArrayBuffer} [valueBeforeDecode] Value of the RDN before decoding from schema
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ /**
+ * @type {Array.}
+ * @desc Array of "type and value" objects
+ */
+ this.typesAndValues = getParametersValue(parameters, "typesAndValues", RelativeDistinguishedNames.defaultValues("typesAndValues"));
+ /**
+ * @type {ArrayBuffer}
+ * @desc Value of the RDN before decoding from schema
+ */
+ this.valueBeforeDecode = getParametersValue(parameters, "valueBeforeDecode", RelativeDistinguishedNames.defaultValues("valueBeforeDecode"));
+ //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 "typesAndValues":
+ return [];
+ case "valueBeforeDecode":
+ return new ArrayBuffer(0);
+ default:
+ throw new Error(`Invalid member name for RelativeDistinguishedNames 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 "typesAndValues":
+ return (memberValue.length === 0);
+ case "valueBeforeDecode":
+ return (memberValue.byteLength === 0);
+ default:
+ throw new Error(`Invalid member name for RelativeDistinguishedNames class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * RDNSequence ::= Sequence OF RelativeDistinguishedName
+ *
+ * RelativeDistinguishedName ::=
+ * SET SIZE (1..MAX) OF AttributeTypeAndValue
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName] Name for entire block
+ * @property {string} [repeatedSequence] Name for "repeatedSequence" block
+ * @property {string} [repeatedSet] Name for "repeatedSet" block
+ * @property {string} [typeAndValue] Name for "typeAndValue" block
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.repeatedSequence || ""),
+ value: new asn1js.Set({
+ value: [
+ new asn1js.Repeated({
+ name: (names.repeatedSet || ""),
+ value: AttributeTypeAndValue.schema(names.typeAndValue || {})
+ })
+ ]
+ })
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "RDN",
+ "typesAndValues"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ RelativeDistinguishedNames.schema({
+ names: {
+ blockName: "RDN",
+ repeatedSet: "typesAndValues"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RelativeDistinguishedNames");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("typesAndValues" in asn1.result) // Could be a case when there is no "types and values"
+ this.typesAndValues = Array.from(asn1.result.typesAndValues, element => new AttributeTypeAndValue({ schema: element }));
+
+ // noinspection JSUnresolvedVariable
+ this.valueBeforeDecode = asn1.result.RDN.valueBeforeDecode;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Decode stored TBS value
+ if(this.valueBeforeDecode.byteLength === 0) // No stored encoded array, create "from scratch"
+ {
+ return (new asn1js.Sequence({
+ value: [new asn1js.Set({
+ value: Array.from(this.typesAndValues, element => element.toSchema())
+ })]
+ }));
+ }
+
+ const asn1 = asn1js.fromBER(this.valueBeforeDecode);
+ //endregion
+
+ //region Construct and return new ASN.1 schema for this object
+ return asn1.result;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ typesAndValues: Array.from(this.typesAndValues, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+ /**
+ * Compare two RDN values, or RDN with ArrayBuffer value
+ * @param {(RelativeDistinguishedNames|ArrayBuffer)} compareTo The value compare to current
+ * @returns {boolean}
+ */
+ isEqual(compareTo)
+ {
+ if(compareTo instanceof RelativeDistinguishedNames)
+ {
+ if(this.typesAndValues.length !== compareTo.typesAndValues.length)
+ return false;
+
+ for(const [index, typeAndValue] of this.typesAndValues.entries())
+ {
+ if(typeAndValue.isEqual(compareTo.typesAndValues[index]) === false)
+ return false;
+ }
+
+ return true;
+ }
+
+ if(compareTo instanceof ArrayBuffer)
+ return isEqualBuffer(this.valueBeforeDecode, compareTo);
+
+ return false;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/Request.js b/pki.js/Request.js
new file mode 100644
index 0000000..d53b44c
--- /dev/null
+++ b/pki.js/Request.js
@@ -0,0 +1,215 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import CertID from "./CertID.js";
+import Extension from "./Extension.js";
+//**************************************************************************************
+/**
+ * Class from RFC6960
+ */
+export default class Request
+{
+ //**********************************************************************************
+ /**
+ * Constructor for Request 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 reqCert
+ */
+ this.reqCert = getParametersValue(parameters, "reqCert", Request.defaultValues("reqCert"));
+
+ if("singleRequestExtensions" in parameters)
+ /**
+ * @type {Array.}
+ * @desc singleRequestExtensions
+ */
+ this.singleRequestExtensions = getParametersValue(parameters, "singleRequestExtensions", Request.defaultValues("singleRequestExtensions"));
+ //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 "reqCert":
+ return new CertID();
+ case "singleRequestExtensions":
+ return [];
+ default:
+ throw new Error(`Invalid member name for Request 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 "reqCert":
+ return (memberValue.isEqual(Request.defaultValues(memberName)));
+ case "singleRequestExtensions":
+ return (memberValue.length === 0);
+ default:
+ throw new Error(`Invalid member name for Request class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * Request ::= SEQUENCE {
+ * reqCert CertID,
+ * singleRequestExtensions [0] EXPLICIT Extensions OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [reqCert]
+ * @property {string} [extensions]
+ * @property {string} [singleRequestExtensions]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ CertID.schema(names.reqCert || {}),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [Extension.schema(names.extensions || {
+ names: {
+ blockName: (names.singleRequestExtensions || "")
+ }
+ })]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "reqCert",
+ "singleRequestExtensions"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ Request.schema({
+ names: {
+ reqCert: {
+ names: {
+ blockName: "reqCert"
+ }
+ },
+ singleRequestExtensions: {
+ names: {
+ blockName: "singleRequestExtensions"
+ }
+ }
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for Request");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.reqCert = new CertID({ schema: asn1.result.reqCert });
+
+ if("singleRequestExtensions" in asn1.result)
+ this.singleRequestExtensions = Array.from(asn1.result.singleRequestExtensions.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 array for output sequence
+ const outputArray = [];
+
+ outputArray.push(this.reqCert.toSchema());
+
+ if("singleRequestExtensions" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Sequence({
+ value: Array.from(this.singleRequestExtensions, 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 = {
+ reqCert: this.reqCert.toJSON()
+ };
+
+ if("singleRequestExtensions" in this)
+ _object.singleRequestExtensions = Array.from(this.singleRequestExtensions, element => element.toJSON());
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/ResponseBytes.js b/pki.js/ResponseBytes.js
new file mode 100644
index 0000000..a1ea366
--- /dev/null
+++ b/pki.js/ResponseBytes.js
@@ -0,0 +1,166 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC6960
+ */
+export default class ResponseBytes
+{
+ //**********************************************************************************
+ /**
+ * Constructor for ResponseBytes 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 {string}
+ * @desc responseType
+ */
+ this.responseType = getParametersValue(parameters, "responseType", ResponseBytes.defaultValues("responseType"));
+ /**
+ * @type {OctetString}
+ * @desc response
+ */
+ this.response = getParametersValue(parameters, "response", ResponseBytes.defaultValues("response"));
+ //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 "responseType":
+ return "";
+ case "response":
+ return new asn1js.OctetString();
+ default:
+ throw new Error(`Invalid member name for ResponseBytes 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 "responseType":
+ return (memberValue === "");
+ case "response":
+ return (memberValue.isEqual(ResponseBytes.defaultValues(memberName)));
+ default:
+ throw new Error(`Invalid member name for ResponseBytes class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * ResponseBytes ::= SEQUENCE {
+ * responseType OBJECT IDENTIFIER,
+ * response OCTET STRING }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [responseType]
+ * @property {string} [response]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.responseType || "") }),
+ new asn1js.OctetString({ name: (names.response || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "responseType",
+ "response"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ ResponseBytes.schema({
+ names: {
+ responseType: "responseType",
+ response: "response"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for ResponseBytes");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.responseType = asn1.result.responseType.valueBlock.toString();
+ this.response = asn1.result.response;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ new asn1js.ObjectIdentifier({ value: this.responseType }),
+ this.response
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ responseType: this.responseType,
+ response: this.response.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/ResponseData.js b/pki.js/ResponseData.js
new file mode 100644
index 0000000..e47d200
--- /dev/null
+++ b/pki.js/ResponseData.js
@@ -0,0 +1,350 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import RelativeDistinguishedNames from "./RelativeDistinguishedNames.js";
+import SingleResponse from "./SingleResponse.js";
+import Extension from "./Extension.js";
+import Extensions from "./Extensions.js";
+//**************************************************************************************
+/**
+ * Class from RFC6960
+ */
+export default class ResponseData
+{
+ //**********************************************************************************
+ /**
+ * Constructor for ResponseData 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 {ArrayBuffer}
+ * @desc tbs
+ */
+ this.tbs = getParametersValue(parameters, "tbs", ResponseData.defaultValues("tbs"));
+ /**
+ * @type {Object}
+ * @desc responderID
+ */
+ this.responderID = getParametersValue(parameters, "responderID", ResponseData.defaultValues("responderID"));
+ /**
+ * @type {Date}
+ * @desc producedAt
+ */
+ this.producedAt = getParametersValue(parameters, "producedAt", ResponseData.defaultValues("producedAt"));
+ /**
+ * @type {Array.}
+ * @desc responses
+ */
+ this.responses = getParametersValue(parameters, "responses", ResponseData.defaultValues("responses"));
+
+ if("responseExtensions" in parameters)
+ /**
+ * @type {Array.}
+ * @desc responseExtensions
+ */
+ this.responseExtensions = getParametersValue(parameters, "responseExtensions", ResponseData.defaultValues("responseExtensions"));
+ //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 "tbs":
+ return new ArrayBuffer(0);
+ case "responderID":
+ return {};
+ case "producedAt":
+ return new Date(0, 0, 0);
+ case "responses":
+ case "responseExtensions":
+ return [];
+ default:
+ throw new Error(`Invalid member name for ResponseData 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 "tbs":
+ return (memberValue.byteLength === 0);
+ case "responderID":
+ return (Object.keys(memberValue).length === 0);
+ case "producedAt":
+ return (memberValue === ResponseData.defaultValues(memberName));
+ case "responses":
+ case "responseExtensions":
+ return (memberValue.length === 0);
+ default:
+ throw new Error(`Invalid member name for ResponseData class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * ResponseData ::= SEQUENCE {
+ * version [0] EXPLICIT Version DEFAULT v1,
+ * responderID ResponderID,
+ * producedAt GeneralizedTime,
+ * responses SEQUENCE OF SingleResponse,
+ * responseExtensions [1] EXPLICIT Extensions OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [version]
+ * @property {string} [ResponseDataByName]
+ * @property {string} [ResponseDataByKey]
+ * @property {string} [producedAt]
+ * @property {string} [response]
+ * @property {string} [extensions]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || "ResponseData"),
+ value: [
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [new asn1js.Integer({ name: (names.version || "ResponseData.version") })]
+ }),
+ new asn1js.Choice({
+ value: [
+ new asn1js.Constructed({
+ name: (names.responderID || "ResponseData.responderID"),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [RelativeDistinguishedNames.schema(names.ResponseDataByName || {
+ names: {
+ blockName: "ResponseData.byName"
+ }
+ })]
+ }),
+ new asn1js.Constructed({
+ name: (names.responderID || "ResponseData.responderID"),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ value: [new asn1js.OctetString({ name: (names.ResponseDataByKey || "ResponseData.byKey") })]
+ })
+ ]
+ }),
+ new asn1js.GeneralizedTime({ name: (names.producedAt || "ResponseData.producedAt") }),
+ new asn1js.Sequence({
+ value: [
+ new asn1js.Repeated({
+ name: "ResponseData.responses",
+ value: SingleResponse.schema(names.response || {})
+ })
+ ]
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [Extensions.schema(names.extensions || {
+ names: {
+ blockName: "ResponseData.responseExtensions"
+ }
+ })]
+ }) // EXPLICIT SEQUENCE value
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "ResponseData",
+ "ResponseData.version",
+ "ResponseData.responderID",
+ "ResponseData.producedAt",
+ "ResponseData.responses",
+ "ResponseData.responseExtensions"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ ResponseData.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for ResponseData");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.tbs = asn1.result.ResponseData.valueBeforeDecode;
+
+ if("ResponseData.version" in asn1.result)
+ this.version = asn1.result["ResponseData.version"].valueBlock.valueDec;
+
+ if(asn1.result["ResponseData.responderID"].idBlock.tagNumber === 1)
+ this.responderID = new RelativeDistinguishedNames({ schema: asn1.result["ResponseData.responderID"].valueBlock.value[0] });
+ else
+ this.responderID = asn1.result["ResponseData.responderID"].valueBlock.value[0]; // OCTETSTRING
+
+ this.producedAt = asn1.result["ResponseData.producedAt"].toDate();
+ this.responses = Array.from(asn1.result["ResponseData.responses"], element => new SingleResponse({ schema: element }));
+
+ if("ResponseData.responseExtensions" in asn1.result)
+ this.responseExtensions = Array.from(asn1.result["ResponseData.responseExtensions"].valueBlock.value, element => new Extension({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @param {boolean} encodeFlag If param equal to false then create TBS schema via decoding stored value. In othe case create TBS schema via assembling from TBS parts.
+ * @returns {Object} asn1js object
+ */
+ toSchema(encodeFlag = false)
+ {
+ //region Decode stored TBS value
+ let tbsSchema;
+
+ if(encodeFlag === false)
+ {
+ if(this.tbs.length === 0) // No stored certificate TBS part
+ return ResponseData.schema();
+
+ tbsSchema = asn1js.fromBER(this.tbs).result;
+ }
+ //endregion
+ //region Create TBS schema via assembling from TBS parts
+ else
+ {
+ const outputArray = [];
+
+ if("version" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [new asn1js.Integer({ value: this.version })]
+ }));
+ }
+
+ if(this.responderID instanceof RelativeDistinguishedNames)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [this.responderID.toSchema()]
+ }));
+ }
+ else
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ value: [this.responderID]
+ }));
+ }
+
+ outputArray.push(new asn1js.GeneralizedTime({ valueDate: this.producedAt }));
+
+ outputArray.push(new asn1js.Sequence({
+ value: Array.from(this.responses, element => element.toSchema())
+ }));
+
+ if("responseExtensions" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [new asn1js.Sequence({
+ value: Array.from(this.responseExtensions, element => element.toSchema())
+ })]
+ }));
+ }
+
+ tbsSchema = new asn1js.Sequence({
+ value: outputArray
+ });
+ }
+ //endregion
+
+ //region Construct and return new ASN.1 schema for this object
+ return tbsSchema;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const _object = {};
+
+ if("version" in this)
+ _object.version = this.version;
+
+ if("responderID" in this)
+ _object.responderID = this.responderID;
+
+ if("producedAt" in this)
+ _object.producedAt = this.producedAt;
+
+ if("responses" in this)
+ _object.responses = Array.from(this.responses, element => element.toJSON());
+
+ if("responseExtensions" in this)
+ _object.responseExtensions = Array.from(this.responseExtensions, element => element.toJSON());
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/RevocationInfoChoices.js b/pki.js/RevocationInfoChoices.js
new file mode 100644
index 0000000..e4ee92f
--- /dev/null
+++ b/pki.js/RevocationInfoChoices.js
@@ -0,0 +1,184 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import CertificateRevocationList from "./CertificateRevocationList.js";
+import OtherRevocationInfoFormat from "./OtherRevocationInfoFormat.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class RevocationInfoChoices
+{
+ //**********************************************************************************
+ /**
+ * Constructor for RevocationInfoChoices 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 {Array.}
+ * @desc crls
+ */
+ this.crls = getParametersValue(parameters, "crls", RevocationInfoChoices.defaultValues("crls"));
+ /**
+ * @type {Array.}
+ * @desc otherRevocationInfos
+ */
+ this.otherRevocationInfos = getParametersValue(parameters, "otherRevocationInfos", RevocationInfoChoices.defaultValues("otherRevocationInfos"));
+ //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 "crls":
+ return [];
+ case "otherRevocationInfos":
+ return [];
+ default:
+ throw new Error(`Invalid member name for RevocationInfoChoices class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * RevocationInfoChoices ::= SET OF RevocationInfoChoice
+ *
+ * RevocationInfoChoice ::= CHOICE {
+ * crl CertificateList,
+ * other [1] IMPLICIT OtherRevocationInfoFormat }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [crls]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Set({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.crls || ""),
+ value: new asn1js.Choice({
+ value: [
+ CertificateRevocationList.schema(),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [
+ new asn1js.ObjectIdentifier(),
+ new asn1js.Any()
+ ]
+ })
+ ]
+ })
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "crls"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ RevocationInfoChoices.schema({
+ names: {
+ crls: "crls"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RevocationInfoChoices");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ for(const element of asn1.result.crls)
+ {
+ if(element.idBlock.tagClass === 1)
+ this.crls.push(new CertificateRevocationList({ schema: element }));
+ else
+ this.otherRevocationInfos.push(new OtherRevocationInfoFormat({ schema: element }));
+ }
+
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output set
+ const outputArray = [];
+
+ outputArray.push(...Array.from(this.crls, element => element.toSchema()));
+
+ outputArray.push(...Array.from(this.otherRevocationInfos, element =>
+ {
+ const schema = element.toSchema();
+
+ schema.idBlock.tagClass = 3;
+ schema.idBlock.tagNumber = 1;
+
+ return schema;
+ }));
+ //endregion
+
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Set({
+ value: outputArray
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ crls: Array.from(this.crls, element => element.toJSON()),
+ otherRevocationInfos: Array.from(this.otherRevocationInfos, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/RevokedCertificate.js b/pki.js/RevokedCertificate.js
new file mode 100644
index 0000000..b1badd0
--- /dev/null
+++ b/pki.js/RevokedCertificate.js
@@ -0,0 +1,184 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import Time from "./Time.js";
+import Extensions from "./Extensions.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class RevokedCertificate
+{
+ //**********************************************************************************
+ /**
+ * Constructor for RevokedCertificate 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 {Integer}
+ * @desc userCertificate
+ */
+ this.userCertificate = getParametersValue(parameters, "userCertificate", RevokedCertificate.defaultValues("userCertificate"));
+ /**
+ * @type {Time}
+ * @desc revocationDate
+ */
+ this.revocationDate = getParametersValue(parameters, "revocationDate", RevokedCertificate.defaultValues("revocationDate"));
+
+ if("crlEntryExtensions" in parameters)
+ /**
+ * @type {Extensions}
+ * @desc crlEntryExtensions
+ */
+ this.crlEntryExtensions = getParametersValue(parameters, "crlEntryExtensions", RevokedCertificate.defaultValues("crlEntryExtensions"));
+ //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 "userCertificate":
+ return new asn1js.Integer();
+ case "revocationDate":
+ return new Time();
+ case "crlEntryExtensions":
+ return new Extensions();
+ default:
+ throw new Error(`Invalid member name for RevokedCertificate class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * revokedCertificates SEQUENCE OF SEQUENCE {
+ * userCertificate CertificateSerialNumber,
+ * revocationDate Time,
+ * crlEntryExtensions Extensions OPTIONAL
+ * -- if present, version MUST be v2
+ * } OPTIONAL,
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [userCertificate]
+ * @property {string} [revocationDate]
+ * @property {string} [crlEntryExtensions]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Integer({ name: (names.userCertificate || "userCertificate") }),
+ Time.schema({
+ names: {
+ utcTimeName: (names.revocationDate || "revocationDate"),
+ generalTimeName: (names.revocationDate || "revocationDate")
+ }
+ }),
+ Extensions.schema({
+ names: {
+ blockName: (names.crlEntryExtensions || "crlEntryExtensions")
+ }
+ }, true)
+ ]
+ });
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "userCertificate",
+ "revocationDate",
+ "crlEntryExtensions"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ RevokedCertificate.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for RevokedCertificate");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.userCertificate = asn1.result.userCertificate;
+ this.revocationDate = new Time({ schema: asn1.result.revocationDate });
+
+ if("crlEntryExtensions" in asn1.result)
+ this.crlEntryExtensions = new Extensions({ schema: asn1.result.crlEntryExtensions });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [
+ this.userCertificate,
+ this.revocationDate.toSchema()
+ ];
+
+ if("crlEntryExtensions" in this)
+ outputArray.push(this.crlEntryExtensions.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 = {
+ userCertificate: this.userCertificate.toJSON(),
+ revocationDate: this.revocationDate.toJSON
+ };
+
+ if("crlEntryExtensions" in this)
+ object.crlEntryExtensions = this.crlEntryExtensions.toJSON();
+
+ return object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/SafeBag.js b/pki.js/SafeBag.js
new file mode 100644
index 0000000..550c1b1
--- /dev/null
+++ b/pki.js/SafeBag.js
@@ -0,0 +1,250 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import Attribute from "./Attribute.js";
+import PrivateKeyInfo from "./PrivateKeyInfo.js";
+import PKCS8ShroudedKeyBag from "./PKCS8ShroudedKeyBag.js";
+import CertBag from "./CertBag.js";
+import CRLBag from "./CRLBag.js";
+import SecretBag from "./SecretBag.js";
+import SafeContents from "./SafeContents.js";
+//**************************************************************************************
+/**
+ * Class from RFC7292
+ */
+export default class SafeBag
+{
+ //**********************************************************************************
+ /**
+ * Constructor for SafeBag 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 {string}
+ * @desc bagId
+ */
+ this.bagId = getParametersValue(parameters, "bagId", SafeBag.defaultValues("bagId"));
+ /**
+ * @type {*}
+ * @desc bagValue
+ */
+ this.bagValue = getParametersValue(parameters, "bagValue", SafeBag.defaultValues("bagValue"));
+
+ if("bagAttributes" in parameters)
+ /**
+ * @type {Array.}
+ * @desc bagAttributes
+ */
+ this.bagAttributes = getParametersValue(parameters, "bagAttributes", SafeBag.defaultValues("bagAttributes"));
+ //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 "bagId":
+ return "";
+ case "bagValue":
+ return (new asn1js.Any());
+ case "bagAttributes":
+ return [];
+ default:
+ throw new Error(`Invalid member name for SafeBag 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 "bagId":
+ return (memberValue === "");
+ case "bagValue":
+ return (memberValue instanceof asn1js.Any);
+ case "bagAttributes":
+ return (memberValue.length === 0);
+ default:
+ throw new Error(`Invalid member name for SafeBag class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * SafeBag ::= SEQUENCE {
+ * bagId BAG-TYPE.&id ({PKCS12BagSet}),
+ * bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}),
+ * bagAttributes SET OF PKCS12Attribute OPTIONAL
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [bagId]
+ * @property {string} [bagValue]
+ * @property {string} [bagAttributes]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.bagId || "bagId") }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [new asn1js.Any({ name: (names.bagValue || "bagValue") })] // EXPLICIT ANY value
+ }),
+ new asn1js.Set({
+ optional: true,
+ value: [
+ new asn1js.Repeated({
+ name: (names.bagAttributes || "bagAttributes"),
+ value: Attribute.schema()
+ })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "bagId",
+ "bagValue",
+ "bagAttributes"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ SafeBag.schema({
+ names: {
+ bagId: "bagId",
+ bagValue: "bagValue",
+ bagAttributes: "bagAttributes"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for SafeBag");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.bagId = asn1.result.bagId.valueBlock.toString();
+
+ switch(this.bagId)
+ {
+ case "1.2.840.113549.1.12.10.1.1": // keyBag
+ this.bagValue = new PrivateKeyInfo({ schema: asn1.result.bagValue });
+ break;
+ case "1.2.840.113549.1.12.10.1.2": // pkcs8ShroudedKeyBag
+ this.bagValue = new PKCS8ShroudedKeyBag({ schema: asn1.result.bagValue });
+ break;
+ case "1.2.840.113549.1.12.10.1.3": // certBag
+ this.bagValue = new CertBag({ schema: asn1.result.bagValue });
+ break;
+ case "1.2.840.113549.1.12.10.1.4": // crlBag
+ this.bagValue = new CRLBag({ schema: asn1.result.bagValue });
+ break;
+ case "1.2.840.113549.1.12.10.1.5": // secretBag
+ this.bagValue = new SecretBag({ schema: asn1.result.bagValue });
+ break;
+ case "1.2.840.113549.1.12.10.1.6": // safeContentsBag
+ this.bagValue = new SafeContents({ schema: asn1.result.bagValue });
+ break;
+ default:
+ throw new Error(`Invalid "bagId" for SafeBag: ${this.bagId}`);
+ }
+
+ if("bagAttributes" in asn1.result)
+ this.bagAttributes = Array.from(asn1.result.bagAttributes, element => new Attribute({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ const outputArray = [
+ new asn1js.ObjectIdentifier({ value: this.bagId }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [this.bagValue.toSchema()]
+ })
+ ];
+
+ if("bagAttributes" in this)
+ {
+ outputArray.push(new asn1js.Set({
+ value: Array.from(this.bagAttributes, element => element.toSchema())
+ }));
+ }
+
+ return (new asn1js.Sequence({
+ value: outputArray
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const output = {
+ bagId: this.bagId,
+ bagValue: this.bagValue.toJSON()
+ };
+
+ if("bagAttributes" in this)
+ output.bagAttributes = Array.from(this.bagAttributes, element => element.toJSON());
+
+ return output;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/SafeContents.js b/pki.js/SafeContents.js
new file mode 100644
index 0000000..875392d
--- /dev/null
+++ b/pki.js/SafeContents.js
@@ -0,0 +1,150 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import SafeBag from "./SafeBag.js";
+//**************************************************************************************
+/**
+ * Class from RFC7292
+ */
+export default class SafeContents
+{
+ //**********************************************************************************
+ /**
+ * Constructor for SafeContents 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 {Array.}
+ * @desc safeBags
+ */
+ this.safeBags = getParametersValue(parameters, "safeBags", SafeContents.defaultValues("safeBags"));
+ //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 "safeBags":
+ return [];
+ default:
+ throw new Error(`Invalid member name for SafeContents 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 "safeBags":
+ return (memberValue.length === 0);
+ default:
+ throw new Error(`Invalid member name for SafeContents class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * SafeContents ::= SEQUENCE OF SafeBag
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [safeBags]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.safeBags || ""),
+ value: SafeBag.schema()
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "safeBags"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ SafeContents.schema({
+ names: {
+ safeBags: "safeBags"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for SafeContents");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.safeBags = Array.from(asn1.result.safeBags, element => new SafeBag({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: Array.from(this.safeBags, element => element.toSchema())
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ safeBags: Array.from(this.safeBags, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/SecretBag.js b/pki.js/SecretBag.js
new file mode 100644
index 0000000..a2e6577
--- /dev/null
+++ b/pki.js/SecretBag.js
@@ -0,0 +1,179 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC7292
+ */
+export default class SecretBag
+{
+ //**********************************************************************************
+ /**
+ * Constructor for SecretBag 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 {string}
+ * @desc secretTypeId
+ */
+ this.secretTypeId = getParametersValue(parameters, "secretTypeId", SecretBag.defaultValues("secretTypeId"));
+ /**
+ * @type {*}
+ * @desc secretValue
+ */
+ this.secretValue = getParametersValue(parameters, "secretValue", SecretBag.defaultValues("secretValue"));
+ //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 "secretTypeId":
+ return "";
+ case "secretValue":
+ return (new asn1js.Any());
+ default:
+ throw new Error(`Invalid member name for SecretBag 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 "secretTypeId":
+ return (memberValue === "");
+ case "secretValue":
+ return (memberValue instanceof asn1js.Any);
+ default:
+ throw new Error(`Invalid member name for SecretBag class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * SecretBag ::= SEQUENCE {
+ * secretTypeId BAG-TYPE.&id ({SecretTypes}),
+ * secretValue [0] EXPLICIT BAG-TYPE.&Type ({SecretTypes}{@secretTypeId})
+ * }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [id]
+ * @property {string} [value]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.ObjectIdentifier({ name: (names.id || "id") }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [new asn1js.Any({ name: (names.value || "value") })] // EXPLICIT ANY value
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "secretTypeId",
+ "secretValue"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ SecretBag.schema({
+ names: {
+ id: "secretTypeId",
+ value: "secretValue"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for SecretBag");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.secretTypeId = asn1.result.secretTypeId.valueBlock.toString();
+ this.secretValue = asn1.result.secretValue;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: [
+ new asn1js.ObjectIdentifier({ value: this.secretTypeId }),
+ new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [this.secretValue.toSchema()]
+ })
+ ]
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ secretTypeId: this.secretTypeId,
+ secretValue: this.secretValue.toJSON()
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/Signature.js b/pki.js/Signature.js
new file mode 100644
index 0000000..aa11851
--- /dev/null
+++ b/pki.js/Signature.js
@@ -0,0 +1,230 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import Certificate from "./Certificate.js";
+//**************************************************************************************
+/**
+ * Class from RFC6960
+ */
+export default class Signature
+{
+ //**********************************************************************************
+ /**
+ * Constructor for Signature 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 {AlgorithmIdentifier}
+ * @desc signatureAlgorithm
+ */
+ this.signatureAlgorithm = getParametersValue(parameters, "signatureAlgorithm", Signature.defaultValues("signatureAlgorithm"));
+ /**
+ * @type {BitString}
+ * @desc signature
+ */
+ this.signature = getParametersValue(parameters, "signature", Signature.defaultValues("signature"));
+
+ if("certs" in parameters)
+ /**
+ * @type {Array.}
+ * @desc certs
+ */
+ this.certs = getParametersValue(parameters, "certs", Signature.defaultValues("certs"));
+ //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 "signatureAlgorithm":
+ return new AlgorithmIdentifier();
+ case "signature":
+ return new asn1js.BitString();
+ case "certs":
+ return [];
+ default:
+ throw new Error(`Invalid member name for Signature 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 "signatureAlgorithm":
+ return ((memberValue.algorithmId === "") && (("algorithmParams" in memberValue) === false));
+ case "signature":
+ return (memberValue.isEqual(Signature.defaultValues(memberName)));
+ case "certs":
+ return (memberValue.length === 0);
+ default:
+ throw new Error(`Invalid member name for Signature class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * Signature ::= SEQUENCE {
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signature BIT STRING,
+ * certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [signatureAlgorithm]
+ * @property {string} [signature]
+ * @property {string} [certs]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ AlgorithmIdentifier.schema(names.signatureAlgorithm || {}),
+ new asn1js.BitString({ name: (names.signature || "") }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Sequence({
+ value: [new asn1js.Repeated({
+ name: (names.certs || ""),
+ value: Certificate.schema(names.certs || {})
+ })]
+ })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "signatureAlgorithm",
+ "signature",
+ "certs"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ Signature.schema({
+ names: {
+ signatureAlgorithm: {
+ names: {
+ blockName: "signatureAlgorithm"
+ }
+ },
+ signature: "signature",
+ certs: "certs"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for Signature");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm });
+ this.signature = asn1.result.signature;
+
+ if("certs" in asn1.result)
+ this.certs = Array.from(asn1.result.certs, element => new Certificate({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array of output sequence
+ const outputArray = [];
+
+ outputArray.push(this.signatureAlgorithm.toSchema());
+ outputArray.push(this.signature);
+
+ if("certs" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [
+ new asn1js.Sequence({
+ value: Array.from(this.certs, 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 = {
+ signatureAlgorithm: this.signatureAlgorithm.toJSON(),
+ signature: this.signature.toJSON()
+ };
+
+ if("certs" in this)
+ _object.certs = Array.from(this.certs, element => element.toJSON());
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/SignedAndUnsignedAttributes.js b/pki.js/SignedAndUnsignedAttributes.js
new file mode 100644
index 0000000..5306eaa
--- /dev/null
+++ b/pki.js/SignedAndUnsignedAttributes.js
@@ -0,0 +1,205 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import Attribute from "./Attribute.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class SignedAndUnsignedAttributes
+{
+ //**********************************************************************************
+ /**
+ * Constructor for SignedAndUnsignedAttributes 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 {number}
+ * @desc type
+ */
+ this.type = getParametersValue(parameters, "type", SignedAndUnsignedAttributes.defaultValues("type"));
+ /**
+ * @type {Array}
+ * @desc attributes
+ */
+ this.attributes = getParametersValue(parameters, "attributes", SignedAndUnsignedAttributes.defaultValues("attributes"));
+ /**
+ * @type {ArrayBuffer}
+ * @desc encodedValue Need to have it in order to successfully process with signature verification
+ */
+ this.encodedValue = getParametersValue(parameters, "encodedValue", SignedAndUnsignedAttributes.defaultValues("encodedValue"));
+ //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 "type":
+ return (-1);
+ case "attributes":
+ return [];
+ case "encodedValue":
+ return new ArrayBuffer(0);
+ default:
+ throw new Error(`Invalid member name for SignedAndUnsignedAttributes 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 "type":
+ return (memberValue === SignedAndUnsignedAttributes.defaultValues("type"));
+ case "attributes":
+ return (memberValue.length === 0);
+ case "encodedValue":
+ return (memberValue.byteLength === 0);
+ default:
+ throw new Error(`Invalid member name for SignedAndUnsignedAttributes class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * SignedAttributes ::= SET SIZE (1..MAX) OF Attribute
+ *
+ * UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {number} [tagNumber]
+ * @property {string} [attributes]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Constructed({
+ name: (names.blockName || ""),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: names.tagNumber // "SignedAttributes" = 0, "UnsignedAttributes" = 1
+ },
+ value: [
+ new asn1js.Repeated({
+ name: (names.attributes || ""),
+ value: Attribute.schema()
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "attributes"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ SignedAndUnsignedAttributes.schema({
+ names: {
+ tagNumber: this.type,
+ attributes: "attributes"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for SignedAndUnsignedAttributes");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.type = asn1.result.idBlock.tagNumber;
+ this.encodedValue = asn1.result.valueBeforeDecode;
+
+ //region Change type from "[0]" to "SET" accordingly to standard
+ const encodedView = new Uint8Array(this.encodedValue);
+ encodedView[0] = 0x31;
+ //endregion
+
+ if(("attributes" in asn1.result) === false)
+ {
+ if(this.type === 0)
+ throw new Error("Wrong structure of SignedUnsignedAttributes");
+ else
+ return; // Not so important in case of "UnsignedAttributes"
+ }
+
+ this.attributes = Array.from(asn1.result.attributes, element => new Attribute({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ if(SignedAndUnsignedAttributes.compareWithDefault("type", this.type) || SignedAndUnsignedAttributes.compareWithDefault("attributes", this.attributes))
+ throw new Error("Incorrectly initialized \"SignedAndUnsignedAttributes\" class");
+
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: this.type // "SignedAttributes" = 0, "UnsignedAttributes" = 1
+ },
+ value: Array.from(this.attributes, element => element.toSchema())
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ if(SignedAndUnsignedAttributes.compareWithDefault("type", this.type) || SignedAndUnsignedAttributes.compareWithDefault("attributes", this.attributes))
+ throw new Error("Incorrectly initialized \"SignedAndUnsignedAttributes\" class");
+
+ return {
+ type: this.type,
+ attributes: Array.from(this.attributes, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/SignedCertificateTimestampList.js b/pki.js/SignedCertificateTimestampList.js
new file mode 100644
index 0000000..0dfab64
--- /dev/null
+++ b/pki.js/SignedCertificateTimestampList.js
@@ -0,0 +1,634 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, utilFromBase, utilToBase, bufferToHexCodes, toBase64, fromBase64, arrayBufferToString, stringToArrayBuffer } from "pvutils";
+import { ByteStream, SeqStream } from "bytestreamjs";
+import { getCrypto, getEngine } from "./common.js";
+import PublicKeyInfo from "./PublicKeyInfo.js";
+//**************************************************************************************
+export class SignedCertificateTimestamp
+{
+ //**********************************************************************************
+ /**
+ * Constructor for SignedCertificateTimestamp 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 {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", SignedCertificateTimestamp.defaultValues("version"));
+ /**
+ * @type {ArrayBuffer}
+ * @desc logID
+ */
+ this.logID = getParametersValue(parameters, "logID", SignedCertificateTimestamp.defaultValues("logID"));
+ /**
+ * @type {Date}
+ * @desc timestamp
+ */
+ this.timestamp = getParametersValue(parameters, "timestamp", SignedCertificateTimestamp.defaultValues("timestamp"));
+ /**
+ * @type {ArrayBuffer}
+ * @desc extensions
+ */
+ this.extensions = getParametersValue(parameters, "extensions", SignedCertificateTimestamp.defaultValues("extensions"));
+ /**
+ * @type {string}
+ * @desc hashAlgorithm
+ */
+ this.hashAlgorithm = getParametersValue(parameters, "hashAlgorithm", SignedCertificateTimestamp.defaultValues("hashAlgorithm"));
+ /**
+ * @type {string}
+ * @desc signatureAlgorithm
+ */
+ this.signatureAlgorithm = getParametersValue(parameters, "signatureAlgorithm", SignedCertificateTimestamp.defaultValues("signatureAlgorithm"));
+ /**
+ * @type {Object}
+ * @desc signature
+ */
+ this.signature = getParametersValue(parameters, "signature", SignedCertificateTimestamp.defaultValues("signature"));
+ //endregion
+
+ //region If input argument array contains "schema" for this object
+ if("schema" in parameters)
+ this.fromSchema(parameters.schema);
+ //endregion
+
+ //region If input argument array contains "stream"
+ if("stream" in parameters)
+ this.fromStream(parameters.stream);
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Return default values for all class members
+ * @param {string} memberName String name for a class member
+ */
+ static defaultValues(memberName)
+ {
+ switch(memberName)
+ {
+ case "version":
+ return 0;
+ case "logID":
+ case "extensions":
+ return new ArrayBuffer(0);
+ case "timestamp":
+ return new Date(0);
+ case "hashAlgorithm":
+ case "signatureAlgorithm":
+ return "";
+ case "signature":
+ return new asn1js.Any();
+ default:
+ throw new Error(`Invalid member name for SignedCertificateTimestamp class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ if((schema instanceof asn1js.RawData) === false)
+ throw new Error("Object's schema was not verified against input data for SignedCertificateTimestamp");
+
+ const seqStream = new SeqStream({
+ stream: new ByteStream({
+ buffer: schema.data
+ })
+ });
+
+ this.fromStream(seqStream);
+ }
+ //**********************************************************************************
+ /**
+ * Convert SeqStream data into current class
+ * @param {!SeqStream} stream
+ */
+ fromStream(stream)
+ {
+ const blockLength = stream.getUint16();
+
+ this.version = (stream.getBlock(1))[0];
+
+ if(this.version === 0)
+ {
+ this.logID = (new Uint8Array(stream.getBlock(32))).buffer.slice(0);
+ this.timestamp = new Date(utilFromBase(new Uint8Array(stream.getBlock(8)), 8));
+
+ //region Extensions
+ const extensionsLength = stream.getUint16();
+ this.extensions = (new Uint8Array(stream.getBlock(extensionsLength))).buffer.slice(0);
+ //endregion
+
+ //region Hash algorithm
+ switch((stream.getBlock(1))[0])
+ {
+ case 0:
+ this.hashAlgorithm = "none";
+ break;
+ case 1:
+ this.hashAlgorithm = "md5";
+ break;
+ case 2:
+ this.hashAlgorithm = "sha1";
+ break;
+ case 3:
+ this.hashAlgorithm = "sha224";
+ break;
+ case 4:
+ this.hashAlgorithm = "sha256";
+ break;
+ case 5:
+ this.hashAlgorithm = "sha384";
+ break;
+ case 6:
+ this.hashAlgorithm = "sha512";
+ break;
+ default:
+ throw new Error("Object's stream was not correct for SignedCertificateTimestamp");
+ }
+ //endregion
+
+ //region Signature algorithm
+ switch((stream.getBlock(1))[0])
+ {
+ case 0:
+ this.signatureAlgorithm = "anonymous";
+ break;
+ case 1:
+ this.signatureAlgorithm = "rsa";
+ break;
+ case 2:
+ this.signatureAlgorithm = "dsa";
+ break;
+ case 3:
+ this.signatureAlgorithm = "ecdsa";
+ break;
+ default:
+ throw new Error("Object's stream was not correct for SignedCertificateTimestamp");
+ }
+ //endregion
+
+ //region Signature
+ const signatureLength = stream.getUint16();
+ const signatureData = (new Uint8Array(stream.getBlock(signatureLength))).buffer.slice(0);
+
+ const asn1 = asn1js.fromBER(signatureData);
+ if(asn1.offset === (-1))
+ throw new Error("Object's stream was not correct for SignedCertificateTimestamp");
+
+ this.signature = asn1.result;
+ //endregion
+
+ if(blockLength !== (47 + extensionsLength + signatureLength))
+ throw new Error("Object's stream was not correct for SignedCertificateTimestamp");
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ const stream = this.toStream();
+
+ return new asn1js.RawData({ data: stream.stream.buffer });
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to SeqStream data
+ * @returns {SeqStream} SeqStream object
+ */
+ toStream()
+ {
+ const stream = new SeqStream();
+
+ stream.appendUint16(47 + this.extensions.byteLength + this.signature.valueBeforeDecode.byteLength);
+ stream.appendChar(this.version);
+ stream.appendView(new Uint8Array(this.logID));
+
+ const timeBuffer = new ArrayBuffer(8);
+ const timeView = new Uint8Array(timeBuffer);
+
+ const baseArray = utilToBase(this.timestamp.valueOf(), 8);
+ timeView.set(new Uint8Array(baseArray), 8 - baseArray.byteLength);
+
+ stream.appendView(timeView);
+ stream.appendUint16(this.extensions.byteLength);
+
+ if(this.extensions.byteLength)
+ stream.appendView(new Uint8Array(this.extensions));
+
+ let _hashAlgorithm;
+
+ switch(this.hashAlgorithm.toLowerCase())
+ {
+ case "none":
+ _hashAlgorithm = 0;
+ break;
+ case "md5":
+ _hashAlgorithm = 1;
+ break;
+ case "sha1":
+ _hashAlgorithm = 2;
+ break;
+ case "sha224":
+ _hashAlgorithm = 3;
+ break;
+ case "sha256":
+ _hashAlgorithm = 4;
+ break;
+ case "sha384":
+ _hashAlgorithm = 5;
+ break;
+ case "sha512":
+ _hashAlgorithm = 6;
+ break;
+ default:
+ throw new Error(`Incorrect data for hashAlgorithm: ${this.hashAlgorithm}`);
+ }
+
+ stream.appendChar(_hashAlgorithm);
+
+ let _signatureAlgorithm;
+
+ switch(this.signatureAlgorithm.toLowerCase())
+ {
+ case "anonymous":
+ _signatureAlgorithm = 0;
+ break;
+ case "rsa":
+ _signatureAlgorithm = 1;
+ break;
+ case "dsa":
+ _signatureAlgorithm = 2;
+ break;
+ case "ecdsa":
+ _signatureAlgorithm = 3;
+ break;
+ default:
+ throw new Error(`Incorrect data for signatureAlgorithm: ${this.signatureAlgorithm}`);
+ }
+
+ stream.appendChar(_signatureAlgorithm);
+
+ const _signature = this.signature.toBER(false);
+
+ stream.appendUint16(_signature.byteLength);
+ stream.appendView(new Uint8Array(_signature));
+
+ return stream;
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ version: this.version,
+ logID: bufferToHexCodes(this.logID),
+ timestamp: this.timestamp,
+ extensions: bufferToHexCodes(this.extensions),
+ hashAlgorithm: this.hashAlgorithm,
+ signatureAlgorithm: this.signatureAlgorithm,
+ signature: this.signature.toJSON()
+ };
+ }
+ //**********************************************************************************
+ /**
+ * Verify SignedCertificateTimestamp for specific input data
+ * @param {Object[]} logs Array of objects with information about each CT Log (like here: https://ct.grahamedgecombe.com/logs.json)
+ * @param {String} logs.log_id Identifier of the CT Log encoded in BASE-64 format
+ * @param {String} logs.key Public key of the CT Log encoded in BASE-64 format
+ * @param {ArrayBuffer} data Data to verify signature against. Could be encoded Certificate or encoded PreCert
+ * @param {Number} [dataType=0] Type = 0 (data is encoded Certificate), type = 1 (data is encoded PreCert)
+ * @return {Promise}
+ */
+ async verify(logs, data, dataType = 0)
+ {
+ //region Initial variables
+ let logId = toBase64(arrayBufferToString(this.logID));
+
+ let publicKeyBase64 = null;
+ let publicKeyInfo;
+
+ let stream = new SeqStream();
+ //endregion
+
+ //region Found and init public key
+ for(const log of logs)
+ {
+ if(log.log_id === logId)
+ {
+ publicKeyBase64 = log.key;
+ break;
+ }
+ }
+
+ if(publicKeyBase64 === null)
+ throw new Error(`Public key not found for CT with logId: ${logId}`);
+
+ const asn1 = asn1js.fromBER(stringToArrayBuffer(fromBase64(publicKeyBase64)));
+ if(asn1.offset === (-1))
+ throw new Error(`Incorrect key value for CT Log with logId: ${logId}`);
+
+ publicKeyInfo = new PublicKeyInfo({ schema: asn1.result });
+ //endregion
+
+ //region Initialize signed data block
+ stream.appendChar(0x00); // sct_version
+ stream.appendChar(0x00); // signature_type = certificate_timestamp
+
+ const timeBuffer = new ArrayBuffer(8);
+ const timeView = new Uint8Array(timeBuffer);
+
+ const baseArray = utilToBase(this.timestamp.valueOf(), 8);
+ timeView.set(new Uint8Array(baseArray), 8 - baseArray.byteLength);
+
+ stream.appendView(timeView);
+
+ stream.appendUint16(dataType);
+
+ if(dataType === 0)
+ stream.appendUint24(data.byteLength);
+
+ stream.appendView(new Uint8Array(data));
+
+ stream.appendUint16(this.extensions.byteLength);
+
+ if(this.extensions.byteLength !== 0)
+ stream.appendView(new Uint8Array(this.extensions));
+ //endregion
+
+ //region Perform verification
+ return getEngine().subtle.verifyWithPublicKey(
+ stream._stream._buffer.slice(0, stream._length),
+ { valueBlock: { valueHex: this.signature.toBER(false) } },
+ publicKeyInfo,
+ { algorithmId: "" },
+ "SHA-256"
+ );
+ //endregion
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
+/**
+ * Class from RFC6962
+ */
+export default class SignedCertificateTimestampList
+{
+ //**********************************************************************************
+ /**
+ * Constructor for SignedCertificateTimestampList 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 {Array.}
+ * @desc timestamps
+ */
+ this.timestamps = getParametersValue(parameters, "timestamps", SignedCertificateTimestampList.defaultValues("timestamps"));
+ //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 "timestamps":
+ return [];
+ default:
+ throw new Error(`Invalid member name for SignedCertificateTimestampList 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 "timestamps":
+ return (memberValue.length === 0);
+ default:
+ throw new Error(`Invalid member name for SignedCertificateTimestampList class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * SignedCertificateTimestampList ::= OCTET STRING
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [optional]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ if(("optional" in names) === false)
+ names.optional = false;
+
+ return (new asn1js.OctetString({
+ name: (names.blockName || "SignedCertificateTimestampList"),
+ optional: names.optional
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Check the schema is valid
+ if((schema instanceof asn1js.OctetString) === false)
+ throw new Error("Object's schema was not verified against input data for SignedCertificateTimestampList");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ const seqStream = new SeqStream({
+ stream: new ByteStream({
+ buffer: schema.valueBlock.valueHex
+ })
+ });
+
+ let dataLength = seqStream.getUint16();
+ if(dataLength !== seqStream.length)
+ throw new Error("Object's schema was not verified against input data for SignedCertificateTimestampList");
+
+ while(seqStream.length)
+ this.timestamps.push(new SignedCertificateTimestamp({ stream: seqStream }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Initial variables
+ const stream = new SeqStream();
+
+ let overallLength = 0;
+
+ const timestampsData = [];
+ //endregion
+
+ //region Get overall length
+ for(const timestamp of this.timestamps)
+ {
+ const timestampStream = timestamp.toStream();
+ timestampsData.push(timestampStream);
+ overallLength += timestampStream.stream.buffer.byteLength;
+ }
+ //endregion
+
+ stream.appendUint16(overallLength);
+
+ //region Set data from all timestamps
+ for(const timestamp of timestampsData)
+ stream.appendView(timestamp.stream.view);
+ //endregion
+
+ return new asn1js.OctetString({ valueHex: stream.stream.buffer.slice(0) });
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ timestamps: Array.from(this.timestamps, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
+/**
+ * Verify SignedCertificateTimestamp for specific certificate content
+ * @param {Certificate} certificate Certificate for which verification would be performed
+ * @param {Certificate} issuerCertificate Certificate of the issuer of target certificate
+ * @param {Object[]} logs Array of objects with information about each CT Log (like here: https://ct.grahamedgecombe.com/logs.json)
+ * @param {String} logs.log_id Identifier of the CT Log encoded in BASE-64 format
+ * @param {String} logs.key Public key of the CT Log encoded in BASE-64 format
+ * @param {Number} [index=-1] Index of SignedCertificateTimestamp inside SignedCertificateTimestampList (for -1 would verify all)
+ * @return {Array} Array of verification results
+ */
+export async function verifySCTsForCertificate(certificate, issuerCertificate, logs, index = (-1))
+{
+ //region Initial variables
+ let parsedValue = null;
+ let tbs;
+ let issuerId;
+
+ const stream = new SeqStream();
+
+ let preCert;
+ //endregion
+
+ //region Get a "crypto" extension
+ const crypto = getCrypto();
+ if(typeof crypto === "undefined")
+ return Promise.reject("Unable to create WebCrypto object");
+ //endregion
+
+ //region Remove certificate extension
+ for(let i = 0; i < certificate.extensions.length; i++)
+ {
+ switch(certificate.extensions[i].extnID)
+ {
+ case "1.3.6.1.4.1.11129.2.4.2":
+ {
+ parsedValue = certificate.extensions[i].parsedValue;
+
+ if(parsedValue.timestamps.length === 0)
+ throw new Error("Nothing to verify in the certificate");
+
+ certificate.extensions.splice(i, 1);
+ }
+ break;
+ default:
+ }
+ }
+ //endregion
+
+ //region Check we do have what to verify
+ if(parsedValue === null)
+ throw new Error("No SignedCertificateTimestampList extension in the specified certificate");
+ //endregion
+
+ //region Prepare modifier TBS value
+ tbs = certificate.encodeTBS().toBER(false);
+ //endregion
+
+ //region Initialize "issuer_key_hash" value
+ issuerId = await crypto.digest({ name: "SHA-256" }, new Uint8Array(issuerCertificate.subjectPublicKeyInfo.toSchema().toBER(false)));
+ //endregion
+
+ //region Make final "PreCert" value
+ stream.appendView(new Uint8Array(issuerId));
+ stream.appendUint24(tbs.byteLength);
+ stream.appendView(new Uint8Array(tbs));
+
+ preCert = stream._stream._buffer.slice(0, stream._length);
+ //endregion
+
+ //region Call verification function for specified index
+ if(index === (-1))
+ {
+ const verifyArray = [];
+
+ for(const timestamp of parsedValue.timestamps)
+ {
+ const verifyResult = await timestamp.verify(logs, preCert, 1);
+ verifyArray.push(verifyResult);
+ }
+
+ return verifyArray;
+ }
+
+ if(index >= parsedValue.timestamps.length)
+ index = (parsedValue.timestamps.length - 1);
+
+ return [await parsedValue.timestamps[index].verify(logs, preCert, 1)];
+ //endregion
+}
+//**********************************************************************************
diff --git a/pki.js/SignedData.js b/pki.js/SignedData.js
new file mode 100644
index 0000000..9c243b7
--- /dev/null
+++ b/pki.js/SignedData.js
@@ -0,0 +1,1067 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, utilConcatBuf, isEqualBuffer, clearProps } from "pvutils";
+import { getCrypto, getEngine, getOIDByAlgorithm, getAlgorithmByOID } from "./common.js";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import EncapsulatedContentInfo from "./EncapsulatedContentInfo.js";
+import Certificate from "./Certificate.js";
+import CertificateRevocationList from "./CertificateRevocationList.js";
+import OtherRevocationInfoFormat from "./OtherRevocationInfoFormat.js";
+import SignerInfo from "./SignerInfo.js";
+import CertificateSet from "./CertificateSet.js";
+import RevocationInfoChoices from "./RevocationInfoChoices.js";
+import IssuerAndSerialNumber from "./IssuerAndSerialNumber.js";
+import TSTInfo from "./TSTInfo.js";
+import CertificateChainValidationEngine from "./CertificateChainValidationEngine.js";
+import BasicOCSPResponse from "./BasicOCSPResponse.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class SignedData
+{
+ //**********************************************************************************
+ /**
+ * Constructor for SignedData 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 {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", SignedData.defaultValues("version"));
+ /**
+ * @type {Array.}
+ * @desc digestAlgorithms
+ */
+ this.digestAlgorithms = getParametersValue(parameters, "digestAlgorithms", SignedData.defaultValues("digestAlgorithms"));
+ /**
+ * @type {EncapsulatedContentInfo}
+ * @desc encapContentInfo
+ */
+ this.encapContentInfo = getParametersValue(parameters, "encapContentInfo", SignedData.defaultValues("encapContentInfo"));
+
+ if("certificates" in parameters)
+ /**
+ * @type {Array.}
+ * @desc certificates
+ */
+ this.certificates = getParametersValue(parameters, "certificates", SignedData.defaultValues("certificates"));
+
+ if("crls" in parameters)
+ /**
+ * @type {Array.}
+ * @desc crls
+ */
+ this.crls = getParametersValue(parameters, "crls", SignedData.defaultValues("crls"));
+
+ if("ocsps" in parameters)
+ /**
+ * @type {Array.}
+ * @desc crls
+ */
+ this.ocsps = getParametersValue(parameters, "ocsps", SignedData.defaultValues("ocsps"));
+
+ /**
+ * @type {Array.}
+ * @desc signerInfos
+ */
+ this.signerInfos = getParametersValue(parameters, "signerInfos", SignedData.defaultValues("signerInfos"));
+ //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 "version":
+ return 0;
+ case "digestAlgorithms":
+ return [];
+ case "encapContentInfo":
+ return new EncapsulatedContentInfo();
+ case "certificates":
+ return [];
+ case "crls":
+ return [];
+ case "ocsps":
+ return [];
+ case "signerInfos":
+ return [];
+ default:
+ throw new Error(`Invalid member name for SignedData 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 "version":
+ return (memberValue === SignedData.defaultValues("version"));
+ case "encapContentInfo":
+ return new EncapsulatedContentInfo();
+ case "digestAlgorithms":
+ case "certificates":
+ case "crls":
+ case "ocsps":
+ case "signerInfos":
+ return (memberValue.length === 0);
+ default:
+ throw new Error(`Invalid member name for SignedData class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * SignedData ::= SEQUENCE {
+ * version CMSVersion,
+ * digestAlgorithms DigestAlgorithmIdentifiers,
+ * encapContentInfo EncapsulatedContentInfo,
+ * certificates [0] IMPLICIT CertificateSet OPTIONAL,
+ * crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
+ * signerInfos SignerInfos }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [optional]
+ * @property {string} [digestAlgorithms]
+ * @property {string} [encapContentInfo]
+ * @property {string} [certificates]
+ * @property {string} [crls]
+ * @property {string} [signerInfos]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ if(("optional" in names) === false)
+ names.optional = false;
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || "SignedData"),
+ optional: names.optional,
+ value: [
+ new asn1js.Integer({ name: (names.version || "SignedData.version") }),
+ new asn1js.Set({
+ value: [
+ new asn1js.Repeated({
+ name: (names.digestAlgorithms || "SignedData.digestAlgorithms"),
+ value: AlgorithmIdentifier.schema()
+ })
+ ]
+ }),
+ EncapsulatedContentInfo.schema(names.encapContentInfo || {
+ names: {
+ blockName: "SignedData.encapContentInfo"
+ }
+ }),
+ new asn1js.Constructed({
+ name: (names.certificates || "SignedData.certificates"),
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: CertificateSet.schema().valueBlock.value
+ }), // IMPLICIT CertificateSet
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: RevocationInfoChoices.schema(names.crls || {
+ names: {
+ crls: "SignedData.crls"
+ }
+ }).valueBlock.value
+ }), // IMPLICIT RevocationInfoChoices
+ new asn1js.Set({
+ value: [
+ new asn1js.Repeated({
+ name: (names.signerInfos || "SignedData.signerInfos"),
+ value: SignerInfo.schema()
+ })
+ ]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "SignedData.version",
+ "SignedData.digestAlgorithms",
+ "SignedData.encapContentInfo",
+ "SignedData.certificates",
+ "SignedData.crls",
+ "SignedData.signerInfos"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ SignedData.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for SignedData");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result["SignedData.version"].valueBlock.valueDec;
+
+ if("SignedData.digestAlgorithms" in asn1.result) // Could be empty SET of digest algorithms
+ this.digestAlgorithms = Array.from(asn1.result["SignedData.digestAlgorithms"], algorithm => new AlgorithmIdentifier({ schema: algorithm }));
+
+ this.encapContentInfo = new EncapsulatedContentInfo({ schema: asn1.result["SignedData.encapContentInfo"] });
+
+ if("SignedData.certificates" in asn1.result)
+ {
+ const certificateSet = new CertificateSet({
+ schema: new asn1js.Set({
+ value: asn1.result["SignedData.certificates"].valueBlock.value
+ })
+ });
+ this.certificates = certificateSet.certificates.slice(0); // Copy all just for making comfortable access
+ }
+
+ if("SignedData.crls" in asn1.result)
+ {
+ this.crls = Array.from(asn1.result["SignedData.crls"], crl =>
+ {
+ if(crl.idBlock.tagClass === 1)
+ return new CertificateRevocationList({ schema: crl });
+
+ //region Create SEQUENCE from [1]
+ crl.idBlock.tagClass = 1; // UNIVERSAL
+ crl.idBlock.tagNumber = 16; // SEQUENCE
+ //endregion
+
+ return new OtherRevocationInfoFormat({ schema: crl });
+ });
+ }
+
+ if("SignedData.signerInfos" in asn1.result) // Could be empty SET SignerInfos
+ this.signerInfos = Array.from(asn1.result["SignedData.signerInfos"], signerInfoSchema => new SignerInfo({ schema: signerInfoSchema }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema(encodeFlag = false)
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.Integer({ value: this.version }));
+
+ //region Create array of digest algorithms
+ outputArray.push(new asn1js.Set({
+ value: Array.from(this.digestAlgorithms, algorithm => algorithm.toSchema(encodeFlag))
+ }));
+ //endregion
+
+ outputArray.push(this.encapContentInfo.toSchema());
+
+ if("certificates" in this)
+ {
+ const certificateSet = new CertificateSet({ certificates: this.certificates });
+ const certificateSetSchema = certificateSet.toSchema();
+
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3,
+ tagNumber: 0
+ },
+ value: certificateSetSchema.valueBlock.value
+ }));
+ }
+
+ if("crls" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: Array.from(this.crls, crl =>
+ {
+ if(crl instanceof OtherRevocationInfoFormat)
+ {
+ const crlSchema = crl.toSchema(encodeFlag);
+
+ crlSchema.idBlock.tagClass = 3;
+ crlSchema.idBlock.tagNumber = 1;
+
+ return crlSchema;
+ }
+
+ return crl.toSchema(encodeFlag);
+ })
+ }));
+ }
+
+ //region Create array of signer infos
+ outputArray.push(new asn1js.Set({
+ value: Array.from(this.signerInfos, signerInfo => signerInfo.toSchema(encodeFlag))
+ }));
+ //endregion
+ //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 = {
+ version: this.version,
+ digestAlgorithms: Array.from(this.digestAlgorithms, algorithm => algorithm.toJSON()),
+ encapContentInfo: this.encapContentInfo.toJSON()
+ };
+
+ if("certificates" in this)
+ _object.certificates = Array.from(this.certificates, certificate => certificate.toJSON());
+
+ if("crls" in this)
+ _object.crls = Array.from(this.crls, crl => crl.toJSON());
+
+ _object.signerInfos = Array.from(this.signerInfos, signerInfo => signerInfo.toJSON());
+
+ return _object;
+ }
+ //**********************************************************************************
+ /**
+ * Verify current SignedData value
+ * @param {Object} [param={}]
+ * @param {Number} [param.signer = -1] Index of the signer which information we need to verify
+ * @param {ArrayBuffer} [param.data=new ArrayBuffer(0)]
+ * @param {Array.} [param.trustedCerts=[]]
+ * @param {Date} [param.checkDate=new Date()]
+ * @param {Boolean} [param.checkChain=false]
+ * @param {Boolean} [param.extendedMode=false]
+ * @param {?Function} [findOrigin=null]
+ * @param {?Function} [findIssuer=null]
+ */
+ verify({
+ signer = (-1),
+ data = (new ArrayBuffer(0)),
+ trustedCerts = [],
+ checkDate = (new Date()),
+ checkChain = false,
+ extendedMode = false,
+ passedWhenNotRevValues = false,
+ findOrigin = null,
+ findIssuer = null
+ } = {})
+ {
+ //region Global variables
+ let sequence = Promise.resolve();
+
+ let messageDigestValue = new ArrayBuffer(0);
+
+ let shaAlgorithm = "";
+
+ let signerCertificate = {};
+
+ let timestampSerial = null;
+
+ let certificatePath = [];
+
+ const engine = getEngine();
+ //endregion
+
+ //region Get a "crypto" extension
+ const crypto = getCrypto();
+ if(typeof crypto === "undefined")
+ return Promise.reject("Unable to create WebCrypto object");
+ //endregion
+
+ //region Get a signer number
+ if(signer === (-1))
+ {
+ if(extendedMode)
+ {
+ return Promise.reject({
+ date: checkDate,
+ code: 1,
+ message: "Unable to get signer index from input parameters",
+ signatureVerified: null,
+ signerCertificate: null,
+ signerCertificateVerified: null
+ });
+ }
+
+ return Promise.reject("Unable to get signer index from input parameters");
+ }
+ //endregion
+
+ //region Check that certificates field was included in signed data
+ if(("certificates" in this) === false)
+ {
+ if(extendedMode)
+ {
+ return Promise.reject({
+ date: checkDate,
+ code: 2,
+ message: "No certificates attached to this signed data",
+ signatureVerified: null,
+ signerCertificate: null,
+ signerCertificateVerified: null
+ });
+ }
+
+ return Promise.reject("No certificates attached to this signed data");
+ }
+ //endregion
+
+ //region Find a certificate for specified signer
+ if(this.signerInfos[signer].sid instanceof IssuerAndSerialNumber)
+ {
+ sequence = sequence.then(() =>
+ {
+ for(const certificate of this.certificates)
+ {
+ if((certificate instanceof Certificate) === false)
+ continue;
+
+ if((certificate.issuer.isEqual(this.signerInfos[signer].sid.issuer)) &&
+ (certificate.serialNumber.isEqual(this.signerInfos[signer].sid.serialNumber)))
+ {
+ signerCertificate = certificate;
+ return Promise.resolve();
+ }
+ }
+
+ if(extendedMode)
+ {
+ return Promise.reject({
+ date: checkDate,
+ code: 3,
+ message: "Unable to find signer certificate",
+ signatureVerified: null,
+ signerCertificate: null,
+ signerCertificateVerified: null
+ });
+ }
+
+ return Promise.reject("Unable to find signer certificate");
+ });
+ }
+ else // Find by SubjectKeyIdentifier
+ {
+ sequence = sequence.then(() =>
+ Promise.all(Array.from(this.certificates.filter(certificate => (certificate instanceof Certificate)), certificate =>
+ crypto.digest({ name: "sha-1" }, new Uint8Array(certificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHex)))
+ ).then(results =>
+ {
+ for(const [index, certificate] of this.certificates.entries())
+ {
+ if((certificate instanceof Certificate) === false)
+ continue;
+
+ if(isEqualBuffer(results[index], this.signerInfos[signer].sid.valueBlock.valueHex))
+ {
+ signerCertificate = certificate;
+ return Promise.resolve();
+ }
+ }
+
+ if(extendedMode)
+ {
+ return Promise.reject({
+ date: checkDate,
+ code: 3,
+ message: "Unable to find signer certificate",
+ signatureVerified: null,
+ signerCertificate: null,
+ signerCertificateVerified: null
+ });
+ }
+
+ return Promise.reject("Unable to find signer certificate");
+ }, () =>
+ {
+ if(extendedMode)
+ {
+ return Promise.reject({
+ date: checkDate,
+ code: 3,
+ message: "Unable to find signer certificate",
+ signatureVerified: null,
+ signerCertificate: null,
+ signerCertificateVerified: null
+ });
+ }
+
+ return Promise.reject("Unable to find signer certificate");
+ })
+ );
+ }
+ //endregion
+
+ //region Verify internal digest in case of "tSTInfo" content type
+ sequence = sequence.then(() =>
+ {
+ if(this.encapContentInfo.eContentType === "1.2.840.113549.1.9.16.1.4")
+ {
+ //region Check "eContent" precense
+ if(("eContent" in this.encapContentInfo) === false)
+ return false;
+ //endregion
+
+ //region Initialize TST_INFO value
+ const asn1 = asn1js.fromBER(this.encapContentInfo.eContent.valueBlock.valueHex);
+ let tstInfo;
+
+ try
+ {
+ tstInfo = new TSTInfo({ schema: asn1.result });
+ }
+ catch(ex)
+ {
+ return false;
+ }
+ //endregion
+
+ //region Change "checkDate" and append "timestampSerial"
+ checkDate = tstInfo.genTime;
+ timestampSerial = tstInfo.serialNumber.valueBlock.valueHex;
+ //endregion
+
+ //region Check that we do have detached data content
+ if(data.byteLength === 0)
+ {
+ if(extendedMode)
+ {
+ return Promise.reject({
+ date: checkDate,
+ code: 4,
+ message: "Missed detached data input array",
+ signatureVerified: null,
+ signerCertificate,
+ signerCertificateVerified: null
+ });
+ }
+
+ return Promise.reject("Missed detached data input array");
+ }
+ //endregion
+
+ return tstInfo.verify({ data });
+ }
+
+ return true;
+ });
+ //endregion
+
+ //region Make additional verification for signer's certificate
+ function checkCA(cert)
+ {
+ /// Certificate to find CA flag for
+
+ //region Do not include signer's certificate
+ if((cert.issuer.isEqual(signerCertificate.issuer) === true) && (cert.serialNumber.isEqual(signerCertificate.serialNumber) === true))
+ return null;
+ //endregion
+
+ let isCA = false;
+
+ if("extensions" in cert)
+ {
+ for(const extension of cert.extensions)
+ {
+ if(extension.extnID === "2.5.29.19") // BasicConstraints
+ {
+ if("cA" in extension.parsedValue)
+ {
+ if(extension.parsedValue.cA === true)
+ isCA = true;
+ }
+ }
+ }
+ }
+
+ if(isCA)
+ return cert;
+
+ return null;
+ }
+
+ if(checkChain)
+ {
+ sequence = sequence.then(result =>
+ {
+ //region Verify result of previous operation
+ if(result === false)
+ return false;
+ //endregion
+
+ const promiseResults = Array.from(this.certificates.filter(certificate => (certificate instanceof Certificate)), certificate => checkCA(certificate));
+
+ const certificateChainValidationEngineParameters = {
+ checkDate,
+ certs: Array.from(promiseResults.filter(_result => (_result !== null))),
+ trustedCerts
+ };
+
+ if(findIssuer !== null)
+ certificateChainValidationEngineParameters.findIssuer = findIssuer;
+
+ if(findOrigin !== null)
+ certificateChainValidationEngineParameters.findOrigin = findOrigin;
+
+ const certificateChainEngine = new CertificateChainValidationEngine(certificateChainValidationEngineParameters);
+
+ certificateChainEngine.certs.push(signerCertificate);
+
+ if("crls" in this)
+ {
+ for(const crl of this.crls)
+ {
+ if("thisUpdate" in crl)
+ certificateChainEngine.crls.push(crl);
+ else // Assumed "revocation value" has "OtherRevocationInfoFormat"
+ {
+ if(crl.otherRevInfoFormat === "1.3.6.1.5.5.7.48.1.1") // Basic OCSP response
+ certificateChainEngine.ocsps.push(new BasicOCSPResponse({ schema: crl.otherRevInfo }));
+ }
+ }
+ }
+
+ if("ocsps" in this)
+ certificateChainEngine.ocsps.push(...(this.ocsps));
+
+ return certificateChainEngine.verify({ passedWhenNotRevValues }).then(verificationResult =>
+ {
+ if("certificatePath" in verificationResult)
+ certificatePath = verificationResult.certificatePath;
+
+ if(verificationResult.result === true)
+ return Promise.resolve(true);
+
+ if(extendedMode)
+ {
+ return Promise.reject({
+ date: checkDate,
+ code: 5,
+ message: `Validation of signer's certificate failed: ${verificationResult.resultMessage}`,
+ signatureVerified: null,
+ signerCertificate,
+ signerCertificateVerified: false
+ });
+ }
+
+ return Promise.reject("Validation of signer's certificate failed");
+ }, error =>
+ {
+ if(extendedMode)
+ {
+ return Promise.reject({
+ date: checkDate,
+ code: 5,
+ message: `Validation of signer's certificate failed with error: ${((error instanceof Object) ? error.resultMessage : error)}`,
+ signatureVerified: null,
+ signerCertificate,
+ signerCertificateVerified: false
+ });
+ }
+
+ return Promise.reject(`Validation of signer's certificate failed with error: ${((error instanceof Object) ? error.resultMessage : error)}`);
+ });
+ });
+ }
+ //endregion
+
+ //region Find signer's hashing algorithm
+ sequence = sequence.then(result =>
+ {
+ //region Verify result of previous operation
+ if(result === false)
+ return false;
+ //endregion
+
+ const signerInfoHashAlgorithm = getAlgorithmByOID(this.signerInfos[signer].digestAlgorithm.algorithmId);
+ if(("name" in signerInfoHashAlgorithm) === false)
+ {
+ if(extendedMode)
+ {
+ return Promise.reject({
+ date: checkDate,
+ code: 7,
+ message: `Unsupported signature algorithm: ${this.signerInfos[signer].digestAlgorithm.algorithmId}`,
+ signatureVerified: null,
+ signerCertificate,
+ signerCertificateVerified: true
+ });
+ }
+
+ return Promise.reject(`Unsupported signature algorithm: ${this.signerInfos[signer].digestAlgorithm.algorithmId}`);
+ }
+
+ shaAlgorithm = signerInfoHashAlgorithm.name;
+
+ return true;
+ });
+ //endregion
+
+ //region Create correct data block for verification
+ sequence = sequence.then(result =>
+ {
+ //region Verify result of previous operation
+ if(result === false)
+ return false;
+ //endregion
+
+ if("eContent" in this.encapContentInfo) // Attached data
+ {
+ if((this.encapContentInfo.eContent.idBlock.tagClass === 1) &&
+ (this.encapContentInfo.eContent.idBlock.tagNumber === 4))
+ {
+ if(this.encapContentInfo.eContent.idBlock.isConstructed === false)
+ data = this.encapContentInfo.eContent.valueBlock.valueHex;
+ else
+ {
+ for(const contentValue of this.encapContentInfo.eContent.valueBlock.value)
+ data = utilConcatBuf(data, contentValue.valueBlock.valueHex);
+ }
+ }
+ else
+ data = this.encapContentInfo.eContent.valueBlock.valueBeforeDecode;
+ }
+ else // Detached data
+ {
+ if(data.byteLength === 0) // Check that "data" already provided by function parameter
+ {
+ if(extendedMode)
+ {
+ return Promise.reject({
+ date: checkDate,
+ code: 8,
+ message: "Missed detached data input array",
+ signatureVerified: null,
+ signerCertificate,
+ signerCertificateVerified: true
+ });
+ }
+
+ return Promise.reject("Missed detached data input array");
+ }
+ }
+
+ if("signedAttrs" in this.signerInfos[signer])
+ {
+ //region Check mandatory attributes
+ let foundContentType = false;
+ let foundMessageDigest = false;
+
+ for(const attribute of this.signerInfos[signer].signedAttrs.attributes)
+ {
+ //region Check that "content-type" attribute exists
+ if(attribute.type === "1.2.840.113549.1.9.3")
+ foundContentType = true;
+ //endregion
+
+ //region Check that "message-digest" attribute exists
+ if(attribute.type === "1.2.840.113549.1.9.4")
+ {
+ foundMessageDigest = true;
+ messageDigestValue = attribute.values[0].valueBlock.valueHex;
+ }
+ //endregion
+
+ //region Speed-up searching
+ if(foundContentType && foundMessageDigest)
+ break;
+ //endregion
+ }
+
+ if(foundContentType === false)
+ {
+ if(extendedMode)
+ {
+ return Promise.reject({
+ date: checkDate,
+ code: 9,
+ message: "Attribute \"content-type\" is a mandatory attribute for \"signed attributes\"",
+ signatureVerified: null,
+ signerCertificate,
+ signerCertificateVerified: true
+ });
+ }
+
+ return Promise.reject("Attribute \"content-type\" is a mandatory attribute for \"signed attributes\"");
+ }
+
+ if(foundMessageDigest === false)
+ {
+ if(extendedMode)
+ {
+ return Promise.reject({
+ date: checkDate,
+ code: 10,
+ message: "Attribute \"message-digest\" is a mandatory attribute for \"signed attributes\"",
+ signatureVerified: null,
+ signerCertificate,
+ signerCertificateVerified: true
+ });
+ }
+
+ return Promise.reject("Attribute \"message-digest\" is a mandatory attribute for \"signed attributes\"");
+ }
+ //endregion
+ }
+
+ return true;
+ });
+ //endregion
+
+ //region Verify "message-digest" attribute in case of "signedAttrs"
+ sequence = sequence.then(result =>
+ {
+ //region Verify result of previous operation
+ if(result === false)
+ return false;
+ //endregion
+
+ if("signedAttrs" in this.signerInfos[signer])
+ return crypto.digest(shaAlgorithm, new Uint8Array(data));
+
+ return true;
+ }).then(
+ /**
+ * @param {ArrayBuffer} result
+ */
+ result =>
+ {
+ //region Verify result of previous operation
+ if(result === false)
+ return false;
+ //endregion
+
+ if("signedAttrs" in this.signerInfos[signer])
+ {
+ if(isEqualBuffer(result, messageDigestValue))
+ {
+ data = this.signerInfos[signer].signedAttrs.encodedValue;
+ return true;
+ }
+
+ return false;
+ }
+
+ return true;
+ });
+ //endregion
+
+ sequence = sequence.then(result =>
+ {
+ //region Verify result of previous operation
+ if(result === false)
+ return false;
+ //endregion
+
+ return engine.subtle.verifyWithPublicKey(data, this.signerInfos[signer].signature, signerCertificate.subjectPublicKeyInfo, signerCertificate.signatureAlgorithm, shaAlgorithm);
+ });
+
+ //region Make a final result
+ sequence = sequence.then(result =>
+ {
+ if(extendedMode)
+ {
+ return {
+ date: checkDate,
+ code: 14,
+ message: "",
+ signatureVerified: result,
+ signerCertificate,
+ timestampSerial,
+ signerCertificateVerified: true,
+ certificatePath
+ };
+ }
+
+ return result;
+ }, error =>
+ {
+ if(extendedMode)
+ {
+ if("code" in error)
+ return Promise.reject(error);
+
+ return Promise.reject({
+ date: checkDate,
+ code: 15,
+ message: `Error during verification: ${error.message}`,
+ signatureVerified: null,
+ signerCertificate,
+ timestampSerial,
+ signerCertificateVerified: true
+ });
+ }
+
+ return Promise.reject(error);
+ });
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+ /**
+ * Signing current SignedData
+ * @param {key} privateKey Private key for "subjectPublicKeyInfo" structure
+ * @param {number} signerIndex Index number (starting from 0) of signer index to make signature for
+ * @param {string} [hashAlgorithm="SHA-1"] Hashing algorithm. Default SHA-1
+ * @param {ArrayBuffer} [data] Detached data
+ * @returns {*}
+ */
+ sign(privateKey, signerIndex, hashAlgorithm = "SHA-1", data = (new ArrayBuffer(0)))
+ {
+ //region Initial checking
+ if(typeof privateKey === "undefined")
+ return Promise.reject("Need to provide a private key for signing");
+ //endregion
+
+ //region Initial variables
+ let sequence = Promise.resolve();
+ let parameters;
+
+ const engine = getEngine();
+ //endregion
+
+ //region Simple check for supported algorithm
+ const hashAlgorithmOID = getOIDByAlgorithm({ name: hashAlgorithm });
+ if(hashAlgorithmOID === "")
+ return Promise.reject(`Unsupported hash algorithm: ${hashAlgorithm}`);
+ //endregion
+
+ //region Append information about hash algorithm
+ if((this.digestAlgorithms.filter(algorithm => algorithm.algorithmId === hashAlgorithmOID)).length === 0)
+ {
+ this.digestAlgorithms.push(new AlgorithmIdentifier({
+ algorithmId: hashAlgorithmOID,
+ algorithmParams: new asn1js.Null()
+ }));
+ }
+
+ this.signerInfos[signerIndex].digestAlgorithm = new AlgorithmIdentifier({
+ algorithmId: hashAlgorithmOID,
+ algorithmParams: new asn1js.Null()
+ });
+ //endregion
+
+ //region Get a "default parameters" for current algorithm and set correct signature algorithm
+ sequence = sequence.then(() => engine.subtle.getSignatureParameters(privateKey, hashAlgorithm));
+
+ sequence = sequence.then(result =>
+ {
+ parameters = result.parameters;
+ this.signerInfos[signerIndex].signatureAlgorithm = result.signatureAlgorithm;
+ });
+ //endregion
+
+ //region Create TBS data for signing
+ sequence = sequence.then(() =>
+ {
+ if("signedAttrs" in this.signerInfos[signerIndex])
+ {
+ if(this.signerInfos[signerIndex].signedAttrs.encodedValue.byteLength !== 0)
+ data = this.signerInfos[signerIndex].signedAttrs.encodedValue;
+ else
+ {
+ data = this.signerInfos[signerIndex].signedAttrs.toSchema(true).toBER(false);
+
+ //region Change type from "[0]" to "SET" acordingly to standard
+ const view = new Uint8Array(data);
+ view[0] = 0x31;
+ //endregion
+ }
+ }
+ else
+ {
+ if("eContent" in this.encapContentInfo) // Attached data
+ {
+ if((this.encapContentInfo.eContent.idBlock.tagClass === 1) &&
+ (this.encapContentInfo.eContent.idBlock.tagNumber === 4))
+ {
+ if(this.encapContentInfo.eContent.idBlock.isConstructed === false)
+ data = this.encapContentInfo.eContent.valueBlock.valueHex;
+ else
+ {
+ for(const content of this.encapContentInfo.eContent.valueBlock.value)
+ data = utilConcatBuf(data, content.valueBlock.valueHex);
+ }
+ }
+ else
+ data = this.encapContentInfo.eContent.valueBlock.valueBeforeDecode;
+ }
+ else // Detached data
+ {
+ if(data.byteLength === 0) // Check that "data" already provided by function parameter
+ return Promise.reject("Missed detached data input array");
+ }
+ }
+
+ return Promise.resolve();
+ });
+ //endregion
+
+ //region Signing TBS data on provided private key
+ sequence = sequence.then(() => engine.subtle.signWithPrivateKey(data, privateKey, parameters));
+
+ sequence = sequence.then(result =>
+ {
+ this.signerInfos[signerIndex].signature = new asn1js.OctetString({ valueHex: result });
+
+ return result;
+ });
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/SignerInfo.js b/pki.js/SignerInfo.js
new file mode 100644
index 0000000..9e9bfe1
--- /dev/null
+++ b/pki.js/SignerInfo.js
@@ -0,0 +1,347 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+import SignedAndUnsignedAttributes from "./SignedAndUnsignedAttributes.js";
+import IssuerAndSerialNumber from "./IssuerAndSerialNumber.js";
+//**************************************************************************************
+/**
+ * Class from RFC5652
+ */
+export default class SignerInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for SignerInfo 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 {string}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", SignerInfo.defaultValues("version"));
+ /**
+ * @type {Object}
+ * @desc sid
+ */
+ this.sid = getParametersValue(parameters, "sid", SignerInfo.defaultValues("sid"));
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc digestAlgorithm
+ */
+ this.digestAlgorithm = getParametersValue(parameters, "digestAlgorithm", SignerInfo.defaultValues("digestAlgorithm"));
+
+ if("signedAttrs" in parameters)
+ /**
+ * @type {SignedAndUnsignedAttributes}
+ * @desc signedAttrs
+ */
+ this.signedAttrs = getParametersValue(parameters, "signedAttrs", SignerInfo.defaultValues("signedAttrs"));
+
+ /**
+ * @type {AlgorithmIdentifier}
+ * @desc digestAlgorithm
+ */
+ this.signatureAlgorithm = getParametersValue(parameters, "signatureAlgorithm", SignerInfo.defaultValues("signatureAlgorithm"));
+ /**
+ * @type {OctetString}
+ * @desc signature
+ */
+ this.signature = getParametersValue(parameters, "signature", SignerInfo.defaultValues("signature"));
+
+ if("unsignedAttrs" in parameters)
+ /**
+ * @type {SignedAndUnsignedAttributes}
+ * @desc unsignedAttrs
+ */
+ this.unsignedAttrs = getParametersValue(parameters, "unsignedAttrs", SignerInfo.defaultValues("unsignedAttrs"));
+ //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 "version":
+ return 0;
+ case "sid":
+ return new asn1js.Any();
+ case "digestAlgorithm":
+ return new AlgorithmIdentifier();
+ case "signedAttrs":
+ return new SignedAndUnsignedAttributes({ type: 0 });
+ case "signatureAlgorithm":
+ return new AlgorithmIdentifier();
+ case "signature":
+ return new asn1js.OctetString();
+ case "unsignedAttrs":
+ return new SignedAndUnsignedAttributes({ type: 1 });
+ default:
+ throw new Error(`Invalid member name for SignerInfo 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 "version":
+ return (SignerInfo.defaultValues("version") === memberValue);
+ case "sid":
+ return (memberValue instanceof asn1js.Any);
+ case "digestAlgorithm":
+ if((memberValue instanceof AlgorithmIdentifier) === false)
+ return false;
+
+ return memberValue.isEqual(SignerInfo.defaultValues("digestAlgorithm"));
+ case "signedAttrs":
+ return ((SignedAndUnsignedAttributes.compareWithDefault("type", memberValue.type))
+ && (SignedAndUnsignedAttributes.compareWithDefault("attributes", memberValue.attributes))
+ && (SignedAndUnsignedAttributes.compareWithDefault("encodedValue", memberValue.encodedValue)));
+ case "signatureAlgorithm":
+ if((memberValue instanceof AlgorithmIdentifier) === false)
+ return false;
+
+ return memberValue.isEqual(SignerInfo.defaultValues("signatureAlgorithm"));
+ case "signature":
+ case "unsignedAttrs":
+ return ((SignedAndUnsignedAttributes.compareWithDefault("type", memberValue.type))
+ && (SignedAndUnsignedAttributes.compareWithDefault("attributes", memberValue.attributes))
+ && (SignedAndUnsignedAttributes.compareWithDefault("encodedValue", memberValue.encodedValue)));
+ default:
+ throw new Error(`Invalid member name for SignerInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * SignerInfo ::= SEQUENCE {
+ * version CMSVersion,
+ * sid SignerIdentifier,
+ * digestAlgorithm DigestAlgorithmIdentifier,
+ * signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
+ * signatureAlgorithm SignatureAlgorithmIdentifier,
+ * signature SignatureValue,
+ * unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }
+ *
+ * SignerIdentifier ::= CHOICE {
+ * issuerAndSerialNumber IssuerAndSerialNumber,
+ * subjectKeyIdentifier [0] SubjectKeyIdentifier }
+ *
+ * SubjectKeyIdentifier ::= OCTET STRING
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [version]
+ * @property {string} [sid]
+ * @property {string} [digestAlgorithm]
+ * @property {string} [signedAttrs]
+ * @property {string} [signatureAlgorithm]
+ * @property {string} [signature]
+ * @property {string} [unsignedAttrs]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (
+ new asn1js.Sequence({
+ name: "SignerInfo",
+ value: [
+ new asn1js.Integer({ name: (names.version || "SignerInfo.version") }),
+ new asn1js.Choice({
+ value: [
+ IssuerAndSerialNumber.schema(names.sid || {
+ names: {
+ blockName: "SignerInfo.sid"
+ }
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ name: (names.sid || "SignerInfo.sid"),
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [new asn1js.OctetString()]
+ })
+ ]
+ }),
+ AlgorithmIdentifier.schema(names.digestAlgorithm || {
+ names: {
+ blockName: "SignerInfo.digestAlgorithm"
+ }
+ }),
+ SignedAndUnsignedAttributes.schema(names.signedAttrs || {
+ names: {
+ blockName: "SignerInfo.signedAttrs",
+ tagNumber: 0
+ }
+ }),
+ AlgorithmIdentifier.schema(names.signatureAlgorithm || {
+ names: {
+ blockName: "SignerInfo.signatureAlgorithm"
+ }
+ }),
+ new asn1js.OctetString({ name: (names.signature || "SignerInfo.signature") }),
+ SignedAndUnsignedAttributes.schema(names.unsignedAttrs || {
+ names: {
+ blockName: "SignerInfo.unsignedAttrs",
+ tagNumber: 1
+ }
+ })
+ ]
+ })
+ );
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "SignerInfo.version",
+ "SignerInfo.sid",
+ "SignerInfo.digestAlgorithm",
+ "SignerInfo.signedAttrs",
+ "SignerInfo.signatureAlgorithm",
+ "SignerInfo.signature",
+ "SignerInfo.unsignedAttrs"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ SignerInfo.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for SignerInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result["SignerInfo.version"].valueBlock.valueDec;
+
+ const currentSid = asn1.result["SignerInfo.sid"];
+ if(currentSid.idBlock.tagClass === 1)
+ this.sid = new IssuerAndSerialNumber({ schema: currentSid });
+ else
+ this.sid = currentSid;
+
+ this.digestAlgorithm = new AlgorithmIdentifier({ schema: asn1.result["SignerInfo.digestAlgorithm"] });
+ if("SignerInfo.signedAttrs" in asn1.result)
+ this.signedAttrs = new SignedAndUnsignedAttributes({ type: 0, schema: asn1.result["SignerInfo.signedAttrs"] });
+
+ this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result["SignerInfo.signatureAlgorithm"] });
+ this.signature = asn1.result["SignerInfo.signature"];
+ if("SignerInfo.unsignedAttrs" in asn1.result)
+ this.unsignedAttrs = new SignedAndUnsignedAttributes({ type: 1, schema: asn1.result["SignerInfo.unsignedAttrs"] });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ if(SignerInfo.compareWithDefault("sid", this.sid))
+ throw new Error("Incorrectly initialized \"SignerInfo\" class");
+
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.Integer({ value: this.version }));
+
+ if(this.sid instanceof IssuerAndSerialNumber)
+ outputArray.push(this.sid.toSchema());
+ else
+ outputArray.push(this.sid);
+
+ outputArray.push(this.digestAlgorithm.toSchema());
+
+ if("signedAttrs" in this)
+ {
+ if(SignerInfo.compareWithDefault("signedAttrs", this.signedAttrs) === false)
+ outputArray.push(this.signedAttrs.toSchema());
+ }
+
+ outputArray.push(this.signatureAlgorithm.toSchema());
+ outputArray.push(this.signature);
+
+ if("unsignedAttrs" in this)
+ {
+ if(SignerInfo.compareWithDefault("unsignedAttrs", this.unsignedAttrs) === false)
+ outputArray.push(this.unsignedAttrs.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()
+ {
+ if(SignerInfo.compareWithDefault("sid", this.sid))
+ throw new Error("Incorrectly initialized \"SignerInfo\" class");
+
+ const _object = {
+ version: this.version
+ };
+
+ if(!(this.sid instanceof asn1js.Any))
+ _object.sid = this.sid.toJSON();
+
+ _object.digestAlgorithm = this.digestAlgorithm.toJSON();
+
+ if(SignerInfo.compareWithDefault("signedAttrs", this.signedAttrs) === false)
+ _object.signedAttrs = this.signedAttrs.toJSON();
+
+ _object.signatureAlgorithm = this.signatureAlgorithm.toJSON();
+ _object.signature = this.signature.toJSON();
+
+ if(SignerInfo.compareWithDefault("unsignedAttrs", this.unsignedAttrs) === false)
+ _object.unsignedAttrs = this.unsignedAttrs.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/SingleResponse.js b/pki.js/SingleResponse.js
new file mode 100644
index 0000000..96edf11
--- /dev/null
+++ b/pki.js/SingleResponse.js
@@ -0,0 +1,323 @@
+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;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/SubjectDirectoryAttributes.js b/pki.js/SubjectDirectoryAttributes.js
new file mode 100644
index 0000000..f39a260
--- /dev/null
+++ b/pki.js/SubjectDirectoryAttributes.js
@@ -0,0 +1,135 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import Attribute from "./Attribute.js";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class SubjectDirectoryAttributes
+{
+ //**********************************************************************************
+ /**
+ * Constructor for SubjectDirectoryAttributes 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 {Array.}
+ * @desc attributes
+ */
+ this.attributes = getParametersValue(parameters, "attributes", SubjectDirectoryAttributes.defaultValues("attributes"));
+ //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 "attributes":
+ return [];
+ default:
+ throw new Error(`Invalid member name for SubjectDirectoryAttributes class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * SubjectDirectoryAttributes ::= SEQUENCE SIZE (1..MAX) OF Attribute
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [utcTimeName] Name for "utcTimeName" choice
+ * @property {string} [generalTimeName] Name for "generalTimeName" choice
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || ""),
+ value: [
+ new asn1js.Repeated({
+ name: (names.attributes || ""),
+ value: Attribute.schema()
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "attributes"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ SubjectDirectoryAttributes.schema({
+ names: {
+ attributes: "attributes"
+ }
+ })
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for SubjectDirectoryAttributes");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.attributes = Array.from(asn1.result.attributes, element => new Attribute({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ return (new asn1js.Sequence({
+ value: Array.from(this.attributes, element => element.toSchema())
+ }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ attributes: Array.from(this.attributes, element => element.toJSON())
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/TBSRequest.js b/pki.js/TBSRequest.js
new file mode 100644
index 0000000..00cf390
--- /dev/null
+++ b/pki.js/TBSRequest.js
@@ -0,0 +1,324 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import GeneralName from "./GeneralName.js";
+import Request from "./Request.js";
+import Extension from "./Extension.js";
+import Extensions from "./Extensions.js";
+//**************************************************************************************
+/**
+ * Class from RFC6960
+ */
+export default class TBSRequest
+{
+ //**********************************************************************************
+ /**
+ * Constructor for TBSRequest 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 {ArrayBuffer}
+ * @desc tbs
+ */
+ this.tbs = getParametersValue(parameters, "tbs", TBSRequest.defaultValues("tbs"));
+
+ if("version" in parameters)
+ /**
+ * @type {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", TBSRequest.defaultValues("version"));
+
+ if("requestorName" in parameters)
+ /**
+ * @type {GeneralName}
+ * @desc requestorName
+ */
+ this.requestorName = getParametersValue(parameters, "requestorName", TBSRequest.defaultValues("requestorName"));
+
+ /**
+ * @type {Array.}
+ * @desc requestList
+ */
+ this.requestList = getParametersValue(parameters, "requestList", TBSRequest.defaultValues("requestList"));
+
+ if("requestExtensions" in parameters)
+ /**
+ * @type {Array.}
+ * @desc requestExtensions
+ */
+ this.requestExtensions = getParametersValue(parameters, "requestExtensions", TBSRequest.defaultValues("requestExtensions"));
+ //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 "tbs":
+ return new ArrayBuffer(0);
+ case "version":
+ return 0;
+ case "requestorName":
+ return new GeneralName();
+ case "requestList":
+ case "requestExtensions":
+ return [];
+ default:
+ throw new Error(`Invalid member name for TBSRequest 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 "tbs":
+ return (memberValue.byteLength === 0);
+ case "version":
+ return (memberValue === TBSRequest.defaultValues(memberName));
+ case "requestorName":
+ return ((memberValue.type === GeneralName.defaultValues("type")) && (Object.keys(memberValue.value).length === 0));
+ case "requestList":
+ case "requestExtensions":
+ return (memberValue.length === 0);
+ default:
+ throw new Error(`Invalid member name for TBSRequest class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * TBSRequest ::= SEQUENCE {
+ * version [0] EXPLICIT Version DEFAULT v1,
+ * requestorName [1] EXPLICIT GeneralName OPTIONAL,
+ * requestList SEQUENCE OF Request,
+ * requestExtensions [2] EXPLICIT Extensions OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [TBSRequestVersion]
+ * @property {string} [requestorName]
+ * @property {string} [requestList]
+ * @property {string} [requests]
+ * @property {string} [requestNames]
+ * @property {string} [extensions]
+ * @property {string} [requestExtensions]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || "TBSRequest"),
+ value: [
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [new asn1js.Integer({ name: (names.TBSRequestVersion || "TBSRequest.version") })]
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [GeneralName.schema(names.requestorName || {
+ names: {
+ blockName: "TBSRequest.requestorName"
+ }
+ })]
+ }),
+ new asn1js.Sequence({
+ name: (names.requestList || "TBSRequest.requestList"),
+ value: [
+ new asn1js.Repeated({
+ name: (names.requests || "TBSRequest.requests"),
+ value: Request.schema(names.requestNames || {})
+ })
+ ]
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ value: [Extensions.schema(names.extensions || {
+ names: {
+ blockName: (names.requestExtensions || "TBSRequest.requestExtensions")
+ }
+ })]
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "TBSRequest",
+ "TBSRequest.version",
+ "TBSRequest.requestorName",
+ "TBSRequest.requests",
+ "TBSRequest.requestExtensions"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ TBSRequest.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for TBSRequest");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.tbs = asn1.result.TBSRequest.valueBeforeDecode;
+
+ if("TBSRequest.version" in asn1.result)
+ this.version = asn1.result["TBSRequest.version"].valueBlock.valueDec;
+ if("TBSRequest.requestorName" in asn1.result)
+ this.requestorName = new GeneralName({ schema: asn1.result["TBSRequest.requestorName"] });
+
+ this.requestList = Array.from(asn1.result["TBSRequest.requests"], element => new Request({ schema: element }));
+
+ if("TBSRequest.requestExtensions" in asn1.result)
+ this.requestExtensions = Array.from(asn1.result["TBSRequest.requestExtensions"].valueBlock.value, element => new Extension({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @param {boolean} encodeFlag If param equal to false then create TBS schema via decoding stored value. In othe case create TBS schema via assembling from TBS parts.
+ * @returns {Object} asn1js object
+ */
+ toSchema(encodeFlag = false)
+ {
+ //region Decode stored TBS value
+ let tbsSchema;
+
+ if(encodeFlag === false)
+ {
+ if(this.tbs.byteLength === 0) // No stored TBS part
+ return TBSRequest.schema();
+
+ tbsSchema = asn1js.fromBER(this.tbs).result;
+ }
+ //endregion
+ //region Create TBS schema via assembling from TBS parts
+ else
+ {
+ const outputArray = [];
+
+ if("version" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [new asn1js.Integer({ value: this.version })]
+ }));
+ }
+
+ if("requestorName" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [this.requestorName.toSchema()]
+ }));
+ }
+
+ outputArray.push(new asn1js.Sequence({
+ value: Array.from(this.requestList, element => element.toSchema())
+ }));
+
+ if("requestExtensions" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 2 // [2]
+ },
+ value: [
+ new asn1js.Sequence({
+ value: Array.from(this.requestExtensions, element => element.toSchema())
+ })
+ ]
+ }));
+ }
+
+ tbsSchema = new asn1js.Sequence({
+ value: outputArray
+ });
+ }
+ //endregion
+
+ //region Construct and return new ASN.1 schema for this object
+ return tbsSchema;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ const _object = {};
+
+ if("version" in this)
+ _object.version = this.version;
+
+ if("requestorName" in this)
+ _object.requestorName = this.requestorName.toJSON();
+
+ _object.requestList = Array.from(this.requestList, element => element.toJSON());
+
+ if("requestExtensions" in this)
+ _object.requestExtensions = Array.from(this.requestExtensions, element => element.toJSON());
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/TSTInfo.js b/pki.js/TSTInfo.js
new file mode 100644
index 0000000..ea594fc
--- /dev/null
+++ b/pki.js/TSTInfo.js
@@ -0,0 +1,455 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, isEqualBuffer, clearProps } from "pvutils";
+import { getCrypto, getAlgorithmByOID } from "./common.js";
+import MessageImprint from "./MessageImprint.js";
+import Accuracy from "./Accuracy.js";
+import GeneralName from "./GeneralName.js";
+import Extension from "./Extension.js";
+//**************************************************************************************
+/**
+ * Class from RFC3161
+ */
+export default class TSTInfo
+{
+ //**********************************************************************************
+ /**
+ * Constructor for TSTInfo 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 {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", TSTInfo.defaultValues("version"));
+ /**
+ * @type {string}
+ * @desc policy
+ */
+ this.policy = getParametersValue(parameters, "policy", TSTInfo.defaultValues("policy"));
+ /**
+ * @type {MessageImprint}
+ * @desc messageImprint
+ */
+ this.messageImprint = getParametersValue(parameters, "messageImprint", TSTInfo.defaultValues("messageImprint"));
+ /**
+ * @type {Integer}
+ * @desc serialNumber
+ */
+ this.serialNumber = getParametersValue(parameters, "serialNumber", TSTInfo.defaultValues("serialNumber"));
+ /**
+ * @type {Date}
+ * @desc genTime
+ */
+ this.genTime = getParametersValue(parameters, "genTime", TSTInfo.defaultValues("genTime"));
+
+ if("accuracy" in parameters)
+ /**
+ * @type {Accuracy}
+ * @desc accuracy
+ */
+ this.accuracy = getParametersValue(parameters, "accuracy", TSTInfo.defaultValues("accuracy"));
+
+ if("ordering" in parameters)
+ /**
+ * @type {boolean}
+ * @desc ordering
+ */
+ this.ordering = getParametersValue(parameters, "ordering", TSTInfo.defaultValues("ordering"));
+
+ if("nonce" in parameters)
+ /**
+ * @type {Integer}
+ * @desc nonce
+ */
+ this.nonce = getParametersValue(parameters, "nonce", TSTInfo.defaultValues("nonce"));
+
+ if("tsa" in parameters)
+ /**
+ * @type {GeneralName}
+ * @desc tsa
+ */
+ this.tsa = getParametersValue(parameters, "tsa", TSTInfo.defaultValues("tsa"));
+
+ if("extensions" in parameters)
+ /**
+ * @type {Array.}
+ * @desc extensions
+ */
+ this.extensions = getParametersValue(parameters, "extensions", TSTInfo.defaultValues("extensions"));
+ //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 "version":
+ return 0;
+ case "policy":
+ return "";
+ case "messageImprint":
+ return new MessageImprint();
+ case "serialNumber":
+ return new asn1js.Integer();
+ case "genTime":
+ return new Date(0, 0, 0);
+ case "accuracy":
+ return new Accuracy();
+ case "ordering":
+ return false;
+ case "nonce":
+ return new asn1js.Integer();
+ case "tsa":
+ return new GeneralName();
+ case "extensions":
+ return [];
+ default:
+ throw new Error(`Invalid member name for TSTInfo 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 "version":
+ case "policy":
+ case "genTime":
+ case "ordering":
+ return (memberValue === TSTInfo.defaultValues(memberName));
+ case "messageImprint":
+ return ((MessageImprint.compareWithDefault("hashAlgorithm", memberValue.hashAlgorithm)) &&
+ (MessageImprint.compareWithDefault("hashedMessage", memberValue.hashedMessage)));
+ case "serialNumber":
+ case "nonce":
+ return (memberValue.isEqual(TSTInfo.defaultValues(memberName)));
+ case "accuracy":
+ return ((Accuracy.compareWithDefault("seconds", memberValue.seconds)) &&
+ (Accuracy.compareWithDefault("millis", memberValue.millis)) &&
+ (Accuracy.compareWithDefault("micros", memberValue.micros)));
+ case "tsa":
+ return ((GeneralName.compareWithDefault("type", memberValue.type)) &&
+ (GeneralName.compareWithDefault("value", memberValue.value)));
+ case "extensions":
+ return (memberValue.length === 0);
+ default:
+ throw new Error(`Invalid member name for TSTInfo class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * TSTInfo ::= SEQUENCE {
+ * version INTEGER { v1(1) },
+ * policy TSAPolicyId,
+ * messageImprint MessageImprint,
+ * serialNumber INTEGER,
+ * genTime GeneralizedTime,
+ * accuracy Accuracy OPTIONAL,
+ * ordering BOOLEAN DEFAULT FALSE,
+ * nonce INTEGER OPTIONAL,
+ * tsa [0] GeneralName OPTIONAL,
+ * extensions [1] IMPLICIT Extensions OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [version]
+ * @property {string} [policy]
+ * @property {string} [messageImprint]
+ * @property {string} [serialNumber]
+ * @property {string} [genTime]
+ * @property {string} [accuracy]
+ * @property {string} [ordering]
+ * @property {string} [nonce]
+ * @property {string} [tsa]
+ * @property {string} [extensions]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || "TSTInfo"),
+ value: [
+ new asn1js.Integer({ name: (names.version || "TSTInfo.version") }),
+ new asn1js.ObjectIdentifier({ name: (names.policy || "TSTInfo.policy") }),
+ MessageImprint.schema(names.messageImprint || {
+ names: {
+ blockName: "TSTInfo.messageImprint"
+ }
+ }),
+ new asn1js.Integer({ name: (names.serialNumber || "TSTInfo.serialNumber") }),
+ new asn1js.GeneralizedTime({ name: (names.genTime || "TSTInfo.genTime") }),
+ Accuracy.schema(names.accuracy || {
+ names: {
+ blockName: "TSTInfo.accuracy"
+ }
+ }),
+ new asn1js.Boolean({
+ name: (names.ordering || "TSTInfo.ordering"),
+ optional: true
+ }),
+ new asn1js.Integer({
+ name: (names.nonce || "TSTInfo.nonce"),
+ optional: true
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [GeneralName.schema(names.tsa || {
+ names: {
+ blockName: "TSTInfo.tsa"
+ }
+ })]
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: [
+ new asn1js.Repeated({
+ name: (names.extensions || "TSTInfo.extensions"),
+ value: Extension.schema(names.extension || {})
+ })
+ ]
+ }) // IMPLICIT Extensions
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "TSTInfo.version",
+ "TSTInfo.policy",
+ "TSTInfo.messageImprint",
+ "TSTInfo.serialNumber",
+ "TSTInfo.genTime",
+ "TSTInfo.accuracy",
+ "TSTInfo.ordering",
+ "TSTInfo.nonce",
+ "TSTInfo.tsa",
+ "TSTInfo.extensions"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ TSTInfo.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for TSTInfo");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result["TSTInfo.version"].valueBlock.valueDec;
+ this.policy = asn1.result["TSTInfo.policy"].valueBlock.toString();
+ this.messageImprint = new MessageImprint({ schema: asn1.result["TSTInfo.messageImprint"] });
+ this.serialNumber = asn1.result["TSTInfo.serialNumber"];
+ this.genTime = asn1.result["TSTInfo.genTime"].toDate();
+ if("TSTInfo.accuracy" in asn1.result)
+ this.accuracy = new Accuracy({ schema: asn1.result["TSTInfo.accuracy"] });
+ if("TSTInfo.ordering" in asn1.result)
+ this.ordering = asn1.result["TSTInfo.ordering"].valueBlock.value;
+ if("TSTInfo.nonce" in asn1.result)
+ this.nonce = asn1.result["TSTInfo.nonce"];
+ if("TSTInfo.tsa" in asn1.result)
+ this.tsa = new GeneralName({ schema: asn1.result["TSTInfo.tsa"] });
+ if("TSTInfo.extensions" in asn1.result)
+ this.extensions = Array.from(asn1.result["TSTInfo.extensions"], element => new Extension({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.Integer({ value: this.version }));
+ outputArray.push(new asn1js.ObjectIdentifier({ value: this.policy }));
+ outputArray.push(this.messageImprint.toSchema());
+ outputArray.push(this.serialNumber);
+ outputArray.push(new asn1js.GeneralizedTime({ valueDate: this.genTime }));
+ if("accuracy" in this)
+ outputArray.push(this.accuracy.toSchema());
+ if("ordering" in this)
+ outputArray.push(new asn1js.Boolean({ value: this.ordering }));
+ if("nonce" in this)
+ outputArray.push(this.nonce);
+ if("tsa" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [this.tsa.toSchema()]
+ }));
+ }
+
+ //region Create array of extensions
+ if("extensions" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 1 // [1]
+ },
+ value: Array.from(this.extensions, element => element.toSchema())
+ }));
+ }
+ //endregion
+ //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 = {
+ version: this.version,
+ policy: this.policy,
+ messageImprint: this.messageImprint.toJSON(),
+ serialNumber: this.serialNumber.toJSON(),
+ genTime: this.genTime
+ };
+
+ if("accuracy" in this)
+ _object.accuracy = this.accuracy.toJSON();
+
+ if("ordering" in this)
+ _object.ordering = this.ordering;
+
+ if("nonce" in this)
+ _object.nonce = this.nonce.toJSON();
+
+ if("tsa" in this)
+ _object.tsa = this.tsa.toJSON();
+
+ if("extensions" in this)
+ _object.extensions = Array.from(this.extensions, element => element.toJSON());
+
+ return _object;
+ }
+ //**********************************************************************************
+ /**
+ * Verify current TST Info value
+ * @param {{data: ArrayBuffer, notBefore: Date, notAfter: Date}} parameters Input parameters
+ * @returns {Promise}
+ */
+ verify(parameters = {})
+ {
+ //region Initial variables
+ let sequence = Promise.resolve();
+
+ let data;
+
+ let notBefore = null;
+ let notAfter = null;
+ //endregion
+
+ //region Get a "crypto" extension
+ const crypto = getCrypto();
+ if(typeof crypto === "undefined")
+ return Promise.reject("Unable to create WebCrypto object");
+ //endregion
+
+ //region Get initial parameters
+ if("data" in parameters)
+ data = parameters.data;
+ else
+ return Promise.reject("\"data\" is a mandatory attribute for TST_INFO verification");
+
+ if("notBefore" in parameters)
+ notBefore = parameters.notBefore;
+
+ if("notAfter" in parameters)
+ notAfter = parameters.notAfter;
+ //endregion
+
+ //region Check date
+ if(notBefore !== null)
+ {
+ if(this.genTime < notBefore)
+ return Promise.reject("Generation time for TSTInfo object is less than notBefore value");
+ }
+
+ if(notAfter !== null)
+ {
+ if(this.genTime > notAfter)
+ return Promise.reject("Generation time for TSTInfo object is more than notAfter value");
+ }
+ //endregion
+
+ //region Find hashing algorithm
+ const shaAlgorithm = getAlgorithmByOID(this.messageImprint.hashAlgorithm.algorithmId);
+ if(("name" in shaAlgorithm) === false)
+ return Promise.reject(`Unsupported signature algorithm: ${this.messageImprint.hashAlgorithm.algorithmId}`);
+ //endregion
+
+ //region Calculate message digest for input "data" buffer
+ // noinspection JSCheckFunctionSignatures
+ sequence = sequence.then(() =>
+ crypto.digest(shaAlgorithm.name, new Uint8Array(data))
+ ).then(
+ result => isEqualBuffer(result, this.messageImprint.hashedMessage.valueBlock.valueHex)
+ );
+ //endregion
+
+ return sequence;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/Time.js b/pki.js/Time.js
new file mode 100644
index 0000000..30749e2
--- /dev/null
+++ b/pki.js/Time.js
@@ -0,0 +1,158 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+//**************************************************************************************
+/**
+ * Class from RFC5280
+ */
+export default class Time
+{
+ //**********************************************************************************
+ /**
+ * Constructor for Time class
+ * @param {Object} [parameters={}]
+ * @param {Object} [parameters.schema] asn1js parsed value to initialize the class from
+ * @property {number} [type] 0 - UTCTime; 1 - GeneralizedTime; 2 - empty value
+ * @property {Date} [value] Value of the TIME class
+ */
+ constructor(parameters = {})
+ {
+ //region Internal properties of the object
+ /**
+ * @type {number}
+ * @desc 0 - UTCTime; 1 - GeneralizedTime; 2 - empty value
+ */
+ this.type = getParametersValue(parameters, "type", Time.defaultValues("type"));
+ /**
+ * @type {Date}
+ * @desc Value of the TIME class
+ */
+ this.value = getParametersValue(parameters, "value", Time.defaultValues("value"));
+ //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 "type":
+ return 0;
+ case "value":
+ return new Date(0, 0, 0);
+ default:
+ throw new Error(`Invalid member name for Time class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * Time ::= CHOICE {
+ * utcTime UTCTime,
+ * generalTime GeneralizedTime }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @param {boolean} optional Flag that current schema should be optional
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {}, optional = false)
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [utcTimeName] Name for "utcTimeName" choice
+ * @property {string} [generalTimeName] Name for "generalTimeName" choice
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Choice({
+ optional,
+ value: [
+ new asn1js.UTCTime({ name: (names.utcTimeName || "") }),
+ new asn1js.GeneralizedTime({ name: (names.generalTimeName || "") })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "utcTimeName",
+ "generalTimeName"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema, schema, Time.schema({
+ names: {
+ utcTimeName: "utcTimeName",
+ generalTimeName: "generalTimeName"
+ }
+ }));
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for Time");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ if("utcTimeName" in asn1.result)
+ {
+ this.type = 0;
+ this.value = asn1.result.utcTimeName.toDate();
+ }
+ if("generalTimeName" in asn1.result)
+ {
+ this.type = 1;
+ this.value = asn1.result.generalTimeName.toDate();
+ }
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Construct and return new ASN.1 schema for this object
+ let result = {};
+
+ if(this.type === 0)
+ result = new asn1js.UTCTime({ valueDate: this.value });
+ if(this.type === 1)
+ result = new asn1js.GeneralizedTime({ valueDate: this.value });
+
+ return result;
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convertion for the class to JSON object
+ * @returns {Object}
+ */
+ toJSON()
+ {
+ return {
+ type: this.type,
+ value: this.value
+ };
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/TimeStampReq.js b/pki.js/TimeStampReq.js
new file mode 100644
index 0000000..b0fc757
--- /dev/null
+++ b/pki.js/TimeStampReq.js
@@ -0,0 +1,289 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import MessageImprint from "./MessageImprint.js";
+import Extension from "./Extension.js";
+//**************************************************************************************
+/**
+ * Class from RFC3161
+ */
+export default class TimeStampReq
+{
+ //**********************************************************************************
+ /**
+ * Constructor for TimeStampReq 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 {number}
+ * @desc version
+ */
+ this.version = getParametersValue(parameters, "version", TimeStampReq.defaultValues("version"));
+ /**
+ * @type {MessageImprint}
+ * @desc messageImprint
+ */
+ this.messageImprint = getParametersValue(parameters, "messageImprint", TimeStampReq.defaultValues("messageImprint"));
+
+ if("reqPolicy" in parameters)
+ /**
+ * @type {string}
+ * @desc reqPolicy
+ */
+ this.reqPolicy = getParametersValue(parameters, "reqPolicy", TimeStampReq.defaultValues("reqPolicy"));
+
+ if("nonce" in parameters)
+ /**
+ * @type {Integer}
+ * @desc nonce
+ */
+ this.nonce = getParametersValue(parameters, "nonce", TimeStampReq.defaultValues("nonce"));
+
+ if("certReq" in parameters)
+ /**
+ * @type {boolean}
+ * @desc certReq
+ */
+ this.certReq = getParametersValue(parameters, "certReq", TimeStampReq.defaultValues("certReq"));
+
+ if("extensions" in parameters)
+ /**
+ * @type {Array.}
+ * @desc extensions
+ */
+ this.extensions = getParametersValue(parameters, "extensions", TimeStampReq.defaultValues("extensions"));
+ //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 "version":
+ return 0;
+ case "messageImprint":
+ return new MessageImprint();
+ case "reqPolicy":
+ return "";
+ case "nonce":
+ return new asn1js.Integer();
+ case "certReq":
+ return false;
+ case "extensions":
+ return [];
+ default:
+ throw new Error(`Invalid member name for TimeStampReq 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 "version":
+ case "reqPolicy":
+ case "certReq":
+ return (memberValue === TimeStampReq.defaultValues(memberName));
+ case "messageImprint":
+ return ((MessageImprint.compareWithDefault("hashAlgorithm", memberValue.hashAlgorithm)) &&
+ (MessageImprint.compareWithDefault("hashedMessage", memberValue.hashedMessage)));
+ case "nonce":
+ return (memberValue.isEqual(TimeStampReq.defaultValues(memberName)));
+ case "extensions":
+ return (memberValue.length === 0);
+ default:
+ throw new Error(`Invalid member name for TimeStampReq class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * TimeStampReq ::= SEQUENCE {
+ * version INTEGER { v1(1) },
+ * messageImprint MessageImprint,
+ * reqPolicy TSAPolicyId OPTIONAL,
+ * nonce INTEGER OPTIONAL,
+ * certReq BOOLEAN DEFAULT FALSE,
+ * extensions [0] IMPLICIT Extensions OPTIONAL }
+ *
+ * TSAPolicyId ::= OBJECT IDENTIFIER
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [type]
+ * @property {string} [setName]
+ * @property {string} [values]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || "TimeStampReq"),
+ value: [
+ new asn1js.Integer({ name: (names.version || "TimeStampReq.version") }),
+ MessageImprint.schema(names.messageImprint || {
+ names: {
+ blockName: "TimeStampReq.messageImprint"
+ }
+ }),
+ new asn1js.ObjectIdentifier({
+ name: (names.reqPolicy || "TimeStampReq.reqPolicy"),
+ optional: true
+ }),
+ new asn1js.Integer({
+ name: (names.nonce || "TimeStampReq.nonce"),
+ optional: true
+ }),
+ new asn1js.Boolean({
+ name: (names.certReq || "TimeStampReq.certReq"),
+ optional: true
+ }),
+ new asn1js.Constructed({
+ optional: true,
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: [new asn1js.Repeated({
+ name: (names.extensions || "TimeStampReq.extensions"),
+ value: Extension.schema()
+ })]
+ }) // IMPLICIT SEQUENCE value
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "TimeStampReq.version",
+ "TimeStampReq.messageImprint",
+ "TimeStampReq.reqPolicy",
+ "TimeStampReq.nonce",
+ "TimeStampReq.certReq",
+ "TimeStampReq.extensions"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ TimeStampReq.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for TimeStampReq");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.version = asn1.result["TimeStampReq.version"].valueBlock.valueDec;
+ this.messageImprint = new MessageImprint({ schema: asn1.result["TimeStampReq.messageImprint"] });
+ if("TimeStampReq.reqPolicy" in asn1.result)
+ this.reqPolicy = asn1.result["TimeStampReq.reqPolicy"].valueBlock.toString();
+ if("TimeStampReq.nonce" in asn1.result)
+ this.nonce = asn1.result["TimeStampReq.nonce"];
+ if("TimeStampReq.certReq" in asn1.result)
+ this.certReq = asn1.result["TimeStampReq.certReq"].valueBlock.value;
+ if("TimeStampReq.extensions" in asn1.result)
+ this.extensions = Array.from(asn1.result["TimeStampReq.extensions"], element => new Extension({ schema: element }));
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(new asn1js.Integer({ value: this.version }));
+ outputArray.push(this.messageImprint.toSchema());
+ if("reqPolicy" in this)
+ outputArray.push(new asn1js.ObjectIdentifier({ value: this.reqPolicy }));
+ if("nonce" in this)
+ outputArray.push(this.nonce);
+ if(("certReq" in this) && (TimeStampReq.compareWithDefault("certReq", this.certReq) === false))
+ outputArray.push(new asn1js.Boolean({ value: this.certReq }));
+
+ //region Create array of extensions
+ if("extensions" in this)
+ {
+ outputArray.push(new asn1js.Constructed({
+ idBlock: {
+ tagClass: 3, // CONTEXT-SPECIFIC
+ tagNumber: 0 // [0]
+ },
+ value: Array.from(this.extensions, element => element.toSchema())
+ }));
+ }
+ //endregion
+ //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 = {
+ version: this.version,
+ messageImprint: this.messageImprint.toJSON()
+ };
+
+ if("reqPolicy" in this)
+ _object.reqPolicy = this.reqPolicy;
+
+ if("nonce" in this)
+ _object.nonce = this.nonce.toJSON();
+
+ if(("certReq" in this) && (TimeStampReq.compareWithDefault("certReq", this.certReq) === false))
+ _object.certReq = this.certReq;
+
+ if("extensions" in this)
+ _object.extensions = Array.from(this.extensions, element => element.toJSON());
+
+ return _object;
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/TimeStampResp.js b/pki.js/TimeStampResp.js
new file mode 100644
index 0000000..1a80987
--- /dev/null
+++ b/pki.js/TimeStampResp.js
@@ -0,0 +1,237 @@
+import * as asn1js from "asn1js";
+import { getParametersValue, clearProps } from "pvutils";
+import PKIStatusInfo from "./PKIStatusInfo.js";
+import ContentInfo from "./ContentInfo.js";
+import SignedData from "./SignedData.js";
+//**************************************************************************************
+/**
+ * Class from RFC3161
+ */
+export default class TimeStampResp
+{
+ //**********************************************************************************
+ /**
+ * Constructor for TimeStampResp 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 {PKIStatusInfo}
+ * @desc status
+ */
+ this.status = getParametersValue(parameters, "status", TimeStampResp.defaultValues("status"));
+
+ if("timeStampToken" in parameters)
+ /**
+ * @type {ContentInfo}
+ * @desc timeStampToken
+ */
+ this.timeStampToken = getParametersValue(parameters, "timeStampToken", TimeStampResp.defaultValues("timeStampToken"));
+ //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 "status":
+ return new PKIStatusInfo();
+ case "timeStampToken":
+ return new ContentInfo();
+ default:
+ throw new Error(`Invalid member name for TimeStampResp 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 "status":
+ return ((PKIStatusInfo.compareWithDefault("status", memberValue.status)) &&
+ (("statusStrings" in memberValue) === false) &&
+ (("failInfo" in memberValue) === false));
+ case "timeStampToken":
+ return ((memberValue.contentType === "") &&
+ (memberValue.content instanceof asn1js.Any));
+ default:
+ throw new Error(`Invalid member name for TimeStampResp class: ${memberName}`);
+ }
+ }
+ //**********************************************************************************
+ /**
+ * Return value of pre-defined ASN.1 schema for current class
+ *
+ * ASN.1 schema:
+ * ```asn1
+ * TimeStampResp ::= SEQUENCE {
+ * status PKIStatusInfo,
+ * timeStampToken TimeStampToken OPTIONAL }
+ * ```
+ *
+ * @param {Object} parameters Input parameters for the schema
+ * @returns {Object} asn1js schema object
+ */
+ static schema(parameters = {})
+ {
+ /**
+ * @type {Object}
+ * @property {string} [blockName]
+ * @property {string} [status]
+ * @property {string} [timeStampToken]
+ */
+ const names = getParametersValue(parameters, "names", {});
+
+ return (new asn1js.Sequence({
+ name: (names.blockName || "TimeStampResp"),
+ value: [
+ PKIStatusInfo.schema(names.status || {
+ names: {
+ blockName: "TimeStampResp.status"
+ }
+ }),
+ ContentInfo.schema(names.timeStampToken || {
+ names: {
+ blockName: "TimeStampResp.timeStampToken",
+ optional: true
+ }
+ })
+ ]
+ }));
+ }
+ //**********************************************************************************
+ /**
+ * Convert parsed asn1js object into current class
+ * @param {!Object} schema
+ */
+ fromSchema(schema)
+ {
+ //region Clear input data first
+ clearProps(schema, [
+ "TimeStampResp.status",
+ "TimeStampResp.timeStampToken"
+ ]);
+ //endregion
+
+ //region Check the schema is valid
+ const asn1 = asn1js.compareSchema(schema,
+ schema,
+ TimeStampResp.schema()
+ );
+
+ if(asn1.verified === false)
+ throw new Error("Object's schema was not verified against input data for TimeStampResp");
+ //endregion
+
+ //region Get internal properties from parsed schema
+ this.status = new PKIStatusInfo({ schema: asn1.result["TimeStampResp.status"] });
+ if("TimeStampResp.timeStampToken" in asn1.result)
+ this.timeStampToken = new ContentInfo({ schema: asn1.result["TimeStampResp.timeStampToken"] });
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Convert current object to asn1js object and set correct values
+ * @returns {Object} asn1js object
+ */
+ toSchema()
+ {
+ //region Create array for output sequence
+ const outputArray = [];
+
+ outputArray.push(this.status.toSchema());
+ if("timeStampToken" in this)
+ outputArray.push(this.timeStampToken.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 = {
+ status: this.status
+ };
+
+ if("timeStampToken" in this)
+ _object.timeStampToken = this.timeStampToken.toJSON();
+
+ return _object;
+ }
+ //**********************************************************************************
+ /**
+ * Sign current TSP Response
+ * @param {Object} privateKey Private key for "subjectPublicKeyInfo" structure
+ * @param {string} [hashAlgorithm] Hashing algorithm. Default SHA-1
+ * @returns {Promise}
+ */
+ sign(privateKey, hashAlgorithm)
+ {
+ //region Check that "timeStampToken" exists
+ if(("timeStampToken" in this) === false)
+ return Promise.reject("timeStampToken is absent in TSP response");
+ //endregion
+
+ //region Check that "timeStampToken" has a right internal format
+ if(this.timeStampToken.contentType !== "1.2.840.113549.1.7.2") // Must be a CMS signed data
+ return Promise.reject(`Wrong format of timeStampToken: ${this.timeStampToken.contentType}`);
+ //endregion
+
+ //region Sign internal signed data value
+ const signed = new ContentInfo({ schema: this.timeStampToken.content });
+
+ return signed.sign(privateKey, 0, hashAlgorithm);
+ //endregion
+ }
+ //**********************************************************************************
+ /**
+ * Verify current TSP Response
+ * @param {Object} verificationParameters Input parameters for verification
+ * @returns {Promise}
+ */
+ verify(verificationParameters = { signer: 0, trustedCerts: [], data: new ArrayBuffer(0) })
+ {
+ //region Check that "timeStampToken" exists
+ if(("timeStampToken" in this) === false)
+ return Promise.reject("timeStampToken is absent in TSP response");
+ //endregion
+
+ //region Check that "timeStampToken" has a right internal format
+ if(this.timeStampToken.contentType !== "1.2.840.113549.1.7.2") // Must be a CMS signed data
+ return Promise.reject(`Wrong format of timeStampToken: ${this.timeStampToken.contentType}`);
+ //endregion
+
+ //region Verify internal signed data value
+ const signed = new SignedData({ schema: this.timeStampToken.content });
+
+ return signed.verify(verificationParameters);
+ //endregion
+ }
+ //**********************************************************************************
+}
+//**************************************************************************************
diff --git a/pki.js/common.js b/pki.js/common.js
new file mode 100644
index 0000000..234f0a0
--- /dev/null
+++ b/pki.js/common.js
@@ -0,0 +1,572 @@
+import * as asn1js from "asn1js";
+import { utilConcatBuf } from "pvutils";
+import CryptoEngine from "./CryptoEngine.js";
+//**************************************************************************************
+//region Crypto engine related function
+//**************************************************************************************
+let engine = {
+ name: "none",
+ crypto: null,
+ subtle: null
+};
+//**************************************************************************************
+export function setEngine(name, crypto, subtle)
+{
+ //region We are in Node
+ // noinspection JSUnresolvedVariable
+ if((typeof process !== "undefined") && ("pid" in process) && (typeof global !== "undefined") && (typeof window === "undefined"))
+ {
+ // noinspection ES6ModulesDependencies, JSUnresolvedVariable
+ if(typeof global[process.pid] === "undefined")
+ {
+ // noinspection JSUnresolvedVariable
+ global[process.pid] = {};
+ }
+ else
+ {
+ // noinspection JSUnresolvedVariable
+ if(typeof global[process.pid] !== "object")
+ {
+ // noinspection JSUnresolvedVariable
+ throw new Error(`Name global.${process.pid} already exists and it is not an object`);
+ }
+ }
+
+ // noinspection JSUnresolvedVariable
+ if(typeof global[process.pid].pkijs === "undefined")
+ {
+ // noinspection JSUnresolvedVariable
+ global[process.pid].pkijs = {};
+ }
+ else
+ {
+ // noinspection JSUnresolvedVariable
+ if(typeof global[process.pid].pkijs !== "object")
+ {
+ // noinspection JSUnresolvedVariable
+ throw new Error(`Name global.${process.pid}.pkijs already exists and it is not an object`);
+ }
+ }
+
+ // noinspection JSUnresolvedVariable
+ global[process.pid].pkijs.engine = {
+ name: name,
+ crypto: crypto,
+ subtle: subtle
+ };
+ }
+ //endregion
+ //region We are in browser
+ else
+ {
+ engine = {
+ name: name,
+ crypto: crypto,
+ subtle: subtle
+ };
+ }
+ //endregion
+}
+//**************************************************************************************
+export function getEngine()
+{
+ //region We are in Node
+ // noinspection JSUnresolvedVariable
+ if((typeof process !== "undefined") && ("pid" in process) && (typeof global !== "undefined") && (typeof window === "undefined"))
+ {
+ let _engine;
+
+ try
+ {
+ // noinspection JSUnresolvedVariable
+ _engine = global[process.pid].pkijs.engine;
+ }
+ catch(ex)
+ {
+ throw new Error("Please call \"setEngine\" before call to \"getEngine\"");
+ }
+
+ return _engine;
+ }
+ //endregion
+
+ return engine;
+}
+//**************************************************************************************
+(function initCryptoEngine()
+{
+ if(typeof self !== "undefined")
+ {
+ if("crypto" in self)
+ {
+ let engineName = "webcrypto";
+
+ /**
+ * Standard crypto object
+ * @type {Object}
+ * @property {Object} [webkitSubtle] Subtle object from Apple
+ */
+ const cryptoObject = self.crypto;
+ let subtleObject;
+
+ // Apple Safari support
+ if("webkitSubtle" in self.crypto)
+ {
+ try
+ {
+ subtleObject = self.crypto.webkitSubtle;
+ }
+ catch(ex)
+ {
+ subtleObject = self.crypto.subtle;
+ }
+
+ engineName = "safari";
+ }
+
+ if("subtle" in self.crypto)
+ subtleObject = self.crypto.subtle;
+
+
+ if(typeof subtleObject === "undefined")
+ {
+ engine = {
+ name: engineName,
+ crypto: cryptoObject,
+ subtle: null
+ };
+ }
+ else
+ {
+ engine = {
+ name: engineName,
+ crypto: cryptoObject,
+ subtle: new CryptoEngine({name: engineName, crypto: self.crypto, subtle: subtleObject})
+ };
+ }
+ }
+ }
+
+ setEngine(engine.name, engine.crypto, engine.subtle);
+})();
+//**************************************************************************************
+//endregion
+//**************************************************************************************
+//region Declaration of common functions
+//**************************************************************************************
+/**
+ * Get crypto subtle from current "crypto engine" or "undefined"
+ * @returns {({decrypt, deriveKey, digest, encrypt, exportKey, generateKey, importKey, sign, unwrapKey, verify, wrapKey}|null)}
+ */
+export function getCrypto()
+{
+ const _engine = getEngine();
+
+ if(_engine.subtle !== null)
+ return _engine.subtle;
+
+ return undefined;
+}
+//**************************************************************************************
+/**
+ * Initialize input Uint8Array by random values (with help from current "crypto engine")
+ * @param {!Uint8Array} view
+ * @returns {*}
+ */
+export function getRandomValues(view)
+{
+ return getEngine().subtle.getRandomValues(view);
+}
+//**************************************************************************************
+/**
+ * Get OID for each specific algorithm
+ * @param {Object} algorithm
+ * @returns {string}
+ */
+export function getOIDByAlgorithm(algorithm)
+{
+ return getEngine().subtle.getOIDByAlgorithm(algorithm);
+}
+//**************************************************************************************
+/**
+ * Get default algorithm parameters for each kind of operation
+ * @param {string} algorithmName Algorithm name to get common parameters for
+ * @param {string} operation Kind of operation: "sign", "encrypt", "generatekey", "importkey", "exportkey", "verify"
+ * @returns {*}
+ */
+export function getAlgorithmParameters(algorithmName, operation)
+{
+ return getEngine().subtle.getAlgorithmParameters(algorithmName, operation);
+}
+//**************************************************************************************
+/**
+ * Create CMS ECDSA signature from WebCrypto ECDSA signature
+ * @param {ArrayBuffer} signatureBuffer WebCrypto result of "sign" function
+ * @returns {ArrayBuffer}
+ */
+export function createCMSECDSASignature(signatureBuffer)
+{
+ //region Initial check for correct length
+ if((signatureBuffer.byteLength % 2) !== 0)
+ return new ArrayBuffer(0);
+ //endregion
+
+ //region Initial variables
+ const length = signatureBuffer.byteLength / 2; // There are two equal parts inside incoming ArrayBuffer
+
+ const rBuffer = new ArrayBuffer(length);
+ const rView = new Uint8Array(rBuffer);
+ rView.set(new Uint8Array(signatureBuffer, 0, length));
+
+ const rInteger = new asn1js.Integer({ valueHex: rBuffer });
+
+ const sBuffer = new ArrayBuffer(length);
+ const sView = new Uint8Array(sBuffer);
+ sView.set(new Uint8Array(signatureBuffer, length, length));
+
+ const sInteger = new asn1js.Integer({ valueHex: sBuffer });
+ //endregion
+
+ return (new asn1js.Sequence({
+ value: [
+ rInteger.convertToDER(),
+ sInteger.convertToDER()
+ ]
+ })).toBER(false);
+}
+//**************************************************************************************
+/**
+ * String preparation function. In a future here will be realization of algorithm from RFC4518
+ * @param {string} inputString JavaScript string. As soon as for each ASN.1 string type we have a specific transformation function here we will work with pure JavaScript string
+ * @returns {string} Formated string
+ */
+export function stringPrep(inputString)
+{
+ //region Initial variables
+ let isSpace = false;
+ let cuttedResult = "";
+ //endregion
+
+ const result = inputString.trim(); // Trim input string
+
+ //region Change all sequence of SPACE down to SPACE char
+ for(let i = 0; i < result.length; i++)
+ {
+ if(result.charCodeAt(i) === 32)
+ {
+ if(isSpace === false)
+ isSpace = true;
+ }
+ else
+ {
+ if(isSpace)
+ {
+ cuttedResult += " ";
+ isSpace = false;
+ }
+
+ cuttedResult += result[i];
+ }
+ }
+ //endregion
+
+ return cuttedResult.toLowerCase();
+}
+//**************************************************************************************
+/**
+ * Create a single ArrayBuffer from CMS ECDSA signature
+ * @param {Sequence} cmsSignature ASN.1 SEQUENCE contains CMS ECDSA signature
+ * @returns {ArrayBuffer}
+ */
+export function createECDSASignatureFromCMS(cmsSignature)
+{
+ //region Check input variables
+ if((cmsSignature instanceof asn1js.Sequence) === false)
+ return new ArrayBuffer(0);
+
+ if(cmsSignature.valueBlock.value.length !== 2)
+ return new ArrayBuffer(0);
+
+ if((cmsSignature.valueBlock.value[0] instanceof asn1js.Integer) === false)
+ return new ArrayBuffer(0);
+
+ if((cmsSignature.valueBlock.value[1] instanceof asn1js.Integer) === false)
+ return new ArrayBuffer(0);
+ //endregion
+
+ const rValue = cmsSignature.valueBlock.value[0].convertFromDER();
+ const sValue = cmsSignature.valueBlock.value[1].convertFromDER();
+
+ //region Check the lengths of two parts are equal
+ switch(true)
+ {
+ case (rValue.valueBlock.valueHex.byteLength < sValue.valueBlock.valueHex.byteLength):
+ {
+ if((sValue.valueBlock.valueHex.byteLength - rValue.valueBlock.valueHex.byteLength) !== 1)
+ throw new Error("Incorrect DER integer decoding");
+
+ const correctedLength = sValue.valueBlock.valueHex.byteLength;
+
+ const rValueView = new Uint8Array(rValue.valueBlock.valueHex);
+
+ const rValueBufferCorrected = new ArrayBuffer(correctedLength);
+ const rValueViewCorrected = new Uint8Array(rValueBufferCorrected);
+
+ rValueViewCorrected.set(rValueView, 1);
+ rValueViewCorrected[0] = 0x00; // In order to be sure we do not have any garbage here
+
+ return utilConcatBuf(rValueBufferCorrected, sValue.valueBlock.valueHex);
+ }
+ case (rValue.valueBlock.valueHex.byteLength > sValue.valueBlock.valueHex.byteLength):
+ {
+ if((rValue.valueBlock.valueHex.byteLength - sValue.valueBlock.valueHex.byteLength) !== 1)
+ throw new Error("Incorrect DER integer decoding");
+
+ const correctedLength = rValue.valueBlock.valueHex.byteLength;
+
+ const sValueView = new Uint8Array(sValue.valueBlock.valueHex);
+
+ const sValueBufferCorrected = new ArrayBuffer(correctedLength);
+ const sValueViewCorrected = new Uint8Array(sValueBufferCorrected);
+
+ sValueViewCorrected.set(sValueView, 1);
+ sValueViewCorrected[0] = 0x00; // In order to be sure we do not have any garbage here
+
+ return utilConcatBuf(rValue.valueBlock.valueHex, sValueBufferCorrected);
+ }
+ default:
+ {
+ //region In case we have equal length and the length is not even with 2
+ if(rValue.valueBlock.valueHex.byteLength % 2)
+ {
+ const correctedLength = (rValue.valueBlock.valueHex.byteLength + 1);
+
+ const rValueView = new Uint8Array(rValue.valueBlock.valueHex);
+
+ const rValueBufferCorrected = new ArrayBuffer(correctedLength);
+ const rValueViewCorrected = new Uint8Array(rValueBufferCorrected);
+
+ rValueViewCorrected.set(rValueView, 1);
+ rValueViewCorrected[0] = 0x00; // In order to be sure we do not have any garbage here
+
+ const sValueView = new Uint8Array(sValue.valueBlock.valueHex);
+
+ const sValueBufferCorrected = new ArrayBuffer(correctedLength);
+ const sValueViewCorrected = new Uint8Array(sValueBufferCorrected);
+
+ sValueViewCorrected.set(sValueView, 1);
+ sValueViewCorrected[0] = 0x00; // In order to be sure we do not have any garbage here
+
+ return utilConcatBuf(rValueBufferCorrected, sValueBufferCorrected);
+ }
+ //endregion
+ }
+ }
+ //endregion
+
+ return utilConcatBuf(rValue.valueBlock.valueHex, sValue.valueBlock.valueHex);
+}
+//**************************************************************************************
+/**
+ * Get WebCrypto algorithm by wel-known OID
+ * @param {string} oid well-known OID to search for
+ * @returns {Object}
+ */
+export function getAlgorithmByOID(oid)
+{
+ return getEngine().subtle.getAlgorithmByOID(oid);
+}
+//**************************************************************************************
+/**
+ * Getting hash algorithm by signature algorithm
+ * @param {AlgorithmIdentifier} signatureAlgorithm Signature algorithm
+ * @returns {string}
+ */
+export function getHashAlgorithm(signatureAlgorithm)
+{
+ return getEngine().subtle.getHashAlgorithm(signatureAlgorithm);
+}
+//**************************************************************************************
+/**
+ * ANS X9.63 Key Derivation Function having a "Counter" as a parameter
+ * @param {string} hashFunction Used hash function
+ * @param {ArrayBuffer} Zbuffer ArrayBuffer containing ECDH shared secret to derive from
+ * @param {number} Counter
+ * @param {ArrayBuffer} SharedInfo Usually DER encoded "ECC_CMS_SharedInfo" structure
+ */
+export function kdfWithCounter(hashFunction, Zbuffer, Counter, SharedInfo)
+{
+ //region Check of input parameters
+ switch(hashFunction.toUpperCase())
+ {
+ case "SHA-1":
+ case "SHA-256":
+ case "SHA-384":
+ case "SHA-512":
+ break;
+ default:
+ return Promise.reject(`Unknown hash function: ${hashFunction}`);
+ }
+
+ if((Zbuffer instanceof ArrayBuffer) === false)
+ return Promise.reject("Please set \"Zbuffer\" as \"ArrayBuffer\"");
+
+ if(Zbuffer.byteLength === 0)
+ return Promise.reject("\"Zbuffer\" has zero length, error");
+
+ if((SharedInfo instanceof ArrayBuffer) === false)
+ return Promise.reject("Please set \"SharedInfo\" as \"ArrayBuffer\"");
+
+ if(Counter > 255)
+ return Promise.reject("Please set \"Counter\" variable to value less or equal to 255");
+ //endregion
+
+ //region Initial variables
+ const counterBuffer = new ArrayBuffer(4);
+ const counterView = new Uint8Array(counterBuffer);
+ counterView[0] = 0x00;
+ counterView[1] = 0x00;
+ counterView[2] = 0x00;
+ counterView[3] = Counter;
+
+ let combinedBuffer = new ArrayBuffer(0);
+ //endregion
+
+ //region Get a "crypto" extension
+ const crypto = getCrypto();
+ if(typeof crypto === "undefined")
+ return Promise.reject("Unable to create WebCrypto object");
+ //endregion
+
+ //region Create a combined ArrayBuffer for digesting
+ combinedBuffer = utilConcatBuf(combinedBuffer, Zbuffer);
+ combinedBuffer = utilConcatBuf(combinedBuffer, counterBuffer);
+ combinedBuffer = utilConcatBuf(combinedBuffer, SharedInfo);
+ //endregion
+
+ //region Return digest of combined ArrayBuffer and information about current counter
+ return crypto.digest({
+ name: hashFunction
+ },
+ combinedBuffer)
+ .then(result =>
+ ({
+ counter: Counter,
+ result
+ }));
+ //endregion
+}
+//**************************************************************************************
+/**
+ * ANS X9.63 Key Derivation Function
+ * @param {string} hashFunction Used hash function
+ * @param {ArrayBuffer} Zbuffer ArrayBuffer containing ECDH shared secret to derive from
+ * @param {number} keydatalen Length (!!! in BITS !!!) of used kew derivation function
+ * @param {ArrayBuffer} SharedInfo Usually DER encoded "ECC_CMS_SharedInfo" structure
+ */
+export function kdf(hashFunction, Zbuffer, keydatalen, SharedInfo)
+{
+ //region Initial variables
+ let hashLength = 0;
+ let maxCounter = 1;
+
+ const kdfArray = [];
+ //endregion
+
+ //region Check of input parameters
+ switch(hashFunction.toUpperCase())
+ {
+ case "SHA-1":
+ hashLength = 160; // In bits
+ break;
+ case "SHA-256":
+ hashLength = 256; // In bits
+ break;
+ case "SHA-384":
+ hashLength = 384; // In bits
+ break;
+ case "SHA-512":
+ hashLength = 512; // In bits
+ break;
+ default:
+ return Promise.reject(`Unknown hash function: ${hashFunction}`);
+ }
+
+ if((Zbuffer instanceof ArrayBuffer) === false)
+ return Promise.reject("Please set \"Zbuffer\" as \"ArrayBuffer\"");
+
+ if(Zbuffer.byteLength === 0)
+ return Promise.reject("\"Zbuffer\" has zero length, error");
+
+ if((SharedInfo instanceof ArrayBuffer) === false)
+ return Promise.reject("Please set \"SharedInfo\" as \"ArrayBuffer\"");
+ //endregion
+
+ //region Calculated maximum value of "Counter" variable
+ const quotient = keydatalen / hashLength;
+
+ if(Math.floor(quotient) > 0)
+ {
+ maxCounter = Math.floor(quotient);
+
+ if((quotient - maxCounter) > 0)
+ maxCounter++;
+ }
+ //endregion
+
+ //region Create an array of "kdfWithCounter"
+ for(let i = 1; i <= maxCounter; i++)
+ kdfArray.push(kdfWithCounter(hashFunction, Zbuffer, i, SharedInfo));
+ //endregion
+
+ //region Return combined digest with specified length
+ return Promise.all(kdfArray).then(incomingResult =>
+ {
+ //region Initial variables
+ let combinedBuffer = new ArrayBuffer(0);
+ let currentCounter = 1;
+ let found = true;
+ //endregion
+
+ //region Combine all buffer together
+ while(found)
+ {
+ found = false;
+
+ for(const result of incomingResult)
+ {
+ if(result.counter === currentCounter)
+ {
+ combinedBuffer = utilConcatBuf(combinedBuffer, result.result);
+ found = true;
+ break;
+ }
+ }
+
+ currentCounter++;
+ }
+ //endregion
+
+ //region Create output buffer with specified length
+ keydatalen >>= 3; // Divide by 8 since "keydatalen" is in bits
+
+ if(combinedBuffer.byteLength > keydatalen)
+ {
+ const newBuffer = new ArrayBuffer(keydatalen);
+ const newView = new Uint8Array(newBuffer);
+ const combinedView = new Uint8Array(combinedBuffer);
+
+ for(let i = 0; i < keydatalen; i++)
+ newView[i] = combinedView[i];
+
+ return newBuffer;
+ }
+
+ return combinedBuffer; // Since the situation when "combinedBuffer.byteLength < keydatalen" here we have only "combinedBuffer.byteLength === keydatalen"
+ //endregion
+ });
+ //endregion
+}
+//**************************************************************************************
+//endregion
+//**************************************************************************************
diff --git a/pki.js/index.js b/pki.js/index.js
new file mode 100644
index 0000000..844a541
--- /dev/null
+++ b/pki.js/index.js
@@ -0,0 +1,216 @@
+import { setEngine, getEngine, getCrypto, getRandomValues, getOIDByAlgorithm, getAlgorithmParameters, createCMSECDSASignature, stringPrep, createECDSASignatureFromCMS, getAlgorithmByOID, getHashAlgorithm, kdfWithCounter, kdf } from "./common.js";
+export { setEngine, getEngine, getCrypto, getRandomValues, getOIDByAlgorithm, getAlgorithmParameters, createCMSECDSASignature, stringPrep, createECDSASignatureFromCMS, getAlgorithmByOID, getHashAlgorithm, kdfWithCounter, kdf };
+import AccessDescription from "./AccessDescription.js";
+export { AccessDescription };
+import Accuracy from "./Accuracy.js";
+export { Accuracy };
+import AlgorithmIdentifier from "./AlgorithmIdentifier.js";
+export { AlgorithmIdentifier };
+import AltName from "./AltName.js";
+export { AltName };
+import Attribute from "./Attribute.js";
+export { Attribute };
+import AttributeTypeAndValue from "./AttributeTypeAndValue.js";
+export { AttributeTypeAndValue };
+import AuthenticatedSafe from "./AuthenticatedSafe.js";
+export { AuthenticatedSafe };
+import AuthorityKeyIdentifier from "./AuthorityKeyIdentifier.js";
+export { AuthorityKeyIdentifier };
+import BasicConstraints from "./BasicConstraints.js";
+export { BasicConstraints };
+import BasicOCSPResponse from "./BasicOCSPResponse.js";
+export { BasicOCSPResponse };
+import CRLBag from "./CRLBag.js";
+export { CRLBag };
+import CRLDistributionPoints from "./CRLDistributionPoints.js";
+export { CRLDistributionPoints };
+import CertBag from "./CertBag.js";
+export { CertBag };
+import CertID from "./CertID.js";
+export { CertID };
+import Certificate from "./Certificate.js";
+export { Certificate };
+import CertificateChainValidationEngine from "./CertificateChainValidationEngine.js";
+export { CertificateChainValidationEngine };
+import CertificatePolicies from "./CertificatePolicies.js";
+export { CertificatePolicies };
+import CertificateRevocationList from "./CertificateRevocationList.js";
+export { CertificateRevocationList };
+import CertificateSet from "./CertificateSet.js";
+export { CertificateSet };
+import CertificationRequest from "./CertificationRequest.js";
+export { CertificationRequest };
+import ContentInfo from "./ContentInfo.js";
+export { ContentInfo };
+import CryptoEngine from "./CryptoEngine.js";
+export { CryptoEngine };
+import DigestInfo from "./DigestInfo.js";
+export { DigestInfo };
+import DistributionPoint from "./DistributionPoint.js";
+export { DistributionPoint };
+import ECCCMSSharedInfo from "./ECCCMSSharedInfo.js";
+export { ECCCMSSharedInfo };
+import ECPrivateKey from "./ECPrivateKey.js";
+export { ECPrivateKey };
+import ECPublicKey from "./ECPublicKey.js";
+export { ECPublicKey };
+import EncapsulatedContentInfo from "./EncapsulatedContentInfo.js";
+export { EncapsulatedContentInfo };
+import EncryptedContentInfo from "./EncryptedContentInfo.js";
+export { EncryptedContentInfo };
+import EncryptedData from "./EncryptedData.js";
+export { EncryptedData };
+import EnvelopedData from "./EnvelopedData.js";
+export { EnvelopedData };
+import ExtKeyUsage from "./ExtKeyUsage.js";
+export { ExtKeyUsage };
+import Extension from "./Extension.js";
+export { Extension };
+import Extensions from "./Extensions.js";
+export { Extensions };
+import GeneralName from "./GeneralName.js";
+export { GeneralName };
+import GeneralNames from "./GeneralNames.js";
+export { GeneralNames };
+import GeneralSubtree from "./GeneralSubtree.js";
+export { GeneralSubtree };
+import InfoAccess from "./InfoAccess.js";
+export { InfoAccess };
+import IssuerAndSerialNumber from "./IssuerAndSerialNumber.js";
+export { IssuerAndSerialNumber };
+import IssuingDistributionPoint from "./IssuingDistributionPoint.js";
+export { IssuingDistributionPoint };
+import KEKIdentifier from "./KEKIdentifier.js";
+export { KEKIdentifier };
+import KEKRecipientInfo from "./KEKRecipientInfo.js";
+export { KEKRecipientInfo };
+import KeyAgreeRecipientIdentifier from "./KeyAgreeRecipientIdentifier.js";
+export { KeyAgreeRecipientIdentifier };
+import KeyAgreeRecipientInfo from "./KeyAgreeRecipientInfo.js";
+export { KeyAgreeRecipientInfo };
+import KeyBag from "./KeyBag.js";
+export { KeyBag };
+import KeyTransRecipientInfo from "./KeyTransRecipientInfo.js";
+export { KeyTransRecipientInfo };
+import MacData from "./MacData.js";
+export { MacData };
+import MessageImprint from "./MessageImprint.js";
+export { MessageImprint };
+import NameConstraints from "./NameConstraints.js";
+export { NameConstraints };
+import OCSPRequest from "./OCSPRequest.js";
+export { OCSPRequest };
+import OCSPResponse from "./OCSPResponse.js";
+export { OCSPResponse };
+import OriginatorIdentifierOrKey from "./OriginatorIdentifierOrKey.js";
+export { OriginatorIdentifierOrKey };
+import OriginatorInfo from "./OriginatorInfo.js";
+export { OriginatorInfo };
+import OriginatorPublicKey from "./OriginatorPublicKey.js";
+export { OriginatorPublicKey };
+import OtherCertificateFormat from "./OtherCertificateFormat.js";
+export { OtherCertificateFormat };
+import OtherKeyAttribute from "./OtherKeyAttribute.js";
+export { OtherKeyAttribute };
+import OtherPrimeInfo from "./OtherPrimeInfo.js";
+export { OtherPrimeInfo };
+import OtherRecipientInfo from "./OtherRecipientInfo.js";
+export { OtherRecipientInfo };
+import OtherRevocationInfoFormat from "./OtherRevocationInfoFormat.js";
+export { OtherRevocationInfoFormat };
+import PBES2Params from "./PBES2Params.js";
+export { PBES2Params };
+import PBKDF2Params from "./PBKDF2Params.js";
+export { PBKDF2Params };
+import PFX from "./PFX.js";
+export { PFX };
+import PKCS8ShroudedKeyBag from "./PKCS8ShroudedKeyBag.js";
+export { PKCS8ShroudedKeyBag };
+import PKIStatusInfo from "./PKIStatusInfo.js";
+export { PKIStatusInfo };
+import PasswordRecipientinfo from "./PasswordRecipientinfo.js";
+export { PasswordRecipientinfo };
+import PolicyConstraints from "./PolicyConstraints.js";
+export { PolicyConstraints };
+import PolicyInformation from "./PolicyInformation.js";
+export { PolicyInformation };
+import PolicyMapping from "./PolicyMapping.js";
+export { PolicyMapping };
+import PolicyMappings from "./PolicyMappings.js";
+export { PolicyMappings };
+import PolicyQualifierInfo from "./PolicyQualifierInfo.js";
+export { PolicyQualifierInfo };
+import PrivateKeyInfo from "./PrivateKeyInfo.js";
+export { PrivateKeyInfo };
+import PrivateKeyUsagePeriod from "./PrivateKeyUsagePeriod.js";
+export { PrivateKeyUsagePeriod };
+import PublicKeyInfo from "./PublicKeyInfo.js";
+export { PublicKeyInfo };
+import RSAESOAEPParams from "./RSAESOAEPParams.js";
+export { RSAESOAEPParams };
+import RSAPrivateKey from "./RSAPrivateKey.js";
+export { RSAPrivateKey };
+import RSAPublicKey from "./RSAPublicKey.js";
+export { RSAPublicKey };
+import RSASSAPSSParams from "./RSASSAPSSParams.js";
+export { RSASSAPSSParams };
+import RecipientEncryptedKey from "./RecipientEncryptedKey.js";
+export { RecipientEncryptedKey };
+import RecipientEncryptedKeys from "./RecipientEncryptedKeys.js";
+export { RecipientEncryptedKeys };
+import RecipientIdentifier from "./RecipientIdentifier.js";
+export { RecipientIdentifier };
+import RecipientInfo from "./RecipientInfo.js";
+export { RecipientInfo };
+import RecipientKeyIdentifier from "./RecipientKeyIdentifier.js";
+export { RecipientKeyIdentifier };
+import RelativeDistinguishedNames from "./RelativeDistinguishedNames.js";
+export { RelativeDistinguishedNames };
+import Request from "./Request.js";
+export { Request };
+import ResponseBytes from "./ResponseBytes.js";
+export { ResponseBytes };
+import ResponseData from "./ResponseData.js";
+export { ResponseData };
+import RevocationInfoChoices from "./RevocationInfoChoices.js";
+export { RevocationInfoChoices };
+import RevokedCertificate from "./RevokedCertificate.js";
+export { RevokedCertificate };
+import SafeBag from "./SafeBag.js";
+export { SafeBag };
+import SafeContents from "./SafeContents.js";
+export { SafeContents };
+import SecretBag from "./SecretBag.js";
+export { SecretBag };
+import Signature from "./Signature.js";
+export { Signature };
+import SignedAndUnsignedAttributes from "./SignedAndUnsignedAttributes.js";
+export { SignedAndUnsignedAttributes };
+import SignedData from "./SignedData.js";
+export { SignedData };
+import SignerInfo from "./SignerInfo.js";
+export { SignerInfo };
+import SingleResponse from "./SingleResponse.js";
+export { SingleResponse };
+import SubjectDirectoryAttributes from "./SubjectDirectoryAttributes.js";
+export { SubjectDirectoryAttributes };
+import TBSRequest from "./TBSRequest.js";
+export { TBSRequest };
+import TSTInfo from "./TSTInfo.js";
+export { TSTInfo };
+import Time from "./Time.js";
+export { Time };
+import TimeStampReq from "./TimeStampReq.js";
+export { TimeStampReq };
+import TimeStampResp from "./TimeStampResp.js";
+export { TimeStampResp };
+import SignedCertificateTimestampList from "./SignedCertificateTimestampList.js";
+import { SignedCertificateTimestamp, verifySCTsForCertificate } from "./SignedCertificateTimestampList.js";
+export { SignedCertificateTimestampList, SignedCertificateTimestamp, verifySCTsForCertificate };
+import CertificateTemplate from "./CertificateTemplate.js";
+export { CertificateTemplate };
+import CAVersion from "./CAVersion.js";
+export { CAVersion };
+import { QCStatement }from "./QCStatements.js";
+import QCStatements from "./QCStatements.js";
+export { QCStatement, QCStatements };