WebCrypto GOST Source: gostASN1.js

/**
 * @file PKCS ASN.1 message syntax and converters
 * @version 1.76
 * @copyright 2014-2016, Rudolf Nickolaev. All rights reserved.
 */

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

(function (root, factory) {

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

}(this, function (gostCrypto) {

    /*
     * Service functions
     * 
     */ // <editor-fold defaultstate="collapsed">

    var root = this;
    var CryptoOperationData = root.ArrayBuffer;
    var Date = root.Date;

    // Security parameters
    var algorithms = gostCrypto.security.algorithms;

    var names = gostCrypto.security.names;

    var identifiers = gostCrypto.security.identifiers;

    var attributes = gostCrypto.security.attributes;

    var parameters = gostCrypto.security.parameters;

    // BER coding
    var BER = gostCrypto.coding.BER;

    // PEM coding
    var PEM = gostCrypto.coding.PEM;

    // Chars coding
    var Chars = gostCrypto.coding.Chars;

    // Hex coding;
    var Hex = gostCrypto.coding.Hex;

    // Hex coding;
    var Int16 = gostCrypto.coding.Int16;

    // 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)
                    r[name] = item[name];
        }
        return r;
    }

    // Swap bytes in buffer
    function swapBytes(src) {
        if (src instanceof CryptoOperationData)
            src = new Uint8Array(src);
        var dst = new Uint8Array(src.length);
        for (var i = 0, n = src.length; i < n; i++)
            dst[n - i - 1] = src[i];
        return dst.buffer;
    }

    function isBinary(value) {
        return value instanceof CryptoOperationData || value.buffer instanceof CryptoOperationData;
    }

    // Left pad zero
    function lpad(n, width) {
        return n.length >= width ? n : new Array(width - n.length + 1).join('0') + n;
    }

    // Nearest power 2
    function npw2(n) {
        return n <= 2 ? n : n <= 4 ? 4 : n <= 8 ? 8 : n <= 16 ? 16 :
                n <= 32 ? 32 : n <= 64 ? 64 : n <= 128 ? 128 : n <= 256 ? 256 :
                n < 512 ? 512 : n < 1024 ? 1024 : undefined;
    }

    // String int encode/decode to buffer
    var SInt = {
        encode: function (value, endian) {
            return '0x' + Hex.encode(value, endian);
        },
        decode: function (value, endian, len) {
            if (typeof value === 'number')
                value = value.toString(16);
            var s = value.replace('0x', '');
            len = len || npw2(s.length);
            return Hex.decode(lpad(s, len), endian);
        }
    };

    // Assert invalid message
    function assert(value) {
        if (value)
            throw Error('Invalid format');
    }

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

    function getOwnPropertyDescriptor(object, name) {
        return  Object.getOwnPropertyDescriptor(object, name);
    }

    // </editor-fold>

    /*
     * Base ASN.1 types and definitions
     * 
     */ // <editor-fold defaultstate="collapsed">

    // Encode object primitive
    function encode(format, object, tagNumber, tagClass, tagConstructed, uniformTitle) {
        assert(object === undefined);
        var source = {
            tagNumber: tagNumber,
            tagClass: tagClass || 0x00,
            tagConstructed: tagConstructed || false,
            object: object
        };
        // Output format
        format = format || 'DER';
        if (format === 'DER' || format === 'CER')
            source = BER.encode(source, format);
        if (format === 'PEM')
            source = PEM.encode(source, uniformTitle);
        return source;
    }

    // Decode object primitive
    function decode(source, tagNumber, tagClass, tagConstructed, uniformTitle) {
        assert(source === undefined);

        // Decode PEM
        if (typeof source === 'string')
            source = PEM.decode(source, uniformTitle, false);
        // Decode binary data
        if (source instanceof CryptoOperationData) {
            try {
                source = PEM.decode(Chars.encode(source), uniformTitle, true);
            } catch (e) {
                source = BER.decode(source);
            }
        }

        tagClass = tagClass || 0;
        tagConstructed = tagConstructed || false;
        // Restore context implicit formats
        if (source.tagNumber === undefined) {
            source = encode(true, source.object, tagNumber, tagClass,
                    source.object instanceof Array);
            source = BER.decode(source);
        }

        // Check format
        assert(source.tagClass !== tagClass ||
                source.tagNumber !== tagNumber ||
                source.tagConstructed !== tagConstructed);
        // Clone value define from redefine original
        if (tagClass === 0 && tagNumber === 0x05)
            return null;
        else
            return source.object;
    }

    // Create class based on super
    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);
        // 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;
    }

    // Base class 
    var ASN1Object = extend(Object, function (object) {
        this.object = object;
    }, {
        // Call set method for a class property
        _set: function (Class, propName, value) {
            Class.property(propName).set.call(this, value);
        },
        // Call get method for a class property
        _get: function (Class, propName) {
            return Class.property(propName).get.call(this);
        },
        // Call method for a class
        _call: function (Class, methodName, args) {
            return Class.method(methodName).apply(this, args);
        },
        hasProperty: function (propName) {
            return this.hasOwnProperty(propName) ||
                    !!this.constructor.property(propName);
        },
        encode: function () {
            return this.object;
        }
    }, {
        decode: function (source) {
            return new this(source);
        },
        // Find ingerited property
        property: function (propName) {
            var proto = this.prototype;
            while (proto) {
                var descriptor = getOwnPropertyDescriptor(proto, propName);
                if (descriptor)
                    return descriptor;
                else
                    proto = proto.superclass;
            }
        },
        // Find method 
        method: function (methodName) {
            var proto = this.prototype;
            while (proto) {
                if (proto[methodName])
                    return proto[methodName];
                else
                    proto = proto.superclass;
            }
        }
    });

    // Primitive metaclass
    var PRIMITIVE = function (tagNumber) {
        return extend(ASN1Object, {
            encode: function (format) {
                return encode(format, this.object, tagNumber);
            }
        }, {
            decode: function (source) {
                return new this(decode(source, tagNumber));
            }
        });
    };

    var ANY = ASN1Object;

    var BOOLEAN = PRIMITIVE(0x01);

    var IA5String = PRIMITIVE(0x16);

    var NumericString = PRIMITIVE(0x12);

    var PrintableString = PRIMITIVE(0x13);

    var TeletexString = PRIMITIVE(0x14);

    var UTF8String = PRIMITIVE(0x0c);

    var UTCTime = PRIMITIVE(0x17);

    var GeneralizedTime = PRIMITIVE(0x18);

    var UniversalString = PRIMITIVE(0x1C);

    var BMPString = PRIMITIVE(0x1e);

    var NULL = extend(PRIMITIVE(0x05), {
        object: {
            get: function () {
                return null;
            },
            set: function (object) {
                assert(object !== null);
            }
        }
    });

