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
JavaScript
/**
* @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