UNPKG

node-gost-crypto

Version:

Node.js implementation of WebCrypto API interfaces and Public Key Infrastructure for GOST algorithms (Russian Cryptographic Standards)

1,326 lines (1,223 loc) 189 kB
/** * @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. * */ const gostCrypto = require('./gostCrypto'); /* * Service functions * */ var root = global; 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); } /* * Base ASN.1 types and definitions * */ // 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; } } }); }; /* * Certificate Version, Name, Attributes, Validity * * http://tools.ietf.org/html/rfc5280 * */ 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 AttributeVal