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; } //********************************************************************************** } //**************************************************************************************