WebCrypto GOST Source: gostKeys.js

/** 
 * @file Key and Certificate Store methods
 * @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.
 *    
 * 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', 'gostCMS'], factory);
    } else if (typeof exports === 'object') {
        module.exports = factory(require('gostCrypto'), require('gostASN1'), require('gostCert'), require('gostCMS'));
    } else {
        root.GostKeys = factory(root.gostCrypto, root.GostASN1, root.GostCert, root.GostCMS);
    }
    // </editor-fold>

}(this, function (gostCrypto) {

    /*
     * Common tools and methods
     */ // <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 providers = gostCrypto.security.providers;
    var cert = gostCrypto.cert;
    var cms = gostCrypto.cms;

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

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

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


    // Get buffer
    function buffer(d) {
        if (d instanceof CryptoOperationData)
            return d;
        else if (d && d.buffer && d.buffer instanceof CryptoOperationData)
            return d.byteOffset === 0 && d.byteLength === d.buffer.byteLength ?
                    d.buffer : new Uint8Array(new Uint8Array(d, d.byteOffset, d.byteLength)).buffer;
        else
            throw new DataError('CryptoOperationData required');
    }
    // Today date + n days with time
    function now(n) {
        var date = new Date();
        if (n)
            date.setDate(date.getDate() + n);
        return date;
    }

    // Today date + n days
    function today(n) {
        var date = now(n);
        date.setHours(0, 0, 0, 0);
        return date;
    }

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

    // Generate new alias
    function generateUUID() {
        var r = new Uint8Array(getSeed(16)), s = '';
        for (var i = 0; i < 16; i++)
            s += ('00' + r[i].toString(16)).slice(-2);
        return s.substr(0, 8) + '-' + s.substr(8, 4) + '-4' + s.substr(13, 3) +
                '-9' + s.substr(17, 3) + '-' + s.substr(20, 12);
    }

    // Return get32 from buffer
    function get32(buffer, offset) {
        var r = new Uint8Array(buffer, offset, 4);
        return (r[3] << 24) | (r[2] << 16) | (r[1] << 8) | r[0];
    }

    function set32(buffer, offset, int) {
        var r = new Uint8Array(buffer, offset, 4);
        r[3] = int >>> 24;
        r[2] = int >>> 16 & 0xff;
        r[1] = int >>> 8 & 0xff;
        r[0] = int & 0xff;
        return r;
    }

    // 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 (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');
    }

    // </editor-fold>

    /**
     * Key and Certificate Store methods
     * 
     * @class GostKeys
     */
    function GostKeys() {
    }

    /**
     * Key templates
     * <ul>
     *      <li>providerName - provider name for key encryption, default 'CP-01'</li>
     *      <li>days - validity of the key in days, default 7305</li>
     * </ul>
     * 
     * @memberOf GostKeys
     * @instance
     */
    var options = {// <editor-fold defaultstate="collapsed">
        providerName: 'CP-01',
        days: 7305 // </editor-fold>
    };

    GostKeys.prototype.options = options;

    /**
     * A class for private keys in PKCS #8 format
     * 
     * @class GostKeys.PKCS8
     * @extends GostASN1.PrivateKeyInfo
     * @param {(FormatedData|GostASN1.PrivateKeyInfo)} keyInfo
     */
    function PKCS8(keyInfo) {
        asn1.PrivateKeyInfo.call(this, keyInfo);
    }

    extend(asn1.PrivateKeyInfo, PKCS8, {
        /**
         * Get the private key
         * 
         * @memberOf GostKeys.PKCS8
         * @instance
         * @returns {Promise} Promise to return the {@link Key}
         */
        getPrivateKey: function () // <editor-fold defaultstate="collapsed">
        {
            var keyUsages = (this.privateKeyAlgorithm.id === 'rsaEncryption') ? ['sign'] :
                    ['sign', 'deriveKey', 'deriveBits'];
            return subtle.importKey('pkcs8', this.encode(), this.privateKeyAlgorithm, 'true', keyUsages);
        }, // </editor-fold>
        /**
         * Set the private key
         * 
         * @memberOf GostKeys.PKCS8
         * @instance
         * @param {Key} privateKey The Private Key
         * @returns {Promise} Promise to return the self object after set the key
         */
        setPrivateKey: function (privateKey) // <editor-fold defaultstate="collapsed">
        {
            var self = this;
            return subtle.exportKey('pkcs8', privateKey).then(function (keyInfo) {
                asn1.PrivateKeyInfo.call(self, keyInfo);
                return self;
            });
        }, // </editor-fold> 
        /**
         * Generate private key and return certification request
         * 
         * @memberOf GostKeys.PKCS8
         * @instance
         * @param {(FormatedData|GostASN1.CertificationRequest)} req The request templates
         * @param {(AlgorithmIdentifier|string)} keyAlgorithm The name of provider or algorithm identifier
         * @returns {Promise} Promise to return the {@link GostCert.Request} after key generation
         */
        generate: function (req, keyAlgorithm) // <editor-fold defaultstate="collapsed">
        {
            var self = this;
            return new Promise(call).then(function () {
                if (!(req instanceof cert.Request))
                    req = new cert.Request(req);
                // Generate request
                return req.generate(keyAlgorithm);
            }).then(function (key) {
                asn1.PrivateKeyInfo.call(self, key);
                ;
                return req;
            });
        } // </editor-fold> 
    });

    /**
     * A class for private keys in PKCS #8 format
     * 
     * @memberOf GostKeys
     * @type GostKeys.PKCS8
     */
    GostKeys.prototype.PKCS8 = PKCS8;

    /**
     * A class for PKCS #5 and PKCS #12 password-encrypted private keys in PKCS #8 format
     * 
     * @class GostKeys.PKCS8Encrypted
     * @extends GostASN1.EncryptedPrivateKeyInfo
     * @param {(FormatedData|GostASN1.EncryptedPrivateKeyInfo)} encryptedKey
     */
    function PKCS8Encrypted(encryptedKey) {
        asn1.EncryptedPrivateKeyInfo.call(this, encryptedKey);
    }

    extend(asn1.EncryptedPrivateKeyInfo, PKCS8Encrypted, {
        /**
         * Get the private key info
         * 
         * @memberOf GostKeys.PKCS8Encrypted
         * @instance
         * @param {(Key|CryptoOperationData|string)} keyPassword The secret key or password for decryption 
         * @returns {Promise} Promise to return decrypted {@link GostKeys.PKCS8}
         */
        getKey: function (keyPassword) // <editor-fold defaultstate="collapsed">
        {
            var self = this, engine;
            return new Promise(call).then(function () {
                engine = new cms.EncryptedDataContentInfo({
                    contentType: 'encryptedData',
                    version: 0,
                    encryptedContentInfo: {
                        contentType: 'data',
                        contentEncryptionAlgorithm: self.encryptionAlgorithm,
                        encryptedContent: self.encryptedData
                    }
                });
                return engine.getEnclosed(keyPassword);
            }).then(function (contentInfo) {
                // Create key object
                return PKCS8.decode(contentInfo.content);
            });
        }, // </editor-fold>
        /**
         * Get the private key
         * 
         * @memberOf GostKeys.PKCS8Encrypted
         * @instance
         * @param {(Key|CryptoOperationData|string)} keyPassword The secret key or password for decryption 
         * @returns {Promise} Promise to return decrypted {@link Key}
         */
        getPrivateKey: function (keyPassword) // <editor-fold defaultstate="collapsed">
        {
            return this.getKey(keyPassword).then(function (keyInfo) {
                return keyInfo.getPrivateKey();
            });
        }, // </editor-fold>
        /**
         * Sets and encrypt the private key info
         * 
         * @memberOf GostKeys.PKCS8Encrypted
         * @instance
         * @param {(FormatedData|GostKeys.PKCS8)} keyInfo The private key info 
         * @param {(Key|CryptoOperationData|string)} keyPassword The secret key or password for encryption
         * @param {(AlgorithmIdentifier|string)} encryptionAlgorithm The encryption algorithm or provider name
         * @returns {Promise} Promise to return self object after set key 
         */
        setKey: function (keyInfo, keyPassword, encryptionAlgorithm) // <editor-fold defaultstate="collapsed">
        {
            var self = this, engine;
            return new Promise(call).then(function () {
                keyInfo = new PKCS8(keyInfo);
                engine = new cms.EncryptedDataContentInfo();
                return engine.encloseContent(keyInfo.encode(), keyPassword, encryptionAlgorithm || options.providerName);
            }).then(function () {
                self.encryptionAlgorithm = engine.encryptedContentInfo.contentEncryptionAlgorithm;
                self.encryptedData = engine.encryptedContentInfo.encryptedContent;
                return self;
            });
        }, // </editor-fold>
        /**
         * Set the private key
         * 
         * @memberOf GostKeys.PKCS8Encrypted
         * @instance
         * @param {Key} privateKey The private key
         * @param {(Key|CryptoOperationData|string)} keyPassword The secret key or password for decryption 
         * @param {(AlgorithmIdentifier|string)} encryptionAlgorithm The encryption algorithm or provider name
         * @returns {Promise} Promise to return self object after set key 
         */
        setPrivateKey: function (privateKey, keyPassword, encryptionAlgorithm) // <editor-fold defaultstate="collapsed">
        {
            var self = this;
            return new PKCS8().setPrivateKey(privateKey).then(function (keyInfo) {
                return self.setKey(keyInfo, keyPassword, encryptionAlgorithm);
            });
        }, // </editor-fold>
        /**
         * Generate private key and return certification request
         * 
         * @memberOf GostKeys.PKCS8Encrypted
         * @instance
         * @param {(FormatedData|GostASN1.CertificationRequest)} req The request templates
         * @param {(Key|CryptoOperationData|string)} keyPassword The secret key or password for decryption 
         * @param {(AlgorithmIdentifier|string)} keyAlgorithm The name of provider or algorithm
         * @param {(AlgorithmIdentifier|string)} encryptionAlgorithm The encryption algorithm or provider name
         * @returns {Promise} Promise to return {@link GostCert.Request}
         */
        generate: function (req, keyPassword, keyAlgorithm, encryptionAlgorithm) // <editor-fold defaultstate="collapsed">
        {
            var self = this;
            return new Promise(call).then(function () {
                if (!(req instanceof cert.Request))
                    req = new cert.Request(req);
                // Generate request
                return req.generate(keyAlgorithm);
            }).then(function (key) {
                return self.setKey(key, keyPassword, encryptionAlgorithm);
            }).then(function () {
                return req;
            });
        }  // </editor-fold>  
    });

    /**
     * A class for PKCS #5 and PKCS #12 password-encrypted private keys in PKCS #8 format
     * 
     * @memberOf GostKeys
     * @type GostKeys.PKCS8Encrypted
     */
    GostKeys.prototype.PKCS8Encrypted = PKCS8Encrypted;

    /**
     * A class for password-encrypted private keys in SignalCom container<br><br>
     * 
     * The container file list:
     * <ul>
     *      <li>mk.db3 - master key data</li>
     *      <li>masks.db3 - encrypted or decrypted masks</li>
     *      <li>kek.opq - wrapped key encryption key</li>
     *      <li>rand.opq - wrapped random data</li>
     * </ul>
     * 
     * @class GostKeys.SignalComKeyContainer
     * @param {SignalComKeyContainer} container 
     */
    function SignalComKeyContainer(container) // <editor-fold defaultstate="collapsed"> 
    {
        if (container) {
            var self = this;
            ['mk.db3', 'masks.db3', 'kek.opq', 'rand.opq'].forEach(function (name) {
                self[name] = container[name];
            });
        }
    } // </editor-fold>

    extend(Object, SignalComKeyContainer, {
        /**
         * Get password-based encryption key
         * 
         * @memberOf GostKeys.SignalComKeyContainer
         * @instance
         * @param {string} keyPassword 
         * @returns {Promise} Promise to return {@link Key}
         */
        getEncryptionKey: function (keyPassword) // <editor-fold defaultstate="collapsed"> 
        {
            var self = this, wrapping = providers['SC-01'].wrapping,
                    encryption = providers['SC-01'].encryption,
                    derivation = providers['SC-01'].derivation,
                    masks = self['masks.db3'], mk = self['mk.db3'], kek = self['kek.opq'];
            // Decrypt key
            return new Promise(call).then(function () {
                if ((!masks || !mk || !kek))
                    throw new Error('Not enougth key container files');
                // Check for encrypted key
                if (masks.byteLength > 32) {
                    if (keyPassword) {
                        // Extract password based encryption mask
                        return subtle.importKey('raw', coding.Chars.decode(keyPassword, 'utf8'),
                                derivation, false, ['deriveKey', 'deriveBits']).then(function (integrityKey) {
                            return subtle.deriveKey(expand(derivation,
                                    {salt: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])}),
                                    integrityKey, encryption, false, ['decrypt']);
                        }).then(function (encryptionKey) {
                            var encrypted = new cms.EncryptedDataContentInfo(masks);
                            return encrypted.getEnclosed(encryptionKey);
                        }).then(function (digested) {
                            return digested.verify();
                        }).then(function (data) {
                            return data.content;
                        });
                    } else
                        throw new Error('Key password is required');
                } else if (keyPassword)
                    throw new Error('Key password is not required');
                return masks;
            }).then(function (decrypedMasks) {
                // Combine masks
                masks = decrypedMasks;
                var mkm = new Uint8Array(mk.byteLength + masks.byteLength);
                mkm.set(new Uint8Array(mk), 0);
                mkm.set(new Uint8Array(masks), mk.byteLength);
                // Import master key
                return subtle.importKey('raw', mkm.buffer, wrapping, false, ['unwrapKey']);
            }).then(function (unwrappingKey) {
                // Unwrap kek
                return subtle.unwrapKey('raw', kek, unwrappingKey, wrapping, encryption,
                        false, ['wrapKey', 'unwrapKey']);
            });
        }, // </editor-fold>
        /**
         * Generate encryption key and container files
         * 
         * @memberOf GostKeys.SignalComKeyContainer
         * @instance
         * @param {string} keyPassword
         * @returns {Promise} Promise to return {@link Key}
         */
        generateContainer: function (keyPassword) // <editor-fold defaultstate="collapsed"> 
        {
            var self = this, wrapping = providers['SC-01'].wrapping,
                    encryption = providers['SC-01'].encryption,
                    derivation = providers['SC-01'].derivation,
                    digest = providers['SC-01'].digest,
                    encryptionKey, wrappingKey;
            return new Promise(call).then(function () {
                // Generate wrapping key
                return subtle.generateKey(wrapping, true, ['wrapKey']);
            }).then(function (key) {
                wrappingKey = key;
                // Split masks
                var len = wrappingKey.buffer.byteLength;
                self['mk.db3'] = new Uint8Array(new Uint8Array(wrappingKey.buffer, 0, len - 32)).buffer;
                var masks = new Uint8Array(new Uint8Array(wrappingKey.buffer, len - 32, 32)).buffer;
                if (keyPassword) {
                    // Encrypt masks
                    var encrypted = new cms.EncryptedDataContentInfo(),
                            digested = new cms.DigestedDataContentInfo();
                    // Digest data
                    return digested.encloseContent(masks, digest).then(function () {
                        digested = {// Double wrapping - SignalCom mistake
                            contentType: 'digestedData',
                            content: digested.encode()
                        };
                        return subtle.importKey('raw', coding.Chars.decode(keyPassword, 'utf8'),
                                derivation, false, ['deriveKey', 'deriveBits']);
                    }).then(function (integrityKey) {
                        return subtle.deriveKey(expand(derivation,
                                {salt: new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])}),
                                integrityKey, encryption, false, ['encrypt']);
                    }).then(function (encryptionKey) {
                        // Encrypt data with password
                        return encrypted.encloseContent(digested, encryptionKey, encryption);
                    }).then(function () {
                        return encrypted.encode();
                    });
                }
                return masks;
            }).then(function (masks) {
                self['masks.db3'] = masks;
                // Generate encryption key
                return subtle.generateKey(encryption, false, ['wrapKey', 'unwrapKey']);
            }).then(function (key) {
                encryptionKey = key;
                // Wrap encryption key
                return subtle.wrapKey('raw', key, wrappingKey, wrapping);
            }).then(function (data) {
                self['kek.opq'] = data;
                // Generate random seed
                return subtle.generateKey(encryption, false, ['wrapKey', 'unwrapKey']);
            }).then(function (key) {
                // Wrap random seed
                return subtle.wrapKey('raw', key, wrappingKey, wrapping);
            }).then(function (data) {
                self['rand.opq'] = data;
                return encryptionKey;
            });
        } // </editor-fold>
    });

    /**
     * A class for password-encrypted private keys in SignalCom container
     * 
     * @memberOf GostKeys
     * @type GostKeys.SignalComKeyContainer
     */
    GostKeys.prototype.SignalComKeyContainer = SignalComKeyContainer;

    /**
     * A class for password-encrypted SignalCom private keys
     * 
     * @class GostKeys.SignalComPrivateKeyInfo
     * @extends GostASN1.GostWrappedPrivateKey
     * @extends GostKeys.SignalComKeyContainer
     * @param {GostASN1.PrivateKeyInfo} keyInfo 
     * @param {GostKeys.SignalComKeyContainer} container 
     */
    function SignalComPrivateKeyInfo(keyInfo, container) // <editor-fold defaultstate="collapsed"> 
    {
        asn1.GostWrappedPrivateKey.call(this, keyInfo);
        SignalComKeyContainer.call(this, container);
    } // </editor-fold>

    extend(asn1.GostWrappedPrivateKey, SignalComPrivateKeyInfo, {
        /**
         * Get the private key info
         * 
         * @memberOf GostKeys.SignalComPrivateKeyInfo
         * @param {string} keyPassword The password for decryption 
         * @returns {Promise} Promise to return {@link GostKeys.PKCS8}
         */
        getKey: function (keyPassword) // <editor-fold defaultstate="collapsed">
        {
            return this.getPrivateKey(keyPassword).then(function (privateKey) {
                return new PKCS8().setPrivateKey(privateKey);
            });
        }, // </editor-fold>
        /**
         * Get the private key
         * 
         * @memberOf GostKeys.SignalComPrivateKeyInfo
         * @instance
         * @param {string} keyPassword The password for decryption 
         * @returns {Promise} Promise to return the {@link Key}
         */
        getPrivateKey: function (keyPassword) // <editor-fold defaultstate="collapsed">
        {
            var self = this, wrapping = providers['SC-01'].wrapping,
                    publicKeyData;
            // Decrypt key
            return new Promise(call).then(function () {
                // Get password key
                return self.getEncryptionKey(keyPassword, true);
            }).then(function (encryptionKey) {
                // Unwrap private key
                return subtle.unwrapKey('raw', self.privateKeyWrapped, encryptionKey, wrapping,
                        self.privateKeyAlgorithm, true, ['sign', 'deriveKey', 'deriveBits']);
            }).then(function (privateKey) {
                publicKeyData = self.attributes && self.attributes['id-sc-gostR3410-2001-publicKey'];
                // Generate key pair
                if (publicKeyData)
                    return subtle.generateKey(expand(privateKey.algorithm, {ukm: privateKey.buffer}),
                            privateKey.extractable, privateKey.usages);
                else
                    return {privateKey: privateKey};
            }).then(function (keyPair) {
                // Compare public key
                if (publicKeyData && !equalBuffers(keyPair.publicKey.buffer, publicKeyData))
                    throw new Error('Check public key failed');
                return keyPair.privateKey;
            });
        }, // </editor-fold>
        /**
         * Sets and encrypt the private key info
         * 
         * @memberOf GostKeys.SignalComPrivateKeyInfo
         * @instance
         * @param {(FormatedData|GostKeys.PKCS8)} keyInfo The private key info 
         * @param {string} keyPassword The password for encryption
         * @returns {Promise} Promise to return self object after set the key
         */
        setKey: function (keyInfo, keyPassword) // <editor-fold defaultstate="collapsed">
        {
            var self = this;
            return new PKCS8(keyInfo).getPrivateKey().then(function (privateKey) {
                return self.setPrivateKey(privateKey, keyPassword);
            });
        }, // </editor-fold>
        /**
         * Set the private key
         * 
         * @memberOf GostKeys.SignalComPrivateKeyInfo
         * @instance
         * @param {Key} privateKey The private key
         * @param {string} keyPassword The secret key encryption 
         * @returns {Promise} Promise to return self object after set the key
         */
        setPrivateKey: function (privateKey, keyPassword) // <editor-fold defaultstate="collapsed">
        {
            var self = this, wrapping = providers['SC-01'].wrapping, wrappedData;
            return new Promise(call).then(function () {
                // Get or generate encryption key
                return self.getEncryptionKey(keyPassword)['catch'](function () {
                    return self.generateContainer(keyPassword);
                });
            }).then(function (encryptionKey) {
                // Encrypt key buffer
                return subtle.wrapKey('raw', privateKey, encryptionKey, wrapping);
            }).then(function (data) {
                wrappedData = data;
                // Generate public key
                return subtle.generateKey(expand(privateKey.algorithm,
                        {ukm: privateKey.buffer}), true, ['sign', 'verify']);
            }).then(function (keyPair) {
                self.object = {
                    version: 0,
                    privateKeyAlgorithm: privateKey.algorithm,
                    privateKeyWrapped: wrappedData,
                    attributes: {
                        'id-sc-gostR3410-2001-publicKey': keyPair.publicKey.buffer
                    }
                };
                return self;
            });
        }, // </editor-fold>
        /**
         * Change key password
         * 
         * @memberOf GostKeys.SignalComPrivateKeyInfo
         * @instance
         * @param {string} oldKeyPassword Old key password
         * @param {string} newKeyPassword New key password
         * @returns {Promise} Promise to return self object after change password
         */
        changePassword: function (oldKeyPassword, newKeyPassword) // <editor-fold defaultstate="collapsed">
        {
            var self = this;
            return self.getPrivateKey(oldKeyPassword).then(function (privateKey) {
                return self.setPrivateKey(privateKey, newKeyPassword);
            });
        }, // </editor-fold>
        /**
         * Generate private key, certificate and return certification request
         * 
         * @memberOf GostKeys.SignalComPrivateKeyInfo
         * @instance
         * @param {(FormatedData|GostASN1.CertificationRequest)} req The request templates
         * @param {(Key|CryptoOperationData|string)} keyPassword The secret key or password for decryption 
         * @param {(AlgorithmIdentifier|string)} keyAlgorithm The name of provider or algorithm
         * @returns {Promise} Promise to return {@link GostCert.Request}
         */
        generate: function (req, keyPassword, keyAlgorithm) // <editor-fold defaultstate="collapsed">
        {
            var self = this, keyInfo;
            return new Promise(call).then(function () {
                if (!(req instanceof cert.Request))
                    req = new cert.Request(req);
                // Generate request
                return req.generate(keyAlgorithm);
            }).then(function (key) {
                keyInfo = key;
                return self.setKey(keyInfo, keyPassword);
            }).then(function () {
                return req;
            });
        } // </editor-fold>
    });
    defineProperties(SignalComPrivateKeyInfo.prototype, SignalComKeyContainer.prototype);

    /**
     * A class for password-encrypted SignalCom private keys
     * 
     * @memberOf GostKeys
     * @type GostKeys.SignalComPrivateKeyInfo
     */
    GostKeys.prototype.SignalComPrivateKeyInfo = SignalComPrivateKeyInfo;

    /**
     * A class for password-encrypted private keys in CryptoPro container
     * 
     * The container file list:
     * <ul>
     *      <li>header - container header @link GostASN1.GostKeyContainer</li>
     *      <li>name - container name @link GostASN1.GostKeyContainerName</li>
     *      <li>primary - private keys data @link GostASN1.GostPrivateKeys</li>
     *      <li>masks - private key masks @link GostASN1.GostPrivateMasks</li>
     *      <li>primary2 - reserve of private keys data @link GostASN1.GostPrivateKeys</li>
     *      <li>masks2 - reserve of private key masks @link GostASN1.GostPrivateMasks</li>
     * </ul>
     * 
     * @class GostKeys.CryptoProKeyContainer
     * @param {Object} container 
     */
    function CryptoProKeyContainer(container) // <editor-fold defaultstate="collapsed"> 
    {
        if (container) {
            this.header = asn1.GostKeyContainer.decode(container.header);
            this.name = asn1.GostKeyContainerName.decode(container.name);
            this.primary = asn1.GostPrivateKeys.decode(container.primary);
            this.masks = asn1.GostPrivateMasks.decode(container.masks);
            if (container.primary2 && container.masks2) {
                this.primary2 = asn1.GostPrivateKeys.decode(container.primary2);
                this.masks2 = asn1.GostPrivateMasks.decode(container.masks2);
            }
        }
    } // </editor-fold>

    extend(Object, CryptoProKeyContainer, (function () {

        // <editor-fold defaultstate="collapsed"> 
        // True if 512 bit
        function isKeySize512(algorithm) {
            return algorithm.name.indexOf('-512') >= 0 || algorithm.length === 512;
        }

        // Test version 2012
        function isVersion2012(algorithm) {
            return !((algorithm.name.indexOf('-94') >= 0 || algorithm.name.indexOf('-2001') >= 0 ||
                    algorithm.version === 1994 || algorithm.version === 2001));
        }

        // Derive password key
        function derivePasswordKey(algorithm, password, salt) {
            var hash = isVersion2012(algorithm) ? 'GOST R 34.11-256' : 'GOST R 34.11-94/' + (algorithm.sBox || 'D-A'),
                    derivation = {name: 'CPKDF', hash: hash, salt: salt, iterations: password ? 2000 : 2};

            // Import password
            return subtle.importKey('raw', passwordData(derivation, password),
                    derivation, false, ['deriveKey', 'deriveBits']).then(function (baseKey) {

                // Derive key
                return subtle.deriveKey(derivation, baseKey, 'GOST 28147',
                        false, ['sign', 'verify', 'encrypt', 'decrypt']);
            });
        }

        // Compute password MAC 
        function computePasswordMAC(algorithm, password, salt) {
            var mac = expand({name: 'GOST 28147-MAC'}, algorithm.encParams);

            // Derive password
            return derivePasswordKey(algorithm, password, salt).then(function (macKey) {

                // Mac for 16 zero bytes
                return subtle.sign(mac, macKey,
                        new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
            });
        }

        //        var lastBuffer;
        // Compute container MAC
        function computeContainerMAC(algorithm, content) {
            var mac = expand({name: 'GOST 28147-MAC'}, algorithm.encParams),
                    keyData = new Uint8Array([// 32 zero bytes
                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
            return subtle.importKey('raw', keyData, mac, false, ['sign']).then(function (macKey) {
                //                var buffer = new Uint8Array(content.encode());
                //                console.log(coding.Hex.encode(buffer));
                //                if (lastBuffer && lastBuffer.length === buffer.length) {
                //                    for (var i = 0; i < buffer.length; i++)
                //                        if (lastBuffer[i] !== buffer[i])
                //                            console.log('diff at ' + i);
                //                } else
                //                    console.log('diff length');
                //                lastBuffer = buffer;
                // Mac for content
                return subtle.sign(mac, macKey, content.encode());
            });
        }

        // Compute mask MAC
        function computeMaskMAC(algorithm, mask, status) {
            // Import mask as key for MAC
            var mac = expand({name: 'GOST 28147-MAC'}, algorithm.encParams),
                    keyData = mask.byteLength === 64 ?
                    new Uint8Array(new Uint8Array(mask, 32, 32)).buffer : mask;
            return subtle.importKey('raw', keyData, mac, false, ['sign']).then(function (macKey) {

                // Verify MAC for maskStatus
                return subtle.sign(mac, macKey, status);
            });
        }

        // Generate mask
        function generateMasks(algorithm) {
            var wrapAlgorithm = expand(algorithm, {mode: 'MASK'}),
                    mask, status = getSeed(12);
            wrapAlgorithm.name = wrapAlgorithm.name.replace('-DH', '');
            return subtle.generateKey(wrapAlgorithm, true, ['wrapKey', 'unwrapKey']).then(function (key) {
                return subtle.exportKey('raw', key);
            }).then(function (data) {
                mask = data;
                return computeMaskMAC(algorithm, mask, status);
            }).then(function (hmac) {
                return new asn1.GostPrivateMasks({
                    mask: mask,
                    randomStatus: status,
                    hmacRandom: hmac
                });
            });
        }

        // Compute FP
        function computeFP(privateKey) {
            // Generate key pair with predefined ukm for check public key
            return subtle.generateKey(expand(privateKey.algorithm, {ukm: privateKey.buffer}), true, ['sign', 'verify']).then(function (keyPair) {
                return new Uint8Array(new Uint8Array(keyPair.publicKey.buffer, 0, 8)).buffer;
            });
        }

        // Unwrap private key
        function unwrapKey(algorithm, encryptionKey, key, mask, fp) {
            var encryption = {name: 'GOST 28147-ECB', sBox: algorithm.encParams && algorithm.encParams.sBox},
            unwrapAlgorithm = expand(algorithm, {mode: 'MASK'}), privateKey;
            unwrapAlgorithm.name = unwrapAlgorithm.name.replace('-DH', '');
            var wrappedKey;

            // Encrypt ukm data for private key
            return subtle.decrypt(encryption, encryptionKey, key).then(function (data) {
                wrappedKey = data;
                // Import mask key
                return subtle.importKey('raw', mask, unwrapAlgorithm, 'false', ['sign', 'unwrapKey']);
            }).then(function (unwrappingKey) {

                // Unwrap private key
                return subtle.unwrapKey('raw', wrappedKey, unwrappingKey,
                        unwrapAlgorithm, algorithm, 'true', ['sign']);
            }).then(function (key) {
                privateKey = key;
                return computeFP(privateKey);
            }).then(function (computedFP) {
                // Check public key buffer
                if (!equalBuffers(computedFP, fp))
                    throw new Error('Incorrect fp');

                return privateKey;
            });

        }

        // Wrap private key
        function wrapKey(algorithm, encryptionKey, privateKey, mask) {
            var encryption = {name: 'GOST 28147-ECB', sBox: algorithm.encParams && algorithm.encParams.sBox},
            wrapAlgorithm = expand(algorithm, {mode: 'MASK'});
            wrapAlgorithm.name = wrapAlgorithm.name.replace('-DH', '');

            // Import mask key
            return subtle.importKey('raw', mask, wrapAlgorithm, false,
                    ['sign', 'wrapKey']).then(function (wrappingKey) {
                // Wrap private key
                return subtle.wrapKey('raw', privateKey, wrappingKey, wrapAlgorithm);
            }).then(function (wrappedKey) {
                // Encrypt key
                return subtle.encrypt(encryption, encryptionKey, wrappedKey);
            });
        }

        // Decrypt private key
        function decryptKey(content, primary, masks, keyPassword, secondary) {
            var algorithm = content.primaryPrivateKeyParameters.privateKeyAlgorithm;
            return new Promise(call).then(function () {
                // Check format
                if (primary.hmacKey)
                    throw new Error('Old key format');

                if (masks.randomStatus.byteLength < 12)
                    throw new Error("Invalid random status length");

                // Import mask as key for MAC
                return computeMaskMAC(algorithm, masks.mask, masks.randomStatus);
            }).then(function (hmac) {
                if (!equalBuffers(hmac, masks.hmacRandom))
                    throw new Error("Imita for mask is invalid");

                // Derive key
                return derivePasswordKey(algorithm, keyPassword, new Uint8Array(masks.randomStatus, 0, 12));
            }).then(function (encryptionKey) {
                // Unwrap keys
                return secondary && primary.secondaryKey ?
                        unwrapKey(content.secondaryPrivateKeyParameters.privateKeyAlgorithm,
                                encryptionKey, primary.secondaryKey, masks.mask, content.secondaryFP) :
                        unwrapKey(algorithm, encryptionKey, primary.primaryKey, masks.mask, content.primaryFP);
            });
        }

        // Encrypt private key
        function encryptKey(algorithm, primary, masks, keyPassword, secondary, privateKey) {
            // Derive key
            return derivePasswordKey(algorithm, keyPassword, new Uint8Array(masks.randomStatus, 0, 12)).then(function (encryptionKey) {
                // Wrap keys
                return wrapKey(algorithm, encryptionKey, privateKey, masks.mask);
            }).then(function (encryptedKey) {
                if (!primary)
                    primary = new asn1.GostPrivateKeys();
                if (secondary) {
                    primary.secondaryKey = encryptedKey;
                } else {
                    primary.primaryKey = encryptedKey;
                }
                return primary;
            });
        }

        // </editor-fold>

        return {
            /**
             * Get the private key info
             * 
             * @memberOf GostKeys.CryptoProKeyContainer
             * @instance
             * @param {string} keyPassword The password for decryption 
             * @param {boolean} secondary True if required secondary key
             * @returns {Promise} Promise to return {@link GostKeys.PKCS8}
             */
            getKey: function (keyPassword, secondary) // <editor-fold defaultstate="collapsed">
            {
                return this.getPrivateKey(keyPassword, secondary).then(function (privateKey) {
                    return new PKCS8().setPrivateKey(privateKey);
                });
            }, // </editor-fold>
            /**
             * Get the private key
             * 
             * @memberOf GostKeys.CryptoProKeyContainer
             * @instance
             * @param {string} keyPassword Rhe password for decryption 
             * @param {boolean} secondary True if required secondary key
             * @returns {Promise} Promise to return the {@link Key}
             */
            getPrivateKey: function (keyPassword, secondary) // <editor-fold defaultstate="collapsed">
            {
                var self = this, content = self.header.keyContainerContent;
                // Decrypt key
                return decryptKey(content, self.primary, self.masks, keyPassword, secondary)['catch'](function (e) {
                    if (self.primary2 && self.masks2)
                        return decryptKey(content, self.primary2, self.masks2, keyPassword, secondary);
                    else
                        throw e;
                });
            }, // </editor-fold>
            /**
             * Get the certificate from the key container
             * 
             * @memberOf GostKeys.CryptoProKeyContainer
             * @instance
             * @param {boolean} secondary True for set secondary certificate
             * @returns {Promise} Promise to return {@link GostCert.X509}
             */
            getCertificate: function (secondary) // <editor-fold defaultstate="collapsed">
            {
                var self = this, content = self.header.keyContainerContent;
                return new Promise(call).then(function () {
                    if (secondary)
                        return new cert.X509(content.secondaryCertificate);
                    else
                        return new cert.X509(content.primaryCertificate);
                });
            }, // </editor-fold>
            /**
             * Get the container name
             * 
             * @memberOf GostKeys.CryptoProKeyContainer
             * @instance
             * @returns {string} Container name
             */
            getContainerName: function () // <editor-fold defaultstate="collapsed">
            {
                return this.name.containerName;
            }, // </editor-fold>
            /**
             * Sets and encrypt the private key info
             * 
             * @memberOf GostKeys.CryptoProKeyContainer
             * @instance
             * @param {(FormatedData|GostKeys.PKCS8)} keyInfo The private key info 
             * @param {string} keyPassword The assword for encryption
             * @param {boolean} secondary True for set secondary key
             * @param {number} days Validity days. Default 7305 days (20 years)
             * @returns {Promise} Promise to return self object after set key
             */
            setKey: function (keyInfo, keyPassword, secondary, days) // <editor-fold defaultstate="collapsed">
            {
                var self = this;
                return new PKCS8(keyInfo).getPrivateKey().then(function (privateKey) {
                    return self.setPrivateKey(privateKey, keyPassword, secondary, days);
                });
            }, // </editor-fold>
            /**
             * Set the private key
             * 
             * @memberOf GostKeys.CryptoProKeyContainer
             * @instance
             * @param {Key} privateKey The private key
             * @param {string} keyPassword The secret key encryption 
             * @param {boolean} secondary True for set secondary key
             * @param {number} days Validity days. Default 7305 days (20 years)
             * @returns {Promise} Promise to return self object after set key
             */
            setPrivateKey: function (privateKey, keyPassword, secondary, days) // <editor-fold defaultstate="collapsed">
            {
                var self = this, content, algorithm;
                return new Promise(call).then(function () {
                    self.header = self.header || new asn1.GostKeyContainer({
                        keyContainerContent: {
                            containerAlgoritmIdentifier: {
                                algorithm: 'id-CryptoPro-GostPrivateKeys-V2-Full'
                            },
                            attributes: ['kccaReservePrimary', 'kccaPrimaryKeyAbsent'],
                            extensions: {
                                keyValidity: {
                                    notAfter: now(days || options.days)
                                }
                            }
                        }
                    });
                    content = self.header.keyContainerContent;
                    // Set private key                    
                    var keyParameters = secondary ? content.secondaryPrivateKeyParameters :
                            content.primaryPrivateKeyParameters;
                    if (!keyParameters) {
                        algorithm = expand(privateKey.algorithm, {
                            sBox: "D-A",
                            encParams: {
                                block: "CFB",
                                sBox: "E-A",
                                keyMeshing: "CP"
                            }
                        });
                        keyParameters = {
                            attributes: ["pkaExportable", "pkaExchange", "pkaDhAllowed"],
                            privateKeyAlgorithm: algorithm
                        };
                        if (secondary) {
                            if (!content.primaryPrivateKeyParameters)
                                throw new Error('Primary key must be defined first');
                            content.secondaryPrivateKeyParameters = keyParameters;
                        } else {
                            content.primaryPrivateKeyParameters = keyParameters;
                            var absent = content.attributes.indexOf('kccaPrimaryKeyAbsent');
                            if (absent >= 0)
                                content.attributes.splice(absent, 1);
                        }
                    } else
                        algorithm = keyParameters.privateKeyAlgorithm;
                    // Generate masks
                    var promises = [];
                    [0, 1].forEach(function (i) {
                        var name = 'masks' + (i > 0 ? '2' : '');
                        if (!self[name])
                            promises.push(generateMasks(algorithm).then(function (masks) {
                                self[name] = masks;
                            }));
                    });
                    return Promise.all(promises);
                }).then(function () {
                    // Encrypt key
                    var promises = [];
                    [0, 1].forEach(function (i) {
                        var name = 'primary' + (i > 0 ? '2' : ''),
                                maskname = 'masks' + (i > 0 ? '2' : '');
                        promises.push(encryptKey(algorithm, self[name], self[maskname], keyPassword, secondary, privateKey).then(function (primary) {
                            self[name] = primary;
                        }));
                    });
                    return Promise.all(promises);
                }).then(function () {
                    // Compute FP for a private key
                    return computeFP(privateKey).then(function (FP) {
                        if (secondary)
                            content.secondaryFP = FP;
                        else
                            content.primaryFP = FP;
                    });
                }).then(function () {
                    // Compute password MAC
                    var softPassword = content.attributes.indexOf('kccaSoftPassword');
                    if (keyPassword) {
                        if (softPassword < 0)
                            content.attributes.push('kccaSoftPassword');
                        return computePasswordMAC(algorithm, keyPassword, content.primaryFP);
                    } else {
                        if (softPassword >= 0)
                            content.attributes.splice(softPassword, 1);
                    }
                }).then(function (hmac) {
                    if (hmac)
                        content.hmacPassword = hmac;
                    // Calculate container MAC
                    return computeContainerMAC(algorithm, content);
                }).then(function (hmac) {
                    self.header.hmacKeyContainerContent = hmac;
                    return self;
                });
            }, // </editor-fold>
            /**
             * Set the certificate to the key container
             * 
             * @memberOf GostKeys.CryptoProKeyContainer
             * @instance
             * @param {(FormatedData|GostCert.X509)} certificate The certificate
             * @param {boolean} secondary True for set secondary certificate
             * @param {number} days Validity days. Default 7305 days (20 years)
             * @returns {Promise} Promise to return self object after set certificate
             */
            setCertificate: function (certificate, secondary, days) // <editor-fold defaultstate="collapsed">
            {
                var self = this, content, algorithm;
                return new Promise(call).then(function () {
                    self.header = self.header || new asn1.GostKeyContainer({
                        keyContainerContent: {
                            containerAlgoritmIdentifier: {
                                algorithm: 'id-CryptoPro-GostPrivateKeys-V2-Full'
                            },
                            attributes: ['kccaReservePrimary', 'kccaPrimaryKeyAbsent'],
                            extensions: {
                                keyValidity: {
                                    notAfter: now(days || options.days)
                                }
                            }
                        }
                    });
                    content = self.header.keyContainerContent;
                    certificate = new cert.X509(certificate);
                    algorithm = (content.primaryPrivateKeyParameters &&
                            content.primaryPrivateKeyParameters.privateKeyAlgorithm) ||
                            expand(certificate.subjectPublicKeyInfo.algorithm, {
                                sBox: "D-A",
                                encParams: {
                                    block: "CFB",
                                    sBox: "E-A",
                                    keyMeshing: "CP"
                                }
                            });
                    return certificate.getPublicKey();
                }).then(function (publicKey) {
                    if (secondary) {
                        if (content.secondaryFP && !equalBuffers(content.secondaryFP,
                                new Uint8Array(publicKey.buffer, 0, content.secondaryFP.byteLength)))
                            throw new Error('The public key of the certificate does not match the private key');
                        content.secondaryCertificate = certificate;
                    } else {
                        if (content.primaryFP && !equalBuffers(content.primaryFP,
                                new Uint8Array(publicKey.buffer, 0, content.primaryFP.byteLength)))
                            throw new Error('The public key of the certificate does not match the private key');
                        content.primaryCertificate = certificate;
                    }
                    // Calculate container MAC
                    return computeContainerMAC(algorithm, content);
                }).then(function (hmac) {
                    self.header.hmacKeyContainerContent = hmac;
                    return self;
                });
            }, // </editor-fold>
            /**
             * Set the container name
             * 
             * @memberOf GostKeys.CryptoProKeyContainer
             * @instance
             * @param {string} name Container name
             */
            setContainerName: function (name) // <editor-fold defaultstate="collapsed">
            {
                this.name = new asn1.GostKeyContainerName({containerName: name});
            }, // </editor-fold>
            /**
             * Verify key container with password
             * 
             * @memberOf GostKeys.CryptoProKeyContainer
             * @instance
             * @param {string} keyPassword the secret key or password for decryption 
             * @returns {Promise} Promise to return self object after verify
             */
            verify: function (keyPassword) // <editor-fold defaultstate="collapsed">
            {
                var self = this, content, algorithm;
                return new Promise(call).then(function () {
                    content = self.header.keyContainerContent;
                    algorithm = content.primaryPrivateKeyParameters.privateKeyAlgorithm;
                    // Verify container MAC
                    return computeContainerMAC(algorithm, content);
                }).then(function (hmac) {
                    if (!equalBuffers(hmac, self.header.hmacKeyContainerContent))
                        throw new Error("Container is not valid.");
                    // Verify key password MAC
                    var needPassword = content.attributes.indexOf('kccaSoftPassword') >= 0;
                    if (!keyPassword && needPassword)
                        throw new Error("Password is required");
                    if (keyPassword && !needPassword)
                        throw new Error("Password is not reqiured.");
                    if (keyPassword)
                        // Derive password
                        return computePasswordMAC(algorithm, keyPassword, content.primaryFP).then(function (hmac) {
                            if (!equalBuffers(hmac, content.hmacPassword))
                                throw new Error("Password is not valid.");
                            return self;
                        });
                    return self;
                });
            }, // </editor-fold>
            /**
             * Change key password
             * 
             * @memberOf GostKeys.CryptoProKeyContainer
             * @instance
             * @param {string} oldKeyPassword Old key password
             * @param {string} newKeyPassword New key password
             * @returns {Promise} Promise to return self object after change password
             */
            changePassword: function (oldKeyPassword, newKeyPassword) // <editor-fold defaultstate="collapsed">
            {
                var self = this, content;
                return new Promise(call).then(function () {
                    content = self.header.keyContainerContent;
                    if (!content.primaryPrivateKeyParameters)
                        throw new Error('Private key not yet defined');
                    return self.getPrivateKey(oldKeyPassword).then(function (privateKey) {
                        return self.setPrivateKey(privateKey, newKeyPassword);
                    });
                }).then(function () {
                    if (content.secondaryPrivateKeyParameters)
                        return self.getPrivateKey(oldKeyPassword, true).then(function (privateKey) {
                            return self.setPrivateKey(privateKey, newKeyPassword, true);
                        });
                    return self;
                });
            }, // </editor-fold>
            /**
             * Generate private key, certificate and return certification request
             * 
             * @memberOf GostKeys.CryptoProKeyContainer
             * @instance
             * @param {(FormatedData|GostASN1.CertificationRequest)} req The request templates
             * @param {(Key|CryptoOperationData|string)} keyPassword The secret key or password for decryption 
             * @param {(AlgorithmIdentifier|string)} keyAlgorithm The name of provider or algorithm
             * @returns {Promise} Promise to return {@link GostCert.Request}
             */
            generate: function (req, keyPassword, keyAlgorithm) // <editor-fold defaultstate="collapsed">
            {
                var self = this, certificate, keyInfo;
                return new Promise(call).then(function () {
                    if (!(req instanceof cert.Request))
                        req = new cert.Request(req);
                    // Generate request
                    return req.generate(keyAlgorithm);
                }).then(function (key) {
                    keyInfo = key;
                    return self.setKey(keyInfo, keyPassword);
                }).then(function () {
                    // Create the new certificate
                    certificate = new cert.X509(req);
                    return certificate.sign(keyInfo);
                }).then(function () {
                    return self.setCertificate(certificate);
                }).then(function () {
                    return req;
                });
            }, // </editor-fold>
            /**
             * Encode key container
             * 
             * @memberOf GostKeys.CryptoProKeyContainer
             * @instance
             * @param {string} format Encode format 'DER', 'CER', 'PEM'
             * @returns {Object} container Object contains encoded files
             */
            encode: function (format) // <editor-fold defaultstate="collapsed">
            {
                return {
                    header: this.header.encode(format),
                    name: this.name.encode(format),
                    masks: this.masks.encode(format),
                    primary: this.primary.encode(format),
                    masks2: this.masks2.encode(format),
                    primary2: this.primary2.encode(format)
                };
            } // </editor-fold>
        };
    })());

    /**
     * A class for password-encrypted private keys in CryptoPro container
     * 
     * @memberOf GostKeys
     * @type GostKeys.SignalComPrivateKeyInfo
     */
    GostKeys.prototype.CryptoProKeyContainer = CryptoProKeyContainer;

    /**
     * A class for password-encrypted private keys in ViPNet container entry
     * 
     * @class GostKeys.ViPNetContainerEntry
     * @extends GostASN1.ViPNetInfo
     * @param {(FormatedData|GostKeys.ViPNetContainerEntry)} entry 
     */
    function ViPNetContainerEntry(entry) // <editor-fold defaultstate="collapsed">
    {
        asn1.ViPNetInfo.call(this, entry || {
            version: 3,
            keyInfo: {
                keyClass: 1,
                keyType: 43556,
                flags: 1
            },
            defenceKeyInfo: {
                keyClass: 1024,
                keyType: 24622,
                keyUID: getSeed(32),
                flags: -2147483648
            }
        });
    } // </editor-fold>

    extend(asn1.ViPNetInfo, ViPNetContainerEntry, (function () {

        function getKeyPassword(keyPassword) // <editor-fold defaultstate="collapsed">
        {
            if (keyPassword === undefined)
                keyPassword = '';
            // Generate key data
            var passwordData = coding.Chars.decode(keyPassword, 'win1251'), keyData;
            return subtle.digest('GOST R 34.11-94', passwordData).then(function (data) {
                keyData = data;
                // Generate mask data
                var secodeData = new Uint8Array(passwordData.byteLength + keyData.byteLength);
                secodeData.set(new Uint8Array(passwordData), 0);
                secodeData.set(new Uint8Array(keyData), passwordData.byteLength);
                return subtle.digest('GOST R 34.11-94', secodeData);
            }).then(function (data) {
                // Remove mask
                return subtle.importKey('raw', data, 'GOST 28147', false, ['unwrapKey']);
            }).then(function (unwrappingKey) {
                // Unwrap secret key
                return subtle.unwrapKey('raw', keyData, unwrappingKey,
                        'GOST 28147-MASK/VN', 'GOST 28147-89',
                        'false', ['encrypt', 'decrypt', 'sign', 'verify']);
            });
        } // </editor-fold>

        return {
            /**
             * Get the private key
             * 
             * @memberOf GostKeys.ViPNetContainerEntry
             * @instance
             * @param {string} keyPassword The password of secrect key for decryption 
             * @returns {Promise} Promise to return the {@link Key}
             */
            getPrivateKey: function (keyPassword) // <editor-fold defaultstate="collapsed">
            {
                var self = this, keyPart, encryptedKey;
                // Decrypt key
                return new Promise(call).then(function () {
                    return !keyPassword || typeof keyPassword === 'string' ?
                            getKeyPassword(keyPassword) : keyPassword;
                }).then(function (key) {
                    keyPassword = key;
                    // Verify password    
                    keyPart = self.keyPart;
                    encryptedKey = new Uint8Array(keyPart, 0, keyPart.byteLength - 4 - 8);
                    var macKey = new Uint8Array(keyPart, encryptedKey.byteLength, 4),
                            encodedKeyInfo = self.keyInfo.encode(),
                            data = new Uint8Array(encryptedKey.byteLength + encodedKeyInfo.byteLength);
                    data.set(new Uint8Array(encryptedKey), 0);
                    data.set(new Uint8Array(encodedKeyInfo), encryptedKey.byteLength);
                    return subtle.verify({name: 'GOST 28147-89-MAC'}, keyPassword, macKey, data);
                }).then(function (result) {
                    if (!result)
                        throw new Error('Invalid key password');
                    var iv = new Uint8Array(keyPart, keyPart.byteLength - 8, 8);
                    // Decrypt key data
                    return subtle.decrypt({name: 'GOST 28147-89-CFB', iv: iv}, keyPassword, encryptedKey);
                }).then(function (keyData) {
                    var l2 = keyData.byteLength / 2;
                    if (self.keyInfo.keyClass & 0x3 === 0) {
                        // Secret key. Remove mask and import
                        return subtle.importKey('raw', new Int32Array(keyData, l2, l2), 'GOST 28147', false,
                                ['unwrapKey']).then(function (unwrappingKey) {
                            // Unwrap secret key
                            return subtle.unwrapKey('raw', new Int32Array(keyData, 0, l2), unwrappingKey,
                                    'GOST 28147-MASK/VN', 'GOST 28147-89',
                                    'false', ['encrypt', 'decrypt', 'sign', 'verify']);
                        });
                    } else {
                        // Private key
                        var algorithm = self.keyInfo.algorithm ||
                                (self.certificate && self.certificate.subjectPublicKeyInfo.algorithm);
                        if (!algorithm)
                            throw new Error('Algorithm is not specified');
                        var unwrapAlgorithm = expand(algorithm, {mode: 'MASK', procreator: 'VN'});
                        unwrapAlgorithm.name = unwrapAlgorithm.name.replace('-DH', '');
                        var wrapped = new Uint8Array(keyData, 0, l2),
                                mask = new Uint8Array(keyData, l2, l2);
                        // Import mask key
                        return subtle.importKey('raw', mask, unwrapAlgorithm, 'false', ['sign', 'unwrapKey']).then(function (unwrappingKey) {
                            // Unwrap private key
                            return subtle.unwrapKey('raw', wrapped, unwrappingKey, unwrapAlgorithm, algorithm, 'true', ['sign', 'deriveBits', 'deriveKey']);
                        }).then(function (privateKey) {
                            // Generate key pair
                            if (self.publicKey)
                                return subtle.generateKey(expand(privateKey.algorithm, {ukm: privateKey.buffer}),
                                        privateKey.extractable, privateKey.usages);
                            else
                                return {privateKey: privateKey};
                        }).then(function (keyPair) {
                            // Compare public key
                            if (self.publicKey && !equalBuffers(keyPair.publicKey.buffer, self.publicKey))
                                throw new Error('Check public key failed');
                            return keyPair.privateKey;
                        });
                    }
                });
            }, // </editor-fold>
            /**
             * Set the private key
             * 
             * @memberOf GostKeys.ViPNetContainerEntry
             * @instance
             * @param {Key} privateKey The private key
             * @param {string} keyPassword The secret key encryption 
             * @param {number} days Validity days. Default 7305 days (20 years)
             * @returns {Promise} Promise to return the self object after set the key
             */
            setPrivateKey: function (privateKey, keyPassword, days) // <editor-fold defaultstate="collapsed">
            {
                var self = this, wrapAlgorithm, wrappingKey, keyData, keyPart;
                // Decrypt key
                return new Promise(call).then(function () {
                    return !keyPassword || typeof keyPassword === 'string' ?
                            getKeyPassword(keyPassword) : keyPassword;
                }).then(function (key) {
                    keyPassword = key;
                    var algorithm = privateKey.algorithm;
                    self.keyInfo.algorithm = algorithm;
                    self.keyInfo.serialNumber = getSeed(16);
                    self.keyInfo.keyUID = getSeed(8);
                    self.keyInfo.validity = {
                        notBefore: today(),
                        notAfter: today(days || options.days)
                    };
                    if (privateKey.type === 'private') {
                        // Generate mask
                        wrapAlgorithm = expand(algorithm, {mode: 'MASK', procreator: 'VN'});
                        wrapAlgorithm.name = wrapAlgorithm.name.replace('-DH', '');
                        self.keyInfo.keyClass = 1;
                        self.keyInfo.keyType = 43556;
                        // Generate public key
                        return subtle.generateKey(expand(algorithm, {ukm: privateKey.buffer}), true,
                                ['sign', 'verify']).then(function (keyPair) {
                            self.publicKey = keyPair.publicKey.buffer;
                            // Check certificate
                            if (self.certificate) {
                                var spki = self.certificate.subjectPublicKeyInfo;
                                return subtle.importKey('spki', spki.encode(), spki.algorithm, true, ['verify']);
                            }
                        }).then(function (publicKey) {
                            if (publicKey && !equalBuffers(publicKey.buffer, self.publicKey))
                                delete self.certificate; // Remove not valid certificate
                        });
                    } else if (privateKey.type === 'secret') {
                        wrapAlgorithm = 'GOST 28147/MASK/VN';
                        delete self.certificate;
                        delete self.publicKey;
                        self.keyInfo.keyClass = 64;
                        self.keyInfo.keyType = 24622;
                    } else
                        throw new Error('Invalid key type');
                }).then(function () {
                    // Generate mask
                    return subtle.generateKey(wrapAlgorithm, true, ['wrapKey', 'unwrapKey']);
                }).then(function (key) {
                    wrappingKey = key;
                    // Wrap private key with mask
                    return subtle.wrapKey('raw', privateKey, wrappingKey, wrapAlgorithm);
                }).then(function (data) {
                    keyData = new Uint8Array(data.byteLength * 2);
                    keyData.set(new Uint8Array(data));
                    return subtle.exportKey('raw', wrappingKey);
                }).then(function (data) {
                    keyData.set(new Uint8Array(data), data.byteLength);
                    keyPart = new Uint8Array(keyData.byteLength + 12);
                    // Encrypt key
                    var encyption = {name: 'GOST 28147-CFB', iv: getSeed(8)};
                    keyPart.set(new Uint8Array(encyption.iv), keyPart.byteLength - 8);
                    return subtle.encrypt(encyption, keyPassword, keyData);
                }).then(function (encryptedKey) {
                    keyPart.set(new Uint8Array(encryptedKey));
                    // Calculate MAC
                    var encodedKeyInfo = self.keyInfo.encode(),
                            data = new Uint8Array(encryptedKey.byteLength + encodedKeyInfo.byteLength);
                    data.set(new Uint8Array(encryptedKey), 0);
                    data.set(new Uint8Array(encodedKeyInfo), encryptedKey.byteLength);
                    return subtle.sign({name: 'GOST 28147-89-MAC'}, keyPassword, data);
                }).then(function (macKey) {
                    keyPart.set(new Uint8Array(macKey), keyPart.byteLength - 12);
                    self.keyPart = keyPart.buffer;
                    return self;
                });
            }, // </editor-fold>
            /**
             * Encode container entry
             * 
             * @memberOf GostKeys.ViPNetContainerEntry
             * @instance
             * @param {string} format The encoded data format
             * @returns {CryptoOperationData}
             */
            encode: function (format) // <editor-fold defaultstate="collapsed"> 
            {
                var header = asn1.ViPNetInfo.method('encode').call(this),
                        result = new Uint8Array(8 + header.byteLength + this.keyPart.byteLength);
                set32(result.buffer, 0, 4 + header.byteLength + this.keyPart.byteLength);
                result.set(new Uint8Array(header), 4);
                set32(result.buffer, 4 + header.byteLength, this.keyPart.byteLength);
                result.set(new Uint8Array(this.keyPart), 8 + header.byteLength);
                if (format === 'PEM')
                    return coding.Base64.encode(result.buffer);
                return result.buffer;
            } // </editor-fold>
        };
    })(), {
        /**
         * Decode container entry
         * 
         * @memberOf GostKeys.ViPNetContainerEntry
         * @param {FormatedData} entry
         * @returns {GostKeys.ViPNetContainer}
         */
        decode: function (entry) // <editor-fold defaultstate="collapsed">
        {
            if (typeof entry === 'string')
                entry = coding.Base64.decode(entry);
            entry = buffer(entry);
            // Entry size 
            var entrySize = get32(entry, 0);
            if (entry.byteLength !== entrySize + 4)
                throw new Error('Invalid container entry size');
            // Decode header info
            var source = coding.BER.decode(new Uint8Array(entry, 4, entrySize));
            var result = asn1.ViPNetInfo.decode.call(this, source);
            // Decode key info
            var headerSize = source.header.byteLength + source.content.byteLength,
                    keyPartSize = get32(entry, 4 + headerSize);
            if (entry.byteLength !== headerSize + keyPartSize + 8)
                throw new Error('Invalid container key part size');
            result.keyPart = new Uint8Array(new Uint8Array(entry, headerSize + 8, keyPartSize)).buffer;
            // Key Info buffer - can be used in case error of format encoding
            // var keyInfoSource = source.object[1];
            // result.encodedKeyInfo = new Uint8Array(new Uint8Array(keyInfoSource.header.buffer, 
            //    keyInfoSource.header.byteOffset, keyInfoSource.header.byteLength + keyInfoSource.content.byteLength)).buffer;
            return result;
        } // </editor-fold>
    });

    /**
     * A class for password-encrypted private keys in CryptoPro container
     * 
     * @memberOf GostKeys
     * @type GostKeys.SignalComPrivateKeyInfo
     */
    GostKeys.prototype.ViPNetContainerEntry = ViPNetContainerEntry;

    /**
     * A class for password-encrypted private keys in ViPNet container
     * 
     * @class GostKeys.ViPNetContainer
     * @param {(FormatedData|GostKeys.ViPNetContainer)} container 
     */
    function ViPNetContainer(container) // <editor-fold defaultstate="collapsed">
    {
        if (container && (container instanceof CryptoOperationData ||
                container.buffer instanceof CryptoOperationData ||
                typeof container === 'string'))
            this.decode(container);
        else {
            container = container || {};
            this.fileType = container.fileType || 'ITCS';
            this.fileVersion = container.fileVersion || 0x10;
            if (container.applicationHeader)
                this.applicationHeader = container.applicationHeader;
            this.entries = container.entries || [];
        }
    } // </editor-fold>


    extend(Object, ViPNetContainer, {
        /**
         * Get the certificate from the container
         * 
         * @memberOf GostKeys.ViPNetContainer
         * @instance
         * @param {number} index Index of the entriy. Default 0
         * @returns {Promise} Promise to return {@link GostCert.X509} 
         */
        getCertificate: function (index) // <editor-fold defaultstate="collapsed">
        {
            var self = this;
            return new Promise(call).then(function () {
                var entry = self.entries[index || 0];
                if (!entry)
                    throw new Error('Entry not defined');
                if (entry.certificate)
                    return new cert.X509(entry.certificate);
            });
        }, // </editor-fold>
        /**
         * Get the private key info
         * 
         * @memberOf GostKeys.ViPNetContainer
         * @instance
         * @param {string} keyPassword The password for decryption 
         * @param {number} index Index of the entriy. Default 0
         * @returns {Promise} Promise to return {@link GostKeys.PKCS8}
         */
        getKey: function (keyPassword, index) // <editor-fold defaultstate="collapsed">
        {
            return this.getPrivateKey(keyPassword, index).then(function (privateKey) {
                return new PKCS8().setPrivateKey(privateKey);
            });
        }, // </editor-fold>
        /**
         * Get the private key
         * 
         * @memberOf GostKeys.ViPNetContainer
         * @instance
         * @param {string} keyPassword The password of secrect key for decryption 
         * @param {number} index Index of the entriy. Default 0
         * @returns {Promise} Promise to return the {@link Key}
         */
        getPrivateKey: function (keyPassword, index) // <editor-fold defaultstate="collapsed">
        {
            var self = this;
            return new Promise(call).then(function () {
                var entry = self.entries[index || 0];
                if (!entry)
                    throw new Error('Entry not defined');
                return entry.getPrivateKey(keyPassword);
            });
        }, // </editor-fold>
        /**
         * Set the certificate to the container
         * 
         * @memberOf GostKeys.ViPNetContainer
         * @instance
         * @param {(FormatedData|GostCert.X509)} certificate The certificate
         * @param {number} index Index of the entriy. Default 0
         * @returns {Promise} Promise to return self object after set certificate
         */
        setCertificate: function (certificate, index) // <editor-fold defaultstate="collapsed">
        {
            var self = this, entry, certificate;
            return new Promise(call).then(function () {
                entry = self.entries[index || 0] ||
                        (self.entries[index || 0] = new ViPNetContainerEntry());
                certificate = new cert.X509(certificate);
                if (entry.publicKey)
                    return certificate.getPublicKey();
            }).then(function (publicKey) {
                if (publicKey && !equalBuffers(entry.publicKey, publicKey.buffer))
                    throw new Error('Invalid certificate for private key');
                entry.certificate = certificate;
                return self;
            });
        }, // </editor-fold>
        /**
         * Set the key to the container
         * 
         * @memberOf GostKeys.ViPNetContainer
         * @instance
         * @param {(FormatedData|GostKeys.PKCS8)} keyInfo The key
         * @param {string} keyPassword The password for decryption 
         * @param {number} index Index of the entriy. Default 0
         * @param {number} days Validity days. Default 7305 days (20 years)
         * @returns {Promise} Promise to return self object after set the key
         */
        setKey: function (keyInfo, keyPassword, index, days) // <editor-fold defaultstate="collapsed">
        {
            var self = this;
            return new PKCS8(keyInfo).getPrivateKey().then(function (privateKey) {
                return self.setPrivateKey(privateKey, keyPassword, index, days);
            });
        }, // </editor-fold>
        /**
         * Set the private key
         * 
         * @memberOf GostKeys.ViPNetContainerEntry
         * @instance
         * @param {Key} privateKey The private key
         * @param {string} keyPassword The secret key encryption 
         * @param {number} index Index of the entriy. Default 0
         * @param {number} days Validity days. Default 7305 days (20 years)
         * @returns {Promise} Promise to return the self object after set the key
         */
        setPrivateKey: function (privateKey, keyPassword, index, days) // <editor-fold defaultstate="collapsed">
        {
            var self = this;
            return new Promise(call).then(function () {
                var entry = self.entries[index || 0] ||
                        (self.entries[index || 0] = new ViPNetContainerEntry());
                return entry.setPrivateKey(privateKey, keyPassword, days);
            }).then(function () {
                return self;
            });
        }, // </editor-fold>
        /**
         * Change key password
         * 
         * @memberOf GostKeys.ViPNetContainerEntry
         * @instance
         * @param {string} oldKeyPassword Old key password
         * @param {string} newKeyPassword New key password
         * @returns {Promise} Promise to return self object after change password
         */
        changePassword: function (oldKeyPassword, newKeyPassword) // <editor-fold defaultstate="collapsed">
        {
            var self = this;
            return new Promise(call).then(function () {
                return self.getPrivateKey(oldKeyPassword).then(function (privateKey) {
                    return self.setPrivateKey(privateKey, newKeyPassword);
                });
            });
        }, // </editor-fold>
        /**
         * Generate private key, certificate and return certification request
         * 
         * @memberOf GostKeys.ViPNetContainerEntry
         * @instance
         * @param {(FormatedData|GostASN1.CertificationRequest)} req The request templates
         * @param {(Key|CryptoOperationData|string)} keyPassword The secret key or password for decryption 
         * @param {(AlgorithmIdentifier|string)} keyAlgorithm The name of provider or algorithm
         * @returns {Promise} Promise to return {@link GostCert.Request}
         */
        generate: function (req, keyPassword, keyAlgorithm) // <editor-fold defaultstate="collapsed">
        {
            var self = this, certificate, keyInfo;
            return new Promise(call).then(function () {
                if (!(req instanceof cert.Request))
                    req = new cert.Request(req);
                // Generate request
                return req.generate(keyAlgorithm);
            }).then(function (key) {
                keyInfo = key;
                return self.setKey(keyInfo, keyPassword);
            }).then(function () {
                // Create the new certificate
                certificate = new cert.X509(req);
                return certificate.sign(keyInfo);
            }).then(function () {
                return self.setCertificate(certificate);
            }).then(function () {
                return req;
            });
        }, // </editor-fold>
        /**
         * Encode objet to container
         * 
         * @memberOf GostKeys.ViPNetContainer
         * @instance
         * @param {string} format The encoded data format
         * @returns {CryptoOperationData}
         */
        encode: function (format) // <editor-fold defaultstate="collapsed">
        {
            // Encode entries
            var entries = [], entriesSize = 0;
            this.entries.forEach(function (entry) {
                var encoded = entry.encode();
                entriesSize += encoded.byteLength;
                entries.push(encoded);
            });
            var headerSize = this.applicationHeader ? this.applicationHeader.byteLength : 0,
                    result = new Uint8Array(12 + headerSize + entriesSize);
            result.set(new Uint8Array(coding.Chars.decode(this.fileType, 'ascii')));
            set32(result.buffer, 4, this.fileVersion);
            set32(result.buffer, 8, headerSize);
            if (headerSize > 0)
                result.set(new Uint8Array(this.applicationHeader), 12);
            var offset = 12 + headerSize;
            entries.forEach(function (entry) {
                result.set(new Uint8Array(entry), offset);
                offset += entry.byteLength;
            });
            if (format === 'PEM')
                return coding.Base64.encode(result.buffer);
            return result.buffer;
        }, // </editor-fold>
        /**
         * Decode container to the object
         * 
         * @memberOf GostKeys.ViPNetContainer
         * @instance
         * @param {FormatedData} container
         * @returns {GostKeys.ViPNetContainer}
         */
        decode: function (container) // <editor-fold defaultstate="collapsed">
        {
            container = this.constructor.decode(container);
            this.fileType = container.fileType;
            this.fileVersion = container.fileVersion;
            if (container.applicationHeader)
                this.applicationHeader = container.applicationHeader;
            this.entries = container.entries;
        } // </editor-fold>
    }, {
        /**
         * Encode object
         * 
         * @memberOf GostKeys.ViPNetContainer
         * @instance
         * @param {GostKeys.ViPNetContainer} object
         * @param {string} format The encoded data format
         * @returns {CryptoOperationData}
         */
        encode: function (object, format) // <editor-fold defaultstate="collapsed"> 
        {
            return new this(object).encode(format);
        }, // </editor-fold>
        /**
         * Decode container
         * 
         * @memberOf GostKeys.ViPNetContainer
         * @instance
         * @param {FormatedData} container
         * @returns {GostKeys.ViPNetContainer}
         */
        decode: function (container) // <editor-fold defaultstate="collapsed">
        {
            if (typeof container === 'string')
                container = coding.Base64.decode(container);
            container = buffer(container);
            // File type
            var fileType = coding.Chars.encode(new Uint8Array(container, 0, 4), 'ascii');
            if (fileType !== 'ITCS' && fileType !== 'PKEY' && fileType !== '_CCK' && fileType !== '_LCK')
                throw new Error('Unsupported ViPNet container type');
            // File version
            var fileVersion = get32(container, 4),
                    i = fileVersion >>> 16, j = fileVersion & 0xffff;
            if ((i !== 0 && i !== 1) || j > 0xff)
                throw new Error('Unsupported ViPNet container version');
            // File header
            var headerSize = get32(container, 8), applicationHeader;
            if (headerSize > 0)
                applicationHeader = buffer(new Uint8Array(container, 12, headerSize));
            // Read entries
            var offset = 12 + headerSize, entries = [];
            while (offset < container.byteLength) {
                // Entry size
                var entrySize = get32(container, offset);
                // Decode entry
                entries.push(ViPNetContainerEntry.decode(
                        new Uint8Array(container, offset, entrySize + 4)));
                offset = offset + entrySize + 4;
            }
            return new ViPNetContainer({
                fileType: fileType,
                fileVersion: fileVersion,
                applicationHeader: applicationHeader,
                entries: entries
            });
        } // </editor-fold>
    });

    /**
     * A class for password-encrypted private keys in CryptoPro container
     * 
     * @memberOf GostKeys
     * @type GostKeys.SignalComPrivateKeyInfo
     */
    GostKeys.prototype.ViPNetContainer = ViPNetContainer;

    /**
     * An implementation of PKCS #12 password encryption/integrity modes. Both input and output are implemented.<br><br>
     * 
     * A PFX object may contain multiple authenticated safes (represented as GostASN1.SafeContents objects). 
     * Each authenticated safe may have its own encryption method, and contains a number of bags 
     * (represented as instances of GostASN1.SafeBag). <br>
     * Note: the methods and constructors that input a PFX object do not automatically check the validity of the MAC. 
     * You need to explicitly call verify() to make this check.
     * 
     * @class GostKeys.PKCS12
     * @extends GostASN1.PFX
     * @param {(FormatedData|GostASN1.PFX)} store
     */
    function PKCS12(store) // <editor-fold defaultstate="collapsed"> 
    {
        asn1.PFX.call(this, store || {
            version: 3,
            authSafe: {
                contentType: 'data'
            }
        });
    } // </editor-fold>

    extend(asn1.PFX, PKCS12, (function () {

        // <editor-fold defaultstate="collapsed"> 
        function calcHMAC(derivation, password, content) {
            var hmac = {name: 'HMAC', hash: derivation.hash};
            // Import password for key generation 
            return subtle.importKey('raw', passwordData(derivation, password),
                    derivation, false, ['deriveKey']).then(function (passwordKey) {
                // Generate key from password. 
                return subtle.deriveKey(derivation, passwordKey, hmac, false, ['sign']);
            }).then(function (integrityKey) {
                // Sign MAC 
                return subtle.sign(hmac, integrityKey, content);
            });
        }

        function verifyHMAC(derivation, password, digest, content) {
            return calcHMAC(derivation, password, content).then(function (test) {
                if (!equalBuffers(digest, test))
                    throw new Error('Invalid password, MAC is not verified');
            });
        }
        // </editor-fold>
        return {
            /**
             * Sign the enclosed content with given digest algorithm
             * 
             * @memberOf GostKeys.PKCS12
             * @instance
             * @param {string} password The password 
             * @param {(AlgorithmIdentifier|string)} digestAlgorithm Digest algorithm or provider name
             * @returns {Promise} Promise to return self object after enclose content
             */
            sign: function (password, digestAlgorithm) // <editor-fold defaultstate="collapsed"> 
            {
                var self = this;
                return new Promise(call).then(function () {
                    // Calculate mac for password integrity
                    if (password) {
                        // Define digeset algorithm
                        var hash, derivation, digestProvider;
                        if (digestAlgorithm)
                            digestProvider = providers[digestAlgorithm];
                        else
                            digestAlgorithm = providers[options.providerName].digest;
                        if (digestProvider) {
                            hash = digestProvider.digest;
                            derivation = digestProvider.derivation;
                        } else {
                            hash = digestAlgorithm;
                            derivation = {name: 'PFXKDF', hash: hash, iterations: 2000};
                        }
                        // Add salt
                        derivation = expand(derivation, {salt: getSeed(saltSize(hash)), diversifier: 3});
                        // Sign HMAC
                        var content = self.authSafe.content;
                        return calcHMAC(derivation, password, content).then(function (digest) {
                            self.macData = {
                                mac: {
                                    digestAlgorithm: hash,
                                    digest: digest
                                },
                                macSalt: derivation.salt,
                                iterations: derivation.iterations
                            };
                            return self;
                        });
                    } else
                        return self;
                });
            }, // </editor-fold>
            /**
             * Verifies the MAC.
             * 
             * @memberOf GostKeys.PKCS12
             * @instance
             * @param password The password for mac verification
             * @returns {Promise} Promise to return self object after verification
             */
            verify: function (password) // <editor-fold defaultstate="collapsed">  
            {
                var self = this, authSafe = self.authSafe, derivation;
                return new Promise(call).then(function () {
                    // Indirectly verification
                    if (authSafe.contentType === 'data') {
                        // Check MAC 
                        if (self.macData) {
                            if (!password)
                                throw new Error('Password must be defined for the MAC verification');
                            derivation = {
                                name: 'PFXKDF',
                                hash: self.macData.mac.digestAlgorithm,
                                salt: self.macData.macSalt,
                                iterations: self.macData.iterations,
                                diversifier: 3
                            };
                            var content = self.authSafe.content, digest = self.macData.mac.digest;
                            // Verify HMAC with PFXKDF (PKCS#12)
                            return verifyHMAC(derivation, password, digest, content)['catch'](function () {
                                // Verify HMAC with PBKDF2 (TC 26, PKCS#5)
                                derivation.name = 'PBKDF2';
                                return verifyHMAC(derivation, password, digest, content);
                            });
                        } // No check with MAC
                    } else
                        throw new Error('Unsupported format');
                }).then(function () {
                    return self;
                });
            } // </editor-fold>
        };
    })());

    /**
     * An implementation of PKCS #12 password encryption/integrity modes. 
     * 
     * @memberOf GostKeys
     * @type GostKeys.PKCS12
     */
    GostKeys.prototype.PKCS12 = PKCS12;

    /**
     * This type of entry holds a cryptographic PrivateKey, which is optionally stored 
     * in a protected format to prevent unauthorized access. It is also accompanied by 
     * a certificate chain for the corresponding public key.
     * 
     * @class GostKeys.KeyEntry
     * @property {(GostKeys.PKCS8|GostKeys.PKCS8Encrypted)} key The Private Key
     * @property {GostCert.X509[]} certs The X.509 Certificates chain
     * @property {GostCert.CRL[]} crls The X.509 CRLs for certificate chain
     */

    /**
     * This class represents a storage facility for cryptographic keys and certificates.
     * 
     * @class GostKeys.KeyStore
     * @param {Object} entries Object contains aliased {@link KeyEntry} objects
     */
    function KeyStore(entries) // <editor-fold defaultstate="collapsed"> 
    {
        this.entries = {};
        if (entries)
            for (var name in entries)
                this.setEntry(name, entries[name]);
    } // </editor-fold>

    extend(Object, KeyStore, {
        /**
         * Lists all the alias names of this keystore.
         * 
         * @memberOf GostKeys.KeyStore
         * @instance
         * @returns {string[]}
         */
        aliases: function () // <editor-fold defaultstate="collapsed"> 
        {
            var result = [];
            for (var name in this.entries)
                result.push(name);
            return result;
        }, // </editor-fold>
        /**
         * Checks if the given alias exists in this keystore.
         * 
         * @memberOf GostKeys.KeyStore
         * @instance
         * @param {string} alias The alias name
         * @returns {boolean} True if the alias exists, false otherwise
         */
        containsAlias: function (alias) // <editor-fold defaultstate="collapsed"> 
        {
            return !!this.entries[alias];
        }, // </editor-fold>
        /**
         * Deletes the entry identified by the given alias from this keystore.
         * 
         * @memberOf GostKeys.KeyStore
         * @instance
         * @param {string} alias The alias name 
         */
        deleteEntry: function (alias) // <editor-fold defaultstate="collapsed"> 
        {
            delete this.entries[alias];
        }, // </editor-fold>
        /**
         * Saves a keystore Entry under the specified alias.
         * 
         * @memberOf GostKeys.KeyStore
         * @instance
         * @param {string} alias The alias name 
         * @param {GostKeys.KeyEntry} entry The entry
         */
        setEntry: function (alias, entry)  // <editor-fold defaultstate="collapsed">
        {
            var r = {};
            // Keys
            if (entry.key) {
                try {
                    r.key = new PKCS8(entry.key, true); // Private key
                } catch (e) {
                    try {
                        r.key = new PKCS8Encrypted(entry.key, true); // Encrypted private key
                    } catch (e1) {
                        if (entry.key instanceof CryptoOperationData)
                            r.key = entry.key; // Secret key
                        else
                            throw new Error('Unknown Key format');
                    }
                }
            }
            // Certs
            if (entry.certs) {
                var certs = entry.certs instanceof Array ? entry.certs : [entry.certs];
                for (var i = 0; i < certs.length; i++) {
                    try {
                        certs[i] = new cert.X509(certs[i]);
                    } catch (e) {
                    }
                }
                r.certs = certs;
            }
            // CRLs
            if (entry.crls) {
                var crls = entry.crls instanceof Array ? entry.crls : [entry.crls];
                for (var i = 0; i < crls.length; i++) {
                    try {
                        crls[i] = new cert.CRL(crls[i]);
                    } catch (e) {
                    }
                }
                r.crls = crls;
            }
            this.entries[alias] = r;
        }, // </editor-fold>
        /**
         * Gets a keystore Entry for the specified alias 
         * 
         * @memberOf GostKeys.KeyStore
         * @instance
         * @param {string} alias The alias name 
         * @returns {GostKeys.KeyEntry} The entry
         */
        getEntry: function (alias) // <editor-fold defaultstate="collapsed"> 
        {
            return this.entries[alias];
        }, // </editor-fold>
        /**
         * Loads this KeyStore from the given input stream.<br><br>
         * A password may be given to unlock the keystore (e.g. the keystore resides on a hardware token device), 
         * or to check the integrity of the keystore data. If a password is not given for integrity checking, 
         * then integrity checking is not performed.
         * 
         * @memberOf GostKeys.KeyStore
         * @instance
         * @param {(FormatedData|GostKeys.PKSC12)} store The input stream from which the keystore is loaded
         * @param {string} password The password used to check the integrity of the keystore, the password used to unlock the keystore
         * @returns {Promise} Promise to return self object after store loaded
         */
        load: function (store, password) // <editor-fold defaultstate="collapsed"> 
        {
            var self = this;
            return new Promise(call).then(function () {
                // Verify store file
                store = new PKCS12(store);
                return store.verify(password);
            }).then(function () {
                if (store.authSafe.contentType !== 'data')
                    throw new Error('Unsupported PFX format');
                var authSafe = asn1.AuthenticatedSafe.decode(store.authSafe.content).object,
                        promises = [];
                // Decrypt encrypted content
                authSafe.forEach(function (info) {
                    if (info.contentType === 'data')
                        promises.push(new cms.DataContentInfo(info));
                    else if (info.contentType === 'encryptedData')
                        promises.push(new cms.EncryptedDataContentInfo(info).getEnclosed(password));
                    else
                        throw new Error('Unsupported PFX format');
                });
                return Promise.all(promises);
            }).then(function (contents) {
                // Read bags
                var entries = {};
                contents.forEach(function (info) {
                    var bags = asn1.SafeContents.decode(info.content).object;
                    bags.forEach(function (bag) {
                        var keyId = coding.Hex.encode((bag.bagAttributes && bag.bagAttributes.localKeyId)
                                || getSeed(4), true), entry = entries[keyId] || (entries[keyId] = {});
                        switch (bag.bagId) {
                            case 'keyBag':
                                entry.key = new PKCS8(bag.bagValue);
                                break;
                            case 'pkcs8ShroudedKeyBag':
                                entry.key = new PKCS8Encrypted(bag.bagValue);
                                break;
                            case 'secretBag':
                                if (bag.bagValue.secretTypeId === 'secret')
                                    entry.key = bag.bagValue.secretValue;
                                break;
                            case 'certBag':
                                var certs = entry.certs || (entry.certs = []);
                                if (bag.bagValue.certId === 'x509Certificate')
                                    certs.push(new cert.X509(bag.bagValue.certValue));
                                break;
                            case 'crlBag':
                                var crls = entry.crls || (entry.crls = []);
                                if (bag.bagValue.crlId === 'x509CRL')
                                    crls.push(new cert.CRL(bag.bagValue.crlValue));
                                break;
                        }
                        if (bag.bagAttributes && bag.bagAttributes.friendlyName)
                            entry.friendlyName = bag.bagAttributes.friendlyName;
                    });
                });
                // Decrypt keys
                var promises = [];
                for (var name in entries)
                    promises.push((function (entry) {
                        // Try to decrypt private key
                        if (entry.key instanceof PKCS8Encrypted)
                            return entry.key.getKey(password).then(function (key) {
                                // Return entry with decrypted key
                                entry.key = key;
                                return entry;
                            })['catch'](function () {
                                // Return entry with encrypted key
                                return entry;
                            });
                        else
                            return entry;
                    })(entries[name]));
                return Promise.all(promises);
            }).then(function (entries) {
                // Set alias names
                entries.forEach(function (entry) {
                    var friendlyName = entry.friendlyName;
                    if (friendlyName) {
                        delete entry.friendlyName;
                        self.entries[friendlyName] = entry;
                    } else
                        self.entries[generateUUID()] = entry;
                });
                return self;
            });
        }, // </editor-fold>
        /**
         * Stores this keystore to the given output stream, and protects its integrity with the given password.
         * 
         * @memberOf GostKeys.KeyStore
         * @instance
         * @param {string} password The password to generate the keystore integrity check
         * @param {string} digestAlgortihm The digest algorithm or provider name for integrity check
         * @param {string} encryptionAlgortihm The encryption algorithm or provider name for encrypt certificates
         * @returns {Promise} Promise to return {@link GostKeyst.PKCS12} 
         */
        store: function (password, digestAlgortihm, encryptionAlgortihm) // <editor-fold defaultstate="collapsed"> 
        {
            var self = this, keys = [], contents = [], authSafe = [];
            return new Promise(call).then(function () {
                // Define encryption algorithm
                if (encryptionAlgortihm)
                    encryptionAlgortihm = providers[encryptionAlgortihm] ?
                            providers[encryptionAlgortihm].pbes : encryptionAlgortihm;
                else if (digestAlgortihm)
                    encryptionAlgortihm = providers[digestAlgortihm] ?
                            providers[digestAlgortihm].pbes : providers[options.providerName].pbes;
                else
                    encryptionAlgortihm = providers[options.providerName].pbes;
                // Prepare keys and certs
                var index = 1;
                for (var name in self.entries) {
                    var keyId = new Uint32Array([index]), entry = self.entries[name];
                    if (entry.key) {
                        (function (key, attributes) {
                            if (key instanceof CryptoOperationData)
                                contents.push({
                                    bagId: 'secretBag',
                                    bagValue: {
                                        secretTypeId: 'secret',
                                        secretValue: key,
                                        bagAttributes: attributes
                                    }
                                });
                            else if (key instanceof PKCS8) {
                                if (encryptionAlgortihm && password)
                                    keys.push(new PKCS8Encrypted().setKey(key, password, encryptionAlgortihm).then(function (encryptedKey) {
                                        return {
                                            bagId: 'pkcs8ShroudedKeyBag',
                                            bagValue: encryptedKey,
                                            bagAttributes: attributes
                                        };
                                    }));
                                else
                                    keys.push({
                                        bagId: 'keyBag',
                                        bagValue: key,
                                        bagAttributes: attributes
                                    });
                            } else if (key instanceof PKCS8Encrypted)
                                keys.push({
                                    bagId: 'pkcs8ShroudedKeyBag',
                                    bagValue: key,
                                    bagAttributes: attributes
                                });
                        })(entry.key, {
                            localKeyId: keyId,
                            friendlyName: name
                        });
                    }
                    if (entry.certs) {
                        entry.certs.forEach(function (certificate) {
                            var attributes = {localKeyId: keyId};
                            if (certificate instanceof cert.X509)
                                contents.push({
                                    bagId: 'certBag',
                                    bagValue: {
                                        certId: 'x509Certificate',
                                        certValue: certificate
                                    },
                                    bagAttributes: attributes
                                });
                        });
                    }
                    if (entry.crls) {
                        entry.crls.forEach(function (crl) {
                            var attributes = {localKeyId: keyId};
                            if (crl instanceof cert.CRL)
                                contents.push({
                                    bagId: 'crlBag',
                                    bagValue: {
                                        crlId: 'x509CRL',
                                        crlValue: crl
                                    },
                                    bagAttributes: attributes
                                });
                        });
                    }
                }
                // Encrypt keys
                if (keys.length > 0)
                    return Promise.all(keys);
            }).then(function (bags) {
                if (bags) {
                    var keyContents = asn1.SafeContents.encode(bags);
                    authSafe.push(new cms.DataContentInfo({
                        contentType: 'data',
                        content: keyContents
                    }));
                }

                // Encrypt certificates and crls
                if (contents.length > 0) {
                    contents = asn1.SafeContents.encode(contents);
                    if (encryptionAlgortihm && password)
                        return new cms.EncryptedDataContentInfo().encloseContent(
                                contents, password, encryptionAlgortihm);
                    else
                        return new cms.DataContentInfo().encloseContent(contents);
                }
            }).then(function (contents) {
                authSafe.push(contents);
                // Set enclosed data
                authSafe = new asn1.AuthenticatedSafe(authSafe);
                var store = new PKCS12();
                store.authSafe = {
                    contentType: 'data',
                    content: authSafe.encode()
                };
                // Return new PKCS12 with enclosed authenificated content
                return store.sign(password, digestAlgortihm);
            });
        } // </editor-fold>
    });

    /**
     * This class represents a storage facility for cryptographic keys and certificates.
     * 
     * @memberOf GostKeys
     * @type GostKeys.KeyStore
     */
    GostKeys.prototype.KeyStore = KeyStore;

    /**
     * Implements the Key and Certificate Store methods
     * 
     * @memberOf gostCrypto
     * @type GostKeys
     */
    gostCrypto.keys = new GostKeys();

    return GostKeys;
}));