pkijs
Version:
Public Key Infrastructure (PKI) is the basis of how identity and key management is performed on the web today. PKIjs is a pure JavaScript library implementing the formats that are used in PKI applications. It is built on WebCrypto and aspires to make it p
678 lines (557 loc) • 24.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _asn1js = require("asn1js");
var asn1js = _interopRequireWildcard(_asn1js);
var _pvutils = require("pvutils");
var _common = require("./common.js");
var _ContentInfo = require("./ContentInfo.js");
var _ContentInfo2 = _interopRequireDefault(_ContentInfo);
var _MacData = require("./MacData.js");
var _MacData2 = _interopRequireDefault(_MacData);
var _DigestInfo = require("./DigestInfo.js");
var _DigestInfo2 = _interopRequireDefault(_DigestInfo);
var _AlgorithmIdentifier = require("./AlgorithmIdentifier.js");
var _AlgorithmIdentifier2 = _interopRequireDefault(_AlgorithmIdentifier);
var _SignedData = require("./SignedData.js");
var _SignedData2 = _interopRequireDefault(_SignedData);
var _EncapsulatedContentInfo = require("./EncapsulatedContentInfo.js");
var _EncapsulatedContentInfo2 = _interopRequireDefault(_EncapsulatedContentInfo);
var _Attribute = require("./Attribute.js");
var _Attribute2 = _interopRequireDefault(_Attribute);
var _SignerInfo = require("./SignerInfo.js");
var _SignerInfo2 = _interopRequireDefault(_SignerInfo);
var _IssuerAndSerialNumber = require("./IssuerAndSerialNumber.js");
var _IssuerAndSerialNumber2 = _interopRequireDefault(_IssuerAndSerialNumber);
var _SignedAndUnsignedAttributes = require("./SignedAndUnsignedAttributes.js");
var _SignedAndUnsignedAttributes2 = _interopRequireDefault(_SignedAndUnsignedAttributes);
var _AuthenticatedSafe = require("./AuthenticatedSafe.js");
var _AuthenticatedSafe2 = _interopRequireDefault(_AuthenticatedSafe);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
//**************************************************************************************
/**
* Class from RFC7292
*/
var PFX = function () {
//**********************************************************************************
/**
* Constructor for PFX class
* @param {Object} [parameters={}]
* @property {Object} [schema] asn1js parsed value
*/
function PFX() {
var parameters = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_classCallCheck(this, PFX);
//region Internal properties of the object
/**
* @type {number}
* @description version
*/
this.version = (0, _pvutils.getParametersValue)(parameters, "version", PFX.defaultValues("version"));
/**
* @type {ContentInfo}
* @description authSafe
*/
this.authSafe = (0, _pvutils.getParametersValue)(parameters, "authSafe", PFX.defaultValues("authSafe"));
if ("macData" in parameters)
/**
* @type {MacData}
* @description macData
*/
this.macData = (0, _pvutils.getParametersValue)(parameters, "macData", PFX.defaultValues("macData"));
if ("parsedValue" in parameters)
/**
* @type {*}
* @description parsedValue
*/
this.parsedValue = (0, _pvutils.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
*/
_createClass(PFX, [{
key: "fromSchema",
//**********************************************************************************
/**
* Convert parsed asn1js object into current class
* @param {!Object} schema
*/
value: function fromSchema(schema) {
//region Check the schema is valid
var 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 _ContentInfo2.default({ schema: asn1.result.authSafe });
if ("macData" in asn1.result) this.macData = new _MacData2.default({ schema: asn1.result.macData });
//endregion
}
//**********************************************************************************
/**
* Convert current object to asn1js object and set correct values
* @returns {Object} asn1js object
*/
}, {
key: "toSchema",
value: function toSchema() {
//region Construct and return new ASN.1 schema for this object
var 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}
*/
}, {
key: "toJSON",
value: function toJSON() {
var 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"
*/
}, {
key: "makeInternalValues",
value: function makeInternalValues() {
var _this = this;
var parameters = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
//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
var sequence = Promise.resolve();
//endregion
//region Get a "crypto" extension
var crypto = (0, _common.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
var saltBuffer = new ArrayBuffer(64);
var saltView = new Uint8Array(saltBuffer);
(0, _common.getRandomValues)(saltView);
var data = this.parsedValue.authenticatedSafe.toSchema().toBER(false);
this.authSafe = new _ContentInfo2.default({
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
var engine = (0, _common.getEngine)();
if ("stampDataWithPassword" in engine.subtle === false) return Promise.reject("No support for \"stampDataWithPassword\" in current engine \"" + engine.name + "\"");
sequence = sequence.then(function () {
return engine.subtle.stampDataWithPassword({
password: parameters.password,
hashAlgorithm: parameters.hmacHashAlgorithm,
salt: saltBuffer,
iterationCount: parameters.iterations,
contentToStamp: data
});
});
//endregion
//region Make "MacData" values
sequence = sequence.then(function (result) {
_this.macData = new _MacData2.default({
mac: new _DigestInfo2.default({
digestAlgorithm: new _AlgorithmIdentifier2.default({
algorithmId: (0, _common.getOIDByAlgorithm)({ name: parameters.hmacHashAlgorithm })
}),
digest: new asn1js.OctetString({ valueHex: result })
}),
macSalt: new asn1js.OctetString({ valueHex: saltBuffer }),
iterations: parameters.iterations
});
}, function (error) {
return 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.
var toBeSigned = this.parsedValue.authenticatedSafe.toSchema().toBER(false);
//endregion
//region Initial variables
var cmsSigned = new _SignedData2.default({
version: 1,
encapContentInfo: new _EncapsulatedContentInfo2.default({
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(function () {
return crypto.digest({ name: parameters.hashAlgorithm }, new Uint8Array(toBeSigned));
});
//endregion
//region Combine all signed extensions
sequence = sequence.then(function (result) {
//region Initial variables
var signedAttr = [];
//endregion
//region contentType
signedAttr.push(new _Attribute2.default({
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 _Attribute2.default({
type: "1.2.840.113549.1.9.5",
values: [new asn1js.UTCTime({ valueDate: new Date() })]
}));
//endregion
//region messageDigest
signedAttr.push(new _Attribute2.default({
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 _SignerInfo2.default({
version: 1,
sid: new _IssuerAndSerialNumber2.default({
issuer: parameters.signingCertificate.issuer,
serialNumber: parameters.signingCertificate.serialNumber
}),
signedAttrs: new _SignedAndUnsignedAttributes2.default({
type: 0,
attributes: signedAttr
})
}));
//endregion
}, function (error) {
return Promise.reject("Error during making digest for message: " + error);
});
//endregion
//endregion
//region Signing CMS Signed Data
sequence = sequence.then(function () {
return cmsSigned.sign(parameters.privateKey, 0, parameters.hashAlgorithm);
});
//endregion
//region Making final CMS_CONTENT_INFO type
sequence = sequence.then(function () {
_this.authSafe = new _ContentInfo2.default({
contentType: "1.2.840.113549.1.7.2",
content: cmsSigned.toSchema(true)
});
}, function (error) {
return 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;
}
//**********************************************************************************
}, {
key: "parseInternalValues",
value: function parseInternalValues(parameters) {
var _this2 = this;
//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
var sequence = Promise.resolve();
//endregion
//region Get a "crypto" extension
var crypto = (0, _common.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
var authSafeContent = new ArrayBuffer(0);
if (this.authSafe.content.valueBlock.isConstructed) {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = this.authSafe.content.valueBlock.value[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var contentValue = _step.value;
authSafeContent = (0, _pvutils.utilConcatBuf)(authSafeContent, contentValue.valueBlock.valueHex);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
} else authSafeContent = this.authSafe.content.valueBlock.valueHex;
//endregion
//region Parse internal ASN.1 data
var 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 _AuthenticatedSafe2.default({ 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
var hashAlgorithm = (0, _common.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
var engine = (0, _common.getEngine)();
sequence = sequence.then(function () {
return engine.subtle.verifyDataStampedWithPassword({
password: parameters.password,
hashAlgorithm: hashAlgorithm.name,
salt: _this2.macData.macSalt.valueBlock.valueHex,
iterationCount: _this2.macData.iterations,
contentToVerify: authSafeContent,
signatureToVerify: _this2.macData.mac.digest.valueBlock.valueHex
});
});
//endregion
//region Verify HMAC signature
sequence = sequence.then(function (result) {
if (result === false) return Promise.reject("Integrity for the PKCS#12 data is broken!");
return Promise.resolve();
}, function (error) {
return 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
var cmsSigned = new _SignedData2.default({ 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
var data = new ArrayBuffer(0);
if (cmsSigned.encapContentInfo.eContent.idBlock.isConstructed === false) data = cmsSigned.encapContentInfo.eContent.valueBlock.valueHex;else {
for (var i = 0; i < cmsSigned.encapContentInfo.eContent.valueBlock.value.length; i++) {
data = (0, _pvutils.utilConcatBuf)(data, cmsSigned.encapContentInfo.eContent.valueBlock.value[i].valueBlock.valueHex);
}
}
//endregion
//region Parse internal ASN.1 data
var _asn = asn1js.fromBER(data);
if (_asn.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 _AuthenticatedSafe2.default({ schema: _asn.result });
//endregion
//region Check integrity
sequence = sequence.then(function () {
return cmsSigned.verify({ signer: 0, checkChain: false });
}).then(function (result) {
if (result === false) return Promise.reject("Integrity for the PKCS#12 data is broken!");
return Promise.resolve();
}, function (error) {
return 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(function () {
return _this2;
}, function (error) {
return Promise.reject("Error during parsing: " + error);
});
//endregion
}
//**********************************************************************************
}], [{
key: "defaultValues",
value: function defaultValues(memberName) {
switch (memberName) {
case "version":
return 3;
case "authSafe":
return new _ContentInfo2.default();
case "macData":
return new _MacData2.default();
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
*/
}, {
key: "compareWithDefault",
value: function compareWithDefault(memberName, memberValue) {
switch (memberName) {
case "version":
return memberValue === PFX.defaultValues(memberName);
case "authSafe":
return _ContentInfo2.default.compareWithDefault("contentType", memberValue.contentType) && _ContentInfo2.default.compareWithDefault("content", memberValue.content);
case "macData":
return _MacData2.default.compareWithDefault("mac", memberValue.mac) && _MacData2.default.compareWithDefault("macSalt", memberValue.macSalt) && _MacData2.default.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 asn1js schema for current class
* @param {Object} parameters Input parameters for the schema
* @returns {Object} asn1js schema object
*/
}, {
key: "schema",
value: function schema() {
var parameters = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
//PFX ::= SEQUENCE {
// version INTEGER {v3(3)}(v3,...),
// authSafe ContentInfo,
// macData MacData OPTIONAL
//}
/**
* @type {Object}
* @property {string} [blockName]
* @property {string} [version]
* @property {string} [authSafe]
* @property {string} [macData]
*/
var names = (0, _pvutils.getParametersValue)(parameters, "names", {});
return new asn1js.Sequence({
name: names.blockName || "",
value: [new asn1js.Integer({ name: names.version || "version" }), _ContentInfo2.default.schema(names.authSafe || {
names: {
blockName: "authSafe"
}
}), _MacData2.default.schema(names.macData || {
names: {
blockName: "macData",
optional: true
}
})]
});
}
}]);
return PFX;
}();
//**************************************************************************************
exports.default = PFX;
//# sourceMappingURL=PFX.js.map