WebCrypto GOST Source: gostCMS.js

/**
 * @file Implements the Cryptographic Message Syntax as specified in RFC-2630.
 * @version 1.76
 * @copyright 2014-2016, Rudolf Nickolaev. All rights reserved.
 */

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *    
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this 
 *    list of conditions and the following disclaimer.
 *    
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *    
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 */

(function (root, factory) {

    /*
     * Module imports and exports
     * 
     */ // <editor-fold defaultstate="collapsed">
    if (typeof define === 'function' && define.amd) {
        define(['gostCrypto', 'gostASN1', 'gostCert'], factory);
    } else if (typeof exports === 'object') {
        module.exports = factory(require('gostCrypto'), require('gostASN1'), require('gostCert'));
    } else {
        root.GostCMS = factory(root.gostCrypto, root.GostASN1, root.GostCert);
    }
    // </editor-fold>

}(this, function (gostCrypto) {

    /*
     * Common algorithms
     */ // <editor-fold defaultstate="collapsed">
    var root = this;
    var Promise = root.Promise;
    var Object = root.Object;
    var CryptoOperationData = root.ArrayBuffer;
    var Date = root.Date;

    var subtle = gostCrypto.subtle;
    var asn1 = gostCrypto.asn1;
    var coding = gostCrypto.coding;
    var cert = gostCrypto.cert;
    var providers = gostCrypto.security.providers;

    // Expand javascript object
    function expand() {
        var r = {};
        for (var i = 0, n = arguments.length; i < n; i++) {
            var item = arguments[i];
            if (typeof item === 'object')
                for (var name in item)
                    if (item.hasOwnProperty(name))
                        r[name] = item[name];
        }
        return r;
    }

    function defineProperty(object, name, descriptor, enumerable) {
        if (typeof descriptor !== 'object')
            descriptor = {value: descriptor};
        if (enumerable !== undefined)
            descriptor.enumerable = enumerable;
        Object.defineProperty(object, name, descriptor);
    }

    function defineProperties(object, properties, enumerable) {
        for (var name in properties)
            defineProperty(object, name, properties[name], enumerable);
    }

    // Extend javascript class
    function extend(Super, Class, propertiesObject, propertiesClass) {
        // If constructor not defined
        if (typeof Class !== 'function') {
            propertiesClass = propertiesObject;
            propertiesObject = Class;
            Class = function () {
                Super.apply(this, arguments);
            };
        }
        // Create prototype properties
        Class.prototype = Object.create(Super.prototype, {
            constructor: {
                value: Class
            },
            superclass: {
                value: Super.prototype
            }
        });
        if (propertiesObject)
            defineProperties(Class.prototype, propertiesObject, true);
        // Inherites super class properties
        if (Super !== Object)
            for (var name in Super)
                Class[name] = Super[name];
        Class.super = Super;
        if (propertiesClass)
            defineProperties(Class, propertiesClass, true);
        return Class;
    }

    // Self resolver
    function call(callback) {
        try {
            callback();
        } catch (e) {
        }
    }

    // Check the buffers to equal
    function equalBuffers(r1, r2) {
        var s1 = new Uint8Array(r1),
                s2 = new Uint8Array(r2);
        if (s1.length !== s2.length)
            return false;
        for (var i = 0, n = s1.length; i < n; i++)
            if (s1[i] !== s2[i])
                return false;
        return true;
    }

    // True if equal numbers
    var equalNumbers = (function () {
        // Convert number to bigendian hex string
        var hex = function (s) {
            var t = typeof s;
            return t === 'undefined' || s === '' ? '0' :
                    t === 'number' || s instanceof Number ? s.toString(16).toLowerCase() :
                    s.replace('0x', '').toLowerCase();
        };
        // Zero left padding
        var lpad = function (s, size) {
            return (new Array(size + 1).join('0') + s).slice(-size);
        };
        return function (s1, s2) {
            s1 = hex(s1);
            s2 = hex(s2);
            var len = Math.max(s1.length, s2.length);
            return lpad(s1, len) === lpad(s2, len);
        };
    })();

    // Check equal names
    function equalNames(name1, name2) {
        for (var key in name1)
            if (name1[key] !== name2[key])
                return false;
        for (var key in name2)
            if (name1[key] !== name2[key])
                return false;
        return true;
    }

    // Add unique value to array
    function addUnique(array, item, comparator) {
        var found = false;
        for (var i = 0, n = array.length; i < n; i++)
            if (comparator(array[i], item)) {
                found = true;
                break;
            }
        if (!found)
            array.push(item);
    }

    // Set content data
    function setContentData(object, data) {
        var content = object.content;
        switch (object.contentType) {
            case 'data':
                object.content = data.content;
                break;
            case 'digestedData':
            case 'signedData':
            case 'authData':
                content.encapContentInfo = {
                    eContentType: data.contentType,
                    eContent: data.content
                };
                break;
            case 'envelopedData':
            case 'encryptedData':
                content.encryptedContentInfo = {
                    contentType: data.contentType,
                    encryptedContent: data.content
                };
                break;
        }
    }

    // Get content data
    function getContentData(object) {
        var content = object.content;
        switch (object.contentType) {
            case 'data':
                return {
                    contentType: object.contentType,
                    content: object.content
                };
            case 'digestedData':
            case 'signedData':
            case 'authData':
                var encap = content.encapContentInfo;
                return  {
                    contentType: encap.eContentType,
                    content: encap.eContent
                };
            case 'envelopedData':
            case 'encryptedData':
                var enc = content.encryptedContentInfo;
                return {
                    contentType: enc.contentType,
                    content: enc.encryptedContent
                };
        }
    }

    // Check content info type
    function checkContentInfo(contentInfo) {
        var content, contentType;
        if (contentInfo) {
            if (typeof contentInfo === 'string')
                try {
                    contentInfo = coding.PEM.decode(contentInfo);
                } catch (e1) {
                    contentInfo = coding.Chars.decode(contentInfo);
                }
            if (contentInfo instanceof CryptoOperationData)
                try {
                    contentInfo = asn1.ContentInfo.decode(contentInfo);
                } catch (e) {
                    contentInfo = {contentType: 'data', content: contentInfo};
                }
            contentType = contentInfo.contentType;
            if (!contentType)
                throw new Error('Invalid content object');
            content = contentInfo.content;
            if (!(content instanceof CryptoOperationData))
                content = content.encode();
            return {contentType: contentType, content: content};
        } else
            contentInfo = {contentType: 'data'};
        return contentInfo;
    }

    function createContentInfo(contentInfo) {
        try {
            // Some provider has mistake to envelop ContentInfo enstead 
            // content field of ContentInfo
            contentInfo = new asn1.ContentInfo(contentInfo.content, true);
        } catch (e) {
        }
        // Create situable content info object
        switch (contentInfo.contentType) {
            case 'data':
                return new DataContentInfo(contentInfo);
            case 'digestedData':
                return new DigestedDataContentInfo(contentInfo);
            case 'signedData':
                return new SignedDataContentInfo(contentInfo);
            case 'encryptedData':
                return new EncryptedDataContentInfo(contentInfo);
            case 'envelopedData':
                return new EnvelopedDataContentInfo(contentInfo);
            default:
                return new asn1.ContentInfo(contentInfo);
        }
    }
    ;

    function matchCert(id, cert) {
        return (id instanceof CryptoOperationData ? cert.extensions &&
                equalBuffers(id, cert.extensions.subjectKeyIdentifier) :
                equalNames(cert.issuer, id.issuer) &&
                equalNumbers(cert.serialNumber, id.serialNumber));
    }

    // Get random values
    function getSeed(length) {
        var seed = new Uint8Array(length);
        gostCrypto.getRandomValues(seed);
        return seed.buffer;
    }

    // Salt size
    function saltSize(algorithm) {
        switch (algorithm.id) {
            case 'pbeWithSHAAnd40BitRC2-CBC':
            case 'pbeWithSHAAnd128BitRC2-CBC':
                return 8;
            case 'pbeUnknownGost':
                return 16;
            case 'sha1':
                return 20;
            default:
                return 32;
        }
    }

    // Password to bytes
    function passwordData(derivation, password) {
        if (!password)
            return new CryptoOperationData(0);
        if (password instanceof CryptoOperationData)
            return password;
        if (typeof password !== 'string')
            throw new Error('The password must be string or raw data type');
        if (derivation.name.indexOf('CPKDF') >= 0) {
            // CryptoPro store password
            var r = [];
            for (var i = 0; i < password.length; i++) {
                var c = password.charCodeAt(i);
                r.push(c & 0xff);
                r.push(c >>> 8 & 0xff);
                r.push(0);
                r.push(0);
            }
            return new Uint8Array(r).buffer;
        } else if (derivation.name.indexOf('PFXKDF') >= 0)
            // PKCS#12 unicode password
            return coding.Chars.decode(password + '\0', 'unicode');
        else
            // PKCS#5 password mode
            return coding.Chars.decode(password, 'utf8');
    }

    // Define provider for encription algorithm
    function encryptionProvider(algorithm) {
        var id = algorithm.id;
        for (var name in providers) {
            var provider = providers[name];
            if (provider.encryption.id === id)
                return provider;
        }
    }

    // </editor-fold>

    /**
     * Provides facilities for handling certificates, CRLs, etc.
     * @class GostCMS
     */
    function GostCMS() {
    }

    /**
     * Message templates
     * <ul>
     *      <li>providerName - provider name for key generation, default 'CP-01'</li>
     *      <li>autoAddCert - automatic add signer certificate to signature, default false</li>
     *      <li>useKeyIdentifier - true to add Signer as the SignerIdentifier (v3), otherwise, as the IssuerAndSerialNumber (v1) (default false).</li>
     * </ul>
     * 
     * @memberOf GostCMS
     * @instance
     */
    var options = {// <editor-fold defaultstate="collapsed">
        providerName: 'CP-01',
        autoAddCert: false,
        useKeyIdentifier: false // </editor-fold>
    };

    GostCMS.prototype.options = options;

    /**
     * The base class for all CMS objects.<br><br>
     * 
     * A CMS object consists of a content type, and content.<br><br>
     * 
     * @class GostCMS.DataContentInfo
     * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The content object.
     * @param {string} defaultSet The default object initialization set.
     * @extends GostASN1.ContentInfo
     */
    function DataContentInfo(contentInfo, defaultSet) // <editor-fold defaultstate="collapsed">
    {
        asn1.ContentInfo.call(this, contentInfo || defaultSet || {contentType: 'data'});
        if (defaultSet && this.contentType !== (defaultSet.contentType || 'data'))
            throw new Error('Invalid content type');
    } // </editor-fold>

    extend(asn1.ContentInfo, DataContentInfo, {
        /**
         * Indicates if this is a detached CMS object.
         * 
         * @memberOf GostCMS.DataContentInfo
         * @instance
         * @returns {boolean} true if detached; false otherwise.
         */
        isDetached: {// <editor-fold defaultstate="collapsed">
            value: false,
            enumerable: true,
            writable: true // </editor-fold>
        },
        /**
         * Indicates if an external (detached) signature must be created.
         * 
         * @memberOf GostCMS.DataContentInfo
         * @instance
         * @param {boolean} createDetached True if detached; false otherwise.
         */
        writeDetached: function (createDetached) // <editor-fold defaultstate="collapsed">
        {
            // Define external signature mode
            this.isDetached = createDetached;
        }, // </editor-fold> 
        /**
         * Encode the message to binary format 'DER' or 'PEM'
         * 
         * @memberOf GostCMS.DataContentInfo
         * @instance
         * @param {string} format
         * @returns {FormatedData}
         */ // <editor-fold defaultstate="collapsed">
        encode: function (format) // <editor-fold defaultstate="collapsed">
        {
            if (this.isDetached) {
                var data = getContentData(this);
                setContentData(this, {contentType: data.contentType});
                var result = asn1.ContentInfo.method('encode').call(this, format);
                setContentData(this, data);
                return result;
            } else
                return asn1.ContentInfo.method('encode').call(this, format);
        }, // </editor-fold> 
        /**
         * Enclose content to document. 
         * 
         * @memberOf GostCMS.DataContentInfo
         * @instance
         * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo
         * @returns {Promise} Promise to return self object after enclose content
         */
        encloseContent: function (contentInfo) // <editor-fold defaultstate="collapsed">
        {
            var self = this;
            return new Promise(call).then(function () {
                self.setEnclosed(contentInfo);
                return self;
            });
        }, // </editor-fold> 
        /**
         * Sets the content of attached document.<br><br>
         * 
         * This is necessary only in detached mode.
         * 
         * @memberOf GostCMS.DataContentInfo
         * @instance
         * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo - The encapsulated CMS Object.
         */
        setEnclosed: function (contentInfo) // <editor-fold defaultstate="collapsed">
        {
            setContentData(this, checkContentInfo(contentInfo));
        }, // </editor-fold> 
        /**
         * Returns the document which attached. If the content is not attached, the CMS object 
         * which is returned will be degenerate.
         * 
         * @memberOf GostCMS.DataContentInfo
         * @instance
         * @returns {GostASN1.ContentInfo} The encapsulated CMS Object.
         */
        getEnclosed: function () // <editor-fold defaultstate="collapsed">
        {
            return createContentInfo(getContentData(this));
        } // </editor-fold>
    });

    /**
     * This class encapsulates a CMS object of content type binary data.
     * 
     * @memberOf GostCMS
     * @type GostCMS.DigestedDataContentInfo
     */
    GostCMS.prototype.DataContentInfo = DataContentInfo;

    /**
     * This class encapsulates a CMS object of content type digested-data.
     * 
     * @class GostCMS.DigestedDataContentInfo
     * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo - The content that is to be signed.
     * @extends GostCMS.DataContentInfo
     * @extends GostASN1.DigestedData
     */
    function DigestedDataContentInfo(contentInfo) // <editor-fold defaultstate="collapsed">
    {
        DataContentInfo.call(this, contentInfo, {
            contentType: 'digestedData',
            version: 0,
            digestAlgorithm: providers[options.providerName].digest,
            encapContentInfo: {
                eContentType: 'data'
            },
            digest: new CryptoOperationData(0)
        });
    }  // </editor-fold>

    extend(DataContentInfo, DigestedDataContentInfo, {
        /**
         * Enclose the content and calculate the message digest with given digest algorithm
         * 
         * @memberOf GostCMS.DigestedDataContentInfo
         * @instance
         * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo Content data to be enclosed.
         * @param {(AlgorithmIdentifier|string)} digestAlgorithm Digest algorithm or provider name
         * @returns {Promise} 
         */
        encloseContent: function (contentInfo, digestAlgorithm) // <editor-fold defaultstate="collapsed"> 
        {
            var self = this;
            return new Promise(call).then(function () {
                // Set enclosed data
                self.setEnclosed(contentInfo);

                // Define digest algorithm
                if (digestAlgorithm) {
                    var digestProvider = providers[digestAlgorithm];
                    self.digestAlgorithm = (digestProvider && digestProvider.digest) || digestAlgorithm;
                }

                // Calculate digest
                return subtle.digest(self.digestAlgorithm, self.encapContentInfo.eContent);
            }).then(function (digest) {

                // Set digest attribute
                self.digest = digest;
            });
        }, // </editor-fold>
        /**
         * Verify the Message Digest. <br><br>
         * 
         * @memberOf GostCMS.DigestedDataContentInfo
         * @instance
         * @param contentInfo Detached content (optional)
         * @returns {Promise} Promise to return enclosed object {@link GostASN1.ContentInfo} if digest verified
         */
        verify: function (contentInfo) // <editor-fold defaultstate="collapsed">
        {
            var self = this;
            return new Promise(call).then(function () {
                // Append attached
                if (contentInfo)
                    self.setEnclosed(contentInfo);

                // Check data
                var dataToVerify = self.encapContentInfo &&
                        self.encapContentInfo.eContent;
                if (!dataToVerify)
                    throw new Error('Detached content is not found');

                // Calculate digest
                return subtle.digest(self.digestAlgorithm, self.encapContentInfo.eContent);
            }).then(function (digest) {
                if (!equalBuffers(digest, self.digest))
                    throw Error('Message digest is not verified');
                // Return content
                return createContentInfo({
                    contentType: self.encapContentInfo.eContentType,
                    content: self.encapContentInfo.eContent
                });
            });
        } // </editor-fold>
    });

    /**
     * This class encapsulates a CMS object of content type digested-data.
     * 
     * @memberOf GostCMS
     * @type GostCMS.DigestedDataContentInfo
     */
    GostCMS.prototype.DigestedDataContentInfo = DigestedDataContentInfo;

    /**
     * This class encapsulates a CMS object of content type signed-data.
     * 
     * Use encloseContent or setEnclosed methods to add a enclosed content before add signatures
     * 
     * @class GostCMS.SignedDataContentInfo
     * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo - The signed content. 
     * @extends GostCMS.DataContentInfo
     * @extends GostASN1.SignedData
     */
    function SignedDataContentInfo(contentInfo) // <editor-fold defaultstate="collapsed">
    {
        DataContentInfo.call(this, contentInfo, {
            contentType: 'signedData',
            version: 1,
            digestAlgorithms: [],
            encapContentInfo: {
                eContentType: 'data'
            },
            signerInfos: []
        });
    }  // </editor-fold>

    extend(DataContentInfo, SignedDataContentInfo, {
        /**
         * Add a Signer using the the IssuerAndSerialNumber as the SignerIdentifier i.e a Version1 CMSSignerInfo 
         * or SubjectPublicKeyIdentifier as the SignerIdentifier i.e a Version3 CMSSignerInfo.
         * 
         * @memberOf GostCMS.SignedDataContentInfo
         * @instance
         * @param {GostASN1.PrivateKeyInfo} signerKey Private Key of the signer.
         * @param {GostCert.X509} signerCert Signer certificate or certificate chain
         * @param {GostASN1.SignedAttributes} signedAttrs The set of signed attributes. Default undefined. If true or {} standard attributes will be appended: contentType and messageDigest
         * @param {GostASN1.UnsignedAttributes} unsignedAttrs  The set of unsigned attributes. Default undefined.
         * @returns {Promise} Promise to return self object after add signature
         */
        addSignature: function (signerKey, signerCert, signedAttrs, unsignedAttrs) // <editor-fold defaultstate="collapsed"> 
        {
            var self = this, signerInfo, dataToSign, signerCertChain;
            return new Promise(call).then(function () {
                // Check attribures
                if (!signerKey || !signerCert)
                    throw new Error('Signer key or certificate is not defined');
                // Cert chain
                if (signerCert instanceof Array) {
                    signerCertChain = signerCert;
                    signerCert = signerCertChain[0];
                } else
                    signerCertChain = [signerCert];
                // Signature algorithm provider
                var provider = signerCert.getProvider() ||
                        providers[options.providerName];
                var useKeyIdentifier = options.useKeyIdentifier && signerCert.extensions &&
                        signerCert.extensions.subjectKeyIdentifier;
                // Get enclosed data
                dataToSign = self.encapContentInfo.eContent;
                // Prepare signer info structure
                signerInfo = {
                    version: useKeyIdentifier ? 2 : 0,
                    sid: useKeyIdentifier ? signerCert.extensions.subjectKeyIdentifier : {
                        issuer: signerCert.issuer,
                        serialNumber: signerCert.serialNumber},
                    digestAlgorithm: provider.digest,
                    signatureAlgorithm: signerCert.subjectPublicKeyInfo.algorithm};
                // Set an unsigned attributes
                if (unsignedAttrs)
                    signerInfo.unsignedAttrs = unsignedAttrs;
                // For a signed attributes calculate digest
                if (signedAttrs) {
                    if (typeof signedAttrs !== 'object')
                        signedAttrs = {};
                    return subtle.digest(signerInfo.digestAlgorithm, dataToSign);
                }
            }).then(function (digest) {
                if (digest) {
                    // Add standard signed attributes
                    signedAttrs.contentType = self.encapContentInfo.eContentType,
                            signedAttrs.messageDigest = digest,
                            signedAttrs.signingTime = new Date();
                    signerInfo.signedAttrs = signedAttrs,
                            // Now data to sign = attributes
                            dataToSign = asn1.SignedAttributes.encode(signerInfo.signedAttrs);
                }

                // Import the private key
                return subtle.importKey('pkcs8', asn1.PrivateKeyInfo.encode(signerKey),
                        signerKey.privateKeyAlgorithm, false, ['sign']);
            }).then(function (key) {

                // Sign data
                var algorithm = expand(signerInfo.signatureAlgorithm, {hash: signerInfo.digestAlgorithm});
                return subtle.sign(algorithm, key, dataToSign);
            }).then(function (signatureValue) {
                signerInfo.signatureValue = signatureValue;

                // Add digest algorithm
                addUnique(self.digestAlgorithms, signerInfo.digestAlgorithm, function (algorithm1, algorithm2) {
                    return algorithm1.id === algorithm2.id;
                });

                // Add signer certificate
                if (options.autoAddCert) {
                    if (!self.certificates)
                        self.certificates = [];
                    for (var i = 0, n = signerCertChain.length; i < n; i++) {
                        addUnique(self.certificates, signerCertChain[i], function (cert1, cert2) {
                            return equalNames(cert1.issuer, cert2.issuer) &&
                                    equalNumbers(cert1.serialNumber, cert2.serialNumber);
                        });
                    }
                }

                // Add signer info
                self.signerInfos.push(signerInfo);
            });
        }, // </editor-fold>
        /**
         * Indicates if this object has any signers i.e. checks for the absence of any SignerInfo structures.
         * CMS (RFC-2630) defines a degenerate object as one which has no signers.
         * 
         * @memberOf GostCMS.SignedDataContentInfo
         * @instance
         * @returns {boolean} True if this object has no signers; false otherwise.
         */
        isDegenerate: {// <editor-fold defaultstate="collapsed">
            get: function () {
                return !(this.signerInfos && this.signerInfos.length > 0);
            } // </editor-fold>
        },
        /**
         * Returns normally if this CMS signed data object contains at least one valid signature, 
         * according to the given trust policy; otherwise throws an Error.<br><br>
         * 
         * In order to be considered valid, there must be at least one signature on this CMS 
         * message which is validated by one of the certificates included with it; furthermore, 
         * the validating certificate must itself be valid according to the given certificate 
         * trust policy. This latter validation process may involve examining the other certificates 
         * or CRLs included with this object, if called for by the trust policy.<br><br>
         * 
         * If a signature is encountered for which a certification path can be found, but is 
         * invalid, an Error will be created, but will not be thrown until 
         * all other signatures have been checked. If another signature is found which is valid, 
         * then the method simply returns and no exception at all is thrown. 
         * 
         * @memberOf GostCMS.SignedDataContentInfo
         * @instance
         * @param {GostCert.CertificateTrustPolicy} trustPolicy The trust prolicy for verification
         * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The content that was signed (optional)
         * @returns {Promise} Promise to return enclosed object {@link GostASN1.ContentInfo} if signature verified
         */
        verify: function (trustPolicy, contentInfo) // <editor-fold defaultstate="collapsed">
        {
            var self = this, result;
            return new Promise(call).then(function () {
                // Append attached
                if (contentInfo)
                    self.setEnclosed(contentInfo);
                if (!self.signerInfos || self.signerInfos.length === 0)
                    throw new Error('No signatures found');
                // Validate certificate of signers
                return Promise.all(self.signerInfos.map(function (signerInfo, i) {
                    var sid = signerInfo.sid, selector = sid instanceof CryptoOperationData ? {
                        subjectKeyIdentifier: sid
                    } : {
                        issuer: sid.issuer,
                        serialNumber: sid.serialNumber};
                    // Signing date
                    var date;
                    if (signerInfo.signedAttrs && signerInfo.signedAttrs.signingTime)
                        date = signerInfo.signedAttrs.signingTime;
                    // Use certificate trust policy validation
                    return trustPolicy.getValidCertificate(selector,
                            self.certificates, self.crls, date).catch(function () {
                        return; // Ignore error
                    });
                }));

            }).then(function (certs) {
                // Get encapsulated data
                var verifiers = [];
                // Verify signatures for each signers
                certs.forEach(function (signerCert) {
                    if (signerCert)
                        verifiers.push(self.verifySignature(signerCert).then(function (data) {
                            result = data; // Enough one valid signature
                        }, function () {
                            return; // Ignore error
                        }));
                });
                if (verifiers.length === 0)
                    throw new Error('Valid verification path not found');
                return Promise.all(verifiers);
            }).then(function () {
                if (!result)
                    throw Error('Verification path found but no valid signature');
                // Return content
                return result;
            });
        }, // </editor-fold>
        /**
         * Returns successfully if this CMS signed data object contains a signature which is 
         * validated by the given certificate and data; otherwise throws an Error.<br><br>
         * 
         * This method verifies the specified signature directly and ignores any certificates 
         * or CRLs which may be contained in this CMS object. A more complex verification process, 
         * which does make use of attached certificates and CRLs, is provided by the verify method.
         * 
         * @memberOf GostCMS.SignedDataContentInfo
         * @instance
         * @param {GostCert.X509} signerCert The signer certificate
         * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The content that was signed (optional)
         * @returns {Promise} Promise to return enclosed object {@link GostASN1.ContentInfo} if signature verified
         */
        verifySignature: function (signerCert, contentInfo) // <editor-fold defaultstate="collapsed">
        {
            var self = this, signerInfo, dataToVerify, dataDigest;
            return new Promise(call).then(function () {
                // Append attached
                if (contentInfo)
                    self.setEnclosed(contentInfo);
                dataToVerify = self.encapContentInfo && self.encapContentInfo.eContent;
                if (!dataToVerify)
                    throw new Error('Detached content is not found');
                // Find signer
                for (var i = 0; i < self.signerInfos.length; i++) {
                    var sid = self.signerInfos[i].sid;
                    if (matchCert(sid, signerCert)) {
                        signerInfo = self.signerInfos[i];
                        break;
                    }
                }
                if (!signerInfo)
                    throw new Error('Signature not found for the certificate');
                // Choice data for verification
                if (signerInfo.signedAttrs) {
                    dataDigest = signerInfo.signedAttrs.messageDigest;
                    if (!dataDigest)
                        throw new Error('Message digest must present in signed attributes');

                    // To exclude implicit [0] need to reassemble signed attributes (auto on CTX object)
                    dataToVerify = signerInfo.signedAttrs.encode();
                }
                if (!dataToVerify)
                    throw new Error('Data for verification not found');
                // Verify signature
                var algorithm = expand(signerInfo.signatureAlgorithm, {hash: signerInfo.digestAlgorithm});
                return signerCert.verifySignature(dataToVerify, signerInfo.signatureValue, algorithm);
            }).then(function (result) {
                if (!result)
                    throw new Error('Signature not verified');
                // Verify digest
                if (signerInfo.signedAttrs)
                    return subtle.digest(signerInfo.digestAlgorithm, self.encapContentInfo.eContent);
            }).then(function (digest) {
                if (digest && !equalBuffers(digest, dataDigest))
                    throw new Error('Message digest not verified');
                // Return content
                return createContentInfo({
                    contentType: self.encapContentInfo.eContentType,
                    content: self.encapContentInfo.eContent
                });
            });
        }  // </editor-fold>
    });

    /**
     * This class encapsulates a CMS object of content type signed-data.
     * 
     * @memberOf GostCMS
     * @type GostCMS.SignedDataContentInfo
     */
    GostCMS.prototype.SignedDataContentInfo = SignedDataContentInfo;

    /**
     * This class encapsulates a CMS object of content type encrypted-data.
     * 
     * @class GostCMS.EncryptedDataContentInfo
     * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The encrypted data content.
     * @extends GostCMS.DataContentInfo
     * @extends GostASN1.EncryptedData
     */
    function EncryptedDataContentInfo(contentInfo) // <editor-fold defaultstate="collapsed">
    {
        DataContentInfo.call(this, contentInfo, {
            contentType: 'encryptedData',
            version: 0,
            encryptedContentInfo: {
                contentType: 'data',
                contentEncryptionAlgorithm: providers[options.providerName].encryption
            }
        });
    }  // </editor-fold>

    extend(DataContentInfo, EncryptedDataContentInfo, {
        /**
         * Encrypt the content with given encryption algorithm, secret key or password
         * 
         * @memberOf GostCMS.EncryptedDataContentInfo
         * @instance
         * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The content data to be enclosed.
         * @param {Key|string} contentEncryptionKey content The encryption key or password for derive key
         * @param {(AlgorithmIdentifier|string)} encryptionAlgorithm The encryption algorithm or provider name
         * @returns {Promise} Promise to return self object after encrypt content
         */
        encloseContent: function (contentInfo, contentEncryptionKey, encryptionAlgorithm) // <editor-fold defaultstate="collapsed"> 
        {
            var self = this, encryption, derivation;
            return new Promise(call).then(function () {
                // Check content info
                contentInfo = checkContentInfo(contentInfo);
                if (!contentInfo.content)
                    throw new Error('Content for encryption must be specified');

                // Define encryption algorithm
                var type = typeof contentEncryptionKey === 'string' ? 'pbes' : 'encryption';
                if (encryptionAlgorithm) {
                    var provider = providers[encryptionAlgorithm];
                    encryptionAlgorithm = (provider && provider[type]) || encryptionAlgorithm;
                } else
                    encryptionAlgorithm = providers[options.providerName][type];
                // Prepare content encryption key
                if (encryptionAlgorithm.derivation) {
                    // Encrypt with password
                    derivation = expand(encryptionAlgorithm.derivation);
                    encryption = expand(encryptionAlgorithm.encryption);
                    derivation.salt = getSeed(saltSize(encryptionAlgorithm));
                    // Import password for key generation
                    var integrityKey;
                    return subtle.importKey('raw', passwordData(derivation, contentEncryptionKey),
                            derivation, false, ['deriveKey', 'deriveBits']).then(function (key) {
                        integrityKey = key;
                        // Derive IV
                        if (derivation.name.indexOf('PFXKDF') >= 0) {
                            derivation.diversifier = 2;
                            return subtle.deriveBits(derivation, integrityKey, 64);
                        }
                    }).then(function (iv) {
                        if (iv)
                            encryption.iv = iv;
                        // Generate key from password 
                        derivation.diversifier = 1;
                        return subtle.deriveKey(derivation, integrityKey, encryption, false, ['encrypt']);
                    }).then(function (encryptionKey) {
                        // Content encryption with key
                        return encryptionKey;
                    });
                } else {
                    // Base encryption
                    encryption = expand(encryptionAlgorithm);
                    if (contentEncryptionKey instanceof CryptoOperationData) {
                        // Import key
                        return subtle.importKey('raw', contentEncryptionKey, encryption, false, ['encrypt']);
                    } else if (contentEncryptionKey.type === 'secret') {
                        return contentEncryptionKey;
                    } else
                        throw new Error('Content encryption key must be raw data or secret key type');
                }
            }).then(function (encryptionKey) {
                // Initial vector
                if (!encryption.iv)
                    encryption.iv = getSeed(8);

                return subtle.encrypt(encryption, encryptionKey, contentInfo.content);
            }).then(function (encryptedContent) {
                if (encryptionAlgorithm.derivation) {
                    delete derivation.diversifier;
                    encryptionAlgorithm = expand(encryptionAlgorithm, {
                        derivation: derivation,
                        encryption: encryption
                    });
                } else
                    encryptionAlgorithm = encryption;
                // Set enclosed data
                self.encryptedContentInfo = {
                    contentType: contentInfo.contentType,
                    contentEncryptionAlgorithm: encryptionAlgorithm,
                    encryptedContent: encryptedContent
                };
                return self;
            });
        }, // </editor-fold>
        /**
         * Returns the decrypted content. 
         * 
         * @memberOf GostCMS.EncryptedDataContentInfo
         * @instance
         * @param {Key|string} decryptionKey The decryption key or password for derive key   
         * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The detached content (optional).
         * @returns {Promise} Promise to return enclosed object {@ling GostASN1.ContentInfo} after decrypt content
         */
        getEnclosed: function (decryptionKey, contentInfo) // <editor-fold defaultstate="collapsed">
        {
            var self = this, encryption, derivation, encryptedContent;
            return new Promise(call).then(function () {
                // Append attached
                if (contentInfo)
                    self.setEnclosed(contentInfo);
                encryptedContent = self.encryptedContentInfo.encryptedContent;
                if (!encryptedContent)
                    throw new Error('Encrypted content must be specified');

                encryption = expand(self.encryptedContentInfo.contentEncryptionAlgorithm);
                if (encryption.derivation) {
                    // Decrypt with password
                    derivation = expand(encryption.derivation);
                    encryption = expand(encryption.encryption);
                    // Derive encryption key from password
                    var integrityKey;
                    return subtle.importKey('raw', passwordData(derivation, decryptionKey),
                            derivation, false, ['deriveKey', 'deriveBits']).then(function (key) {
                        integrityKey = key;
                        // Derive iv for PFX
                        if (derivation.name.indexOf('PFXKDF') >= 0) {
                            derivation.diversifier = 2;
                            return subtle.deriveBits(derivation, integrityKey, 64);
                        }
                    }).then(function (iv) {
                        if (iv)
                            encryption.iv = iv;
                        // Generate key from password 
                        derivation.diversifier = 1;
                        return subtle.deriveKey(derivation, integrityKey, encryption, false, ['decrypt']);
                    });
                } else {
                    // Base encryption. Password should be secret key
                    if (decryptionKey instanceof CryptoOperationData) {
                        // Import key
                        return subtle.importKey('raw', decryptionKey, encryption, false, ['decrypt']);
                    } else if (decryptionKey.type === 'secret') {
                        return decryptionKey;
                    } else
                        throw new Error('Decryption key must be raw data or secret key type');
                }
            }).then(function (encryptionKey) {
                // Decrypt key with encryption key
                return subtle.decrypt(encryption, encryptionKey, encryptedContent);
            }).then(function (decryptedContent) {
                // Create content info object
                return createContentInfo({
                    contentType: self.encryptedContentInfo.contentType,
                    content: decryptedContent
                });
            });
        } // </editor-fold>
    });

    /**
     * This class encapsulates a CMS object of content type encrypted-data.
     * 
     * @memberOf GostCMS
     * @type GostCMS.EncryptedDataContentInfo
     */
    GostCMS.prototype.EncryptedDataContentInfo = EncryptedDataContentInfo;
    /**
     * This class encapsulates a CMS object of content type enveloped-data.
     * 
     * @class GostCMS.EnvelopedDataContentInfo
     * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The encrypted data content.
     * @extends GostCMS.DataContentInfo
     * @extends GostASN1.EnvelopedData
     */
    function EnvelopedDataContentInfo(contentInfo) // <editor-fold defaultstate="collapsed">
    {
        DataContentInfo.call(this, contentInfo, {
            contentType: 'envelopedData',
            version: 0,
            recipientInfos: [],
            encryptedContentInfo: {
                contentType: 'data',
                contentEncryptionAlgorithm: providers[options.providerName].encryption
            }
        });
    }  // </editor-fold>

    extend(DataContentInfo, EnvelopedDataContentInfo, {
        /**
         * Generate content encryption key with given encryption algorithm and encrypt the content
         * 
         * @memberOf GostCMS.EnvelopedDataContentInfo
         * @instance
         * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The content data to be enclosed.
         * @param {(AlgorithmIdentifier|string)} encryptionAlgorithm The encryption algorithm or provider name
         * @returns {Promise} Promise to return self object after encrypt content
         */
        encloseContent: function (contentInfo, encryptionAlgorithm) // <editor-fold defaultstate="collapsed"> 
        {
            var self = this;
            return new Promise(call).then(function () {
                // Check content info
                contentInfo = checkContentInfo(contentInfo);
                if (!contentInfo.content)
                    throw new Error('Content for encryption must be specified');
                // Define encryption algorithm
                if (encryptionAlgorithm) {
                    var provider = providers[encryptionAlgorithm];
                    encryptionAlgorithm = (provider && provider.encryption) || encryptionAlgorithm;
                } else
                    encryptionAlgorithm = providers[options.providerName].encryption;
                // Generate key for encryption content
                return subtle.generateKey(encryptionAlgorithm, true, ['encrypt']);
            }).then(function (encryptionKey) {
                // Encrypt content
                self.contentEncryptionKey = encryptionKey;
                // Initial vector
                if (!encryptionAlgorithm.iv)
                    encryptionAlgorithm.iv = getSeed(8);
                return subtle.encrypt(encryptionAlgorithm, encryptionKey, contentInfo.content);
            }).then(function (encryptedContent) {
                self.encryptedContentInfo = {
                    contentType: contentInfo.contentType,
                    contentEncryptionAlgorithm: encryptionAlgorithm,
                    encryptedContent: encryptedContent
                };
                return self;
            });
        }, // </editor-fold>
        /**
         * Add a recipient. <br><br>
         * 
         * Uses the Recipient Information with IssuerAndSerialNumber as the Recipient Identifier.
         * Note: If senderCert specified uses the Key Agreement algorithm overwise Key Transport algorithm.
         * 
         * @memberOf GostCMS.EnvelopedDataContentInfo
         * @instance
         * @param {GostCert.X509} recipientCert The certificate of recepient
         * @param {(AlgorithmIdentifier|string)} keyEncryptionAlgorithm Key encryption algorithm or provider name
         * @param {GostASN1.PrivateKeyInfo} senderKey The private key of sender for key agreement protocol
         * @param {GostCert.X509} senderCert The certificate of sender for key agreement protocol
         * @returns {Promise} Promise to return self object after add recipient
         */
        addRecipient: function (recipientCert, keyEncryptionAlgorithm, senderKey, senderCert) // <editor-fold defaultstate="collapsed">
        {
            var self = this, privateKey, encryptionProvider, derivation, wrapping;
            return new Promise(call).then(function () {
                // Check for recepient cert
                recipientCert = new cert.X509(recipientCert);
                if (keyEncryptionAlgorithm && typeof keyEncryptionAlgorithm !== 'string' &&
                        !keyEncryptionAlgorithm.algorithm) {
                    // Sender parameters
                    senderCert = senderKey;
                    senderKey = keyEncryptionAlgorithm;
                    keyEncryptionAlgorithm = undefined;
                }
                if (keyEncryptionAlgorithm) {
                    encryptionProvider = providers[keyEncryptionAlgorithm];
                } else
                    encryptionProvider = recipientCert.getProvider();

                // Check for content encryption key
                if (!self.contentEncryptionKey)
                    throw new Error('The content encryption key is not assigned');

                if (senderCert) {
                    // Sender certificate for agreement protocol
                    var senderCertChain;
                    if (senderCert instanceof Array) {
                        senderCertChain = senderCert;
                        senderCert = senderCertChain[0];
                    } else
                        senderCertChain = [senderCert];

                    // Add sender certificate
                    if (options.autoAddCert) {
                        if (!self.originatorInfo)
                            self.originatorInfo = {certs: []};
                        else if (!self.originatorInfo.certs)
                            self.originatorInfo.certs = [];
                        for (var i = 0, n = senderCertChain.length; i < n; i++) {
                            addUnique(self.originatorInfo.certs, senderCertChain[i], function (cert1, cert2) {
                                return equalNames(cert1.issuer, cert2.issuer) &&
                                        equalNumbers(cert1.serialNumber, cert2.serialNumber);
                            });
                        }
                    }
                    // Key Agreement
                    if (encryptionProvider)
                        keyEncryptionAlgorithm = expand(encryptionProvider.agreement);
                    else
                        encryptionProvider = recipientCert.getProvider();
                    // Certificates must have similar curve parameters
                    if (recipientCert.subjectPublicKeyInfo.algorithm.namedCurve !==
                            senderCert.subjectPublicKeyInfo.algorithm.namedCurve)
                        throw new Error('The sender and the recipient have different public key algorithms');
                    // Get private sender key
                    return subtle.importKey('pkcs8', senderKey.encode(), senderKey.privateKeyAlgorithm,
                            false, ['deriveKey']);
                } else {
                    // Key Transport
                    if (encryptionProvider)
                        keyEncryptionAlgorithm = expand(recipientCert.subjectPublicKeyInfo.algorithm);
                    else
                        encryptionProvider = recipientCert.getProvider();
                    // Generate key pair
                    return subtle.generateKey(keyEncryptionAlgorithm, true, ['deriveKey']).then(function (keyPair) {
                        keyEncryptionAlgorithm['public'] = keyPair.publicKey;
                        return keyPair.privateKey;
                    });
                }
            }).then(function (key) {
                privateKey = key;
                // Get public key from recipient certificate
                return subtle.importKey('spki', recipientCert.subjectPublicKeyInfo.encode(),
                        recipientCert.subjectPublicKeyInfo.algorithm, false, ['deriveKey', 'deriveBits']);
            }).then(function (publicKey) {
                // Derivate key encryption key
                keyEncryptionAlgorithm.ukm = getSeed(8);
                derivation = expand(encryptionProvider.agreement,
                        {sBox: keyEncryptionAlgorithm.sBox, ukm: keyEncryptionAlgorithm.ukm, 'public': publicKey});
                wrapping = expand(keyEncryptionAlgorithm.wrapping || encryptionProvider.wrapping,
                        {ukm: keyEncryptionAlgorithm.ukm});
                return subtle.deriveKey(derivation, privateKey, wrapping, true, ['wrapKey']);
            }).then(function (wrappingKey) {
                // Wrap content encryption key 
                keyEncryptionAlgorithm.wrapping = wrapping;
                return subtle.wrapKey('raw', self.contentEncryptionKey, wrappingKey, wrapping);
            }).then(function (wrappedKey) {
                // Create recipient info
                var recipientInfo;
                var useKeyIdentifier = options.useKeyIdentifier && recipientCert.extensions &&
                        recipientCert.extensions.subjectKeyIdentifier,
                        rid = useKeyIdentifier ? recipientCert.extensions.subjectKeyIdentifier : {
                            issuer: recipientCert.issuer,
                            serialNumber: recipientCert.serialNumber};
                if (senderCert) {
                    var spki = senderCert.subjectPublicKeyInfo;
                    recipientInfo = {// KeyAgreeRecipientInfo
                        version: 3, // always set to 3
                        originator: {
                            algorithm: spki.algorithm,
                            publicKey: spki.subjectPublicKey
                        },
                        ukm: keyEncryptionAlgorithm.ukm,
                        keyEncryptionAlgorithm: keyEncryptionAlgorithm,
                        recipientEncryptedKeys: [{// use only one recipient in domain
                                rid: rid,
                                encryptedKey: asn1.GostEncryptedKey(keyEncryptionAlgorithm).encode(wrappedKey)
                            }]
                    };
                } else {
                    recipientInfo = {
                        version: 0, // always set to 0 or 2
                        rid: rid,
                        keyEncryptionAlgorithm: keyEncryptionAlgorithm,
                        encryptedKey: asn1.GostEncryptedKey(keyEncryptionAlgorithm).encode({
                            algorithm: keyEncryptionAlgorithm,
                            sessionEncryptedKey: wrappedKey
                        })
                    };
                }
                self.recipientInfos.push(recipientInfo);
                return self;
            });
        }, // </editor-fold>
        /**
         * Returns the decrypted content. 
         * 
         * @memberOf GostCMS.EnvelopedDataContentInfo
         * @instance
         * @param {GostASN1.PrivateKeyInfo} recipientKey The decryption key or password for derive key   
         * @param {GostCert.X509} recipientCert  The decryption key or password for derive key   
         * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The detached content (optional).
         * @param {GostCert.X509} originatorCert The originator certificate (optional).
         * @returns {Promise} Promise to return enclosed object {@ling GostASN1.ContentInfo} after decrypt content
         */
        getEnclosed: function (recipientKey, recipientCert, contentInfo, originatorCert) // <editor-fold defaultstate="collapsed">
        {
            var self = this, wrappedKey, encryptedContent, derivation, wrapping, encryption;
            return new Promise(call).then(function () {
                var encryptionProvider = recipientCert.getProvider();
                // Append attached
                if (contentInfo)
                    self.setEnclosed(contentInfo);
                encryptedContent = self.encryptedContentInfo.encryptedContent;
                if (!encryptedContent)
                    throw new Error('Encrypted content must be specified');

                encryption = self.encryptedContentInfo.contentEncryptionAlgorithm;

                // Find receiver
                for (var i = 0; i < self.recipientInfos.length; i++) {
                    var recipientInfo = self.recipientInfos[i],
                            algorithm = expand(recipientInfo.keyEncryptionAlgorithm);
                    if (recipientInfo.rid) {
                        if (matchCert(recipientInfo.rid, recipientCert)) {
                            // Algorithm and wrapped key
                            var transportKey = asn1.GostEncryptedKey(algorithm).decode(recipientInfo.encryptedKey).object;
                            wrappedKey = transportKey.sessionEncryptedKey;
                            algorithm = expand(algorithm, transportKey.algorithm);
                            derivation = expand(encryptionProvider.agreement, {ukm: algorithm.ukm, sBox: algorithm.sBox});
                            wrapping = expand(encryptionProvider.wrapping, algorithm.wrapping, {ukm: algorithm.ukm});
                            return algorithm['public'];
                        }
                    } else {
                        var keys = recipientInfo.recipientEncryptedKeys;
                        if (keys) {
                            for (var j = 0; j < keys.length; j++) {
                                if (matchCert(keys[j].rid, recipientCert)) {
                                    // Algorithm and wrapped key
                                    algorithm = expand(encryptionProvider.agreement, algorithm, {ukm: recipientInfo.ukm});
                                    wrappedKey = asn1.GostEncryptedKey(algorithm).decode(keys[j].encryptedKey).object;
                                    derivation = algorithm;
                                    wrapping = expand(algorithm.wrapping || encryptionProvider.wrapping, {ukm: recipientInfo.ukm});
                                    // Check originator
                                    var originator = recipientInfo.originator;
                                    if (originator.algorithm) {
                                        var spki = new asn1.SubjectPublicKeyInfo({
                                            algorithm: originator.algorithm,
                                            subjectPublicKey: originator.publicKey});
                                        return subtle.importKey('spki', spki.encode(), spki.algorithm, false, ['deriveKey', 'deriveBits']);
                                    } else if (originatorCert && matchCert(originator, originatorCert))
                                        return importKey('pkcs', originatorCert.subjectPublicKeyInfo.encode(),
                                                originatorCert.subjectPublicKeyInfo.algorithm, false, ['deriveKey', 'deriveBits']);
                                    else
                                        throw Error('Originator certificate not specified or not valid');
                                }
                            }
                        }
                    }
                }
                throw new Error('Recipient not found or format not supported');
            }).then(function (publicKey) {
                derivation['public'] = publicKey;
                // Import private key
                return subtle.importKey('pkcs8', recipientKey.encode(), recipientKey.privateKeyAlgorithm,
                        false, ['deriveKey', 'deriveBits']);
            }).then(function (privateKey) {
                // Derive key
                return subtle.deriveKey(derivation, privateKey, wrapping, true, ['unwrapKey']);
            }).then(function (unwrappingKey) {
                // Unwrap key
                return subtle.unwrapKey('raw', wrappedKey, unwrappingKey,
                        wrapping, encryption, false, ['decrypt']);
            }).then(function (encryptionKey) {
                // Decrypt content
                return subtle.decrypt(encryption, encryptionKey, encryptedContent);
            }).then(function (decryptedContent) {
                return createContentInfo({
                    contentType: self.encryptedContentInfo.contentType,
                    content: decryptedContent
                });
            });
        } // </editor-fold>
    });

    /**
     * This class encapsulates a CMS object of content type enveloped-data.
     * 
     * @memberOf GostCMS
     * @type GostCMS.EnvelopedDataContentInfo
     */
    GostCMS.prototype.EnvelopedDataContentInfo = EnvelopedDataContentInfo;

    /**
     * Implements the Cryptographic Message Syntax as specified in RFC-2630.
     * 
     * @memberOf gostCrypto
     * @type GostCMS
     */
    gostCrypto.cms = new GostCMS();

    return GostCMS;

}));