UNPKG

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

1,358 lines (1,137 loc) 61.3 kB
"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 _OriginatorInfo = require("./OriginatorInfo.js"); var _OriginatorInfo2 = _interopRequireDefault(_OriginatorInfo); var _RecipientInfo = require("./RecipientInfo.js"); var _RecipientInfo2 = _interopRequireDefault(_RecipientInfo); var _EncryptedContentInfo = require("./EncryptedContentInfo.js"); var _EncryptedContentInfo2 = _interopRequireDefault(_EncryptedContentInfo); var _Attribute = require("./Attribute.js"); var _Attribute2 = _interopRequireDefault(_Attribute); var _AlgorithmIdentifier = require("./AlgorithmIdentifier.js"); var _AlgorithmIdentifier2 = _interopRequireDefault(_AlgorithmIdentifier); var _RSAESOAEPParams = require("./RSAESOAEPParams.js"); var _RSAESOAEPParams2 = _interopRequireDefault(_RSAESOAEPParams); var _KeyTransRecipientInfo = require("./KeyTransRecipientInfo.js"); var _KeyTransRecipientInfo2 = _interopRequireDefault(_KeyTransRecipientInfo); var _IssuerAndSerialNumber = require("./IssuerAndSerialNumber.js"); var _IssuerAndSerialNumber2 = _interopRequireDefault(_IssuerAndSerialNumber); var _RecipientEncryptedKey = require("./RecipientEncryptedKey.js"); var _RecipientEncryptedKey2 = _interopRequireDefault(_RecipientEncryptedKey); var _KeyAgreeRecipientIdentifier = require("./KeyAgreeRecipientIdentifier.js"); var _KeyAgreeRecipientIdentifier2 = _interopRequireDefault(_KeyAgreeRecipientIdentifier); var _KeyAgreeRecipientInfo = require("./KeyAgreeRecipientInfo.js"); var _KeyAgreeRecipientInfo2 = _interopRequireDefault(_KeyAgreeRecipientInfo); var _RecipientEncryptedKeys = require("./RecipientEncryptedKeys.js"); var _RecipientEncryptedKeys2 = _interopRequireDefault(_RecipientEncryptedKeys); var _KEKRecipientInfo = require("./KEKRecipientInfo.js"); var _KEKRecipientInfo2 = _interopRequireDefault(_KEKRecipientInfo); var _KEKIdentifier = require("./KEKIdentifier.js"); var _KEKIdentifier2 = _interopRequireDefault(_KEKIdentifier); var _PBKDF2Params = require("./PBKDF2Params.js"); var _PBKDF2Params2 = _interopRequireDefault(_PBKDF2Params); var _PasswordRecipientinfo = require("./PasswordRecipientinfo.js"); var _PasswordRecipientinfo2 = _interopRequireDefault(_PasswordRecipientinfo); var _ECCCMSSharedInfo = require("./ECCCMSSharedInfo.js"); var _ECCCMSSharedInfo2 = _interopRequireDefault(_ECCCMSSharedInfo); var _OriginatorIdentifierOrKey = require("./OriginatorIdentifierOrKey.js"); var _OriginatorIdentifierOrKey2 = _interopRequireDefault(_OriginatorIdentifierOrKey); var _OriginatorPublicKey = require("./OriginatorPublicKey.js"); var _OriginatorPublicKey2 = _interopRequireDefault(_OriginatorPublicKey); 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 RFC5652 */ var EnvelopedData = function () { //********************************************************************************** /** * Constructor for EnvelopedData class * @param {Object} [parameters={}] * @property {Object} [schema] asn1js parsed value */ function EnvelopedData() { var parameters = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _classCallCheck(this, EnvelopedData); //region Internal properties of the object /** * @type {number} * @description version */ this.version = (0, _pvutils.getParametersValue)(parameters, "version", EnvelopedData.defaultValues("version")); if ("originatorInfo" in parameters) /** * @type {OriginatorInfo} * @description originatorInfo */ this.originatorInfo = (0, _pvutils.getParametersValue)(parameters, "originatorInfo", EnvelopedData.defaultValues("originatorInfo")); /** * @type {Array.<RecipientInfo>} * @description recipientInfos */ this.recipientInfos = (0, _pvutils.getParametersValue)(parameters, "recipientInfos", EnvelopedData.defaultValues("recipientInfos")); /** * @type {EncryptedContentInfo} * @description encryptedContentInfo */ this.encryptedContentInfo = (0, _pvutils.getParametersValue)(parameters, "encryptedContentInfo", EnvelopedData.defaultValues("encryptedContentInfo")); if ("unprotectedAttrs" in parameters) /** * @type {Array.<Attribute>} * @description unprotectedAttrs */ this.unprotectedAttrs = (0, _pvutils.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 */ _createClass(EnvelopedData, [{ 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, 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) { asn1.result.originatorInfo.idBlock.tagClass = 1; // UNIVERSAL asn1.result.originatorInfo.idBlock.tagNumber = 16; // SEQUENCE this.originatorInfo = new _OriginatorInfo2.default({ schema: asn1.result.originatorInfo }); } this.recipientInfos = Array.from(asn1.result.recipientInfos, function (element) { return new _RecipientInfo2.default({ schema: element }); }); this.encryptedContentInfo = new _EncryptedContentInfo2.default({ schema: asn1.result.encryptedContentInfo }); if ("unprotectedAttrs" in asn1.result) this.unprotectedAttrs = Array.from(asn1.result.unprotectedAttrs, function (element) { return new _Attribute2.default({ schema: element }); }); //endregion } //********************************************************************************** /** * Convert current object to asn1js object and set correct values * @returns {Object} asn1js object */ }, { key: "toSchema", value: function toSchema() { //region Create array for output sequence var 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, function (element) { return 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, function (element) { return 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} */ }, { key: "toJSON", value: function toJSON() { var _object = { version: this.version }; if ("originatorInfo" in this) _object.originatorInfo = this.originatorInfo.toJSON(); _object.recipientInfos = Array.from(this.recipientInfos, function (element) { return element.toJSON(); }); _object.encryptedContentInfo = this.encryptedContentInfo.toJSON(); if ("unprotectedAttrs" in this) _object.unprotectedAttrs = Array.from(this.unprotectedAttrs, function (element) { return 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. */ }, { key: "addRecipientByCertificate", value: function addRecipientByCertificate(certificate, parameters, variant) { //region Initial variables var 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 var oaepOID = (0, _common.getOIDByAlgorithm)({ name: "RSA-OAEP" }); if (oaepOID === "") throw new Error("Can not find OID for OAEP"); //endregion //region RSAES-OAEP-params var hashOID = (0, _common.getOIDByAlgorithm)({ name: encryptionParameters.oaepHashAlgorithm }); if (hashOID === "") throw new Error("Unknown OAEP hash algorithm: " + encryptionParameters.oaepHashAlgorithm); var hashAlgorithm = new _AlgorithmIdentifier2.default({ algorithmId: hashOID, algorithmParams: new asn1js.Null() }); var rsaOAEPParams = new _RSAESOAEPParams2.default({ hashAlgorithm: hashAlgorithm, maskGenAlgorithm: new _AlgorithmIdentifier2.default({ algorithmId: "1.2.840.113549.1.1.8", // id-mgf1 algorithmParams: hashAlgorithm.toSchema() }) }); //endregion //region KeyTransRecipientInfo var keyInfo = new _KeyTransRecipientInfo2.default({ version: 0, rid: new _IssuerAndSerialNumber2.default({ issuer: certificate.issuer, serialNumber: certificate.serialNumber }), keyEncryptionAlgorithm: new _AlgorithmIdentifier2.default({ 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 _RecipientInfo2.default({ variant: 1, value: keyInfo })); //endregion } break; case 2: // Key agreement scheme { //region RecipientEncryptedKey var encryptedKey = new _RecipientEncryptedKey2.default({ rid: new _KeyAgreeRecipientIdentifier2.default({ variant: 1, value: new _IssuerAndSerialNumber2.default({ issuer: certificate.issuer, serialNumber: certificate.serialNumber }) }) // "encryptedKey" will be calculated in "encrypt" function }); //endregion //region keyEncryptionAlgorithm var aesKWoid = (0, _common.getOIDByAlgorithm)({ name: "AES-KW", length: encryptionParameters.kekEncryptionLength }); if (aesKWoid === "") throw new Error("Unknown length for key encryption algorithm: " + encryptionParameters.kekEncryptionLength); var aesKW = new _AlgorithmIdentifier2.default({ algorithmId: aesKWoid, algorithmParams: new asn1js.Null() }); //endregion //region KeyAgreeRecipientInfo var ecdhOID = (0, _common.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 var ukmBuffer = new ArrayBuffer(64); var ukmView = new Uint8Array(ukmBuffer); (0, _common.getRandomValues)(ukmView); // Generate random values in 64 bytes long buffer var _keyInfo = new _KeyAgreeRecipientInfo2.default({ version: 3, // "originator" will be calculated in "encrypt" function because ephemeral key would be generated there ukm: new asn1js.OctetString({ valueHex: ukmBuffer }), keyEncryptionAlgorithm: new _AlgorithmIdentifier2.default({ algorithmId: ecdhOID, algorithmParams: aesKW.toSchema() }), recipientEncryptedKeys: new _RecipientEncryptedKeys2.default({ encryptedKeys: [encryptedKey] }), recipientCertificate: certificate }); //endregion //region Final values for "CMS_ENVELOPED_DATA" this.recipientInfos.push(new _RecipientInfo2.default({ 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. */ }, { key: "addRecipientByPreDefinedData", value: function addRecipientByPreDefinedData(preDefinedData, parameters, variant) { //region Initial variables var 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) { var keyIdentifierBuffer = new ArrayBuffer(16); var keyIdentifierView = new Uint8Array(keyIdentifierBuffer); (0, _common.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 var kekOID = (0, _common.getOIDByAlgorithm)(encryptionParameters.keyEncryptionAlgorithm); if (kekOID === "") throw new Error("Incorrect value for \"keyEncryptionAlgorithm\""); //endregion //region KEKRecipientInfo var keyInfo = new _KEKRecipientInfo2.default({ version: 4, kekid: new _KEKIdentifier2.default({ keyIdentifier: new asn1js.OctetString({ valueHex: encryptionParameters.keyIdentifier }) }), keyEncryptionAlgorithm: new _AlgorithmIdentifier2.default({ 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 _RecipientInfo2.default({ variant: 3, value: keyInfo })); //endregion } break; case 2: // PasswordRecipientinfo { //region keyDerivationAlgorithm var pbkdf2OID = (0, _common.getOIDByAlgorithm)({ name: "PBKDF2" }); if (pbkdf2OID === "") throw new Error("Can not find OID for PBKDF2"); //endregion //region Salt var saltBuffer = new ArrayBuffer(64); var saltView = new Uint8Array(saltBuffer); (0, _common.getRandomValues)(saltView); //endregion //region HMAC-based algorithm var hmacOID = (0, _common.getOIDByAlgorithm)({ name: "HMAC", hash: { name: encryptionParameters.hmacHashAlgorithm } }); if (hmacOID === "") throw new Error("Incorrect value for \"hmacHashAlgorithm\": " + encryptionParameters.hmacHashAlgorithm); //endregion //region PBKDF2-params var pbkdf2Params = new _PBKDF2Params2.default({ salt: new asn1js.OctetString({ valueHex: saltBuffer }), iterationCount: encryptionParameters.iterationCount, prf: new _AlgorithmIdentifier2.default({ algorithmId: hmacOID, algorithmParams: new asn1js.Null() }) }); //endregion //region keyEncryptionAlgorithm var _kekOID = (0, _common.getOIDByAlgorithm)(encryptionParameters.keyEncryptionAlgorithm); if (_kekOID === "") throw new Error("Incorrect value for \"keyEncryptionAlgorithm\""); //endregion //region PasswordRecipientinfo var _keyInfo2 = new _PasswordRecipientinfo2.default({ version: 0, keyDerivationAlgorithm: new _AlgorithmIdentifier2.default({ algorithmId: pbkdf2OID, algorithmParams: pbkdf2Params.toSchema() }), keyEncryptionAlgorithm: new _AlgorithmIdentifier2.default({ 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 _RecipientInfo2.default({ variant: 4, value: _keyInfo2 })); //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} */ }, { key: "encrypt", value: function encrypt(contentEncryptionAlgorithm, contentToEncrypt) { var _this2 = this; //region Initial variables var sequence = Promise.resolve(); var ivBuffer = new ArrayBuffer(16); // For AES we need IV 16 bytes long var ivView = new Uint8Array(ivBuffer); (0, _common.getRandomValues)(ivView); var contentView = new Uint8Array(contentToEncrypt); var sessionKey = void 0; var encryptedContent = void 0; var exportedSessionKey = void 0; var recipientsPromises = []; var _this = this; //endregion //region Check for input parameters var contentEncryptionOID = (0, _common.getOIDByAlgorithm)(contentEncryptionAlgorithm); if (contentEncryptionOID === "") return Promise.reject("Wrong \"contentEncryptionAlgorithm\" value"); //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 Generate new content encryption key sequence = sequence.then(function () { return crypto.generateKey(contentEncryptionAlgorithm, true, ["encrypt"]); }); //endregion //region Encrypt content sequence = sequence.then(function (result) { sessionKey = result; return crypto.encrypt({ name: contentEncryptionAlgorithm.name, iv: ivView }, sessionKey, contentView); }, function (error) { return Promise.reject(error); }); //endregion //region Export raw content of content encryption key sequence = sequence.then(function (result) { //region Create output OCTETSTRING with encrypted content encryptedContent = result; //endregion return crypto.exportKey("raw", sessionKey); }, function (error) { return Promise.reject(error); }).then(function (result) { exportedSessionKey = result; return true; }, function (error) { return Promise.reject(error); }); //endregion //region Append common information to CMS_ENVELOPED_DATA sequence = sequence.then(function () { _this2.version = 2; _this2.encryptedContentInfo = new _EncryptedContentInfo2.default({ contentType: "1.2.840.113549.1.7.1", // "data" contentEncryptionAlgorithm: new _AlgorithmIdentifier2.default({ algorithmId: contentEncryptionOID, algorithmParams: new asn1js.OctetString({ valueHex: ivBuffer }) }), encryptedContent: new asn1js.OctetString({ valueHex: encryptedContent }) }); }, function (error) { return Promise.reject(error); }); //endregion //region Special sub-functions to work with each recipient's type function SubKeyAgreeRecipientInfo(index) { //region Initial variables var currentSequence = Promise.resolve(); var ecdhPublicKey = void 0; var ecdhPrivateKey = void 0; var recipientCurve = void 0; var recipientCurveLength = void 0; var exportedECDHPublicKey = void 0; //endregion //region Get "namedCurve" parameter from recipient's certificate currentSequence = currentSequence.then(function () { var curveObject = _this.recipientInfos[index].value.recipientCertificate.subjectPublicKeyInfo.algorithm.algorithmParams; if (curveObject instanceof asn1js.ObjectIdentifier === false) return Promise.reject("Incorrect \"recipientCertificate\" for index " + index); var 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; }, function (error) { return Promise.reject(error); }); //endregion //region Generate ephemeral ECDH key currentSequence = currentSequence.then(function (result) { return crypto.generateKey({ name: "ECDH", namedCurve: result }, true, ["deriveBits"]); }, function (error) { return Promise.reject(error); }); //endregion //region Export public key of ephemeral ECDH key pair currentSequence = currentSequence.then(function (result) { ecdhPublicKey = result.publicKey; ecdhPrivateKey = result.privateKey; return crypto.exportKey("spki", ecdhPublicKey); }, function (error) { return Promise.reject(error); }); //endregion //region Import recipient's public key currentSequence = currentSequence.then(function (result) { exportedECDHPublicKey = result; return _this.recipientInfos[index].value.recipientCertificate.getPublicKey({ algorithm: { algorithm: { name: "ECDH", namedCurve: recipientCurve }, usages: [] } }); }, function (error) { return Promise.reject(error); }); //endregion //region Create shared secret currentSequence = currentSequence.then(function (result) { return crypto.deriveBits({ name: "ECDH", public: result }, ecdhPrivateKey, recipientCurveLength); }, function (error) { return Promise.reject(error); }); //endregion //region Apply KDF function to shared secret currentSequence = currentSequence.then( /** * @param {ArrayBuffer} result */ function (result) { //region Get length of used AES-KW algorithm var aesKWAlgorithm = new _AlgorithmIdentifier2.default({ schema: _this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmParams }); var KWalgorithm = (0, _common.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 var kwLength = KWalgorithm.length; var kwLengthBuffer = new ArrayBuffer(4); var kwLengthView = new Uint8Array(kwLengthBuffer); for (var j = 3; j >= 0; j--) { kwLengthView[j] = kwLength; kwLength >>= 8; } //endregion //region Create and encode "ECC-CMS-SharedInfo" structure var eccInfo = new _ECCCMSSharedInfo2.default({ keyInfo: new _AlgorithmIdentifier2.default({ 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 }) }); var encodedInfo = eccInfo.toSchema().toBER(false); //endregion //region Get SHA algorithm used together with ECDH var ecdhAlgorithm = (0, _common.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 (0, _common.kdf)(ecdhAlgorithm.kdf, result, KWalgorithm.length, encodedInfo); }, function (error) { return Promise.reject(error); }); //endregion //region Import AES-KW key from result of KDF function currentSequence = currentSequence.then(function (result) { return crypto.importKey("raw", result, { name: "AES-KW" }, true, ["wrapKey"]); }, function (error) { return Promise.reject(error); }); //endregion //region Finally wrap session key by using AES-KW algorithm currentSequence = currentSequence.then(function (result) { return crypto.wrapKey("raw", sessionKey, result, { name: "AES-KW" }); }, function (error) { return Promise.reject(error); }); //endregion //region Append all neccessary data to current CMS_RECIPIENT_INFO object currentSequence = currentSequence.then(function (result) { //region OriginatorIdentifierOrKey var asn1 = asn1js.fromBER(exportedECDHPublicKey); var originator = new _OriginatorIdentifierOrKey2.default(); originator.variant = 3; originator.value = new _OriginatorPublicKey2.default({ 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 }, function (error) { return Promise.reject(error); }); //endregion return currentSequence; } function SubKeyTransRecipientInfo(index) { //region Initial variables var currentSequence = Promise.resolve(); //endregion //region Get recipient's public key currentSequence = currentSequence.then(function () { //region Get current used SHA algorithm var schema = _this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmParams; var rsaOAEPParams = new _RSAESOAEPParams2.default({ schema: schema }); var hashAlgorithm = (0, _common.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"] } }); }, function (error) { return Promise.reject(error); }); //endregion //region Encrypt early exported session key on recipient's public key currentSequence = currentSequence.then(function (result) { return crypto.encrypt(result.algorithm, result, exportedSessionKey); }, function (error) { return Promise.reject(error); }); //endregion //region Append all neccessary data to current CMS_RECIPIENT_INFO object currentSequence = currentSequence.then(function (result) { //region RecipientEncryptedKey _this.recipientInfos[index].value.encryptedKey = new asn1js.OctetString({ valueHex: result }); //endregion }, function (error) { return Promise.reject(error); }); //endregion return currentSequence; } function SubKEKRecipientInfo(index) { //region Initial variables var currentSequence = Promise.resolve(); var kekAlgorithm = void 0; //endregion //region Import KEK from pre-defined data currentSequence = currentSequence.then(function () { //region Get WebCrypto form of "keyEncryptionAlgorithm" kekAlgorithm = (0, _common.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 }, function (error) { return Promise.reject(error); }); //endregion //region Wrap previously exported session key currentSequence = currentSequence.then(function (result) { return crypto.wrapKey("raw", sessionKey, result, kekAlgorithm); }, function (error) { return Promise.reject(error); }); //endregion //region Append all neccessary data to current CMS_RECIPIENT_INFO object currentSequence = currentSequence.then(function (result) { //region RecipientEncryptedKey _this.recipientInfos[index].value.encryptedKey = new asn1js.OctetString({ valueHex: result }); //endregion }, function (error) { return Promise.reject(error); }); //endregion return currentSequence; } function SubPasswordRecipientinfo(index) { //region Initial variables var currentSequence = Promise.resolve(); var pbkdf2Params = void 0; var kekAlgorithm = void 0; //endregion //region Check that we have encoded "keyDerivationAlgorithm" plus "PBKDF2_params" in there currentSequence = currentSequence.then(function () { 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 _PBKDF2Params2.default({ schema: _this.recipientInfos[index].value.keyDerivationAlgorithm.algorithmParams }); } catch (ex) { return Promise.reject("Incorrectly encoded \"keyDerivationAlgorithm\""); } return Promise.resolve(); }, function (error) { return Promise.reject(error); }); //endregion //region Derive PBKDF2 key from "password" buffer currentSequence = currentSequence.then(function () { var passwordView = new Uint8Array(_this.recipientInfos[index].value.password); return crypto.importKey("raw", passwordView, "PBKDF2", false, ["deriveKey"]); }, function (error) { return Promise.reject(error); }); //endregion //region Derive key for "keyEncryptionAlgorithm" currentSequence = currentSequence.then(function (result) { //region Get WebCrypto form of "keyEncryptionAlgorithm" kekAlgorithm = (0, _common.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 var hmacHashAlgorithm = "SHA-1"; if ("prf" in pbkdf2Params) { var algorithm = (0, _common.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 var saltView = new Uint8Array(pbkdf2Params.salt.valueBlock.valueHex); //endregion //region Get PBKDF2 iterations count var iterations = pbkdf2Params.iterationCount; //endregion return crypto.deriveKey({ name: "PBKDF2", hash: { name: hmacHashAlgorithm }, salt: saltView, iterations: iterations }, result, kekAlgorithm, true, ["wrapKey"]); // Usages are too specific for KEK algorithm }, function (error) { return Promise.reject(error); }); //endregion //region Wrap previously exported session key (Also too specific for KEK algorithm) currentSequence = currentSequence.then(function (result) { return crypto.wrapKey("raw", sessionKey, result, kekAlgorithm); }, function (error) { return Promise.reject(error); }); //endregion //region Append all neccessary data to current CMS_RECIPIENT_INFO object currentSequence = currentSequence.then(function (result) { //region RecipientEncryptedKey _this.recipientInfos[index].value.encryptedKey = new asn1js.OctetString({ valueHex: result }); //endregion }, function (error) { return Promise.reject(error); }); //endregion return currentSequence; } //endregion //region Create special routines for each "recipient" sequence = sequence.then(function () { for (var i = 0; i < _this2.recipientInfos.length; i++) { //region Initial variables var currentSequence = Promise.resolve(); //endregion switch (_this2.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); }, function (error) { return 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} */ }, { key: "decrypt", value: function decrypt(recipientIndex, parameters) { var _this3 = this; //region Initial variables var sequence = Promise.resolve(); var decryptionParameters = parameters || {}; var _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 var crypto = (0, _common.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 var currentSequence = Promise.resolve(); var recipientCurve = void 0; var recipientCurveLength = void 0; var curveOID = void 0; var ecdhPrivateKey = void 0; //endregion //region Get "namedCurve" parameter from recipient's certificate currentSequence = currentSequence.then(function () { 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\""); var curveObject = decryptionParameters.recipientCertificate.subjectPublicKeyInfo.algorithm.algorithmParams; if (curveObject instanceof asn1js.ObjectIdentifier === false) 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"]); }, function (error) { return Promise.reject(error); }); //endregion //region Import sender's ephemeral public key currentSequence = currentSequence.then(function (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 var buffer = _this.recipientInfos[index].value.originator.value.toSchema().toBER(false); //endregion return crypto.importKey("spki", buffer, { name: "ECDH", namedCurve: recipientCurve }, true, []); }, function (error) { return Promise.reject(error); }); //endregion //region Create shared secret currentSequence = currentSequence.then(function (result) { return crypto.deriveBits({ name: "ECDH", public: result }, ecdhPrivateKey, recipientCurveLength); }, function (error) { return Promise.reject(error); }); //endregion //region Apply KDF function to shared secret currentSequence = currentSequence.then( /** * @param {ArrayBuffer} result */ function (result) { //region Get length of used AES-KW algorithm var aesKWAlgorithm = new _AlgorithmIdentifier2.default({ schema: _this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmParams }); var KWalgorithm = (0, _common.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 var kwLength = KWalgorithm.length; var kwLengthBuffer = new ArrayBuffer(4); var kwLengthView = new Uint8Array(kwLengthBuffer); for (var j = 3; j >= 0; j--) { kwLengthView[j] = kwLength; kwLength >>= 8; } //endregion //region Create and encode "ECC-CMS-SharedInfo" structure var eccInfo = new _ECCCMSSharedInfo2.default({ keyInfo: new _AlgorithmIdentifier2.default({ 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 }) }); var encodedInfo = eccInfo.toSchema().toBER(false); //endregion //region Get SHA algorithm used together with ECDH var ecdhAlgorithm = (0, _common.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 (0, _common.kdf)(ecdhAlgorithm.kdf, result, KWalgorithm.length, encodedInfo); }, function (error) { return Promise.reject(error); }); //endregion //region Import AES-KW key from result of KDF function currentSequence = currentSequence.then(function (result) { return crypto.importKey("raw", result, { name: "AES-KW" }, true, ["unwrapKey"]); }, function (error) { return Promise.reject(error); }); //endregion //region Finally unwrap session key currentSequence = currentSequence.then(function (result) { //region Get WebCrypto form of content encryption algorithm var contentEncryptionAlgorithm = (0, _common.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"]); }, function (error) { return Promise.reject(error); }); //endregion return currentSequence; } function SubKeyTransRecipientInfo(index) { //region Initial variables var currentSequence = Promise.resolve(); //endregion //region Import recipient's private key currentSequence = currentSequence.then(function () { if ("recipientPrivateKey" in decryptionParameters === false) return Promise.reject("Parameter \"recipientPrivateKey\" is mandatory for \"KeyTransRecipientInfo\""); //region Get current used SHA algorithm var schema = _this.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmParams; var rsaOAEPParams = new _RSAESOAEPParams2.default({ schema: schema }); var hashAlgorithm = (0, _common.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"]); }, function (error) { return Promise.reject(error); }); //endregion //region Decrypt encrypted session key currentSequence = currentSequence.then(function (result) { return crypto.decrypt(result.algorithm, result, _this.recipientInfos[index].value.encryptedKey.valueBlock.valueHex); }, function (error) { return Promise.reject(error); }); //endregion //region Import decrypted session key currentSequence = currentSequence.then(function (result) { //region Get WebCrypto form of content encryption algorithm var contentEncryptionAlgorithm = (0, _common.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"]); }, function (error) { return Promise.reject(error); }); //endregion return currentSequence; } function SubKEKRecipientInfo(index) { //region Initial variables var currentSequence = Promise.resolve(); var kekAlgorithm = void 0; //endregion //region Import KEK from pre-defined data currentSequence = currentSequence.then(function () { if ("preDefinedData" in decryptionParameters === false) return Promise.reject("Parameter \"preDefinedData\" is mandatory for \"KEKRecipientInfo\""); //region Get WebCrypto form of "keyEncryptionAlgorithm" kekAlgorithm = (0, _common.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 }, function (error) { return Promise.reject(error); }); //endregion