// Primitive class with value coding
    var PRIMITIVE_CODE = function (tagNumber) {

        // Base class primitive
        var Class = extend(PRIMITIVE(tagNumber), function (object) {
            if (this instanceof Class)
                Class.super.apply(this, arguments);
            else
                return CODE(object);
        });

        // Create Class with encoded
        function CODE(structure) {
            // Structured class
            return extend(PRIMITIVE(tagNumber), function (object) {
                Class.super.call(this, object);
            }, {
                // Transformation to code values
                encode: function (format) {
                    return encode(format, structure[this.object], tagNumber);
                }
            }, {
                decode: function (source) {
                    var id = decode(source, tagNumber);
                    for (var name in structure)
                        if (id === structure[name])
                            return new this(name);
                    assert(true);
                }
            });
        }
        return Class;
    };

    var INTEGER = PRIMITIVE_CODE(0x02);

    var ENUMERATED = PRIMITIVE_CODE(0x0a);

    var OCTET_STRING = (function () {
        // Base class primitive
        var Class = extend(PRIMITIVE(0x04), function (object) {
            if (this instanceof Class)
                Class.super.apply(this, arguments);
            else
                return WRAPPING(object);
        });
        // Wrapping class
        function WRAPPING(WrappedClass) {
            if (WrappedClass) {
                return extend(WrappedClass, {
                    encode: function (format) {
                        return encode(format, WrappedClass.method('encode').call(this, true), 0x04);
                    }
                }, {
                    decode: function (source) {
                        return WrappedClass.decode.call(this, decode(source, 0x04));
                    }
                });
            } else
                return Class;
        }
        return Class;
    })();

    var BIT_STRING = (function () {
        // Base class primitive
        var Class = extend(PRIMITIVE(0x03), function (object) {
            if (this instanceof Class)
                Class.super.apply(this, arguments);
            else if (typeof object === 'object')
                return MASK(object);
            else
                return WRAPPING(object);
        });

        // Wrapping class
        function WRAPPING(WrappedClass) {
            if (WrappedClass) {
                return extend(WrappedClass, {
                    encode: function (format) {
                        return encode(format, WrappedClass.method('encode').call(this, true), 0x03);
                    }
                }, {
                    decode: function (source) {
                        return WrappedClass.decode.call(this, decode(source, 0x03));
                    }
                });
            } else
                return Class;
        }

        // Create new class for a mask
        function MASK(structure) {
            // Bit string masked class
            return extend(ASN1Object, function (object, numbits) {
                ASN1Object.call(this, object);
                this.numbits = numbits || 0;
            }, {
                encode: function (format) {
                    var object = this.object, data = [];
                    if (object instanceof Array) {
                        for (var i = 0, n = object.length; i < n; i++) {
                            var j = structure[object[i]];
                            if (j !== undefined)
                                data[j] = '1';
                        }
                        for (var i = 0, n = Math.max(data.length, this.numbits); i < n; i++)
                            if (!data[i])
                                data[i] = '0';
                        data = data.join('');
                    } else
                        data = '0';
                    return encode(format, data, 0x03);
                }
            }, {
                // Transformation to array of values
                decode: function (source) {
                    var data = decode(source, 0x03), object = [];
                    for (var name in structure) {
                        var i = structure[name];
                        if (data.charAt(i) === '1')
                            object.push(name);
                    }
                    return new this(object, data.length);
                }
            });
        }

        return Class;
    })();

    // Combine sequence object properties with owner object
    var COMBINE = function (Class) {
        Class.combine = function (owner, valueName) {
            for (var name in Class.prototype) {
                if (Class.prototype.hasOwnProperty(name) && !owner.hasProperty(name)) {
                    defineProperty(owner, name, (function (name) {
                        return {
                            get: function () {
                                // Get object property
                                return this[valueName] && this[valueName][name];
                            },
                            set: function (object) {
                                // Set object property
                                if (!this[valueName])
                                    this[valueName] = {};
                                this[valueName][name] = object;
                            },
                            configurable: false,
                            enumerable: true
                        };
                    })(name));
                }
            }
        };
        return Class;
    };

    var SEQUENCE = function (structure, uniformTitle) {

        /**
         * Create SEQUENCE ASN.1 metaclass
         * 
         * @class GostASN1.Sequence
         * @param {(Object|FormatedData)} object Initialization object
         * @param {boolean} check Check structure after initialization
         */
        var Class = extend(ASN1Object, function (object, check) {
            // Define hidden properties
            defineProperty(this, 'items', {
                writable: true,
                value: {}
            });
            if (typeof object === 'string' || object instanceof CryptoOperationData)
                this.decode(object);
            else if (object !== undefined) {
                this.object = object;
                // Check structure
                if (check)
                    this.check();
            }
        }, {
            object: {
                get: function () {
                    return this;
                },
                set: function (object) {
                    if (object instanceof Class) {
                        // Set the same sequence class
                        this.items = object.items;
                        for (var name in structure) {
                            var ItemClass = this.getItemClass(name, this.items);
                            if (ItemClass.combine)
                                ItemClass.combine(this, name);
                        }
                    } else {
                        // Set other object structure
                        var data = {};
                        for (var name in structure) {
                            var item = object[name];
                            var ItemClass = this.getItemClass(name, data);
                            if (item !== undefined) {
                                data[name] = new ItemClass(item);
                            } else if (ItemClass.combine) {
                                // Create combined object
                                data[name] = new ItemClass(object);
                            }
                            if (ItemClass.combine)
                                ItemClass.combine(this, name);
                        }
                        this.items = data;
                    }
                }
            },
            getItemClass: function (name, items) {
                return structure[name];
            },
            /**
             * Encode the object
             * 
             * @memberOf GostASN1.Sequence
             * @instance
             * @param {string} format Encoding format 'DER', 'CER' or 'PEM'
             * @returns {FormatedData} 
             */
            encode: function (format) {
                var source = [], items = this.items;
                // Encode objects in structure
                for (var name in structure) {
                    // console.log(name, 'encoding...', items[name]);
                    if (items[name]) {
                        var encoded = items[name].encode(true);// Source from object
                        if (encoded !== undefined) // Can be optional
                            source.push(encoded);

                    }
                }
                return encode(format, source, 0x10, 0, true, uniformTitle);
            },
            /**
             * Decode the source to self object
             * 
             * @memberOf GostASN1.Sequence
             * @instance
             * @param {FormatedData} source Encoded data
             */
            decode: function (source) {
                this.object = this.constructor.decode(source);
            },
            /**
             * Check the object structure
             * 
             * @memberOf GostASN1.Sequence
             * @instance
             */
            check: function () {
                this.constructor.decode(this.encode(true));
            }
        }, {
            /**
             * Encode data values with creating object
             * 
             * @memberOf GostASN1.Sequence
             * @static
             * @param {Object} object Javascript object to encoding
             * @param {string} format Encoding format 'DER', 'CER' or 'PEM'
             * @returns {FormatedData} 
             */
            encode: function (object, format) {
                return new this(object).encode(format);
            },
            /**
             * Decode source and create object
             * 
             * @memberOf GostASN1.Sequence
             * @static
             * @param {FormatedData} source Encoded data
             * @returns {GostASN1.Sequence}
             * 
             */
            decode: function (source) {
                // Decode structure
                source = decode(source, 0x10, 0, true, uniformTitle);
                var i = 0, result = new this(), data = result.items = {};
                for (var name in structure) {
                    // console.log(name, 'decoding...');
                    // try to create and decode object
                    var ItemClass = result.getItemClass(name, data);
                    var item = ItemClass.decode(source[i]);
                    // success? item can be optional
                    if (item !== undefined) {
                        data[name] = item;
                        if (ItemClass.combine)
                            ItemClass.combine(result, name);
                        i++;
                    }
                }
                return result;
            }
        });

        // Append structure items
        for (var name in structure) {
            defineProperty(Class.prototype, name, (function (name) {
                return {
                    get: function () {
                        // Get object property
                        return this.items[name] && this.items[name].object;
                    },
                    set: function (object) {
                        // Set object property
                        if (object !== undefined) {
                            var ItemClass = this.getItemClass(name, this.items);
                            this.items[name] = new ItemClass(object);
                        } else
                            delete this.items[name];
                    },
                    configurable: false,
                    enumerable: !structure[name].combine
                };
            })(name));
            if (structure[name].combine)
                structure[name].combine(Class.prototype, name);
        }
        return Class;
    };

    var ATTRIBUTE = function (structure, typeName, valueName, ownerDafaultType, uniformName) {

        var BaseClass = SEQUENCE(structure, uniformName);

        // Define attribute sequence
        var DEFINE = function (typeSet, defaultType) {

            typeName = typeName || 'type';
            valueName = valueName || 'value';
            defaultType = defaultType || ownerDafaultType || ANY;

            var Class = extend(BaseClass, function (object) {
                // Constructor - "matrioshka"
                if (this instanceof Class) {
                    // Call super
                    BaseClass.apply(this, arguments);
                } else
                    return DEFINE.apply(this, arguments);
            }, {
                getItemClass: function (name, items) {
                    var ItemClass = structure[name];
                    if (valueName === name) {
                        // Define type of value attribute based on type attribute
                        var type, typeId = items && items[typeName];
                        if (typeId) {
                            var id = typeId.object;
                            if (typeSet) {
                                if (typeof typeSet === 'function')
                                    type = typeSet(id);
                                else
                                    type = typeSet[id];
                            }
                        }
                        type = type || defaultType || ANY;
                        ItemClass = ItemClass === ANY ? type :
                                ItemClass(type);
                    }
                    return ItemClass;
                }
            });

            // Redefine type property
            defineProperty(Class.prototype, typeName, {
                get: function () {
                    // Get value property of object
                    return this.items[typeName] && this.items[typeName].object;
                },
                set: function () {
                    // Can't set type definition property separatery
                    assert(true);
                },
                configurable: false,
                enumerable: true
            });

            return Class;
        };

        return DEFINE();
    };


    var OBJECT_IDENTIFIER = extend(ASN1Object, {
        encode: function (format) {
            var object = this.object;
            object = /^(\d+\.)+\d+$/.test(object) ? object : identifiers[object];
            assert(!object);
            return encode(format, object, 0x06);
        }
    }, {
        decode: function (source) {
            var object = decode(source, 0x06);
            return new this(names[object] || object);
        }
    });

    var IMPLICIT = function (Class) {
        Class = Class || ANY;
        // Add constracted tag
        return extend(Class, {
            encode: function (format) {
                // Format encoding without CTX header
                var source = Class.method('encode').call(this, format);
                if (typeof source === 'string' || source instanceof CryptoOperationData)
                    return source;
                if (source.tagNumber !== 0x04 && source.tagClass === 0 &&
                        !(source.object instanceof Array))
                    // Encode primitive source
                    return {object: BER.encode(source, 'DER', true)};
                else
                    return {object: source.object};
            }
        }, {
            decode: function (source) {
                if (typeof source === 'string' || source instanceof CryptoOperationData) {
                    return Class.decode.call(this, source);
                } else {
                    source = {
                        object: source.object,
                        header: source.header,
                        content: source.content
                    };
                    return Class.decode.call(this, source);
                }
            }
        });
    };

    var EXPLICIT = function (Class) {
        Class = Class || ANY;
        // Add constracted tag
        return extend(Class, {
            encode: function (format) {
                // Format encoding without CTX header
                var source = Class.method('encode').call(this, format);
                if (typeof source === 'string' || source instanceof CryptoOperationData)
                    return source;
                return {object: [source]};
            }
        }, {
            decode: function (source) {
                if (typeof source === 'string' || source instanceof CryptoOperationData) {
                    return Class.decode.call(this, source);
                } else
                    return Class.decode.call(this, source.object[0]);
            }
        });
    };

    var CTX = function (number, ContentClass) {
        function CTX() {
            ContentClass.apply(this, arguments);
        }
        // Create CTX number class with wrapped content class
        return extend(ContentClass, CTX, {
            encode: function (format) {
                var source = ContentClass.method('encode').call(this, format);
                if (typeof source === 'string' || source instanceof CryptoOperationData)
                    return source;
                source.tagNumber = number;
                source.tagClass = 0x02;
                source.tagConstructed = source.object instanceof Array;
                return source;
            }
        }, {
            decode: function (source) {
                // Format decoding without CTX
                assert(source.tagNumber !== undefined &&
                        (source.tagClass !== 0x02 || source.tagNumber !== number));
                return ContentClass.decode.call(this, source);
            }
        });
    };

    var ARRAY_OF = function (tagNumber) {

        return function (ItemClassDef, typeAndValue) {
            // Difininition of item class
            ItemClassDef = ItemClassDef || ANY;

            // Metaclass definition 
            var DEFINE = function (typeSet, defaultType) {

                // Define item class
                var ItemClass = typeof ItemClassDef === 'function' &&
                        typeSet !== undefined ?
                        ItemClassDef(typeSet, defaultType) : ItemClassDef;

                if (typeAndValue) {
                    /**
                     * Create class with type and value structure<br><br>
                     * 
                     * SET OF attribute and SEQUENCE OF attribute metaclass
                     * 
                     * @class GostASN1.Set
                     * @param {Object} object object value
                     */
                    var Class = extend(ASN1Object, function (object) {
                        // Constructor - "matrioshka"
                        if (this instanceof Class) {
                            // Define hidden items property
                            defineProperty(this, 'items', {
                                writable: true,
                                value: {}
                            });
                            // Call super
                            ASN1Object.call(this, object || {});
                        } else
                            return DEFINE.apply(this, arguments);
                    }, {
                        object: {
                            get: function () {
                                // refresh items from object properties
                                this.read();
                                return this;
                            },
                            set: function (object) {
                                if (object instanceof Class) {
                                    object.read();
                                    this.items = object.items;
                                } else {
                                    // Set other object structure
                                    var data = {};
                                    for (var id in object) {
                                        var item = object[id];
                                        data[id] = this.createItem(item, id);
                                    }
                                    this.items = data;
                                }
                                // refresh object properties to items
                                this.reset();
                            }
                        },
                        createItem: function (value, type) {
                            if (typeAndValue) {
                                var object = {};
                                object[typeAndValue.typeName] = type;
                                object[typeAndValue.valueName] = value;
                            } else
                                object = value;
                            return new ItemClass(object);
                        },
                        getItemValue: function (id) {
                            var item = this.items[id];
                            return typeAndValue ? item.object[typeAndValue.valueName] : item.object;
                        },
                        setItemValue: function (id, value) {
                            var item = this.items[id];
                            if (typeAndValue)
                                item.object[typeAndValue.valueName] = value;
                            else
                                item.object = value;
                        },
                        isItemType: function (id) {
                            return typeAndValue ? identifiers[id] : !isNaN(parseInt(id));
                        },
                        reset: function () {
                            // remove unused properties
                            var items = this.items;
                            for (var id in this)
                                if (this.hasOwnProperty(id) && !this.items[id] &&
                                        this.isItemType(id))
                                    delete this[id];
                            // add new properties
                            for (var id in items)
                                this[id] = this.getItemValue(id);
                        },
                        read: function () {
                            var items = this.items;
                            for (var id in this) {
                                if (this.isItemType(id)) {
                                    if (!this.items[id]) {
                                        items[id] = this.createItem(this[id], id);
                                        this[id] = this.getItemValue(id);
                                    } else if (this.getItemValue(id) !== this[id]) {
                                        this.setItemValue(id, this[id]);
                                    }
                                }
                            }
                        },
                        /**
                         * Encode the object
                         * 
                         * @memberOf GostASN1.Set
                         * @instance
                         * @param {string} format Encoding format 'DER', 'CER' or 'PEM'
                         * @returns {FormatedData} 
                         */
                        encode: function (format) {
                            // refresh items from object properties
                            this.read();
                            // repare source
                            var object = this.items, source = [];
                            for (var id in object) {
                                // console.log(id, object[id], 'encoding...');
                                var encoded = object[id].encode(true);
                                if (encoded !== undefined)
                                    source.push(encoded);
                            }
                            return encode(format, source, tagNumber, 0, true);
                        },
                        /**
                         * Decode the source to self object
                         * 
                         * @memberOf GostASN1.Set
                         * @instance
                         * @param {FormatedData} source Encoded data
                         */
                        decode: function (source) {
                            this.object = this.constructor.decode(source);
                        },
                        /**
                         * Check the object structure
                         * 
                         * @memberOf GostASN1.Set
                         * @instance
                         */
                        check: function () {
                            this.constructor.decode(this.encode(true));
                        }
                    }, {
                        /**
                         * Encode data values with creating object
                         * 
                         * @memberOf GostASN1.Set
                         * @static
                         * @param {Object} object Javascript object to encoding
                         * @param {string} format Encoding format 'DER', 'CER' or 'PEM'
                         * @returns {FormatedData} 
                         */
                        encode: function (object, format) {
                            return new this(object).encode(format);
                        },
                        /**
                         * Decode source and create object
                         * 
                         * @memberOf GostASN1.Set
                         * @static
                         * @param {FormatedData} source Encoded data
                         * @returns {GostASN1.Sequence}
                         * 
                         */
                        decode: function (source) {
                            // Decode structure
                            source = decode(source, tagNumber, 0, true);
                            var result = new this(), data = result.items = {};
                            for (var i = 0, n = source.length; i < n; i++) {
                                var item = ItemClass.decode(source[i]);
                                var id = typeAndValue ? item.object[typeAndValue.typeName] : i;
                                data[id] = item;
                            }
                            result.reset();
                            return result;
                        }
                    });

                    return Class;
                } else {
                    // Create array class
                    var ArrayClass = extend(ASN1Object, function (object) {
                        // Constructor - "matrioshka"
                        if (this instanceof ArrayClass) {
                            // Define hidden items property
                            defineProperties(this, {
                                items: {
                                    writable: true,
                                    value: []
                                },
                                values: {
                                    writable: true,
                                    value: []
                                }
                            });
                            // Call super
                            ASN1Object.call(this, object || []);
                        } else
                            return DEFINE.apply(this, arguments);
                    }, {
                        object: {
                            get: function () {
                                // refresh items from object properties
                                this.read();
                                return this.values;
                            },
                            set: function (object) {
                                if (object instanceof ArrayClass) {
                                    object.read();
                                    this.items = object.items;
                                } else {
                                    // Set other object structure
                                    var data = [];
                                    for (var i = 0, n = object.length; i < n; i++)
                                        data[i] = new ItemClass(object[i]);
                                    this.items = data;
                                }
                                // refresh object properties to items
                                this.reset();
                            }
                        },
                        encode: function (format) {
                            // refresh items from object properties
                            this.read();
                            // repare source
                            var data = this.items, source = [];
                            for (var i = 0, n = data.length; i < n; i++) {
                                var encoded = data[i].encode(true);
                                if (encoded !== undefined)
                                    source.push(encoded);
                            }
                            return encode(format, source, tagNumber, 0, true);
                        },
                        decode: function (source) {
                            this.object = this.constructor.decode(source);
                        },
                        check: function () {
                            this.constructor.decode(this.encode(true));
                        },
                        reset: function () {
                            // remove unused properties
                            for (var i = 0, n = this.items.length; i < n; i++)
                                this.values.push(this.items[i].object);
                        },
                        read: function () {
                            var items = this.items, values = this.values;
                            for (var i = 0, n = values.length; i < n; i++) {
                                if (!this.items[i]) {
                                    items[i] = new ItemClass(values[i]);
                                    values[i] = items[i].object;
                                } else if (items[i].object !== values[i])
                                    items[i].object = values[i];
                            }
                        }
                    }, {
                        encode: function (object, format) {
                            return new this(object).encode(format);
                        },
                        decode: function (source) {
                            source = decode(source, tagNumber, 0, true);
                            var result = new this();
                            result.items = [];
                            for (var i = 0, n = source.length; i < n; i++)
                                result.items.push(ItemClass.decode(source[i]));
                            result.reset();
                            return result;
                        }
                    });

                    return ArrayClass;
                }
            };
            return DEFINE(); // Create simple class w/o any parameters
        };
    };

    var SEQUENCE_OF = ARRAY_OF(0x10);

    var SET_OF = ARRAY_OF(0x11);

    var ENCLOSURE = function (BaseClass, modifier) {
        if (modifier) {
            var Class = extend(ASN1Object, {
                object: {
                    get: function () {
                        if (this.item)
                            return modifier.decode(this.item.object);
                        else
                            return undefined;
                    },
                    set: function (object) {
                        if (object !== undefined)
                            this.item = new BaseClass(modifier.encode(object));
                        else
                            delete this.item;
                    }
                },
                encode: function (format) {
                    return this.item.encode(format);
                }
            }, {
                decode: function (source) {
                    var result = new this();
                    result.item = BaseClass.decode(source);
                    return result;
                }
            });
            for (var name in BaseClass)
                if (!Class[name])
                    Class[name] = BaseClass[name];
            return Class;
        } else
            return BaseClass;
    };

    var SET_OF_SINGLE = function (ItemClass) {

        var Class = ENCLOSURE(SET_OF(ItemClass), {
            encode: function (item) {
                return [item];
            },
            decode: function (item) {
                return item[0];
            }
        });
        return Class;
    };

    var CHOICE = function (structure, define) {

        return extend(ASN1Object, {
            object: {
                get: function () {
                    return this.item && this.item.object;
                },
                set: function (object) {
                    // Try to find appropriate type in structure
                    if (object instanceof ASN1Object) {
                        for (var name in structure)
                            if (object instanceof structure[name]) {
                                this.item = object;
                                return;
                            }
                    }
                    // Define class
                    var name = typeof define === 'function' ? define(object) : define;
                    assert(!name || !structure[name]);
                    object = new structure[name](object);
                    this.item = object;
                }
            },
            encode: function (format) {
                // Already in class
                return this.item.encode(format);
            }
        }, {
            decode: function (source) {
                // Try to find class structure
                for (var name in structure) {
                    try {
                        var item = structure[name].decode(source);
                        if (item !== undefined)
                            return new this(item);
                    } catch (e) {
                    }
                }
                assert(true);
            }
        });
    };

    var ENCAPSULATES = function (WrappedClass) {
        WrappedClass = WrappedClass || ANY;
        // BER Encode/Decode values
        return extend(WrappedClass, {
            encode: function () {
                return BER.encode(WrappedClass.method('encode').call(this, true));
            }
        }, {
            encode: function (object, format) {
                return new this(object).encode(format);
            },
            decode: function (source) {
                return WrappedClass.decode.call(this, BER.decode(source));
            }
        });
    };

    var DEFAULT = function (Class, optional) {
        Class = Class || ANY;
        return extend(Class, {
            encode: function (format) {
                if (this.object === optional)
                    return undefined;
                return Class.method('encode').call(this, format);
            }
        }, {
            decode: function (source) {
                if (source === undefined)
                    return new this(optional);
                else
                    try {
                        return Class.decode.call(this, source);
                    } catch (e) {
                        return undefined;
                    }
            }
        });
    };

    var OPTIONAL = function (Class) {
        Class = Class || ANY;
        return extend(Class, {}, {
            decode: function (source) {
                if (source === undefined)
                    return undefined;
                else
                    try {
                        return Class.decode.call(this, source);
                    } catch (e) {
                        return undefined;
                    }
            }
        });
    };

    var DEFAULT_NULL = function (Class, optional) {
        Class = Class || ANY;
        return extend(Class, {
            encode: function (format) {
                if (this.object === optional)
                    return new NULL(null).encode(format);
                return Class.method('encode').call(this, format);
            }
        }, {
            decode: function (source) {
                if (source === undefined)
                    return undefined;
                else if (source === null ||
                        (source.tagNumber === 0x05 && source.tagClass === 0))
                    return new this(optional);
                else
                    try {
                        return Class.decode.call(this, source);
                    } catch (e) {
                        return undefined;
                    }
            }
        });
    };

    // </editor-fold>    

    /*
     * Certificate Version, Name, Attributes, Validity
     * 
     * http://tools.ietf.org/html/rfc5280
     * 
     */ // <editor-fold defaultstate="collapsed">

    var DirectoryString = CHOICE({
        teletexString: TeletexString,
        printableString: PrintableString,
        universalString: UniversalString,
        utf8String: UTF8String,
        bmpString: BMPString,
        numericString: NumericString
    }, function (value) {
        // PrintableString - for characters and symbols with no spaces, overrise UTF8String
        return /^[A-Za-z0-9\.@\+\-\:\=\\\/\?\!\#\$\%\^\&\*\(\)\[\]\{\}\>\<\|\~]*$/.test(value) ? 'printableString' : 'utf8String';
    });

    var Time = CHOICE({
        utcTime: UTCTime,
        generalTime: GeneralizedTime
    }, function (value) {
        return value.getYear() >= 2050 ? 'generalTime' : 'utcTime';
    });

    // Attribute
    var AttributeType = OBJECT_IDENTIFIER;

    var AttributeValue = ANY;

    var AttributeTypeAndValue = ATTRIBUTE({
        type: AttributeType,
        value: AttributeValue
    });

    var typeAndValue = {
        typeName: 'type',
        valueName: 'value'
    };

    /**
     * X.501 type Name
     * The Name describes a hierarchical name composed of attributes, such
     * as country name, and corresponding values, such as US.  The type of
     * the component AttributeValue is determined by the AttributeType; in
     * general it will be a DirectoryString.
     
     * The DirectoryString type is defined as a choice of PrintableString,
     * TeletexString, BMPString, UTF8String, and UniversalString.  The
     * UTF8String encoding [RFC 2279] is the preferred encoding, and all
     * certificates issued after December 31, 2003 MUST use the UTF8String
     * encoding of DirectoryString.
     * 
     * Standard sets of attributes have been defined in the X.500 series of
     * specifications [X.520].  Implementations of this specification MUST
     * be prepared to receive the following standard attribute types in
     * issuer and subject (section 4.1.2.6) names:
     *  <ul>
     *      <li>country,</li>
     *      <li>organization,</li>
     *      <li>organizational-unit,</li>
     *      <li>distinguished name qualifier,</li>
     *      <li>state or province name,</li>
     *      <li>common name (e.g., "Susan Housley"), and</li>
     *      <li>serial number.</li>
     *  </ul>
     * In addition, implementations of this specification SHOULD be prepared
     * to receive the following standard attribute types in issuer and
     * subject names:
     *  <ul>
     *      <li>locality,</li>
     *      <li>title,</li>
     *      <li>surname,</li>
     *      <li>given name,</li>
     *      <li>initials,</li>
     *      <li>pseudonym, and</li>
     *      <li>generation qualifier (e.g., "Jr.", "3rd", or "IV").</li>
     *  </ul>
     The syntax for type Name:
     *  <pre>
     *  Name ::= CHOICE {
     *    rdnSequence RDNSequence }
     *
     *  RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
     *
     *  RelativeDistinguishedName ::=
     *    SET OF AttributeTypeAndValue
     *
     *  AttributeTypeAndValue ::= SEQUENCE {
     *    type     AttributeType,
     *    value    AttributeValue }
     *
     *  AttributeType ::= OBJECT IDENTIFIER
     *
     *  AttributeValue ::= ANY DEFINED BY AttributeType
     *
     *  DirectoryString ::= CHOICE {
     *        teletexString           TeletexString (SIZE (1..MAX)),
     *        printableString         PrintableString (SIZE (1..MAX)),
     *        universalString         UniversalString (SIZE (1..MAX)),
     *        utf8String              UTF8String (SIZE (1..MAX)),
     *        bmpString               BMPString (SIZE (1..MAX)) }
     *  </pre>
     * @class GostASN1.Name
     */
    var RelativeDistinguishedName = SET_OF_SINGLE(AttributeTypeAndValue({
        serialName: PrintableString,
        countryName: PrintableString,
        dnQualifier: PrintableString,
        emailAddress: IA5String,
        domainComponent: IA5String,
        SNILS: NumericString,
        OGRN: NumericString,
        INN: NumericString
    }, DirectoryString));

    var RDNSequence = SEQUENCE_OF(RelativeDistinguishedName, typeAndValue)();

    var Name = CHOICE({
        // only one possibility for now
        rdnSequence: RDNSequence}, 'rdnSequence');

    /**
     * Validity
     * @class GostASN1.Validity
     * @extends GostASN1.Sequence
     */
    var Validity = COMBINE(SEQUENCE({
        notBefore: Time,
        notAfter: Time}));

    var Version = INTEGER;

    var Attribute = ATTRIBUTE({
        type: OBJECT_IDENTIFIER,
        value: ANY
    });

    /**
     * Type and Value Attributes <br>
     * 
     * Suggested naming attributes: Definition of the following 
     * information object set may be augmented to meet local 
     * requirements.  Note that deleting members of the set may 
     * prevent interoperability with conforming implementations.
     * presented in pairs: the AttributeType followed by the type 
     * definition for the corresponding AttributeValue
     * 
     * @class GostASN1.Attributes
     * @extends GostASN1.Set
     */
    var Attributes = SET_OF(Attribute, typeAndValue);

    var AttributeSequence = SEQUENCE_OF(Attribute, typeAndValue);
    // </editor-fold>    

    /*
     * Algorithm identifiers
     * 
     * http://tools.ietf.org/html/rfc3279
     * http://tools.ietf.org/html/rfc4357
     * http://tools.ietf.org/html/rfc2898
     * 
     */ // <editor-fold defaultstate="collapsed">

    var FieldElement = INTEGER;
    var Curve = SEQUENCE({
        a: FieldElement,
        b: FieldElement,
        seed: OPTIONAL(BIT_STRING)});

    var ECPoint = OCTET_STRING(extend(ASN1Object, {
        encode: function () {
            var value = this.object;
            var len = Math.max(npw2(value.x.length - 2), npw2(value.y.length - 2)) / 2,
                    r = new Uint8Array(2 * len + 1);
            r[0] = 0x04;
            r.set(new Uint8Array(SInt.decode(value.x, false, len)), 1); // x
            r.set(new Uint8Array(SInt.decode(value.y, false, len)), len + 1); // y
            return r.buffer;
        }
    }, {
        decode: function (value) {
            var len = (value.byteLength - 1) / 2;
            return new this({
                x: SInt.encode(new Uint8Array(value, 1, len)),
                y: SInt.encode(new Uint8Array(value, len + 1, len))
            });
        }
    }));

    var FieldID = SEQUENCE({
        fieldType: OBJECT_IDENTIFIER,
        parameters: INTEGER});

    var ECParameters = SEQUENCE({
        version: Version, // version is always 1         
        fieldID: FieldID, // identifies the finite field over which the curve is defined 
        curve: Curve, // coefficients a and b of the elliptic curve         
        base: ECPoint, // specifies the base point P on the elliptic curve         
        order: INTEGER, // the order n of the base point         
        cofactor: OPTIONAL(INTEGER)}); // The integer h = #E(Fq)/n  

    var GostR3410Parameters = SEQUENCE({
        publicKeyParamSet: OBJECT_IDENTIFIER,
        digestParamSet: OBJECT_IDENTIFIER,
        encryptionParamSet: OPTIONAL(OBJECT_IDENTIFIER)});

    var GostR3411Parameters = DEFAULT_NULL(OBJECT_IDENTIFIER, 'id-GostR3411-94-CryptoProParamSet');

    var ECDHParameters = CHOICE({
        namedParameters: OBJECT_IDENTIFIER,
        ecParameters: ECParameters,
        implicitly: OPTIONAL(NULL)}, function (value) {
        return typeof value === 'string' || value instanceof String ?
                'namedParameters' : 'ecParameters';
    });

    var Algorithm = function (paramType, modifier) {
        return ENCLOSURE(SEQUENCE({
            algorithm: OBJECT_IDENTIFIER,
            parameters: OPTIONAL(paramType)}), modifier);
    };

    var AlgorithmIdentifier = (function () {

        var DefaultAlgorithm = Algorithm(ANY),
                Class = extend(ASN1Object, function (object) {
                    if (this instanceof Class)
                        Class.super.apply(this, arguments);
                    else
                        return DEFINE(object);
                }, {
                    encode: function (format) {
                        return new DefaultAlgorithm(this.object).encode(format);
                    }
                }, {
                    decode: function (source) {
                        return new this(DefaultAlgorithm.decode(source).object);
                    }
                });

        var DEFINE = function (algorithmSet) {

            return extend(ASN1Object, {
                object: {
                    get: function () {
                        if (this.item)
                            return this.item.object;
                        else
                            return undefined;
                    },
                    set: function (object) {
                        if (object) {
                            var ItemClass = algorithmSet[object.id];
                            if (!ItemClass)
                                throw new Error('Algorithm not supported');
                            this.item = new ItemClass(object);
                        } else
                            delete this.item;
                    }
                },
                encode: function (format) {
                    return this.item.encode(format);
                }
            }, {
                decode: function (source) {
                    // Decode PEM
                    if (typeof source === 'string')
                        source = PEM.decode(source, undefined, false);
                    // Decode binary data
                    if (source instanceof CryptoOperationData)
                        source = BER.decode(source);
                    var ItemClass = algorithmSet[names[source.object[0].object]];
                    if (ItemClass) {
                        var result = new this();
                        result.item = ItemClass.decode(source);
                        return result;
                    } else
                        throw new Error('Algorithm not supported');
                }
            });
        };

        return Class;
    })();

    var ECDHKeyAlgorithm = Algorithm(ECDHParameters, {
        encode: function (value) {
            var params;
            if (typeof value.namedCurve === 'string')
                params = attributes['namedCurve'][value.namedCurve];
            else
                params = {
                    version: 1,
                    fieldID: {
                        fieldType: 'id-prime-Field',
                        parameters: value.curve.p
                    },
                    curve: {
                        a: value.curve.a,
                        b: value.curve.b
                    },
                    base: {
                        x: value.curve.x,
                        y: value.curve.y
                    },
                    order: value.curve.q,
                    cofactor: 1
                };
            return {
                algorithm: value.id,
                parameters: params
            };
        },
        decode: function (value) {
            var params = value.parameters,
                    result = algorithms[value.algorithm];
            if (typeof params === 'string' || params instanceof String) {
                result = expand(result, parameters[params]);
            } else if (typeof params === 'object') {
                result = expand(result, {
                    curve: {
                        p: params.fieldID.parameters,
                        a: params.curve.a,
                        b: params.curve.b,
                        x: params.base.x,
                        y: params.base.y,
                        q: params.order
                    }
                });
            } else
                throw new DataError('Invalid key paramters');
            return result;
        }
    });

    var GostKeyAlgorithm = Algorithm(GostR3410Parameters, {
        encode: function (value) {
            var paramName = value.namedCurve ? 'namedCurve' : 'namedParam',
                    sBox = (value.name.indexOf('-94') >= 0 || value.name.indexOf('-2001') >= 0 ||
                            value.version === 1994 || value.version === 2001) ? value.sBox || 'D-A' :
                    (value.name.indexOf('-512') >= 0 || value.length === 512) ? 'D-512' : 'D-256';
            return {
                algorithm: value.id,
                parameters: {
                    publicKeyParamSet: attributes[paramName][value[paramName]],
                    digestParamSet: attributes['sBox'][sBox],
                    encryptionParamSet: value.encParams && value.encParams.sBox ?
                            attributes['sBox'][value.encParams.sBox] : undefined
                }
            };
        },
        decode: function (value) {
            var params = value.parameters,
                    algorithm = expand(algorithms[value.algorithm],
                            parameters[params.publicKeyParamSet],
                            parameters[params.digestParamSet]);
            if (params.encryptionParamSet)
                algorithm.encParams = parameters[params.encryptionParamSet];
            return algorithm;
        }
    });

    var AlgorithmWithNoParam = Algorithm(ANY, {
        encode: function (value) {
            return {algorithm: value.id};
        },
        decode: function (value) {
            return algorithms[value.algorithm];
        }
    });

    var AlgorithmWithNullParam = Algorithm(NULL, {
        encode: function (value) {
            return {
                algorithm: value.id,
                parameters: null
            };
        },
        decode: function (value) {
            return algorithms[value.algorithm];
        }
    });

    var Gost341194DigestAlgorithm = Algorithm(GostR3411Parameters, {
        encode: function (value) {
            return {
                algorithm: value.id,
                parameters: attributes['sBox'][value.sBox || (value.hash && value.hash.sBox) || 'D-A']
            };
        },
        decode: function (value) {
            var algorithm = expand(algorithms[value.algorithm]),
                    parameter = parameters[value.parameters];
            if (algorithm.hash)
                algorithm.hash = expand(algorithm.hash, parameter);
            else
                algorithm = expand(algorithm, parameter);
            return algorithm;
        }
    });

    var KeyAlgorithmIdentifier = AlgorithmIdentifier({
        ecdsa: ECDHKeyAlgorithm,
        noSignature: AlgorithmWithNullParam,
        rsaEncryption: AlgorithmWithNullParam,
        'id-sc-gostR3410-2001': ECDHKeyAlgorithm,
        'id-GostR3410-2001': GostKeyAlgorithm,
        'id-GostR3410-94': GostKeyAlgorithm,
        'id-GostR3410-2001DH': GostKeyAlgorithm,
        'id-GostR3410-94DH': GostKeyAlgorithm,
        'id-tc26-gost3410-12-256': GostKeyAlgorithm,
        'id-tc26-gost3410-12-512': GostKeyAlgorithm,
        'id-tc26-agreement-gost-3410-12-256': GostKeyAlgorithm,
        'id-tc26-agreement-gost-3410-12-512': GostKeyAlgorithm,
        'id-sc-gost28147-gfb': AlgorithmWithNoParam,
        'id-Gost28147-89': AlgorithmWithNoParam
    });

    var SignatureAlgorithmIdentifier = AlgorithmIdentifier({
        noSignature: AlgorithmWithNullParam,
        rsaEncryption: AlgorithmWithNullParam,
        sha1withRSAEncryption: AlgorithmWithNullParam,
        sha256withRSAEncryption: AlgorithmWithNullParam,
        sha384withRSAEncryption: AlgorithmWithNullParam,
        sha512withRSAEncryption: AlgorithmWithNullParam,
        'ecdsa': AlgorithmWithNoParam,
        'ecdsa-with-SHA1': AlgorithmWithNoParam,
        'ecdsa-with-SHA256': AlgorithmWithNoParam,
        'ecdsa-with-SHA384': AlgorithmWithNoParam,
        'ecdsa-with-SHA512': AlgorithmWithNoParam,
        'id-GostR3410-94': AlgorithmWithNullParam,
        'id-GostR3410-2001': AlgorithmWithNullParam,
        'id-GostR3411-94-with-GostR3410-2001': AlgorithmWithNoParam,
        'id-GostR3411-94-with-GostR3410-94': AlgorithmWithNoParam,
        'id-tc26-gost3410-12-256': AlgorithmWithNullParam,
        'id-tc26-gost3410-12-512': AlgorithmWithNullParam,
        'id-tc26-signwithdigest-gost3410-12-94': AlgorithmWithNoParam,
        'id-tc26-signwithdigest-gost3410-12-256': AlgorithmWithNoParam,
        'id-tc26-signwithdigest-gost3410-12-512': AlgorithmWithNoParam,
        'id-sc-gostR3410-94': AlgorithmWithNullParam,
        'id-sc-gostR3410-2001': AlgorithmWithNullParam,
        'id-sc-gostR3411-94-with-gostR3410-94': AlgorithmWithNullParam,
        'id-sc-gostR3411-94-with-gostR3410-2001': AlgorithmWithNullParam
    });

    var DigestAlgorithmIdentifier = AlgorithmIdentifier({
        sha1: AlgorithmWithNoParam,
        sha256: AlgorithmWithNullParam,
        sha384: AlgorithmWithNullParam,
        sha512: AlgorithmWithNullParam,
        'id-GostR3411-94': Gost341194DigestAlgorithm,
        'id-tc26-gost3411-94': Gost341194DigestAlgorithm,
        'id-tc26-gost3411-12-256': AlgorithmWithNullParam,
        'id-tc26-gost3411-12-512': AlgorithmWithNullParam,
        'id-sc-gostR3411-94': AlgorithmWithNoParam});

    var Gost2814789Key = OCTET_STRING; //(SIZE (32))

    var Gost2814789MAC = OCTET_STRING; // (SIZE (1..4))

    var Gost2814789ParamSet = OBJECT_IDENTIFIER;

    var Gost2814789IV = OCTET_STRING; // (SIZE (8))

    var Gost2814789Parameters = SEQUENCE({
        iv: Gost2814789IV,
        encryptionParamSet: Gost2814789ParamSet});

    var Gost2814789KeyWrapParameters = SEQUENCE({
        encryptionParamSet: Gost2814789ParamSet,
        ukm: OPTIONAL(OCTET_STRING)}); // (SIZE (8)) must be absent in key agreement

    var Gost2814789Algorithm = Algorithm(Gost2814789Parameters, {
        encode: function (value) {
            return {
                algorithm: value.id,
                parameters: {
                    iv: value.iv,
                    encryptionParamSet: attributes['sBox'][value.sBox || 'E-A']
                }
            };
        },
        decode: function (value) {
            var algorithm = expand(algorithms[value.algorithm],
                    parameters[value.parameters.encryptionParamSet]);
            algorithm.iv = value.parameters.iv;
            return algorithm;
        }
    });

    var SCGostAlgorithm = Algorithm(Gost2814789IV, {
        encode: function (value) {
            return {
                algorithm: value.id,
                parameters: value.iv
            };
        },
        decode: function (value) {
            var algorithm = expand(algorithms[value.algorithm]);
            algorithm.iv = value.parameters || new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
            return algorithm;
        }
    });

    var GostKeyWrapAlgorithm = Algorithm(Gost2814789KeyWrapParameters, {
        encode: function (value) {
            return {
                algorithm: value.id,
                parameters: {
                    encryptionParamSet: attributes['sBox'][value.sBox || 'E-A'],
                    ukm: value.ukm
                }
            };
        },
        decode: function (value) {
            var algorithm = expand(algorithms[value.algorithm],
                    parameters[value.parameters.encryptionParamSet]);
            if (value.parameters.ukm)
                algorithm.ukm = value.parameters.ukm;
            return algorithm;
        }
    });

    var KeyWrapAlgorithmIdentifier = AlgorithmIdentifier({
        'id-Gost28147-89-None-KeyWrap': GostKeyWrapAlgorithm,
        'id-Gost28147-89-CryptoPro-KeyWrap': GostKeyWrapAlgorithm});

    var GostKeyAgreementAlgorithm = Algorithm(KeyWrapAlgorithmIdentifier, {
        encode: function (value) {
            return {
                algorithm: value.id,
                parameters: value.wrapping
            };
        },
        decode: function (value) {
            var algorithm = expand(algorithms[value.algorithm]);
            algorithm.wrapping = value.parameters;
            return algorithm;
        }
    });

    var BaseEncryptionAlgorithmIdentifier = AlgorithmIdentifier({
        'id-sc-gost28147-gfb': SCGostAlgorithm,
        'id-Gost28147-89': Gost2814789Algorithm});

    var MessageAuthenticationCodeAlgorithm = AlgorithmIdentifier({
        'id-Gost28147-89-MAC': Gost2814789Parameters,
        'id-HMACGostR3411-94': Gost341194DigestAlgorithm,
        'id-tc26-hmac-gost-3411-12-256': Gost341194DigestAlgorithm,
        'id-tc26-hmac-gost-3411-12-512': Gost341194DigestAlgorithm,
        'hmacWithSHA1': AlgorithmWithNoParam,
        'hmacWithSHA224': AlgorithmWithNoParam,
        'hmacWithSHA256': AlgorithmWithNoParam,
        'hmacWithSHA384': AlgorithmWithNoParam,
        'hmacWithSHA512': AlgorithmWithNoParam,
        'id-sc-gost28147-mac': AlgorithmWithNoParam,
        'id-sc-hmacWithGostR3411': AlgorithmWithNoParam});

    // rfc2898 PKCS #5: Password-Based Cryptography Specification
    // PBKDF2
    var PBKDF2params = SEQUENCE({
        salt: CHOICE({
            specified: OCTET_STRING,
            otherSource: AlgorithmIdentifier
        }, function (value) {
            return isBinary(value) ? 'specified' : 'otherSource';
        }),
        iterationCount: INTEGER,
        keyLength: OPTIONAL(INTEGER),
        prf: MessageAuthenticationCodeAlgorithm});

    var PBKDF2Algorithm = Algorithm(PBKDF2params, {
        encode: function (value) {
            return {
                algorithm: value.id,
                parameters: {
                    salt: value.salt,
                    iterationCount: value.iterations,
                    prf: value.hmac
                }
            };
        },
        decode: function (value) {
            var algorithm = expand(algorithms[value.algorithm]);
            algorithm.salt = value.parameters.salt;
            algorithm.iterations = value.parameters.iterationCount;
            algorithm.hmac = value.parameters.prf;
            algorithm.hash = algorithm.hmac.hash;
            return algorithm;
        }
    });

    var KeyDerivationAlgorithmIdentifier = AlgorithmIdentifier({
        'PBKDF2': PBKDF2Algorithm});

    var PBEParameter = SEQUENCE({
        salt: OCTET_STRING,
        iterationCount: INTEGER});

    var PBES1Algorithm = Algorithm(PBEParameter, {
        paramType: PBEParameter,
        encode: function (value) {
            return {
                algorithm: value.id,
                parameters: {
                    salt: value.derivation.salt,
                    iterationCount: value.derivation.iterations
                }
            };
        },
        decode: function (value) {
            var algorithm = expand(algorithms[value.algorithm]);
            algorithm.derivation = expand(algorithm.derivation,
                    {salt: value.parameters.salt, iterations: value.parameters.iterationCount});
            return algorithm;
        }
    });

    // PBES2
    var PBES2params = SEQUENCE({
        keyDerivationFunc: KeyDerivationAlgorithmIdentifier, // {{PBES2-KDFs}},
        encryptionScheme: BaseEncryptionAlgorithmIdentifier}); // {{PBES2-Encs}}

    var PBES2Algorithm = Algorithm(PBES2params, {
        encode: function (value) {
            return {
                algorithm: value.id,
                parameters: {
                    keyDerivationFunc: value.derivation,
                    encryptionScheme: value.encryption
                }
            };
        },
        decode: function (value) {
            var algorithm = expand(algorithms[value.algorithm]);
            algorithm.derivation = value.parameters.keyDerivationFunc;
            algorithm.encryption = value.parameters.encryptionScheme;
            return algorithm;
        }
    });

    var PasswordEncryptionAlgorithmIndentifier = AlgorithmIdentifier({
        // PBES1
        'pbeWithSHAAndAES128-CBC': PBES1Algorithm,
        'pbeWithSHAAndAES192-CBC': PBES1Algorithm,
        'pbeWithSHAAndAES256-CBC': PBES1Algorithm,
        'pbeWithSHA256AndAES128-CBC': PBES1Algorithm,
        'pbeWithSHA256AndAES192-CBC': PBES1Algorithm,
        'pbeWithSHA256AndAES256-CBC': PBES1Algorithm,
        'id-sc-pbeWithGost3411AndGost28147': PBES1Algorithm,
        'id-sc-pbeWithGost3411AndGost28147CFB': PBES1Algorithm,
        // PKCS12 PBES1 
        'pbeWithSHAAnd3-KeyTripleDES-CBC': PBES1Algorithm,
        'pbeWithSHAAnd2-KeyTripleDES-CBC': PBES1Algorithm,
        'pbeWithSHAAnd128BitRC2-CBC': PBES1Algorithm,
        'pbeWithSHAAnd40BitRC2-CBC': PBES1Algorithm,
        'pbeUnknownGost': PBES1Algorithm,
        // PBES2
        'PBES2': PBES2Algorithm});

    var KeyEncryptionAlgorithmIdentifier = AlgorithmIdentifier({
        ecdsa: ECDHKeyAlgorithm,
        rsaEncryption: AlgorithmWithNullParam,
        // Base encryption
        'id-sc-gost28147-gfb': SCGostAlgorithm,
        'id-Gost28147-89': Gost2814789Algorithm,
        // Key transport algorithms
        'id-sc-gostR3410-2001': ECDHKeyAlgorithm,
        'id-GostR3410-2001': GostKeyAlgorithm,
        'id-GostR3410-94': GostKeyAlgorithm,
        'id-tc26-gost3410-12-256': GostKeyAlgorithm,
        'id-tc26-gost3410-12-512': GostKeyAlgorithm,
        // Key agreement algorithms
        'id-GostR3410-94-CryptoPro-ESDH': GostKeyAgreementAlgorithm,
        'id-GostR3410-2001-CryptoPro-ESDH': GostKeyAgreementAlgorithm,
        'id-tc26-agreement-gost-3410-12-256': GostKeyAgreementAlgorithm,
        'id-tc26-agreement-gost-3410-12-512': GostKeyAgreementAlgorithm,
        'id-sc-r3410-ESDH-r3411kdf': AlgorithmWithNullParam,
        // Key encryption key algorithms
        'id-Gost28147-89-None-KeyWrap': GostKeyWrapAlgorithm, // Add ukm to algorithm
        'id-Gost28147-89-CryptoPro-KeyWrap': GostKeyWrapAlgorithm,
        'id-sc-cmsGostWrap': AlgorithmWithNoParam, // SC don't use ukm
        'id-sc-cmsGost28147Wrap': AlgorithmWithNoParam,
        // Password based encryption
        'pbeWithSHAAndAES128-CBC': PBES1Algorithm,
        'pbeWithSHAAndAES192-CBC': PBES1Algorithm,
        'pbeWithSHAAndAES256-CBC': PBES1Algorithm,
        'pbeWithSHA256AndAES128-CBC': PBES1Algorithm,
        'pbeWithSHA256AndAES192-CBC': PBES1Algorithm,
        'pbeWithSHA256AndAES256-CBC': PBES1Algorithm,
        'id-sc-pbeWithGost3411AndGost28147': PBES1Algorithm,
        'id-sc-pbeWithGost3411AndGost28147CFB': PBES1Algorithm,
        // PKCS12 PBES1 
        'pbeWithSHAAnd3-KeyTripleDES-CBC': PBES1Algorithm,
        'pbeWithSHAAnd2-KeyTripleDES-CBC': PBES1Algorithm,
        'pbeWithSHAAnd128BitRC2-CBC': PBES1Algorithm,
        'pbeWithSHAAnd40BitRC2-CBC': PBES1Algorithm,
        'pbeUnknownGost': PBES1Algorithm,
        // PBES2
        'PBES2': PBES2Algorithm
    });

    var PBMAC1params = SEQUENCE({
        keyDerivationFunc: KeyDerivationAlgorithmIdentifier, // {{PBMAC1-KDFs}},
        messageAuthScheme: MessageAuthenticationCodeAlgorithm}); // {{PBMAC1-MACs}}

    var PasswordMACAlgorithm = Algorithm(PBMAC1params, {
        encode: function (value) {
            return {
                algorithm: value.id,
                parameters: {
                    keyDerivationFunc: value.derivation,
                    messageAuthScheme: value.hmac}};
        },
        decode: function (value) {
            var algorithm = expand(algorithms[value.algorithm]);
            algorithm.derivation = value.parameters.keyDerivationFunc;
            algorithm.hmac = value.parameters.messageAuthScheme;
            return algorithm;
        }
    });

    var PasswordMACAlgorithmIdentifier = AlgorithmIdentifier({
        'PBMAC1': PasswordMACAlgorithm
    });

    var ContentEncryptionAlgorithmIdentifier = AlgorithmIdentifier({
        // Base encryption
        'id-sc-gost28147-gfb': SCGostAlgorithm,
        'id-Gost28147-89': Gost2814789Algorithm,
        // Password based encryption
        'pbeWithSHAAndAES128-CBC': PBES1Algorithm,
        'pbeWithSHAAndAES192-CBC': PBES1Algorithm,
        'pbeWithSHAAndAES256-CBC': PBES1Algorithm,
        'pbeWithSHA256AndAES128-CBC': PBES1Algorithm,
        'pbeWithSHA256AndAES192-CBC': PBES1Algorithm,
        'pbeWithSHA256AndAES256-CBC': PBES1Algorithm,
        'id-sc-pbeWithGost3411AndGost28147': PBES1Algorithm,
        'id-sc-pbeWithGost3411AndGost28147CFB': PBES1Algorithm,
        // PKCS12 PBES1 
        'pbeWithSHAAnd3-KeyTripleDES-CBC': PBES1Algorithm,
        'pbeWithSHAAnd2-KeyTripleDES-CBC': PBES1Algorithm,
        'pbeWithSHAAnd128BitRC2-CBC': PBES1Algorithm,
        'pbeWithSHAAnd40BitRC2-CBC': PBES1Algorithm,
        'pbeUnknownGost': PBES1Algorithm,
        // PBES2
        'PBES2': PBES2Algorithm
    });

    // </editor-fold>    

    /*
     * Public Key Info 
     * 
     * http://tools.ietf.org/html/rfc5280
     * 
     */ // <editor-fold defaultstate="collapsed">

    var KeyData = ENCLOSURE;

    var DHPublicKey = KeyData(BIT_STRING(ENCAPSULATES(INTEGER)), {
        encode: function (value) {
            return Int16.encode(swapBytes(value));
        },
        decode: function (value) {
            return swapBytes(Int16.decode(value));
        }
    });

    var ECDHPublicKey = KeyData(BIT_STRING(ENCAPSULATES(OCTET_STRING)), {
        encode: function (value) {
            var r = new Uint8Array(value.byteLength + 1),
                    d = swapBytes(value),
                    len = value.byteLength / 2;
            r[0] = 0x04; // type hex;
            r.set(new Uint8Array(d, len, len), 1); // x
            r.set(new Uint8Array(d, 0, len), len + 1); // y
            return r.buffer;
        },
        decode: function (value) {
            assert((value.byteLength & 1) === 0);
            var d = new Uint8Array(value.byteLength - 1),
                    len = d.byteLength / 2;
            d.set(new Uint8Array(value, len + 1, len), 0); // y
            d.set(new Uint8Array(value, 1, len), len); // x
            return swapBytes(d);
        }
    });

    var GostR3410PublicKey = BIT_STRING(ENCAPSULATES(OCTET_STRING));

    /**
     * Subject Public Key Info Syntax X.509
     * <pre>
     *  SubjectPublicKeyInfo  ::=  SEQUENCE  {
     *      algorithm            AlgorithmIdentifier,
     *      subjectPublicKey     BIT STRING  }
     *  
     *  AlgorithmIdentifier  ::=  SEQUENCE  {
     *      algorithm               OBJECT IDENTIFIER,
     *      parameters              ANY DEFINED BY algorithm OPTIONAL  }
     -- contains a value of the type
     -- registered for use with the
     -- algorithm object identifier value     
     * </pre>
     * RFC 5280 references {@link http://tools.ietf.org/html/rfc5280} 
     * @class GostASN1.SubjectPublicKeyInfo
     * @extends GostASN1.Sequence
     * @property {AlgorithmIdentifier} algorithm Identifies the public-key algorithm.
     * @property {CryptoOperationData} subjectPublicKey An binary data whose contents are the value of the public key
     */
    var SubjectPublicKeyInfo = SEQUENCE({
        algorithm: KeyAlgorithmIdentifier,
        subjectPublicKey: BIT_STRING}, 'PUBLIC KEY');

    var GostSubjectPublicKeyInfo = (function (PKTypes) {

        /**
         * Coding methods for {@link Algorithm} and {@link GostASN1.SubjectPublicKeyInfo}
         * Supported types for GOST algorithms:
         * <pre>
         *  {
         *      'id-sc-gostR3410-2001': ECDHPublicKey,
         *      'id-sc-gostR3410-94': DHPublicKey,
         *      'id-GostR3410-2001': GostR3410PublicKey,
         *      'id-GostR3410-94': GostR3410PublicKey,
         *      'id-tc26-gost3410-12-256': GostR3410PublicKey,
         *      'id-tc26-gost3410-12-512': GostR3410PublicKey
         *  }
         * </pre>
         * 
         * @class GostASN1.GostSubjectPublicKeyInfo
         * @extends GostASN1.SubjectPublicKeyInfo
         * @extends Key
         */
        return ENCLOSURE(ATTRIBUTE({
            algorithm: KeyAlgorithmIdentifier,
            subjectPublicKey: ANY},
        'algorithm', 'subjectPublicKey')(function (algorithm) {
            return PKTypes[algorithm.id];
        }), {
            encode: function (value) {
                return {
                    algorithm: value.algorithm,
                    subjectPublicKey: value.buffer
                };
            },
            decode: function (value) {
                return {
                    algorithm: value.algorithm,
                    type: 'public',
                    extractable: true,
                    usages: ['verify', 'deriveKey', 'deriveBits'],
                    buffer: value.subjectPublicKey
                };
            }
        });
    })({
        'id-sc-gostR3410-2001': ECDHPublicKey,
        'id-sc-gostR3410-94': DHPublicKey,
        'id-GostR3410-2001': GostR3410PublicKey,
        'id-GostR3410-94': GostR3410PublicKey,
        'id-tc26-gost3410-12-256': GostR3410PublicKey,
        'id-tc26-gost3410-12-512': GostR3410PublicKey
    });
    // </editor-fold>    

    /*
     * Private Key Info PKCS#8
     * 
     * http://tools.ietf.org/html/rfc5208
     * 
     */ // <editor-fold defaultstate="collapsed">

    var PrivateKey = OCTET_STRING;

    var DHPrivateKey = KeyData(PrivateKey(ENCAPSULATES(INTEGER)), {
        encode: function (value) { // for SignalCom INTEGER d
            return SInt.encode(value, true);
        },
        decode: function (value) {
            return SInt.decode(value, true);
        }
    });

    var GostR3410KeyValueMask = OCTET_STRING;

    var GostR3410KeyValueInfo = SEQUENCE({
        keyValueMask: GostR3410KeyValueMask,
        keyValyePublicKey: OCTET_STRING});

    var GostR3410PrivateKey = CHOICE({
        privateKey: PrivateKey(ENCAPSULATES(CHOICE({
            keyValueMask: GostR3410KeyValueMask,
            keyValueInfo: GostR3410KeyValueInfo
        }, function (value) {
            if (isBinary(value))
                return 'keyValueMask';
            else
                return 'keyValueInfo';
        }))),
        keyValueMask: GostR3410KeyValueMask
    }, function (value) {
        return value.enclosed ? 'keyValueMask' : 'privateKey';
    });

    var GostWrappedPrivateKey = (function (PKTypes) {

        /**
         * Gost Wrapped Private Key for SignalCom key container
         * 
         * @class GostASN1.GostWrappedPrivateKey
         * @extends GostASN1.PrivateKeyInfo
         */
        return ATTRIBUTE({
            version: Version,
            privateKeyAlgorithm: KeyAlgorithmIdentifier,
            privateKeyWrapped: KeyData(PrivateKey(ENCAPSULATES(SEQUENCE({
                keyData: INTEGER,
                keyMac: INTEGER
            }))), {
                encode: function (value) {
                    var size = value.byteLength - 4;
                    return {
                        keyData: SInt.encode(new Uint8Array(value, 0, size)),
                        keyMac: SInt.encode(new Uint8Array(value, size, 4))
                    };
                },
                decode: function (value) {
                    var data = SInt.decode(value.keyData),
                            mac = SInt.decode(value.keyMac),
                            result = new Uint8Array(data.byteLength + mac.byteLength);
                    result.set(new Uint8Array(data));
                    result.set(new Uint8Array(mac), data.byteLength);
                    return result;
                }
            }),
            attributes: ANY
        }, 'privateKeyAlgorithm', 'attributes')(function (algorithm) {
            return OPTIONAL(CTX(0, IMPLICIT(Attributes({
                'id-sc-gostR3410-2001-publicKey': SET_OF_SINGLE(PKTypes[algorithm.id])
            }))));
        });
    })({
        // Signature keys
        'id-sc-gostR3410-2001': ECDHPublicKey,
        'id-sc-gostR3410-94': DHPublicKey,
        'id-GostR3410-2001': GostR3410PublicKey,
        'id-GostR3410-94': GostR3410PublicKey,
        'id-GostR3410-2001DH': GostR3410PublicKey,
        'id-GostR3410-94DH': GostR3410PublicKey,
        'id-tc26-gost3410-12-256': GostR3410PublicKey,
        'id-tc26-gost3410-12-512': GostR3410PublicKey,
        'id-tc26-agreement-gost-3410-12-256': GostR3410PublicKey,
        'id-tc26-agreement-gost-3410-12-512': GostR3410PublicKey
    });

    /**
     * Private-Key Information Syntax PKSC#8
     * <pre>
     *  -- Private-key information syntax
     *
     *  PrivateKeyInfo ::= SEQUENCE {
     *      version Version,
     *      privateKeyAlgorithm AlgorithmIdentifier {{PrivateKeyAlgorithms}},
     *      privateKey PrivateKey,
     *      attributes [0] Attributes OPTIONAL }
     *
     *  Version ::= INTEGER {v1(0)} (v1,...)
     *
     *  PrivateKey ::= OCTET STRING
     *
     *  Attributes ::= SET OF Attribute
     * </pre>
     * RFC 5208 references {@link http://tools.ietf.org/html/rfc5208} 
     * @class GostASN1.PrivateKeyInfo
     * @extends GostASN1.Sequence
     * @property {number} version The syntax version number. Should be 0
     * @property {AlgorithmIndentifier} privateKeyAlgorithm Identifies the private-key algorithm
     * @property {CryptoOperationData} privateKey Is an binary data whose contents are the value of the private key.  
     * @property {GostASN1.Attributes} attributes A set of attributes
     */
    var PrivateKeyInfo = SEQUENCE({
        version: Version,
        privateKeyAlgorithm: KeyAlgorithmIdentifier,
        privateKey: PrivateKey,
        attributes: OPTIONAL(CTX(0, IMPLICIT(Attributes)))}, 'PRIVATE KEY');

    var PrivateKeyAlgorithmIdentifier = KeyAlgorithmIdentifier;

    var PublicKey = BIT_STRING;

    var OneAsymmetricKey = SEQUENCE({
        version: Version,
        privateKeyAlgorithm: PrivateKeyAlgorithmIdentifier,
        privateKey: PrivateKey,
        attributes: OPTIONAL(CTX(0, IMPLICIT(Attributes))),
        publicKey: OPTIONAL(CTX(1, IMPLICIT(PublicKey)))});

    var AsymmetricKeyPackage = SEQUENCE_OF(OneAsymmetricKey);

    var GostPrivateKeyInfo = (function (PKTypes) {

        /**
         * Coding methods for {@link Algorithm} and {@link GostASN1.PrivateKeyInfo}
         * Supported types for GOST algorithms:
         * <pre>
         *  {
         *      'id-sc-gostR3410-2001': DHPrivateKey,
         *      'id-sc-gostR3410-94': DHPrivateKey,
         *      'id-GostR3410-2001': GostR3410PrivateKey,
         *      'id-GostR3410-94': GostR3410PrivateKey,
         *      'id-tc26-gost3410-12-256': GostR3410PrivateKey,
         *      'id-tc26-gost3410-12-512': GostR3410PrivateKey
         *  }
         * </pre>
         * 
         * @class GostASN1.GostPrivateKeyInfo
         * @extends GostASN1.PrivateKeyInfo
         * @extends Key
         */
        return ENCLOSURE(ATTRIBUTE({
            version: Version,
            privateKeyAlgorithm: KeyAlgorithmIdentifier,
            privateKey: ANY,
            attributes: OPTIONAL(CTX(0, IMPLICIT(Attributes)))},
        'privateKeyAlgorithm', 'privateKey')(function (algorithm) {
            return PKTypes[algorithm.id];
        }), {
            encode: function (value) {
                return {
                    version: 0,
                    privateKeyAlgorithm: value.algorithm,
                    privateKey: value.buffer
                };
            },
            decode: function (value) {
                return {
                    algorithm: value.privateKeyAlgorithm,
                    type: 'private',
                    extractable: true,
                    usages: ['sign', 'deriveKey', 'deriveBits'],
                    buffer: isBinary(value.privateKey) ? value.privateKey :
                            value.privateKey.keyValueMask
                };
            }
        });
    })({
        // Signature keys
        'id-sc-gostR3410-2001': DHPrivateKey,
        'id-sc-gostR3410-94': DHPrivateKey,
        'id-GostR3410-2001': GostR3410PrivateKey,
        'id-GostR3410-94': GostR3410PrivateKey,
        'id-GostR3410-2001DH': GostR3410PrivateKey,
        'id-GostR3410-94DH': GostR3410PrivateKey,
        'id-tc26-gost3410-12-256': GostR3410PrivateKey,
        'id-tc26-gost3410-12-512': GostR3410PrivateKey,
        'id-tc26-agreement-gost-3410-12-256': GostR3410PrivateKey,
        'id-tc26-agreement-gost-3410-12-512': GostR3410PrivateKey
    });

    var KeyEncryptedData = OCTET_STRING;
    /**
     * Encrypted Private-Key Information Syntax
     * <pre>
     *  -- Encrypted private-key information syntax
     *
     *  EncryptedPrivateKeyInfo ::= SEQUENCE {
     *      encryptionAlgorithm AlgorithmIdentifier {{KeyEncryptionAlgorithms}},
     *      encryptedData KeyEncryptedData
     *  }
     *
     *  KeyEncryptedData ::= OCTET STRING
     *
     *  PrivateKeyAlgorithms ALGORITHM-IDENTIFIER ::= {
     *      ... -- For local profiles
     *  }
     *
     *  KeyEncryptionAlgorithms ALGORITHM-IDENTIFIER ::= {
     *      ... -- For local profiles
     *  }
     * </pre>
     * RFC 5208 references {@link http://tools.ietf.org/html/rfc5208} 
     * @class GostASN1.EncryptedPrivateKeyInfo
     * @extends GostASN1.Sequence
     * @property {AlgorithmIdentifier} encryptionAlgorithm Identifies key encryption algorithm
     * @property {CryptoOperationData} encryptedData Encrypted {@link GostASN1.PrivateKeyInfo}
     */
    var EncryptedPrivateKeyInfo = SEQUENCE({
        encryptionAlgorithm: KeyEncryptionAlgorithmIdentifier,
        encryptedData: KeyEncryptedData}, 'ENCRYPTED PRIVATE KEY');
    // </editor-fold>    

    /*
     * Certificate Extensions
     * 
     * http://tools.ietf.org/html/rfc5280
     * 
     */ // <editor-fold defaultstate="collapsed">
    var UniqueIdentifier = BIT_STRING;

    var CertificateSerialNumber = INTEGER;

    var BasicConstraints = SEQUENCE({
        cA: DEFAULT(BOOLEAN, false),
        pathLenConstraint: OPTIONAL(INTEGER)
    });

    var KeyUsage = BIT_STRING({
        digitalSignature: 0,
        nonRepudiation: 1,
        keyEncipherment: 2,
        dataEncipherment: 3,
        keyAgreement: 4,
        keyCertSign: 5,
        cRLSign: 6,
        encipherOnly: 7,
        decipherOnly: 8});
    var KeyPurposeId = OBJECT_IDENTIFIER,
            ExtKeyUsageSyntax = SEQUENCE_OF(KeyPurposeId);

    var KeyIdentifier = OCTET_STRING;

    var OtherName = SEQUENCE({
        type: OBJECT_IDENTIFIER,
        value: CTX(0, EXPLICIT(ANY))});

    var EDIPartyName = SEQUENCE({
        nameAssigner: OPTIONAL(CTX(0, IMPLICIT(DirectoryString))),
        partyName: OPTIONAL(CTX(1, IMPLICIT(DirectoryString)))});

    var ORAddress = SEQUENCE({});

    var GeneralName = CHOICE({
        otherName: CTX(0, IMPLICIT(OtherName)),
        rfc822Name: CTX(1, IMPLICIT(DirectoryString)),
        dNSName: CTX(2, IMPLICIT(DirectoryString)),
        x400Address: CTX(3, IMPLICIT(ORAddress)),
        directoryName: CTX(4, EXPLICIT(Name)), // Name is CHOICE(RDNSequence)
        ediPartyName: CTX(5, IMPLICIT(EDIPartyName)),
        uniformResourceIdentifier: CTX(6, IMPLICIT(DirectoryString)),
        iPAddress: CTX(7, IMPLICIT(OCTET_STRING)),
        registeredID: CTX(8, IMPLICIT(OBJECT_IDENTIFIER))}, function (value) {
        return typeof value === 'string' || value instanceof String ?
                (value.indexOf('@') >= 0 ? 'rfc822Name' : 'dNSName') :
                isBinary(value) ? 'iPAddress' : 'directoryName';
    });

    var GeneralNames = SEQUENCE_OF(GeneralName);

    var AuthorityKeyIdentifier = SEQUENCE({
        keyIdentifier: OPTIONAL(CTX(0, IMPLICIT(KeyIdentifier))),
        authorityCertIssuer: OPTIONAL(CTX(1, IMPLICIT(GeneralNames))),
        authorityCertSerialNumber: OPTIONAL(CTX(2, IMPLICIT(CertificateSerialNumber)))});

    var PrivateKeyUsagePeriod = SEQUENCE({
        notBefore: OPTIONAL(CTX(0, IMPLICIT(GeneralizedTime))),
        notAfter: OPTIONAL(CTX(1, IMPLICIT(GeneralizedTime)))});

    var CertPolicyId = OBJECT_IDENTIFIER,
            PolicyQualifierId = OBJECT_IDENTIFIER;

    var PolicyQualifierInfo = SEQUENCE({
        policyQualifierId: PolicyQualifierId,
        qualifier: ANY});

    var PolicyInformation = SEQUENCE({
        policyIdentifier: CertPolicyId,
        policyQualifiers: OPTIONAL(SEQUENCE_OF(PolicyQualifierInfo))});

    var PolicyMapping = SEQUENCE({
        issuerDomainPolicy: CertPolicyId,
        subjectDomainPolicy: CertPolicyId});

    var BaseDistance = INTEGER;

    var GeneralSubtree = SEQUENCE({
        base: GeneralName,
        minimum: DEFAULT(CTX(0, IMPLICIT(BaseDistance)), 0),
        maximum: OPTIONAL(CTX(1, IMPLICIT(BaseDistance)))});

    var GeneralSubtrees = SEQUENCE_OF(GeneralSubtree);

    var NameConstraints = SEQUENCE({
        permittedSubtrees: OPTIONAL(CTX(0, IMPLICIT(GeneralSubtrees))),
        excludedSubtrees: OPTIONAL(CTX(1, IMPLICIT(GeneralSubtrees)))});

    var SkipCerts = INTEGER;

    var PolicyConstraints = SEQUENCE({
        requireExplicitPolicy: OPTIONAL(CTX(0, IMPLICIT(SkipCerts))),
        inhibitPolicyMapping: OPTIONAL(CTX(1, IMPLICIT(SkipCerts)))});

    var ReasonFlags = BIT_STRING({
        unused: 0,
        keyCompromise: 1,
        cACompromise: 2,
        affiliationChanged: 3,
        superseded: 4,
        cessationOfOperation: 5,
        certificateHold: 6,
        privilegeWithdrawn: 7,
        aACompromise: 8});

    var DistributionPointName = CHOICE({
        fullName: CTX(0, IMPLICIT(GeneralNames)),
        nameRelativeToCRLIssuer: CTX(1, IMPLICIT(RelativeDistinguishedName))}, function (value) {
        return value instanceof Array ? 'fullName' : 'nameRelativeToCRLIssuer';
    });

    var DistributionPoint = SEQUENCE({
        distributionPoint: OPTIONAL(CTX(0, EXPLICIT(DistributionPointName))), // DistributionPointName CHOICE
        reasons: OPTIONAL(CTX(1, IMPLICIT(ReasonFlags))),
        cRLIssuer: OPTIONAL(CTX(2, IMPLICIT(GeneralNames)))});

    var CRLDistributionPoints = SEQUENCE_OF(DistributionPoint);

    var FreshestCRL = CRLDistributionPoints;

    var AccessDescription = SEQUENCE({
        accessMethod: OBJECT_IDENTIFIER,
        accessLocation: GeneralName});

    var Extension = function (typeSet, defaultCritical) {

        var Attribute = ATTRIBUTE({
            extnID: OBJECT_IDENTIFIER,
            critical: DEFAULT(BOOLEAN, false),
            extnValue: function (type) {
                return OCTET_STRING(ENCAPSULATES(type));
            }
        }, 'extnID', 'extnValue');

        var Class = extend(Attribute(typeSet), {
            object: {
                get: function () {
                    var value = this._get(Class.super, 'object');
                    if (value && typeof value.extnValue === 'object')
                        this.defineValue(value.extnValue);
                    return value;
                },
                set: function (object) {
                    this._set(Class.super, 'object', object);
                    // Define critical
                    if (object && object.extnValue)
                        if (object.extnValue.critical !== undefined)
                            this.critical = object.extnValue.critical;
                        else if (this.critical === undefined && defaultCritical)
                            this.critical = defaultCritical(this.extnID, object.extnValue);
                }
            },
            extnValue: {
                get: function () {
                    // Get value property of object
                    var value = this._get(Class.super, 'extnValue');
                    if (typeof value === 'object')
                        this.defineValue(value);
                    return value;
                },
                set: function (object) {
                    // Set value property of object
                    this._set(Class.super, 'extnValue', object);
                    // Define critical
                    if (object) {
                        if (object.critical !== undefined)
                            this.critical = object.critical;
                        else if (this.critical === undefined && defaultCritical)
                            this.critical = defaultCritical(this.extnID, object);
                    }
                }
            },
            defineValue: function (value) {
                if (typeof value === 'object')
                    if (!getOwnPropertyDescriptor(value, 'critical')) {
                        var self = this;
                        defineProperty(value, 'critical', {
                            get: function () {
                                return self.critical;
                            },
                            set: function (value) {
                                self.critical = value;
                            },
                            enumerable: true,
                            configurable: false
                        });
                    }
            }
        });
        return Class;
    };

    // http://base.garant.ru/70133464/#ixzz4KaOTGI1l
    var IssuerSignTool = SEQUENCE({
        signTool: UTF8String,
        cATool: UTF8String,
        signToolCert: UTF8String,
        cAToolCert: UTF8String});

    /**
     * Extensions is a base class for extension attributes of certificates, CRLs, requests and etc.
     * 
     * @class GostASN1.Extensions
     * @extends GostASN1.Set
     */
    var Extensions = SEQUENCE_OF(Extension, {
        typeName: 'extnID',
        valueName: 'extnValue'
    });

    var CertExtensions = Extensions({
        authorityKeyIdentifier: AuthorityKeyIdentifier,
        subjectKeyIdentifier: KeyIdentifier,
        keyUsage: KeyUsage,
        privateKeyUsagePeriod: PrivateKeyUsagePeriod,
        certificatePolicies: SEQUENCE_OF(PolicyInformation),
        policyMappings: SEQUENCE_OF(PolicyMapping),
        subjectAltName: GeneralNames,
        issuerAltName: GeneralNames,
        subjectDirectoryAttributes: AttributeSequence,
        basicConstraints: BasicConstraints,
        nameConstraints: NameConstraints,
        policyConstraints: PolicyConstraints,
        extKeyUsage: ExtKeyUsageSyntax,
        cRLDistributionPoints: CRLDistributionPoints,
        inhibitAnyPolicy: SkipCerts,
        freshestCRL: FreshestCRL,
        authorityInfoAccess: SEQUENCE_OF(AccessDescription),
        subjectInfoAccess: SEQUENCE_OF(AccessDescription),
        subjectSignTool: UTF8String,
        issuerSignTool: IssuerSignTool
    }, function (id, value) {
        return id === 'keyUsage' ||
                (id === 'basicConstraints' && value.pathLenConstraint === undefined);
    });
    // </editor-fold>    

    /*
     * Signature Values
     * 
     * http://tools.ietf.org/html/rfc5280
     * http://tools.ietf.org/html/rfc4491
     * 
     */ // <editor-fold defaultstate="collapsed">

    /**
     * Gost Signature encode signature values for different GOST signatures
     * Support algorithms:
     * <pre>
     *  {
     *      'id-GostR3410-94': GostR3410Signature,
     *      'id-GostR3410-2001': GostR3410Signature,
     *      'id-tc26-gost3410-12-256': GostR3410Signature,
     *      'id-tc26-gost3410-12-512': GostR3410Signature,
     *      'id-GostR3411-94-with-GostR3410-2001': GostR3410Signature,
     *      'id-GostR3411-94-with-GostR3410-94': GostR3410Signature,
     *      'id-tc26-signwithdigest-gost3410-12-94': GostR3410Signature,
     *      'id-tc26-signwithdigest-gost3410-12-256': GostR3410Signature,
     *      'id-tc26-signwithdigest-gost3410-12-512': GostR3410Signature,
     *      'id-sc-gostR3410-94': ECDHSignature,
     *      'id-sc-gostR3410-2001': ECDHSignature,
     *      'id-sc-gostR3411-94-with-gostR3410-94': ECDHSignature,
     *      'id-sc-gostR3411-94-with-gostR3410-2001': ECDHSignature
     *  }
     * </pre>
     * 
     * @class GostASN1.GostSignature
     * @extends GostASN1.Sequence
     */

    // SignalCom signature
    var ECDHSignature = SEQUENCE({
        r: INTEGER,
        s: INTEGER});

    var GostR3410Signature = ANY;

    // Signature value (only need for CryptoPro
//    var GostSignature = extend(CHOICE({
//        ecdhSignature: ECDHSignature}, 'ecdhSignature'));
    var GostSignature = ECDHSignature;

    // </editor-fold>    

    /*
     * Certificate
     * 
     * http://tools.ietf.org/html/rfc5280
     * 
     */ // <editor-fold defaultstate="collapsed">

    /**
     * The sequence TBSCertificate contains information associated with the
     * subject of the certificate and the CA who issued it.  Every
     * TBSCertificate contains the names of the subject and issuer, a public
     * key associated with the subject, a validity period, a version number,
     * and a serial number; some MAY contain optional unique identifier
     * fields.  The remainder of this section describes the syntax and
     * semantics of these fields.  A TBSCertificate usually includes
     * extensions. 
     * <pre>
     *  TBSCertificate  ::=  SEQUENCE  {
     *       version         [0]  EXPLICIT Version DEFAULT v1,
     *       serialNumber         CertificateSerialNumber,
     *       signature            AlgorithmIdentifier,
     *       issuer               Name,
     *       validity             Validity,
     *       subject              Name,
     *       subjectPublicKeyInfo SubjectPublicKeyInfo,
     *       issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
     *                            -- If present, version MUST be v2 or v3
     *       subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
     *                            -- If present, version MUST be v2 or v3
     *       extensions      [3]  EXPLICIT Extensions OPTIONAL
     *                            -- If present, version MUST be v3
     *       }
     *
     *  Version  ::=  INTEGER  {  v1(0), v2(1), v3(2)  }
     *
     *  CertificateSerialNumber  ::=  INTEGER
     *
     *  Validity ::= SEQUENCE {
     *       notBefore      Time,
     *       notAfter       Time }
     *
     *  Time ::= CHOICE {
     *       utcTime        UTCTime,
     *       generalTime    GeneralizedTime }
     *
     *  UniqueIdentifier  ::=  BIT STRING
     *
     *  SubjectPublicKeyInfo  ::=  SEQUENCE  {
     *       algorithm            AlgorithmIdentifier,
     *       subjectPublicKey     BIT STRING  }
     *
     *  Extensions  ::=  SEQUENCE SIZE (1..MAX) OF Extension
     *
     *  Extension  ::=  SEQUENCE  {
     *       extnID      OBJECT IDENTIFIER,
     *       critical    BOOLEAN DEFAULT FALSE,
     *       extnValue   OCTET STRING  }
     * </pre>
     * See {@link GostASN1.Certificate} and {@link GostASN1.SubjectPublicKeyInfo}<br><br>
     * RFC 5280 references {@link http://tools.ietf.org/html/rfc5280} 
     * 
     * @class GostASN1.TBSCertificate
     * @extends GostASN1.Sequence
     * @extends GostASN1.Validity
     * @property {number} version The version of the encoded certificate
     * @property {(number|string)} serialNumber The serial number MUST be a positive integer assigned by the CA to each certificate.
     * @property {AlgorithmIdentifier} signature The algorithm identifier for the algorithm used by the CA to sign the certificate.
     * @property {GostASN1.Name} issuer The issuer field identifies the entity that has signed and issued the certificate.
     * @property {GostASN1.Validity} validity The certificate validity period
     * @property {GostASN1.Name} subject The subject field identifies the entity associated with the public key stored in the subject public key field.
     * @property {GostASN1.SubjectPublicKeyInfo} subject The public key and identify the algorithm with which the key is used
     * @property {CryptoOperationData} issuerUniqueID The issuer unique identifier
     * @property {CryptoOperationData} subjectUniqueID The subject unique identifier
     * @property {GostASN1.Extensions} extensions The extensions defined for X.509 v3 certificates
     */
    var TBSCertificate = COMBINE(SEQUENCE({
        version: CTX(0, EXPLICIT(Version)),
        serialNumber: CertificateSerialNumber,
        signature: SignatureAlgorithmIdentifier,
        issuer: Name,
        validity: Validity,
        subject: Name,
        subjectPublicKeyInfo: SubjectPublicKeyInfo,
        issuerUniqueID: OPTIONAL(CTX(1, IMPLICIT(UniqueIdentifier))), // If present, version MUST be v2 or v3        
        subjectUniqueID: OPTIONAL(CTX(2, IMPLICIT(UniqueIdentifier))), // If present, version MUST be v2 or v3
        extensions: OPTIONAL(CTX(3, EXPLICIT(CertExtensions)))})); // If present, version MUST be v3        

    /**
     * The X.509 v3 certificate basic syntax is as follows.  For signature
     * calculation, the data that is to be signed is encoded using the ASN.1
     * distinguished encoding rules (DER) [X.690].  ASN.1 DER encoding is a
     * tag, length, value encoding system for each element.
     * <pre>
     *  Certificate  ::=  SEQUENCE  {
     *       tbsCertificate       TBSCertificate,
     *       signatureAlgorithm   AlgorithmIdentifier,
     *       signatureValue       BIT STRING  }
     * </pre>
     * See {@link GostASN1.TBSCertificate}<br><br>
     * RFC 5280 references {@link http://tools.ietf.org/html/rfc5280} 
     * 
     * @class GostASN1.Certificate
     * @extends GostASN1.TBSCertificate
     * @property {GostASN1.TBSCertificate} tbsCertificate The sequence TBSCertificate
     * @property {AlgorithmIndentifier} signatureAlgorithm Identifies signature algorithm
     * @property {CryptoOperationData} signatureValue Signature value
     */
    var Certificate = SEQUENCE({
        tbsCertificate: TBSCertificate,
        signatureAlgorithm: SignatureAlgorithmIdentifier,
        signatureValue: BIT_STRING}, 'CERTIFICATE');
    // </editor-fold>    

    /*
     * Certification Request
     * 
     * http://tools.ietf.org/html/rfc2986
     * 
     */ // <editor-fold defaultstate="collapsed">

    var ExtensionRequest = CertExtensions;

    var CRIAttributes = Attributes({
        challengePassword: SET_OF_SINGLE(DirectoryString),
        extensionRequest: SET_OF_SINGLE(ExtensionRequest),
        msCertExtensions: SET_OF_SINGLE(CertExtensions),
        extendedCertificateAttributes: SET_OF_SINGLE(Attributes)});

    /**
     * Certification request information shall have ASN.1 type CertificationRequestInfo:
     * <pre>
     *  CertificationRequestInfo ::= SEQUENCE {
     *       version       INTEGER { v1(0) } (v1,...),
     *       subject       Name,
     *       subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
     *       attributes    [0] Attributes{{ CRIAttributes }}
     *  }
     *
     *  SubjectPublicKeyInfo { ALGORITHM : IOSet} ::= SEQUENCE {
     *       algorithm        AlgorithmIdentifier {{IOSet}},
     *       subjectPublicKey BIT STRING
     *  }
     *
     *  PKInfoAlgorithms ALGORITHM ::= {
     *       ...  -- add any locally defined algorithms here -- }
     *
     *  Attributes { ATTRIBUTE:IOSet } ::= SET OF Attribute{{ IOSet }}
     *
     *  CRIAttributes  ATTRIBUTE  ::= {
     *       ... -- add any locally defined attributes here -- }
     *
     *  Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE {
     *       type   ATTRIBUTE.&id({IOSet}),
     *       values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type})
     *  }
     * </pre>
     * See {@link GostASN1.CertificationRequest} and {@link GostASN1.SubjectPublicKeyInfo}<br><br>
     * RFC 2986 references {@link http://tools.ietf.org/html/rfc2986} 
     * 
     * @class GostASN1.CertificationRequestInfo
     * @extends GostASN1.Sequence
     * @property {number} version The version of the encoded request
     * @property {GostASN1.Name} subject The subject field identifies the entity associated with the public key stored in the subject public key field.
     * @property {GostASN1.SubjectPublicKeyInfo} subject The public key and identify the algorithm with which the key is used
     * @property {GostASN1.Attributes} attributes The request attributes
     */
    var CertificationRequestInfo = COMBINE(SEQUENCE({
        version: INTEGER,
        subject: Name,
        subjectPublicKeyInfo: SubjectPublicKeyInfo,
        attributes: CTX(0, IMPLICIT(CRIAttributes))}));

    /**
     * A certification request consists of three parts: "certification
     * request information," a signature algorithm identifier, and a digital
     * signature on the certification request information.  The
     * certification request information consists of the entity's
     * distinguished name, the entity's public key, and a set of attributes
     * providing other information about the entity.
     * <pre>
     *  A certification request shall have ASN.1 type CertificationRequest:
     *
     *  CertificationRequest ::= SEQUENCE {
     *       certificationRequestInfo CertificationRequestInfo,
     *       signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }},
     *       signature          BIT STRING
     *  }
     *
     *  AlgorithmIdentifier {ALGORITHM:IOSet } ::= SEQUENCE {
     *       algorithm          ALGORITHM.&id({IOSet}),
     *       parameters         ALGORITHM.&Type({IOSet}{@algorithm}) OPTIONAL
     *  }
     *
     *  SignatureAlgorithms ALGORITHM ::= {
     *       ... -- add any locally defined algorithms here -- }
     * </pre>
     * See {@link GostASN1.CertificationRequestInfo}
     * RFC 2986 references {@link http://tools.ietf.org/html/rfc2986} 
     * 
     * @class GostASN1.CertificationRequest
     * @extends GostASN1.CertificationRequestInfo
     * @property {GostASN1.CertificationRequestInfo} requestInfo Request information
     * @property {AlgorithmIndentifier} signatureAlgorithm Identifies signature algorithm
     * @property {CryptoOperationData} signatureValue Signature value
     */
    var CertificationRequest = SEQUENCE({
        requestInfo: CertificationRequestInfo,
        signatureAlgorithm: SignatureAlgorithmIdentifier,
        signatureValue: BIT_STRING}, 'CERTIFICATE REQUEST');
    // </editor-fold>    

    /*
     * Certificate Revocation List
     * 
     * http://tools.ietf.org/html/rfc5280
     * 
     */ // <editor-fold defaultstate="collapsed">

    var CRLNumber = INTEGER;

    var CRLReason = ENUMERATED({
        unspecified: 0,
        keyCompromise: 1,
        cACompromise: 2,
        affiliationChanged: 3,
        superseded: 4,
        cessationOfOperation: 5,
        certificateHold: 6,
        removeFromCRL: 8,
        privilegeWithdrawn: 9,
        aACompromise: 10});

    var IssuingDistributionPoint = SEQUENCE({
        distributionPoint: OPTIONAL(CTX(0, EXPLICIT(DistributionPointName))), // DistributionPointName is CHOICE
        onlyContainsUserCerts: DEFAULT(CTX(1, IMPLICIT(BOOLEAN)), false),
        onlyContainsCACerts: DEFAULT(CTX(2, IMPLICIT(BOOLEAN)), false),
        onlySomeReasons: OPTIONAL(CTX(3, IMPLICIT(ReasonFlags))),
        indirectCRL: DEFAULT(CTX(4, IMPLICIT(BOOLEAN)), false),
        onlyContainsAttributeCerts: DEFAULT(CTX(5, IMPLICIT(BOOLEAN)), false)});

    var CLRExtensions = Extensions({
        authorityKeyIdentifier: AuthorityKeyIdentifier,
        issuerAltName: GeneralNames,
        cRLNumber: CRLNumber,
        deltaCRLIndicator: CRLNumber,
        issuingDistributionPoint: IssuingDistributionPoint,
        freshestCRL: FreshestCRL
    }, function (id) {
        return id === 'cRLNumber';
    });

    var CLREntryExtensions = Extensions({
        cRLReason: CRLReason,
        instructionCode: OBJECT_IDENTIFIER,
        invalidityDate: GeneralizedTime,
        certificateIssuer: GeneralNames});

    /**
     * This field is itself a sequence containing the name of the issuer, 
     * issue date, issue date of the next list, the optional list of revoked
     * certificates, and optional CRL extensions.  When there are no revoked
     * certificates, the revoked certificates list is absent.  When one or
     * more certificates are revoked, each entry on the revoked certificate
     * list is defined by a sequence of user certificate serial number,
     * revocation date, and optional CRL entry extensions.
     * <pre>
     *  TBSCertList  ::=  SEQUENCE  {
     *       version                 Version OPTIONAL,
     *                                    -- if present, MUST be v2
     *       signature               AlgorithmIdentifier,
     *       issuer                  Name,
     *       thisUpdate              Time,
     *       nextUpdate              Time OPTIONAL,
     *       revokedCertificates     SEQUENCE OF SEQUENCE  {
     *            userCertificate         CertificateSerialNumber,
     *            revocationDate          Time,
     *            crlEntryExtensions      Extensions OPTIONAL
     *                                          -- if present, MUST be v2
     *                                 }  OPTIONAL,
     *       crlExtensions           [0]  EXPLICIT Extensions OPTIONAL
     *                                          -- if present, MUST be v2
     *                                 }
     * </pre>
     * See {@link GostASN1.CertificateList}<br><br>
     * RFC 5280 references {@link http://tools.ietf.org/html/rfc5280} 
     * 
     * @class GostASN1.TBSCertList
     * @extends GostASN1.Sequence
     * @property {number} version The version of the encoded CRL
     * @property {AlgorithmIdentifier} signature The algorithm identifier for the algorithm used to sign the CRL
     * @property {Name} issuer The issuer name identifies the entity that has signed and issued the CRL
     * @property {Date} thisUpdate The issue date of this CRL
     * @property {Date} nextUpdate The date by which the next CRL will be issued
     * @property {Array} revokedCertificates The revoked certificates are listed by their serial numbers
     * @property {Extensions} crlExtensions The CRL extensions
     */
    var TBSCertList = COMBINE(SEQUENCE({
        version: OPTIONAL(Version), // if present, MUST be v2
        signature: SignatureAlgorithmIdentifier,
        issuer: Name,
        thisUpdate: Time,
        nextUpdate: OPTIONAL(Time),
        revokedCertificates: OPTIONAL(SEQUENCE_OF(SEQUENCE({
            userCertificate: CertificateSerialNumber,
            revocationDate: Time,
            crlEntryExtensions: OPTIONAL(CLREntryExtensions) // if present, MUST be v2
        }))),
        crlExtensions: OPTIONAL(CTX(0, EXPLICIT(CLRExtensions)))})); // if present, MUST be v2

    /**
     * The X.509 v2 CRL syntax is as follows.  For signature calculation,
     * the data that is to be signed is ASN.1 DER encoded.  ASN.1 DER
     * encoding is a tag, length, value encoding system for each element.
     * <pre>
     *  CertificateList  ::=  SEQUENCE  {
     *       tbsCertList          TBSCertList,
     *       signatureAlgorithm   AlgorithmIdentifier,
     *       signatureValue       BIT STRING  }
     * </pre>
     * See {@link GostASN1.TBSCertList}<br><br>
     * RFC 5280 references {@link http://tools.ietf.org/html/rfc5280} 
     * 
     * @class GostASN1.CertificateList
     * @extends GostASN1.TBSCertList
     * @property {GostASN1.TBSCertList} tbsCertList The tbsCertList
     * @property {AlgorithmIndentifier} signatureAlgorithm Identifies signature algorithm
     * @property {CryptoOperationData} signatureValue Signature value
     */
    var CertificateList = SEQUENCE({
        tbsCertList: TBSCertList,
        signatureAlgorithm: SignatureAlgorithmIdentifier,
        signatureValue: BIT_STRING}, 'CRL');
    // </editor-fold>    

    /*
     * Attribute Certificate Definision
     * http://tools.ietf.org/html/rfc5755
     * 
     */ // <editor-fold defaultstate="collapsed">

    var AttCertVersion = INTEGER;

    var ObjectDigestInfo = SEQUENCE({
        digestedObjectType: ENUMERATED({
            publicKey: 0,
            publicKeyCert: 1,
            otherObjectTypes: 2
        }), // otherObjectTypes MUST NOT be used in this profile
        otherObjectTypeID: OPTIONAL(OBJECT_IDENTIFIER),
        digestAlgorithm: DigestAlgorithmIdentifier,
        objectDigest: BIT_STRING});

    var IssuerSerial = SEQUENCE({
        issuer: GeneralNames,
        serial: CertificateSerialNumber,
        issuerUID: OPTIONAL(UniqueIdentifier)});

    var V2Form = SEQUENCE({
        issuerName: OPTIONAL(GeneralNames),
        baseCertificateID: OPTIONAL(CTX(0, IMPLICIT(IssuerSerial))),
        // issuerName MUST be present in this profile baseCertificateID and 
        // objectDigestInfo MUST NOT be present in this profile
        objectDigestInfo: OPTIONAL(CTX(1, IMPLICIT(ObjectDigestInfo)))});

    var TargetCert = SEQUENCE({
        targetCertificate: IssuerSerial,
        targetName: OPTIONAL(GeneralName),
        certDigestInfo: OPTIONAL(ObjectDigestInfo)});

    var Target = CHOICE({
        targetName: CTX(0, EXPLICIT(GeneralName)), // GeneralName is CHOICE
        targetGroup: CTX(1, EXPLICIT(GeneralName)),
        targetCert: CTX(2, IMPLICIT(TargetCert))});

    var Targets = SEQUENCE_OF(Target);

    var AttCertExtensions = Extensions({
        auditIdentity: OCTET_STRING,
        targetInformation: Targets,
        authorityKeyIdentifier: AuthorityKeyIdentifier,
        authorityInfoAccess: SEQUENCE_OF(AccessDescription),
        cRLDistributionPoints: CRLDistributionPoints,
        noRevAvail: NULL
    }, function (id) {
        return id === 'auditIdentity' || id === 'targetInformation';
    });

    var Holder = SEQUENCE({
        // the issuer and serial number of the holder's Public Key Certificate
        baseCertificateID: OPTIONAL(CTX(0, IMPLICIT(IssuerSerial))),
        // the name of the claimant or role
        entityName: OPTIONAL(CTX(1, IMPLICIT(GeneralNames))),
        // used to directly authenticate the holder, for example, an executable
        objectDigestInfo: OPTIONAL(CTX(2, IMPLICIT(ObjectDigestInfo)))});

    var AttCertIssuer = CHOICE({
        v1Form: GeneralNames, // MUST NOT be used in this profile
        v2Form: CTX(0, IMPLICIT(V2Form))}, 'v2Form');     // v2 only

    var AttCertValidityPeriod = SEQUENCE({
        notBeforeTime: GeneralizedTime,
        notAfterTime: GeneralizedTime});

    var SvceAuthInfo = SEQUENCE({
        service: GeneralName,
        ident: GeneralName,
        authInfo: OPTIONAL(OCTET_STRING)});

    var RoleSyntax = SEQUENCE({
        roleAuthority: OPTIONAL(CTX(0, IMPLICIT(GeneralNames))),
        roleName: CTX(1, EXPLICIT(GeneralName))}); // GeneralName is CHOICE

    var ClassList = BIT_STRING({
        unmarked: 0,
        unclassified: 1,
        restricted: 2,
        confidential: 3,
        secret: 4,
        topSecret: 5});

    var SecurityCategory = SEQUENCE({
        type: CTX(0, IMPLICIT(OBJECT_IDENTIFIER)),
        value: CTX(1, IMPLICIT(ANY))});

    var Clearance = SEQUENCE({
        policyId: CTX(0, IMPLICIT(OBJECT_IDENTIFIER)),
        classList: DEFAULT(CTX(1, IMPLICIT(ClassList)), ['unclassified']),
        securityCategories: OPTIONAL(CTX(2, IMPLICIT(SET_OF(SecurityCategory))))});

    var IetfAttrSyntax = SEQUENCE({
        policyAuthority: OPTIONAL(CTX(0, IMPLICIT(GeneralNames))),
        values: SEQUENCE_OF(CHOICE({
            octets: OCTET_STRING,
            oid: OBJECT_IDENTIFIER,
            string: UTF8String}, function (value) {
            return isBinary ? 'octets' : getIdentifier(value) ? 'oid' : 'string';
        }))});

    /**
     * X.509 Attribute Certificate Definition<br><br>
     * 
     * X.509 contains the definition of an AC given below.  All types that
     * are not defined in this document can be found in [PKIXPROF].
     * <pre>
     *           AttributeCertificateInfo ::= SEQUENCE {
     *                version              AttCertVersion -- version is v2,
     *                holder               Holder,
     *                issuer               AttCertIssuer,
     *                signature            AlgorithmIdentifier,
     *                serialNumber         CertificateSerialNumber,
     *                attrCertValidityPeriod   AttCertValidityPeriod,
     *                attributes           SEQUENCE OF Attribute,
     *                issuerUniqueID       UniqueIdentifier OPTIONAL,
     *                extensions           Extensions OPTIONAL
     *           }
     * <pre>
     * RFC 3281 references {@link http://tools.ietf.org/html/rfc3281} 
     * 
     * @class GostASN1.AttributeCertificateInfo
     * @extends GostASN1.Sequence
     * @property {number} version The version of the encoded certificate
     * @property {GostASN1.Name} holder Identifies the holder.
     * @property {GostASN1.Name} issuer Identifies the issuer.
     * @property {AlgorithmIdentifier} signature The algorithm identifier for the algorithm used by the CA to sign the certificate.
     * @property {(number|string)} serialNumber The serial number MUST be a positive integer assigned by the CA to each certificate.
     * @property {GostASN1.Validity} attrCertValidityPeriod The certificate validity period
     * @property {GostASN1.Attributes} attributes The certificate attributes
     * @property {CryptoOperationData} issuerUniqueID The issuer unique identifier
     * @property {GostASN1.Extensions} extensions The certificate extensions
     */
    var AttributeCertificateInfo = COMBINE(SEQUENCE({
        version: AttCertVersion, // version is v2,
        holder: Holder,
        issuer: AttCertIssuer,
        signature: SignatureAlgorithmIdentifier,
        serialNumber: CertificateSerialNumber,
        attrCertValidityPeriod: AttCertValidityPeriod,
        attributes: AttributeSequence({
            authenticationInfo: SET_OF(SvceAuthInfo),
            accessIdentity: SET_OF(SvceAuthInfo),
            chargingIdentity: SET_OF_SINGLE(IetfAttrSyntax),
            group: SET_OF_SINGLE(IetfAttrSyntax),
            role: SET_OF(RoleSyntax),
            clearance: SET_OF(Clearance)
        }),
        issuerUniqueID: OPTIONAL(UniqueIdentifier),
        extensions: OPTIONAL(AttCertExtensions)
    }));

    /**
     * Attribute Certificate Profile<br></br>
     *
     * ACs may be used in a wide range of applications and environments
     * covering a broad spectrum of interoperability goals and a broader
     * spectrum of operational and assurance requirements.  The goal of this
     * document is to establish a common baseline for generic applications
     * requiring broad interoperability and limited special purpose
     * requirements.  In particular, the emphasis will be on supporting the
     * use of attribute certificates for informal Internet electronic mail,
     * IPSec, and WWW applications.
     * <pre>
     *           AttributeCertificate ::= SEQUENCE {
     *                acinfo               AttributeCertificateInfo,
     *                signatureAlgorithm   AlgorithmIdentifier,
     *                signatureValue       BIT STRING
     *           }
     * </pre>
     * See {@link GostASN1.AttributeCertificateInfo}<br><br>
     * RFC 3281 references {@link http://tools.ietf.org/html/rfc3281} 
     * 
     * @class GostASN1.AttributeCertificate
     * @extends GostASN1.AttributeCertificateInfo
     * @property {GostASN1.AttributeCertificateInfo} acinfo Attribute certificate information
     * @property {AlgorithmIndentifier} signatureAlgorithm Identifies signature algorithm
     * @property {CryptoOperationData} signatureValue Signature value
     */
    var AttributeCertificate = SEQUENCE({
        acinfo: AttributeCertificateInfo,
        signatureAlgorithm: SignatureAlgorithmIdentifier,
        signatureValue: BIT_STRING}, 'ATTRIBUTE CERTIFICATE');
    // </editor-fold>    

    /*
     * Encrypted Key with CMS
     * 
     * http://tools.ietf.org/html/rfc5652
     * http://tools.ietf.org/html/rfc4490
     * 
     */ // <editor-fold defaultstate="collapsed">

    // RecipientInfo
    var EncryptedKey = OCTET_STRING;

    var EncryptedContent = OCTET_STRING;

    var SubjectKeyIdentifier = OCTET_STRING;

    var UserKeyingMaterial = OCTET_STRING;

    var ECCCMSSharedInfo = SEQUENCE({
        keyInfo: KeyWrapAlgorithmIdentifier,
        entityUInfo: OPTIONAL(CTX(0, EXPLICIT(OCTET_STRING))),
        suppPubInfo: CTX(2, EXPLICIT(OCTET_STRING))
    });

    // GOST Key Transport & Key agreement rfc4490
    var Gost2814789EncryptedKey = ENCLOSURE(SEQUENCE({
        encryptedKey: Gost2814789Key,
        maskKey: OPTIONAL(CTX(0, IMPLICIT(Gost2814789Key))),
        macKey: Gost2814789MAC
    }), {
        encode: function (value) {
            // wrappedKey: (CEK_ENC(32) | CEK_MAC(4))
            var encryptedKey = new Uint8Array(new Uint8Array(value, 0, 32)).buffer,
                    macKey = new Uint8Array(new Uint8Array(value, 32, 4)).buffer;
            return {// from wrapped key
                encryptedKey: encryptedKey,
                macKey: macKey
            };
        },
        decode: function (value) {
            var encryptedKey = value.encryptedKey,
                    maskKey = value.maskKey,
                    macKey = value.macKey;
            if (maskKey) {
                var m = new Int32Array(maskKey), k = new Int32Array(encryptedKey);
                for (var i = 0, n = m.length / k.length; i < n; i++) {
                    for (var j = 0, l = k.length; j < l; j++)
                        k[j] = (k[j] + m[l * i + j]) & 0xffffffff;
                }
            }
            var result = new Uint8Array(encryptedKey.byteLength + macKey.byteLength);
            result.set(new Uint8Array(encryptedKey), 0);
            result.set(new Uint8Array(macKey), 32);
            return result.buffer;
        }
    });


    var GostR3410TransportParameters = SEQUENCE({
        encryptionParamSet: Gost2814789ParamSet,
        ephemeralPublicKey: OPTIONAL(CTX(0, IMPLICIT(GostSubjectPublicKeyInfo))),
        ukm: OCTET_STRING}); // ( SIZE(8) )

    var GostR3410KeyTransport = ENCLOSURE(SEQUENCE({
        sessionEncryptedKey: Gost2814789EncryptedKey,
        transportParameters: OPTIONAL(CTX(0, IMPLICIT(GostR3410TransportParameters)))
    }), {
        encode: function (value) {
            var algorithm = value.algorithm;
            return {
                sessionEncryptedKey: value.sessionEncryptedKey,
                transportParameters: {
                    encryptionParamSet: attributes['sBox'][algorithm.wrapping.sBox || 'E-A'],
                    ephemeralPublicKey: algorithm['public'],
                    ukm: algorithm.ukm
                }
            };
        },
        decode: function (value) {
            return {
                algorithm: {
                    wrapping: parameters[value.transportParameters.encryptionParamSet],
                    ukm: value.transportParameters.ukm,
                    'public': value.transportParameters.ephemeralPublicKey
                },
                sessionEncryptedKey: value.sessionEncryptedKey
            };
        }
    });

    var SCGostKeyTransport = ENCLOSURE(SEQUENCE({
        sessionEncryptedKey: Gost2814789EncryptedKey,
        ukm: SEQUENCE({
            ephemeralPublicKey: GostSubjectPublicKeyInfo,
            addedukm: OPTIONAL(CTX(0, EXPLICIT(UserKeyingMaterial)))})
    }), {
        encode: function (value) {
            var algorithm = value.algorithm;
            return {
                sessionEncryptedKey: value.sessionEncryptedKey,
                ukm: {
                    ephemeralPublicKey: algorithm['public'],
                    addedukm: algorithm.ukm
                }
            };
        },
        decode: function (value) {
            return {
                algorithm: {
                    ukm: value.ukm.addedukm,
                    'public': value.ukm.ephemeralPublicKey
                },
                sessionEncryptedKey: value.sessionEncryptedKey
            };
        }
    });

    var GostEncryptedKey = (function (typeSet) {
        /**
         * Gost Encrypted key encoder for CMS
         * 
         * @class GostASN1.GostEncryptedKey
         * @extends GostASN1.Sequence
         * @param {AlgorithmIdentifier} algorithm
         */
        return function (algorithm) {
            var type = typeSet[algorithm.id];
            return type ? ENCAPSULATES(type) : ANY;
        };
    })({
        // Key transport algorithms
        'id-sc-gostR3410-2001': SCGostKeyTransport,
        'id-sc-gostR3410-94': SCGostKeyTransport,
        'id-GostR3410-2001': GostR3410KeyTransport,
        'id-GostR3410-94': GostR3410KeyTransport,
        'id-tc26-gost3410-12-256': GostR3410KeyTransport,
        'id-tc26-gost3410-12-512': GostR3410KeyTransport,
        // Key agreement algorithms
        'id-GostR3410-94-CryptoPro-ESDH': Gost2814789EncryptedKey,
        'id-GostR3410-2001-CryptoPro-ESDH': Gost2814789EncryptedKey,
        'id-tc26-agreement-gost-3410-12-256': Gost2814789EncryptedKey,
        'id-tc26-agreement-gost-3410-12-512': Gost2814789EncryptedKey,
        'id-sc-r3410-ESDH-r3411kdf': Gost2814789EncryptedKey,
        // Key encryption key algorithms
        'id-Gost28147-89-None-KeyWrap': Gost2814789EncryptedKey,
        'id-Gost28147-89-CryptoPro-KeyWrap': Gost2814789EncryptedKey,
        'id-sc-cmsGostWrap': Gost2814789EncryptedKey,
        'id-sc-cmsGost28147Wrap': Gost2814789EncryptedKey});

    // </editor-fold>

    /*
     * CryptoPro Gost Private Key Store
     */ // <editor-fold defaultstate="collapsed">

    var GostKeyContainerContentAttributes = BIT_STRING({
        kccaSoftPassword: 0,
        kccaReservePrimary: 1,
        kccaPrimaryKeyAbsent: 2,
        kccaFKCShared: 3});

    var GostPrivateKeyAttributes = BIT_STRING({
        pkaExportable: 0,
        pkaUserProtect: 1,
        pkaExchange: 2,
        pkaEphemeral: 3,
        pkaNonCachable: 4,
        pkaDhAllowed: 5
    });

    var GostPrivateKeyParameters = SEQUENCE({
        attributes: OPTIONAL(GostPrivateKeyAttributes),
        privateKeyAlgorithm: OPTIONAL(CTX(0, IMPLICIT(KeyAlgorithmIdentifier)))
    });

    var CertificateLink = SEQUENCE({
        path: IA5String,
        hmac: Gost2814789MAC
    });

    var PasswordPolicy = AlgorithmIdentifier;

    var GostKeyContainerContent = SEQUENCE({
        containerAlgoritmIdentifier: OPTIONAL(CTX(0, IMPLICIT(AlgorithmIdentifier))),
        containerName: OPTIONAL(IA5String),
        attributes: GostKeyContainerContentAttributes,
        primaryPrivateKeyParameters: GostPrivateKeyParameters,
        hmacPassword: OPTIONAL(CTX(2, IMPLICIT(Gost2814789MAC))),
        secondaryEncryptedPrivateKey: OPTIONAL(CTX(3, IMPLICIT(Gost2814789EncryptedKey))),
        secondaryPrivateKeyParameters: OPTIONAL(CTX(4, IMPLICIT(GostPrivateKeyParameters))),
        primaryCertificate: OPTIONAL(CTX(5, IMPLICIT(OCTET_STRING(ENCAPSULATES(Certificate))))),
        secondaryCertificate: OPTIONAL(CTX(6, IMPLICIT(OCTET_STRING(ENCAPSULATES(Certificate))))),
        encryptionContainerName: OPTIONAL(CTX(7, IMPLICIT(IA5String))),
        primaryCertificateLink: OPTIONAL(CTX(8, IMPLICIT(CertificateLink))),
        secondaryCertificateLink: OPTIONAL(CTX(9, IMPLICIT(CertificateLink))),
        primaryFP: OPTIONAL(CTX(10, IMPLICIT(OCTET_STRING))),
        secondaryFP: OPTIONAL(CTX(11, IMPLICIT(OCTET_STRING))),
        passwordPolicy: OPTIONAL(PasswordPolicy),
        containerSecurityLevel: OPTIONAL(INTEGER),
        extensions: OPTIONAL(CTX(12, IMPLICIT(Extensions({
            keyValidity: SEQUENCE({
                notBefore: OPTIONAL(CTX(0, IMPLICIT(GeneralizedTime))),
                notAfter: OPTIONAL(CTX(1, IMPLICIT(GeneralizedTime)))})
        })))),
        secondaryEncryptionContainerName: OPTIONAL(CTX(13, IMPLICIT(IA5String)))
    });

    /**
     * CryptoPro key container header
     * 
     * @class GostASN1.GostKeyContainer
     * @extends GostASN1.Sequence
     */
    var GostKeyContainer = SEQUENCE({
        keyContainerContent: GostKeyContainerContent,
        hmacKeyContainerContent: Gost2814789MAC
    });

    /**
     * CryptoPro key container name
     * 
     * @class GostASN1.GostKeyContainerName
     * @extends GostASN1.Sequence
     */
    var GostKeyContainerName = SEQUENCE({
        containerName: IA5String,
        extElem1: OPTIONAL(ANY)
    });

    /**
     * PrivateKey encrypted content for CryptoPro key containers
     * 
     * @class GostASN1.GostPrivateKeys
     * @extends GostASN1.Sequence
     */
    var GostPrivateKeys = SEQUENCE({
        primaryKey: Gost2814789Key,
        secondaryKey: OPTIONAL(Gost2814789Key),
        hmacKey: OPTIONAL(Gost2814789MAC)
    });

    /**
     * PrivateKey masks for CryptoPro key containers
     * 
     * @class GostASN1.GostPrivateMasks
     * @extends GostASN1.Sequence
     */
    var GostPrivateMasks = SEQUENCE({
        mask: Gost2814789Key,
        randomStatus: OCTET_STRING,
        hmacRandom: Gost2814789MAC
    });

    // </editor-fold>

    /*
     * ViPNet Gost Private Key Store
     */ // <editor-fold defaultstate="collapsed">

    var ViPNetKeyInfo = SEQUENCE({
        keyClass: INTEGER,
        keyType: INTEGER,
        algorithm: OPTIONAL(CTX(0, EXPLICIT(KeyAlgorithmIdentifier))),
        serialNumber: OPTIONAL(CTX(1, EXPLICIT(OCTET_STRING))),
        addSerialNumber: OPTIONAL(CTX(2, EXPLICIT(OCTET_STRING))),
        certSerialNumber: OPTIONAL(CTX(3, EXPLICIT(OCTET_STRING))),
        subjectUID: OPTIONAL(CTX(4, EXPLICIT(OCTET_STRING))),
        recipientUID: OPTIONAL(CTX(5, EXPLICIT(OCTET_STRING))),
        validity: OPTIONAL(CTX(6, EXPLICIT(CHOICE({
            validity: Validity,
            keyValidity: SEQUENCE({
                notBefore: OPTIONAL(CTX(0, IMPLICIT(GeneralizedTime))),
                notAfter: OPTIONAL(CTX(1, IMPLICIT(GeneralizedTime)))})
        }, function () {
            return 'keyValidity';
        })))),
        keyUID: OPTIONAL(CTX(7, EXPLICIT(BIT_STRING))),
        flags: OPTIONAL(CTX(10, EXPLICIT(INTEGER)))
    });

    /**
     * ViPNet key container info
     * 
     * @class GostASN1.ViPNetInfo
     * @extends GostASN1.Sequence
     */
    var ViPNetInfo = SEQUENCE({
        version: INTEGER,
        keyInfo: ViPNetKeyInfo,
        defenceKeyInfo: ViPNetKeyInfo,
        certificate: OPTIONAL(CTX(0, EXPLICIT(Certificate))),
        publicKey: OPTIONAL(CTX(1, EXPLICIT(OCTET_STRING)))
    });

    // </editor-fold>

    /*
     * Cryptographic Message Syntax
     * 
     * http://tools.ietf.org/html/rfc5652
     * 
     */ // <editor-fold defaultstate="collapsed">

    // CMS signed data
    var CMSVersion = INTEGER;

    var ContentType = OBJECT_IDENTIFIER;

    var SigningTime = Time;

    var SubjectKeyIdentifier = OCTET_STRING;

    var Digest = OCTET_STRING;

    var MessageAuthenticationCode = OCTET_STRING;

    var BodyPartID = INTEGER;

    var BodyPartPath = SEQUENCE_OF(BodyPartID);

    var CMCUnsignedData = SEQUENCE({
        bodyPartPath: BodyPartPath,
        identifier: OBJECT_IDENTIFIER,
        content: ANY}); // DEFINED BY identifier
    /**
     * SignedAttributes is a collection of attributes that are signed.  The
     * field is optional, but it MUST be present if the content type of
     * the EncapsulatedContentInfo value being signed is not id-data.
     * SignedAttributes MUST be DER encoded, even if the rest of the
     * structure is BER encoded.  Useful attribute types, such as signing
     * time, are defined in Section 11.  If the field is present, it MUST
     * contain, at a minimum, the following two attributes: <br>
     *
     * A content-type attribute having as its value the content type
     * of the EncapsulatedContentInfo value being signed.  Section
     * 11.1 defines the content-type attribute.  However, the
     * content-type attribute MUST NOT be used as part of a
     * countersignature unsigned attribute as defined in Section 11.4.<br>
     *
     * A message-digest attribute, having as its value the message
     * digest of the content.  Section 11.2 defines the message-digest
     * attribute.
     * 
     * @class GostASN1.SignedAttributes
     * @extends GostASN1.Attributes
     */
    var SignedAttributes = Attributes({
        contentType: SET_OF_SINGLE(ContentType),
        signingTime: SET_OF_SINGLE(SigningTime),
        messageDigest: SET_OF_SINGLE(OCTET_STRING)});

    var UnsignedAttributes = Attributes(function (type) {
        /**
         * UnsignedAttributes is a collection of attributes that are not signed.
         * The field is optional.  Useful attribute types, such as
         * countersignatures.
         * 
         * @class GostASN1.UnsignedAttributes
         * @extends GostASN1.Attributes
         */
        //    var UnsignedAttributes = Attributes({
        //        countersignature: SET_OF(Countersignature), // -- check forward
        //        unsignedData: SET_OF(CMCUnsignedData)
        //    });
        return ({
            countersignature: SET_OF(Countersignature), // recursion
            unsignedData: SET_OF(CMCUnsignedData)
        })[type];
    });

    var AuthAttributes = SignedAttributes,
            UnauthAttributes = Attributes,
            UnprotectedAttributes = Attributes;

    var IssuerAndSerialNumber = SEQUENCE({
        issuer: Name,
        serialNumber: CertificateSerialNumber});

    var SignerIdentifier = CHOICE({
        issuerAndSerialNumber: IssuerAndSerialNumber,
        subjectKeyIdentifier: CTX(0, IMPLICIT(SubjectKeyIdentifier))}, function (value) {
        return isBinary(value) ? 'subjectKeyIdentifier' : 'issuerAndSerialNumber';
    });

    var SignerInfo = SEQUENCE({
        version: CMSVersion,
        sid: SignerIdentifier,
        digestAlgorithm: DigestAlgorithmIdentifier,
        signedAttrs: OPTIONAL(CTX(0, IMPLICIT(SignedAttributes))),
        signatureAlgorithm: SignatureAlgorithmIdentifier,
        signatureValue: OCTET_STRING,
        unsignedAttrs: OPTIONAL(CTX(1, IMPLICIT(UnsignedAttributes)))});

    var Countersignature = SignerInfo,
            SignerInfos = SET_OF(SignerInfo),
            DigestAlgorithmIdentifiers = SET_OF(DigestAlgorithmIdentifier),
            AttributeCertificateV2 = AttributeCertificate;

    var ExtendedCertificateInfo = COMBINE(SEQUENCE({
        version: CMSVersion,
        certificate: Certificate,
        attributes: UnauthAttributes}));

    var ExtendedCertificate = SEQUENCE({
        extendedCertificateInfo: ExtendedCertificateInfo,
        signatureAlgorithm: SignatureAlgorithmIdentifier,
        signatureValue: BIT_STRING});

    var OtherCertificateFormat = SEQUENCE({
        otherCertFormat: OBJECT_IDENTIFIER,
        otherCert: ANY});

    var AttributeCertificateInfoV1 = COMBINE(SEQUENCE({
        version: INTEGER,
        subject: CHOICE({
            baseCertificateID: CTX(0, IMPLICIT(IssuerSerial)), // associated with a Public Key Certificate
            subjectName: CTX(1, IMPLICIT(GeneralNames))}, function (value) {
            return value.issuer ? 'baseCertificateID' : 'subjectName';
        }), //associated with a name
        issuer: GeneralNames,
        signature: SignatureAlgorithmIdentifier,
        serialNumber: CertificateSerialNumber,
        attCertValidityPeriod: AttCertValidityPeriod,
        attributes: AttributeSequence,
        issuerUniqueID: OPTIONAL(UniqueIdentifier),
        extensions: OPTIONAL(CertExtensions)}));

    var AttributeCertificateV1 = SEQUENCE({
        acInfo: AttributeCertificateInfoV1,
        signatureAlgorithm: SignatureAlgorithmIdentifier,
        signatureValue: BIT_STRING});

    var EncapsulatedContentInfo = SEQUENCE({
        eContentType: ContentType,
        eContent: OPTIONAL(CTX(0, EXPLICIT(OCTET_STRING)))});

    var CertificateChoices = CHOICE({
        certificate: Certificate,
        extendedCertificate: CTX(0, IMPLICIT(ExtendedCertificate)), // Obsolete
        v1AttrCert: CTX(1, IMPLICIT(AttributeCertificateV1)), // Obsolete
        v2AttrCert: CTX(2, IMPLICIT(AttributeCertificateV2)),
        other: CTX(3, IMPLICIT(OtherCertificateFormat))}, function (value) {
        return value.holder ? 'AttributeCertificateV2' : value.certificate ?
                'ExtendedCertificate' : value.otherCertFormat ? 'other' :
                'certificate';
    });

    var OtherRevocationInfoFormat = SEQUENCE({
        otherRevInfoFormat: OBJECT_IDENTIFIER,
        otherRevInfo: ANY});

    var RevocationInfoChoice = CHOICE({
        crl: CertificateList,
        other: CTX(1, IMPLICIT(OtherRevocationInfoFormat))}, function (value) {
        return value.otherRevInfoFormat ? 'other' : 'crl';
    });

    var CertificateSet = SET_OF(CertificateChoices),
            RevocationInfoChoices = SET_OF(RevocationInfoChoice);

    /**
     * The signed-data content type consists of a content of any type and zero or more 
     * signature values.  Any number of signers in parallel can sign any type of content.<br><br>
     * 
     * The signed-data content type shall have ASN.1 type SignedData:<br><br>
     *<pre>
     *
     *      SignedData ::= SEQUENCE {
     *        version CMSVersion,
     *        digestAlgorithms DigestAlgorithmIdentifiers,
     *        encapContentInfo EncapsulatedContentInfo,
     *        certificates [0] IMPLICIT CertificateSet OPTIONAL,
     *        crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
     *        signerInfos SignerInfos }
     *
     *      DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
     *
     *      SignerInfos ::= SET OF SignerInfo
     *      
     *       EncapsulatedContentInfo ::= SEQUENCE {
     *        eContentType ContentType,
     *        eContent [0] EXPLICIT OCTET STRING OPTIONAL }
     *        
     *</pre>
     * Per-signer information is represented in the type SignerInfo:<br><br>
     *<pre>
     *
     *      SignerInfo ::= SEQUENCE {
     *        version CMSVersion,
     *        sid SignerIdentifier,
     *        digestAlgorithm DigestAlgorithmIdentifier,
     *        signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
     *        signatureAlgorithm SignatureAlgorithmIdentifier,
     *        signature SignatureValue,
     *        unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }
     *
     *      SignerIdentifier ::= CHOICE {
     *        issuerAndSerialNumber IssuerAndSerialNumber,
     *        subjectKeyIdentifier [0] SubjectKeyIdentifier }
     *
     *      SignedAttributes ::= SET SIZE (1..MAX) OF Attribute
     *
     *      UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute
     *
     *     SignatureValue ::= OCTET STRING
     *     
     *</pre>
     * See also {@link GostASN1.SignedAttributes} and {@link GostASN1.UnsignedAttributes}
     * @class GostASN1.SignedData
     * @extends GostASN1.Sequence
     * @property {number} version The syntax version number
     * @property {AlgorithmIdentifier[]} digestAlgorithms Collection of message digest algorithm identifiers
     * @property {GostASN1.Sequence} encapContentInfo The content is represented in the type EncapsulatedContentInfo
     * @property {GostASN1.Certificate[]} certificates Certificates
     * @property {GostASN1.CertificateList[]} crls Certificates
     * @property {GostASN1.Sequence[]} signerInfos The Signer information
     */
    var SignedData = SEQUENCE({
        version: CMSVersion,
        digestAlgorithms: DigestAlgorithmIdentifiers,
        encapContentInfo: EncapsulatedContentInfo,
        certificates: OPTIONAL(CTX(0, IMPLICIT(CertificateSet))),
        crls: OPTIONAL(CTX(1, IMPLICIT(RevocationInfoChoices))),
        signerInfos: SignerInfos});

    var RecipientIdentifier = CHOICE({
        issuerAndSerialNumber: IssuerAndSerialNumber,
        subjectKeyIdentifier: CTX(0, IMPLICIT(SubjectKeyIdentifier))}, function (value) {
        return isBinary(value) ? 'subjectKeyIdentifier' : 'issuerAndSerialNumber';
    });

    var KeyTransRecipientInfo = SEQUENCE({
        version: CMSVersion, // always set to 0 or 2
        rid: RecipientIdentifier,
        keyEncryptionAlgorithm: KeyEncryptionAlgorithmIdentifier,
        encryptedKey: EncryptedKey});

    var OtherKeyAttribute = SEQUENCE({
        keyAttrId: OBJECT_IDENTIFIER,
        keyAttr: OPTIONAL(ANY)});

    var RecipientKeyIdentifier = SEQUENCE({
        subjectKeyIdentifier: SubjectKeyIdentifier,
        date: OPTIONAL(GeneralizedTime),
        other: OPTIONAL(OtherKeyAttribute)});

    var KeyAgreeRecipientIdentifier = CHOICE({
        issuerAndSerialNumber: IssuerAndSerialNumber,
        rKeyId: CTX(0, IMPLICIT(RecipientKeyIdentifier))}, function (value) {
        return isBinary(value) ? 'rKeyId' : 'issuerAndSerialNumber';
    });

    var RecipientEncryptedKey = SEQUENCE({
        rid: KeyAgreeRecipientIdentifier,
        encryptedKey: EncryptedKey});

    var RecipientEncryptedKeys = SEQUENCE_OF(RecipientEncryptedKey);

    var OriginatorPublicKey = SEQUENCE({
        algorithm: KeyAlgorithmIdentifier,
        publicKey: BIT_STRING});

    var MQVuserKeyingMaterial = SEQUENCE({// ECC rfc5753 KeyAgreeRecipientInfo in ukm
        ephemeralPublicKey: OriginatorPublicKey,
        addedukm: OPTIONAL(CTX(0, EXPLICIT(UserKeyingMaterial)))
    });

    var OriginatorIdentifierOrKey = CHOICE({
        issuerAndSerialNumber: IssuerAndSerialNumber,
        subjectKeyIdentifier: CTX(0, IMPLICIT(SubjectKeyIdentifier)),
        originatorKey: CTX(1, IMPLICIT(OriginatorPublicKey))}, function (value) {
        return isBinary(value) ? 'subjectKeyIdentifier' : value.algorithm ?
                'originatorKey' : 'issuerAndSerialNumber';
    });

    var KeyAgreeRecipientInfo = SEQUENCE({
        version: CMSVersion, // always set to 3
        originator: CTX(0, EXPLICIT(OriginatorIdentifierOrKey)),
        ukm: OPTIONAL(CTX(1, EXPLICIT(UserKeyingMaterial))),
        keyEncryptionAlgorithm: KeyEncryptionAlgorithmIdentifier,
        recipientEncryptedKeys: RecipientEncryptedKeys});

    var KEKIdentifier = SEQUENCE({
        keyIdentifier: OCTET_STRING,
        date: OPTIONAL(GeneralizedTime),
        other: OPTIONAL(OtherKeyAttribute)});

    var KEKRecipientInfo = SEQUENCE({
        version: CMSVersion, // always set to 4
        kekid: KEKIdentifier,
        keyEncryptionAlgorithm: KeyEncryptionAlgorithmIdentifier,
        encryptedKey: EncryptedKey});

    var PasswordRecipientInfo = SEQUENCE({
        version: CMSVersion, // always set to 0
        friendlyName: OPTIONAL(CTX(0, IMPLICIT(KeyDerivationAlgorithmIdentifier))),
        keyEncryptionAlgorithm: KeyEncryptionAlgorithmIdentifier,
        encryptedKey: EncryptedKey});

    var OtherRecipientInfo = SEQUENCE({
        oriType: OBJECT_IDENTIFIER,
        oriValue: ANY});

    var RecipientInfo = CHOICE({
        ktri: KeyTransRecipientInfo,
        kari: CTX(1, IMPLICIT(KeyAgreeRecipientInfo)),
        kekri: CTX(2, IMPLICIT(KEKRecipientInfo)),
        pwri: CTX(3, IMPLICIT(PasswordRecipientInfo)),
        ori: CTX(4, IMPLICIT(OtherRecipientInfo))}, function (value) {
        return value.rid ? 'ktri' : value.originator ? 'kari' :
                value.kekid ? 'kekri' : value.oriType ? 'ori' : 'pwri';
    });

    var OriginatorInfo = SEQUENCE({
        certs: OPTIONAL(CTX(0, IMPLICIT(CertificateSet))),
        crls: OPTIONAL(CTX(1, IMPLICIT(RevocationInfoChoices)))});

    var RecipientInfos = SET_OF(RecipientInfo);

    // EncryptedContentInfo
    var EncryptedContentInfo = SEQUENCE({
        contentType: ContentType,
        contentEncryptionAlgorithm: ContentEncryptionAlgorithmIdentifier,
        encryptedContent: OPTIONAL(CTX(0, IMPLICIT(EncryptedContent)))});

    /**
     * The enveloped-data content type consists of an encrypted content of 
     * any type and encrypted content-encryption keys for one or more
     * recipients.  The combination of the encrypted content and one
     * encrypted content-encryption key for a recipient is a "digital
     * envelope" for that recipient.  Any type of content can be enveloped
     * for an arbitrary number of recipients using any of the supported key
     * management techniques for each recipient.<br><br>   
     * 
     * The typical application of the enveloped-data content type will
     * represent one or more recipients' digital envelopes on content of the
     * data or signed-data content types.<br><br>
     * <pre>
     * 
     *      EnvelopedData ::= SEQUENCE {
     *        version CMSVersion,
     *        originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
     *        recipientInfos RecipientInfos,
     *        encryptedContentInfo EncryptedContentInfo,
     *        unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }
     * 
     *      OriginatorInfo ::= SEQUENCE {
     *        certs [0] IMPLICIT CertificateSet OPTIONAL,
     *        crls [1] IMPLICIT RevocationInfoChoices OPTIONAL }
     *
     *      RecipientInfos ::= SET SIZE (1..MAX) OF RecipientInfo
     *
     *      EncryptedContentInfo ::= SEQUENCE {
     *        contentType ContentType,
     *        contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
     *        encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL }
     *
     *      EncryptedContent ::= OCTET STRING
     *
     *      UnprotectedAttributes ::= SET SIZE (1..MAX) OF Attribute
     *      
     * </pre>
     * @class GostASN1.EnvelopedData
     * @extends GostASN1.Sequence
     * @property {number} version The syntax version number
     * @property {GostASN1.Sequence} originatorInfo Optionally provides information about the originator.
     * @property {GostASN1.Sequence[]} recipientInfos Collection of per-recipient information. 
     * @property {GostASN1.Sequence} encryptedContentInfo The content is represented in the type EncryptedContentInfo
     * @property {Attributes} unprotectedAttrs The unprotected attributes
     */
    var EnvelopedData = SEQUENCE({
        version: CMSVersion,
        originatorInfo: OPTIONAL(CTX(0, IMPLICIT(OriginatorInfo))),
        recipientInfos: RecipientInfos,
        encryptedContentInfo: EncryptedContentInfo,
        unprotectedAttrs: OPTIONAL(CTX(1, IMPLICIT(UnprotectedAttributes)))});

    /**
     * The digested-data content type consists of content of any type and a
     * message digest of the content.<br><br>
     * Typically, the digested-data content type is used to provide content
     * integrity, and the result generally becomes an input to the
     * enveloped-data content type.<br><br>
     * <pre>
     * 
     *      DigestedData ::= SEQUENCE {
     *        version CMSVersion,
     *        digestAlgorithm DigestAlgorithmIdentifier,
     *        encapContentInfo EncapsulatedContentInfo,
     *        digest Digest }
     *
     *      Digest ::= OCTET STRING
     *      
     *</pre>
     * @class GostASN1.DigestedData
     * @extends GostASN1.Sequence
     * @property {number} version The syntax version number
     * @property {AlgorithmIdentifier} digestAlgorithm Message digest algorithm identifier
     * @property {GostASN1.Sequence} encapContentInfo The content is represented in the type EncapsulatedContentInfo
     * @property {CryptoOperationData} digest The degist
     */
    var DigestedData = SEQUENCE({
        version: CMSVersion,
        digestAlgorithm: DigestAlgorithmIdentifier,
        encapContentInfo: EncapsulatedContentInfo,
        digest: Digest});

    /**
     * The encrypted-data content type consists of encrypted content of any
     * type.  Unlike the enveloped-data content type, the encrypted-data
     * content type has neither recipients nor encrypted content-encryption
     * keys.  Keys MUST be managed by other means.<br><br>
     * 
     * The typical application of the encrypted-data content type will be to
     * encrypt the content of the data content type for local storage,
     * perhaps where the encryption key is derived from a password.<br><br>
     * <pre>
     * 
     *      EncryptedData ::= SEQUENCE {
     *        version CMSVersion,
     *        encryptedContentInfo EncryptedContentInfo,
     *        unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }
     *        
     *      EncryptedContentInfo ::= SEQUENCE {
     *        contentType ContentType,
     *        contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
     *        encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL }
     *
     *      EncryptedContent ::= OCTET STRING
     *      
     * </pre>
     * @class GostASN1.EncryptedData
     * @extends GostASN1.Sequence
     * @property {number} version The syntax version number
     * @property {GostASN1.Sequence} encryptedContentInfo The content is represented in the type EncryptedContentInfo
     * @property {Attributes} unprotectedAttrs The unprotected attributes
     */
    var EncryptedData = SEQUENCE({
        version: CMSVersion,
        encryptedContentInfo: EncryptedContentInfo,
        unprotectedAttrs: OPTIONAL(CTX(1, IMPLICIT(UnprotectedAttributes)))});

    /**
     * The authenticated-data content type consists of content of any type, 
     * a message authentication code (MAC), and encrypted authentication   
     * keys for one or more recipients.  The combination of the MAC and one
     * encrypted authentication key for a recipient is necessary for that
     * recipient to verify the integrity of the content.  Any type of
     * content can be integrity protected for an arbitrary number of
     * recipients.<br><br>
     * <pre>
     * 
     *      AuthenticatedData ::= SEQUENCE {
     *        version CMSVersion,
     *        originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
     *        recipientInfos RecipientInfos,
     *        macAlgorithm MessageAuthenticationCodeAlgorithm,
     *        digestAlgorithm [1] DigestAlgorithmIdentifier OPTIONAL,
     *        encapContentInfo EncapsulatedContentInfo,
     *        authAttrs [2] IMPLICIT AuthAttributes OPTIONAL,
     *        mac MessageAuthenticationCode,
     *        unauthAttrs [3] IMPLICIT UnauthAttributes OPTIONAL }
     *
     *      AuthAttributes ::= SET SIZE (1..MAX) OF Attribute
     *
     *      UnauthAttributes ::= SET SIZE (1..MAX) OF Attribute
     *
     *      MessageAuthenticationCode ::= OCTET STRING
     *
     * </pre>
     * @class GostASN1.AuthenticatedData
     * @extends GostASN1.Sequence
     * @property {number} version The syntax version number
     * @property {GostASN1.Sequence} originatorInfo Optionally provides information about the originator.
     * @property {GostASN1.Sequence[]} recipientInfos Collection of per-recipient information. 
     * @property {AlgorithmIdentifier} macAlgorithm Identifies the Message Authentication Code algorithm 
     * @property {AlgorithmIdentifier} digestAlgorithm Identifies the Digest algorithm 
     * @property {GostASN1.Sequence} encapContentInfo The content is represented in the type EncapsulatedContentInfo
     * @property {Attributes} authAttrs The autheniticated attributes
     * @property {CryptoOpertionData} mac The Message Authentication Code
     * @property {Attributes} unauthAttrs The unautheniticated attributes
     */
    var AuthenticatedData = SEQUENCE({
        version: CMSVersion,
        originatorInfo: OPTIONAL(CTX(0, IMPLICIT(OriginatorInfo))),
        recipientInfos: RecipientInfos,
        macAlgorithm: MessageAuthenticationCodeAlgorithm,
        digestAlgorithm: OPTIONAL(CTX(1, DigestAlgorithmIdentifier)),
        encapContentInfo: EncapsulatedContentInfo,
        authAttrs: OPTIONAL(CTX(2, IMPLICIT(AuthAttributes))),
        mac: MessageAuthenticationCode,
        unauthAttrs: OPTIONAL(CTX(3, IMPLICIT(UnauthAttributes)))});

    // AuthEnvelopedData RFC 5911
    var AuthEnvelopedData = SEQUENCE({
        version: CMSVersion,
        originatorInfo: OPTIONAL(CTX(0, IMPLICIT(OriginatorInfo))),
        recipientInfos: RecipientInfos,
        authEncryptedContentInfo: EncryptedContentInfo,
        authAttrs: OPTIONAL(CTX(1, IMPLICIT(AuthAttributes))),
        mac: MessageAuthenticationCode,
        unauthAttrs: OPTIONAL(CTX(2, IMPLICIT(UnauthAttributes)))});

    // EncryptedKeyPackage rfc6032
    var EncryptedKeyPackage = CHOICE({
        encrypted: EncryptedData,
        enveloped: CTX(0, IMPLICIT(EnvelopedData)),
        authEnveloped: CTX(1, IMPLICIT(AuthEnvelopedData))}, function (value) {
        return value.encryptedContentInfo ? (value.recipientInfos ?
                'enveloped' : 'encrypted') : 'authEnveloped';
    });

    /**
     * Cryptographic Message Syntax<br>
     * The CMS associates a content type identifier with a content. The syntax 
     * MUST have ASN.1 type ContentInfo:
     * <pre>
     * 
     *  ContentInfo ::= SEQUENCE {
     *    contentType ContentType,
     *    content [0] EXPLICIT ANY DEFINED BY contentType }
     *
     *  ContentType ::= OBJECT IDENTIFIER
     *  
     * </pre>
     * The fields of ContentInfo have the following meanings:
     * <ul>
     * <li>contentType indicates the type of the associated content.  It is
     * an object identifier; it is a unique string of integers assigned
     * by an authority that defines the content type.</li>
     * <li>content is the associated content.  The type of content can be
     * determined uniquely by contentType.  Content types for data,
     * signed-data, enveloped-data, digested-data, encrypted-data, and
     * authenticated-data are defined in this document.  If additional
     * content types are defined in other documents, the ASN.1 type
     * defined SHOULD NOT be a CHOICE type.</li>
     * </ul>
     * RFC 5652 references {@link http://tools.ietf.org/html/rfc5652} 
     * 
     * @class GostASN1.ContentInfo
     * @extends GostASN1.Sequence
     * @property {string} contentType The content type identifier
     * @property {(GostASN1.Sequence|CryptoOperationData)} content The content
     */
    var ContentType = OBJECT_IDENTIFIER;

    var ContentInfo = ATTRIBUTE({
        contentType: ContentType,
        content: function (type) {
            return CTX(0, EXPLICIT(type));
        }
    }, 'contentType', 'content', undefined, 'CMS')({
        data: OCTET_STRING,
        signedData: COMBINE(SignedData),
        envelopedData: COMBINE(EnvelopedData),
        digestedData: COMBINE(DigestedData),
        encryptedData: COMBINE(EncryptedData),
        authData: COMBINE(AuthenticatedData),
        encryptedKeyPkg: COMBINE(EncryptedKeyPackage),
        aKeyPackage: COMBINE(AsymmetricKeyPackage)});

    var DigestInfo = SEQUENCE({
        digestAlgorithm: DigestAlgorithmIdentifier,
        digest: Digest});
    // </editor-fold>    

    /*
     * PFX format syntax PKCS#12
     * 
     * http://tools.ietf.org/html/rfc7292
     * 
     */ // <editor-fold defaultstate="collapsed">

    var PKCS12Attributes = Attributes({
        friendlyName: SET_OF_SINGLE(BMPString),
        keyProviderNameAttr: SET_OF_SINGLE(BMPString),
        localKeyId: SET_OF_SINGLE(OCTET_STRING),
        certKeyIdentifierPropId: SET_OF_SINGLE(OCTET_STRING)});

    var SafeBagType = OBJECT_IDENTIFIER;

    var CertType = OBJECT_IDENTIFIER;

    var CRLType = OBJECT_IDENTIFIER;

    var SecretType = OBJECT_IDENTIFIER;

    var KeyBag = PrivateKeyInfo;

    var PKCS8ShroudedKeyBag = EncryptedPrivateKeyInfo;

    var CertBag = ATTRIBUTE({
        certId: CertType,
        certValue: function (type) {
            return CTX(0, EXPLICIT(type));
        }
    }, 'certId', 'certValue')({
        // DER-encoded X.509 certificate stored in OCTET STRING
        x509Certificate: OCTET_STRING(ENCAPSULATES(Certificate)),
        // Base64-encoded SDSI certificate stored in IA5String
        sdsiCertificate: IA5String
    }, OCTET_STRING);

    var CRLBag = ATTRIBUTE({
        crlId: CRLType,
        crlValue: function (type) {
            return CTX(0, EXPLICIT(type));
        }
    }, 'crlId', 'crlValue')({
        // DER-encoded X.509 certificate stored in OCTET STRING
        x509CRL: OCTET_STRING(ENCAPSULATES(CertificateList))
    }, OCTET_STRING);

    var SecretBag = ATTRIBUTE({
        secretTypeId: SecretType,
        secretValue: function (type) {
            return CTX(0, EXPLICIT(type));
        }
    }, 'secretTypeId', 'secretValue')({
        secret: OCTET_STRING
    }, OCTET_STRING);

    var SafeBag = ATTRIBUTE({
        bagId: SafeBagType,
        bagValue: function (type) {
            return CTX(0, EXPLICIT(type));
        },
        bagAttributes: OPTIONAL(PKCS12Attributes)
    }, 'bagId', 'bagValue')(function (type) {
        return ({
            keyBag: KeyBag,
            pkcs8ShroudedKeyBag: PKCS8ShroudedKeyBag,
            certBag: CertBag,
            crlBag: CRLBag,
            secretBag: SecretBag,
            safeContentsBag: SafeContents // recursion
        })[type];
    });

    /**
     * The SafeContents Type<br><br>
     * 
     * The sixth type of bag that can be held in a SafeBag is a
     * SafeContents. This recursive structure allows for arbitrary nesting
     * of multiple KeyBags, PKCS8ShroudedKeyBags, CertBags, CRLBags, and
     * SecretBags within the top-level SafeContents.
     * <pre>
     *  SafeContents ::= SEQUENCE OF SafeBag
     *   SafeBag ::= SEQUENCE {
     *       bagId BAG-TYPE.&id ({PKCS12BagSet})
     *       bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}),
     *       bagAttributes SET OF PKCS12Attribute OPTIONAL
     *   }
     *   
     *   PKCS12Attribute ::= SEQUENCE {
     *       attrId ATTRIBUTE.&id ({PKCS12AttrSet}),
     *       attrValues SET OF ATTRIBUTE.&Type ({PKCS12AttrSet}{@attrId})
     *   } -- This type is compatible with the X.500 type ’Attribute’
     *   
     *   PKCS12AttrSet ATTRIBUTE ::= {
     *       friendlyName | -- from PKCS #9 [23]
     *       localKeyId, -- from PKCS #9
     *       ... -- Other attributes are allowed
     *   }
     * </pre>
     * The SafeContents type is made up of SafeBags. Each SafeBag holds one
     * piece of information -- a key, a certificate, etc. -- which is
     * identified by an object identifier.<br><br>
     * 
     * See {@link GostASN1.ContentInfo} and {@link GostASN1.PFX}<br><br>
     * 
     * RFC 7292 references {@link http://tools.ietf.org/html/rfc7292} 
     * @class GostASN1.SafeContents
     */
    var SafeContents = SEQUENCE_OF(SafeBag);

    /**
     * The AuthenticatedSafe<br><br>
     * Each compliant platform shall be able to import and export
     * AuthenticatedSafe PDUs wrapped in PFX PDUs.<br>
     * For integrity, the AuthenticatedSafe is either signed (if public-key
     * integrity mode is used) or MACed (if password integrity mode is used)
     * to produce a PFX PDU.
     * <pre>
     *      AuthenticatedSafe ::= SEQUENCE OF ContentInfo
     * 
     *      -- Data if unencrypted
     *      -- EncryptedData if password-encrypted
     *      -- EnvelopedData if public key-encrypted
     * </pre>
     * As mentioned, the contentType field of authSafe shall be of type data
     * or signedData. The content field of the authSafe shall, either
     * directly (data case) or indirectly (signedData case), contain a BER-
     * encoded value of type AuthenticatedSafe.<br><br>
     * 
     * See {@link GostASN1.ContentInfo} and {@link GostASN1.PFX}<br><br>
     * 
     * RFC 7292 references {@link http://tools.ietf.org/html/rfc7292} 
     * 
     * @class GostASN1.AuthenticatedSafe
     */
    var AuthenticatedSafe = SEQUENCE_OF(ContentInfo);

    var MacData = SEQUENCE({
        mac: DigestInfo,
        macSalt: OCTET_STRING,
        // Note: The default is for historical reasons and its use is deprecated.
        iterations: DEFAULT(INTEGER, 1)});

    /**
     * PFX format syntax<br><br>
     * 
     * This format corresponds to the data model presented above, with
     * wrappers for privacy and integrity. This section makes free
     * reference to PKCS #7 {@link GostASN1.ContentInfo}<br>
     * All modes of direct exchange use the same PDU format.  ASN.1 and BER-
     * encoding ensure platform independence.<br>
     * This standard has one ASN.1 export: PFX.  This is the outer integrity
     * wrapper.<br><br>
     * Instances of PFX contain:
     *  <ol>
     *  <li>A version indicator.  The version shall be v3 for this version of
     *      this document.</li>
     *  <li>A PKCS #7 ContentInfo, whose contentType is signedData in public-
     *      key integrity mode and data in password integrity mode.</li>
     *  <li>An optional instance of MacData, present only in password
     *      integrity.  This object, if present, contains a PKCS #7
     *      DigestInfo, which holds the MAC value, a macSalt, and an
     *      iterationCount.  As described in Appendix B, the MAC key is
     *      derived from the password, the macSalt, and the iterationCount;
     *      the MAC is computed from the authSafe value and the MAC key via HMAC.  
     *      The password and the MAC key are not actually present anywhere in the PFX.  
     *      The salt and (to a certain extent) the iteration count thwarts dictionary
     *      attacks against the integrity password. </li>
     *  </ol>
     *  <pre>
     *  PFX ::= SEQUENCE {
     *      version     INTEGER {v3(3)}(v3,...),
     *      authSafe    ContentInfo,
     *      macData     MacData OPTIONAL
     *  }
     *
     *  MacData ::= SEQUENCE {
     *      mac         DigestInfo,
     *      macSalt     OCTET STRING,
     *      iterations  INTEGER DEFAULT 1
     *      -- Note: The default is for historical reasons and its
     *      --       use is deprecated.
     *  }
     *  </pre>
     * See {@link GostASN1.ContentInfo}<br><br>
     * 
     * RFC 7292 references {@link http://tools.ietf.org/html/rfc7292} 
     * @class GostASN1.PFX
     * @extends GostASN1.Sequence
     * @property {number} version Encoded version number
     * @property {GostASN1.ContentInfo} authSafe ContentInfo with {@link GostASN1.AuthenticatedSafe} content 
     */
    var PFX = SEQUENCE({
        version: INTEGER,
        authSafe: ContentInfo,
        macData: OPTIONAL(MacData)}, 'PFX');
    // </editor-fold>    

    /*
     * Certificate Request Message Format 
     * 
     * http://tools.ietf.org/html/rfc4211
     * 
     */ // <editor-fold defaultstate="collapsed">

    var RegToken = UTF8String;

    var Authenticator = UTF8String;

    var CertId = SEQUENCE({
        issuer: GeneralName,
        serialNumber: INTEGER});
    var OldCertId = CertId;

    var ProtocolEncrKey = SubjectPublicKeyInfo;

    var EncryptedValue = SEQUENCE({
        // the intended algorithm for which the value will be used
        intendedAlg: OPTIONAL(CTX(0, IMPLICIT(AlgorithmIdentifier))),
        // the symmetric algorithm used to encrypt the value
        symmAlg: OPTIONAL(CTX(1, IMPLICIT(AlgorithmIdentifier))),
        // the (encrypted) symmetric key used to encrypt the value
        encSymmKey: OPTIONAL(CTX(2, IMPLICIT(BIT_STRING))),
        // algorithm used to encrypt the symmetric key
        keyAlg: OPTIONAL(CTX(3, IMPLICIT(AlgorithmIdentifier))),
        valueHint: OPTIONAL(CTX(4, IMPLICIT(OCTET_STRING))),
        // a brief description or identifier of the encValue content
        // (may be meaningful only to the sending entity, and used only
        // if EncryptedValue might be re-examined by the sending entity
        // in the future)
        encValue: BIT_STRING});
    var KeyGenParameters = OCTET_STRING;

    // The encrypted private key MUST be placed in the envelopedData
    // encryptedContentInfo encryptedContent OCTET STRING.
    var EncryptedKey = CHOICE({
        encryptedValue: EncryptedValue, // Deprecated
        envelopedData: CTX(0, IMPLICIT(EnvelopedData))}, function (value) {
        return value.encryptedContentInfo ? 'envelopedData' : 'encryptedValue';
    });

    var PKIArchiveOptions = CHOICE({
        // the actual value of the private key
        encryptedPrivKey: CTX(0, EncryptedKey),
        // parameters that allow the private key to be re-generated
        keyGenParameters: CTX(1, IMPLICIT(KeyGenParameters)),
        // set to TRUE if sender wishes receiver to archive the private
        // key of a key pair that the receiver generates in response to
        // this request; set to FALSE if no archival is desired.
        archiveRemGenPrivKey: CTX(2, IMPLICIT(BOOLEAN))});

    var SinglePubInfo = SEQUENCE({
        pubMethod: INTEGER({
            dontCare: 0,
            x500: 1,
            web: 2,
            ldap: 3}),
        pubLocation: OPTIONAL(GeneralName)});

    // pubInfos MUST NOT be present if action is "dontPublish"
    // (if action is "pleasePublish" and pubInfos is omitted,
    // "dontCare" is assumed)
    var PKIPublicationInfo = SEQUENCE({
        action: INTEGER({
            dontPublish: 0,
            pleasePublish: 1}),
        pubInfos: OPTIONAL(SEQUENCE_OF(SinglePubInfo))});

    var SubsequentMessage = INTEGER({
        // requests that resulting certificate be encrypted for the
        // end entity (following which, POP will be proven in a
        // confirmation message)
        encrCert: 0,
        // requests that CA engage in challenge-response exchange with
        // end entity in order to prove private key possession
        challengeResp: 1});

    var POPOPrivKey = CHOICE({
        // possession is proven in this message (which contains the private
        // key itself (encrypted for the CA))
        thisMessage: CTX(0, IMPLICIT(BIT_STRING)), // Deprecated
        subsequentMessage: CTX(1, IMPLICIT(SubsequentMessage)),
        // possession will be proven in a subsequent message
        dhMAC: CTX(2, IMPLICIT(BIT_STRING)), // Deprecated
        agreeMAC: CTX(3, IMPLICIT(PKMACValue)),
        encryptedKey: CTX(4, IMPLICIT(EnvelopedData))});

    var PBMParameter = SEQUENCE({
        salt: OCTET_STRING,
        // AlgId for a One-Way Function (SHA-1 recommended)
        owf: AlgorithmIdentifier,
        // number of times the OWF is applied
        iterationCount: INTEGER,
        // the MAC AlgId (e.g., DES-MAC, Triple-DES-MAC [PKCS11], or HMAC [HMAC, RFC2202])
        mac: AlgorithmIdentifier});

    var PKMACValue = SEQUENCE({
        // algorithm value shall be PasswordBasedMac {1 2 840 113533 7 66 13}
        // parameter value is PBMParameter
        algId: AlgorithmIdentifier,
        value: BIT_STRING});

    var POPOSigningKeyInput = SEQUENCE({
        authInfo: CHOICE({
            // used only if an authenticated identity has been
            // established for the sender (e.g., a DN from a
            // previously-issued and currently-valid certificate)
            sender: CTX(0, EXPLICIT(GeneralName)), // GeneralName choice - explicit
            // used if no authenticated GeneralName currently exists for
            // the sender; publicKeyMAC contains a password-based MAC
            // on the DER-encoded value of publicKey
            publicKeyMAC: PKMACValue}),
        publicKey: SubjectPublicKeyInfo});  // from CertTemplate

    var POPOSigningKey = SEQUENCE({
        poposkInput: OPTIONAL(CTX(0, POPOSigningKeyInput)),
        algorithmIdentifier: AlgorithmIdentifier,
        signature: BIT_STRING});

    var ProofOfPossession = CHOICE({
        // used if the RA has already verified that the requester is in
        // possession of the private key
        raVerified: CTX(0, IMPLICIT(NULL)),
        signature: CTX(1, IMPLICIT(POPOSigningKey)),
        keyEncipherment: CTX(2, IMPLICIT(POPOPrivKey)),
        keyAgreement: CTX(3, IMPLICIT(POPOPrivKey))});

    var Controls = SEQUENCE_OF(AttributeTypeAndValue({
        regToken: RegToken,
        authenticator: Authenticator,
        pkiPublicationInfo: PKIPublicationInfo,
        pkiArchiveOptions: PKIArchiveOptions,
        oldCertID: OldCertId,
        protocolEncrKey: ProtocolEncrKey}));

    var OptionalValidity = SEQUENCE({
        notBefore: OPTIONAL(CTX(0, IMPLICIT(Time))),
        notAfter: OPTIONAL(CTX(1, IMPLICIT(Time)))}); // at least one MUST be present

    var CertTemplate = SEQUENCE({
        version: OPTIONAL(CTX(0, IMPLICIT(Version))),
        serialNumber: OPTIONAL(CTX(1, IMPLICIT(INTEGER))),
        signingAlg: OPTIONAL(CTX(2, IMPLICIT(AlgorithmIdentifier))),
        issuer: OPTIONAL(CTX(3, IMPLICIT(Name))),
        validity: OPTIONAL(CTX(4, IMPLICIT(OptionalValidity))),
        subject: OPTIONAL(CTX(5, IMPLICIT(Name))),
        publicKey: OPTIONAL(CTX(6, IMPLICIT(SubjectPublicKeyInfo))),
        issuerUID: OPTIONAL(CTX(7, IMPLICIT(UniqueIdentifier))),
        subjectUID: OPTIONAL(CTX(8, IMPLICIT(UniqueIdentifier))),
        extensions: OPTIONAL(CTX(9, IMPLICIT(Extensions)))});

    var CertRequest = SEQUENCE({
        certReqId: INTEGER, // ID for matching request and reply
        certTemplate: CertTemplate, // Selected fields of cert to be issued
        controls: OPTIONAL(Controls)});   // Attributes affecting issuance

    var UTF8Pairs = UTF8String;

    var CertReq = CertRequest;

    var EncKeyWithID = SEQUENCE({
        privateKey: PrivateKeyInfo,
        identifier: OPTIONAL(CHOICE({
            string: UTF8String,
            generalName: GeneralName}, function (value) {
            return typeof value === 'string' || value instanceof String ?
                    'string' : 'generalName';
        }))});

    var CertReqMsg = SEQUENCE({
        certReq: CertRequest,
        popo: OPTIONAL(ProofOfPossession),
        // content depends upon key type
        regInfo: OPTIONAL(SEQUENCE_OF(AttributeTypeAndValue({
            utf8Pairs: UTF8Pairs,
            certReq: CertReq,
            encKeyWithID: EncKeyWithID})))});

    var CertReqMessages = SEQUENCE_OF(CertReqMsg);

    // </editor-fold>    

    /*
     * Certificate Management over CMS
     * 
     * http://tools.ietf.org/html/rfc5272
     * 
     */ // <editor-fold defaultstate="collapsed">

    var PendInfo = SEQUENCE({
        pendToken: OCTET_STRING,
        pendTime: GeneralizedTime});

    var CMCStatus = INTEGER({
        success: 0,
        failed: 2,
        pending: 3,
        noSupport: 4,
        confirmRequired: 5,
        popRequired: 6,
        partial: 7});

    var CMCFailInfo = INTEGER({
        badAlg: 0,
        badMessageCheck: 1,
        badRequest: 2,
        badTime: 3,
        badCertId: 4,
        unsupportedExt: 5,
        mustArchiveKeys: 6,
        badIdentity: 7,
        popRequired: 8,
        popFailed: 9,
        noKeyReuse: 10,
        internalCAError: 11,
        tryLater: 12,
        authDataFail: 13});

    var CMCStatusInfo = SEQUENCE({
        cMCStatus: CMCStatus,
        bodyList: SEQUENCE_OF(BodyPartID),
        statusString: OPTIONAL(UTF8String),
        otherInfo: OPTIONAL(CHOICE({
            failInfo: CMCFailInfo,
            pendInfo: PendInfo}))});

    var AddExtensions = SEQUENCE({
        pkiDataReference: BodyPartID,
        certReferences: SEQUENCE_OF(BodyPartID),
        extensions: SEQUENCE_OF(Extension)});

    var LraPopWitness = SEQUENCE({
        pkiDataBodyid: BodyPartID,
        bodyIds: SEQUENCE_OF(BodyPartID)});

    var GetCert = SEQUENCE({
        issuerName: GeneralName,
        serialNumber: INTEGER});

    var GetCRL = SEQUENCE({
        issuerName: Name,
        cRLName: OPTIONAL(GeneralName),
        time: OPTIONAL(GeneralizedTime),
        reasons: OPTIONAL(ReasonFlags)});

    var RevokeRequest = SEQUENCE({
        issuerName: Name,
        serialNumber: INTEGER,
        reason: CRLReason,
        invalidityDate: OPTIONAL(GeneralizedTime),
        passphrase: OPTIONAL(OCTET_STRING),
        comment: OPTIONAL(UTF8String)});

    var DecryptedPOP = SEQUENCE({
        bodyPartID: BodyPartID,
        thePOPAlgID: AlgorithmIdentifier,
        thePOP: OCTET_STRING});

    var CMCCertId = IssuerAndSerialNumber;

    var BodyPartReference = CHOICE({
        bodyPartID: BodyPartID,
        bodyPartPath: BodyPartPath});

    var CMCStatusInfoV2 = SEQUENCE({
        cMCStatus: CMCStatus,
        bodyList: SEQUENCE_OF(BodyPartReference),
        statusString: OPTIONAL(UTF8String),
        otherInfo: OPTIONAL(CHOICE({
            failInfo: CMCFailInfo,
            pendInfo: PendInfo,
            extendedFailInfo: SEQUENCE({
                failInfoOID: OBJECT_IDENTIFIER,
                failInfoValue: AttributeValue})}))});

    var PublishTrustAnchors = SEQUENCE({
        seqNumber: INTEGER,
        hashAlgorithm: AlgorithmIdentifier,
        anchorHashes: SEQUENCE_OF(OCTET_STRING)});

    var AuthPublish = BodyPartID;

    var BodyPartList = SEQUENCE_OF(BodyPartID);

    var CMCPublicationInfo = SEQUENCE({
        hashAlg: AlgorithmIdentifier,
        certHashes: SEQUENCE_OF(OCTET_STRING),
        pubInfo: PKIPublicationInfo});

    var ModCertTemplate = SEQUENCE({
        pkiDataReference: BodyPartPath,
        certReferences: BodyPartList,
        replace: DEFAULT(BOOLEAN, true),
        certTemplate: CertTemplate});

    var ControlsProcessed = SEQUENCE({
        bodyList: SEQUENCE_OF(BodyPartReference)});

    var IdentifyProofV2 = SEQUENCE({
        proofAlgID: AlgorithmIdentifier,
        macAlgId: AlgorithmIdentifier,
        witness: OCTET_STRING});

    var PopLinkWitnessV2 = SEQUENCE({
        keyGenAlgorithm: AlgorithmIdentifier,
        macAlgorithm: AlgorithmIdentifier,
        witness: OCTET_STRING});

    var TaggedCertificationRequest = SEQUENCE({
        bodyPartID: BodyPartID,
        certificationRequest: CertificationRequest});

    var TaggedContentInfo = SEQUENCE({
        bodyPartID: BodyPartID,
        contentInfo: ContentInfo});

    var OtherMsg = SEQUENCE({
        bodyPartID: BodyPartID,
        otherMsgType: OBJECT_IDENTIFIER,
        otherMsgValue: ANY}); //DEFINED BY otherMsgType 

    var TaggedRequest = CHOICE({
        tcr: CTX(0, IMPLICIT(TaggedCertificationRequest)),
        crm: CTX(1, IMPLICIT(CertReqMsg)),
        orm: CTX(2, IMPLICIT(SEQUENCE({
            bodyPartID: BodyPartID,
            requestMessageType: OBJECT_IDENTIFIER,
            requestMessageValue: ANY})))}); // DEFINED BY requestMessageType

    var EncryptedPOP = SEQUENCE({
        request: TaggedRequest,
        cms: ContentInfo,
        thePOPAlgID: AlgorithmIdentifier,
        witnessAlgID: AlgorithmIdentifier,
        witness: OCTET_STRING});

    var TaggedAttribute = ATTRIBUTE({
        bodyPartID: BodyPartID,
        attrType: OBJECT_IDENTIFIER,
        attrValues: function (type) {
            return SET_OF(type);
        }
    }, 'attrType', 'attrValues', AttributeValue)({
        statusInfo: CMCStatusInfo,
        identification: UTF8String,
        identityProof: OCTET_STRING,
        dataReturn: OCTET_STRING,
        transactionId: INTEGER,
        senderNonce: OCTET_STRING,
        recipientNonce: OCTET_STRING,
        addExtensions: AddExtensions,
        encryptedPOP: EncryptedPOP,
        decryptedPOP: DecryptedPOP,
        lraPOPWitness: LraPopWitness,
        getCert: GetCert,
        getCRL: GetCRL,
        revokeRequest: RevokeRequest,
        regInfo: OCTET_STRING,
        responseInfo: OCTET_STRING,
        queryPending: OCTET_STRING,
        popLinkRandom: OCTET_STRING,
        popLinkWitness: OCTET_STRING,
        confirmCertAcceptance: CMCCertId,
        statusInfoV2: CMCStatusInfoV2,
        trustedAnchors: PublishTrustAnchors,
        authPublish: AuthPublish,
        batchRequests: BodyPartList,
        batchResponses: BodyPartList,
        publishCert: CMCPublicationInfo,
        modCertTemplate: ModCertTemplate,
        controlProcessed: ControlsProcessed,
        popLinkWitnessV2: PopLinkWitnessV2,
        identityProofV2: IdentifyProofV2
    });
    /**
     * PKIData Content Type<br><br>
     *
     * The PKIData content type is used for the Full PKI Request.  A PKIData
     * content type is identified by:
     * <pre>
     *   id-cct-PKIData ::= {id-pkix id-cct(12) 2 }
     * </pre>
     * The ASN.1 structure corresponding to the PKIData content type is:
     * <pre>
     *   PKIData ::= SEQUENCE {
     *       controlSequence    SEQUENCE SIZE(0..MAX) OF TaggedAttribute,
     *       reqSequence        SEQUENCE SIZE(0..MAX) OF TaggedRequest,
     *       cmsSequence        SEQUENCE SIZE(0..MAX) OF TaggedContentInfo,
     *       otherMsgSequence   SEQUENCE SIZE(0..MAX) OF OtherMsg
     *   }
     * </pre>
     * All certification requests encoded into a single PKIData SHOULD be
     * for the same identity.  RAs that batch process (see Section 6.17) are
     * expected to place the PKI Requests received into the cmsSequence of a
     * PKIData. <br><br>
     * See {@link GostASN1.ContentInfo} and {@link GostASN1.PKIResponse}<br><br>
     * RFC 5272 references {@link http://tools.ietf.org/html/rfc5272} 
     * 
     * @class GostASN1.PKIData
     * @extends GostASN1.Sequence
     * @property {GostASN1.Attributes[]} controlSequence
     * @property {GostASN1.Sequence[]} reqSequence
     * @property {GostASN1.ContentInfo[]} cmsSequence
     * @property {GostASN1.Sequence[]} otherMsgSequence
     */
    var PKIData = SEQUENCE({
        controlSequence: SEQUENCE_OF(TaggedAttribute),
        reqSequence: SEQUENCE_OF(TaggedRequest),
        cmsSequence: SEQUENCE_OF(TaggedContentInfo),
        otherMsgSequence: SEQUENCE_OF(OtherMsg)}, 'PKI REQUEST');

    /**
     * PKIResponse Content Type<br><br>
     *
     * The PKIResponse content type is used for the Full PKI Response.  The
     * PKIResponse content type is identified by:
     * <pre>
     *   id-cct-PKIResponse ::= {id-pkix id-cct(12) 3  }
     * </pre>
     * The ASN.1 structure corresponding to the PKIResponse content type is:
     * <pre>
     *    PKIResponse ::= SEQUENCE {
     *        controlSequence   SEQUENCE SIZE(0..MAX) OF TaggedAttribute,
     *        cmsSequence       SEQUENCE SIZE(0..MAX) OF TaggedContentInfo,
     *        otherMsgSequence  SEQUENCE SIZE(0..MAX) OF OtherMsg
     *    }
     *    
     *    ReponseBody ::= PKIResponse
     * </pre>
     *
     * Note: In [RFC2797], this ASN.1 type was named ResponseBody.  It has
     * been renamed to PKIResponse for clarity and the old name kept as a
     * synonym.<br><br>
     *
     * See {@link GostASN1.ContentInfo} and {@link GostASN1.PKIData}<br><br>
     * 
     * RFC 5272 references {@link http://tools.ietf.org/html/rfc5272} 
     * 
     * @class GostASN1.PKIResponse
     * @extends GostASN1.Sequence
     * @property {GostASN1.Attributes[]} controlSequence
     * @property {GostASN1.ContentInfo[]} cmsSequence
     * @property {GostASN1.Sequence[]} otherMsgSequence
     */
    var PKIResponse = SEQUENCE({
        controlSequence: SEQUENCE_OF(TaggedAttribute),
        cmsSequence: SEQUENCE_OF(TaggedContentInfo),
        otherMsgSequence: SEQUENCE_OF(OtherMsg)}, 'PKI RESPONSE');

    // </editor-fold>    

    /**
     * ASN.1 syntax definitions
     * 
     * @class GostASN1
     */
    function GostASN1() {
    }

    GostASN1.prototype = {
        /**
         * Gost PrivateKey info encoder
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.GostPrivateKeyInfo
         */
        GostPrivateKeyInfo: GostPrivateKeyInfo,
        /**
         * Gost subject PublicKey info encoder
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.GostSubjectPublicKeyInfo
         */
        GostSubjectPublicKeyInfo: GostSubjectPublicKeyInfo,
        /**
         * CryptoPro key container header
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.GostKeyContainer
         */
        GostKeyContainer: GostKeyContainer,
        /**
         * CryptoPro key container name
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.GostKeyContainerName
         */
        GostKeyContainerName: GostKeyContainerName,
        /**
         * CryptoPro encrypted PrivateKey for key containers
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.GostPrivateKeys
         */
        GostPrivateKeys: GostPrivateKeys,
        /**
         * CryptoPro PrivateKey masks for key containers
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.GostPrivateMasks
         */
        GostPrivateMasks: GostPrivateMasks,
        /**
         * ViPNet key container
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.ViPNetInfo
         */
        ViPNetInfo: ViPNetInfo,
        /**
         * Gost Signature encoders
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.GostSignature
         */
        GostSignature: GostSignature,
        /**
         * Gost Encrypted key encoder for CMS
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.GostEncryptedKey
         */
        GostEncryptedKey: GostEncryptedKey,
        /**
         * SignalCom wrapped PrivateKey
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.GostWrappedPrivateKey
         */
        GostWrappedPrivateKey: GostWrappedPrivateKey,
        /**
         * PKCS#8 PrivateKey info
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.PrivateKeyInfo
         */
        PrivateKeyInfo: PrivateKeyInfo,
        /**
         * PKCS#8 encrypted PrivateKey info
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.EncryptedPrivateKeyInfo
         */
        EncryptedPrivateKeyInfo: EncryptedPrivateKeyInfo,
        /**
         * X.509 subject PublicKey info
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.SubjectPublicKeyInfo
         */
        SubjectPublicKeyInfo: SubjectPublicKeyInfo,
        /**
         * X.509 To be signed Certificate
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.TBSCertificate
         */
        TBSCertificate: TBSCertificate,
        /**
         * X.509 Certificate
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.Certificate
         */
        Certificate: Certificate,
        /**
         * PKCS#10 Certification request definition
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.CertificationRequestInfo
         */
        CertificationRequestInfo: CertificationRequestInfo,
        /**
         * PKCS#10 Certification request 
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.CertificationRequest
         */
        CertificationRequest: CertificationRequest,
        /**
         * X.509 To be signed CRL
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.TBSCertList
         */
        TBSCertList: TBSCertList,
        /**
         * X.509 CRL
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.CertificateList
         */
        CertificateList: CertificateList,
        /**
         * X.509 Attribute Certificate definition
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.AttributeCertificateInfo
         */
        AttributeCertificateInfo: AttributeCertificateInfo,
        /**
         * X.509 Attribute Certificate
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.AttributeCertificate
         */
        AttributeCertificate: AttributeCertificate,
        /**
         * CMS Signed Attributes
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.SignedAttributes
         */
        SignedAttributes: SignedAttributes,
        /**
         * CMS Unsigned Attributes
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.UnsignedAttributes
         */
        UnsignedAttributes: UnsignedAttributes,
        /**
         * CMS Content definition
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.ContentInfo
         */
        ContentInfo: ContentInfo,
        /**
         * PKCS#12 Safe Contents
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.SafeContents
         */
        SafeContents: SafeContents,
        /**
         * PKCS#12 Authenticated Safe
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.AuthenticatedSafe
         */
        AuthenticatedSafe: AuthenticatedSafe,
        /**
         * PKCS#12 Personal Information Exchange (PFX)
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.PFX
         */
        PFX: PFX,
        /**
         * PKI Request
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.PKIData
         */
        PKIData: PKIData,
        /**
         * PKI Response
         * 
         * @memberOf GostASN1
         * @instance
         * @type GostASN1.PKIResponse
         */
        PKIResponse: PKIResponse
    };

    /**
     * PKCS ASN.1 message syntax and converters  
     * 
     * @memberOf gostCrypto
     * @type GostASN1
     */
    gostCrypto.asn1 = new GostASN1();

    return GostASN1;

}));