[Pkg-javascript-commits] [node-asn1.js] 03/202: der: encoder
Bastien Roucariès
rouca at moszumanska.debian.org
Thu Apr 20 19:18:49 UTC 2017
This is an automated email from the git hooks/post-receive script.
rouca pushed a commit to branch master
in repository node-asn1.js.
commit e7c2bb12da23ad45fae136a51b1592371a40d8ff
Author: Fedor Indutny <fedor.indutny at gmail.com>
Date: Sun Dec 1 22:31:22 2013 +0400
der: encoder
---
lib/asn1.js | 1 +
lib/asn1/base/buffer.js | 54 ++++-
lib/asn1/base/index.js | 3 +-
lib/asn1/base/node.js | 382 +++++++++++++++++++++-----------
lib/asn1/constants/der.js | 42 ++++
lib/asn1/constants/index.js | 19 ++
lib/asn1/decoders/der.js | 84 +++----
lib/asn1/encoders/der.js | 193 ++++++++++++++++
lib/asn1/encoders/index.js | 3 +
test/ping-pong-test.js | 115 ++++++++++
test/{basic-test.js => rfc2560-test.js} | 5 +-
11 files changed, 711 insertions(+), 190 deletions(-)
diff --git a/lib/asn1.js b/lib/asn1.js
index d4a1ce5..7dc4b33 100644
--- a/lib/asn1.js
+++ b/lib/asn1.js
@@ -2,6 +2,7 @@ var asn1 = exports;
asn1.define = require('./asn1/api').define;
asn1.base = require('./asn1/base');
+asn1.constants = require('./asn1/constants');
asn1.decoders = require('./asn1/decoders');
asn1.encoders = require('./asn1/encoders');
asn1.modules = require('./modules');
diff --git a/lib/asn1/base/buffer.js b/lib/asn1/base/buffer.js
index bab837f..30d9a8d 100644
--- a/lib/asn1/base/buffer.js
+++ b/lib/asn1/base/buffer.js
@@ -8,7 +8,7 @@ function DecoderBuffer(base) {
this.offset = 0;
this.length = base.length;
}
-module.exports = DecoderBuffer;
+exports.DecoderBuffer = DecoderBuffer;
DecoderBuffer.prototype.save = function save() {
return { offset: this.offset };
@@ -46,3 +46,55 @@ DecoderBuffer.prototype.skip = function skip(bytes) {
DecoderBuffer.prototype.raw = function raw() {
return this.base.slice(this.offset, this.length);
}
+
+function EncoderBuffer(value) {
+ if (Array.isArray(value)) {
+ this.length = 0;
+ this.value = value.map(function(item) {
+ if (!(item instanceof EncoderBuffer))
+ item = new EncoderBuffer(item);
+ this.length += item.length;
+ return item;
+ }, this);
+ } else if (typeof value === 'number') {
+ assert(0 <= value && value <= 0xff, 'non-byte EncoderBuffer value');
+ this.value = value;
+ this.length = 1;
+ } else if (typeof value === 'string') {
+ this.value = value;
+ this.length = Buffer.byteLength(value);
+ } else if (Buffer.isBuffer(value)) {
+ this.value = value;
+ this.length = value.length;
+ } else {
+ assert(0, 'Unsupported type: ' + typeof value);
+ }
+}
+exports.EncoderBuffer = EncoderBuffer;
+
+EncoderBuffer.prototype.join = function join(out, offset) {
+ if (!out)
+ out = new Buffer(this.length);
+ if (!offset)
+ offset = 0;
+
+ if (this.length === 0)
+ return out;
+
+ if (Array.isArray(this.value)) {
+ this.value.forEach(function(item) {
+ item.join(out, offset);
+ offset += item.length;
+ });
+ } else {
+ if (typeof this.value === 'number')
+ out[offset] = this.value;
+ else if (typeof this.value === 'string')
+ out.write(this.value, offset);
+ else if (Buffer.isBuffer(this.value))
+ this.value.copy(out, offset);
+ offset += this.length;
+ }
+
+ return out;
+};
diff --git a/lib/asn1/base/index.js b/lib/asn1/base/index.js
index 9a71ab1..a46492c 100644
--- a/lib/asn1/base/index.js
+++ b/lib/asn1/base/index.js
@@ -1,4 +1,5 @@
var base = exports;
-base.DecoderBuffer = require('./buffer');
+base.DecoderBuffer = require('./buffer').DecoderBuffer;
+base.EncoderBuffer = require('./buffer').EncoderBuffer;
base.Node = require('./node');
diff --git a/lib/asn1/base/node.js b/lib/asn1/base/node.js
index d7d8a59..7686fda 100644
--- a/lib/asn1/base/node.js
+++ b/lib/asn1/base/node.js
@@ -1,29 +1,33 @@
var assert = require('assert');
+var EncoderBuffer = require('../base').EncoderBuffer;
// Supported tags
var tags = [
- 'seq', 'seqof', 'octstr', 'bitstr', 'objid',
+ 'seq', 'seqof', 'set', 'setof', 'octstr', 'bitstr', 'objid', 'bool',
'gentime', 'utctime', 'null_', 'enum', 'int'
];
// Public methods list
var methods = [
- 'key', 'obj', 'use', 'optional', 'explicit', 'implicit', 'def', 'choice'
+ 'key', 'obj', 'use', 'optional', 'explicit', 'implicit', 'def', 'choice',
+ 'any'
].concat(tags);
// Overrided methods list
var overrided = [
- '_peekTag', '_execTag', '_execUse',
- '_execStr', '_execObjid', '_execTime', '_execNull', '_execInt', '_execBool',
- '_execOf'
+ '_peekTag', '_decodeTag', '_use',
+ '_decodeStr', '_decodeObjid', '_decodeTime',
+ '_decodeNull', '_decodeInt', '_decodeBool', '_decodeList',
+
+ '_encodeComposite', '_encodeStr', '_encodeObjid', '_encodeTime',
+ '_encodeNull', '_encodeInt', '_encodeBool'
];
-function Node(enc, type, parent) {
+function Node(enc, parent) {
var state = {};
this._baseState = state;
state.enc = enc;
- state.type = type;
state.parent = parent || null;
state.children = null;
@@ -31,6 +35,7 @@ function Node(enc, type, parent) {
// State
state.tag = null;
state.args = null;
+ state.reverseArgs = null;
state.choice = null;
state.optional = false;
state.any = false;
@@ -96,131 +101,26 @@ Node.prototype._useArgs = function useArgs(args) {
if (args.length !== 0) {
assert(state.args === null);
state.args = args;
- }
-};
-
-// Execute node
-Node.prototype._exec = function exec(input, obj) {
- var state = this._baseState;
-
- // Exec root node
- if (state.parent === null)
- return state.children[0]._exec(input);
-
- var result = state['default'];
- var present = true;
-
- // Check if tag is there
- if (state.optional) {
- present = this._peekTag(
- input,
- state.explicit !== null ? state.explicit :
- state.implicit !== null ? state.implicit :
- state.tag || 0
- );
- }
-
- // Push object on stack
- if (state.obj && present) {
- var prevObj = obj;
- obj = {};
- }
-
- if (present) {
- // Unwrap explicit values
- if (state.explicit !== null)
- input = this._execTag(input, state.explicit);
-
- // Unwrap implicit and normal values
- if (state.use === null && !state.any && state.choice === null) {
- input = this._execTag(
- input,
- state.implicit !== null ? state.implicit : state.tag,
- state.any
- );
- }
-
- // Select proper method for tag
- if (state.any)
- result = input.raw();
- if (state.choice !== null)
- result = this._execChoice(input, obj);
- else
- result = this._execByTag(state.tag, input);
-
- // Execute children
- if (!state.any && state.choice === null && state.children !== null) {
- state.children.forEach(function execChildren(child) {
- child._exec(input, obj);
+ state.reverseArgs = args.map(function(arg) {
+ if (typeof arg !== 'object' || arg.constructor !== Object)
+ return arg;
+
+ var res = {};
+ Object.keys(arg).forEach(function(key) {
+ if (key == (key | 0))
+ key |= 0;
+ var value = arg[key];
+ res[value] = key;
});
- }
- }
-
- // Pop object
- if (state.obj && present) {
- result = obj;
- obj = prevObj;
+ return res;
+ });
}
-
- // Set key
- if (state.key !== null)
- obj[state.key] = result;
-
- return result;
-};
-
-Node.prototype._execChoice = function execChoice(input, obj) {
- var state = this._baseState;
- var result = null;
- var match = false;
-
- Object.keys(state.choice).some(function(key) {
- var save = input.save();
- var node = state.choice[key];
- try {
- result = { type: key, value: node._exec(input, obj) };
- match = true;
- } catch (e) {
- input.restore(save);
- return false;
- }
- return true;
- }, this);
-
- assert(match, 'Choice not matched');
- return result;
-};
-
-Node.prototype._execByTag = function execByTag(tag, input) {
- var state = this._baseState;
-
- if (tag === 'seq' || tag === 'set')
- return null;
- if (tag === 'seqof' || tag === 'setof')
- return this._execOf(input, tag, state.args[0]);
- else if (tag === 'octstr' || tag === 'bitstr')
- return this._execStr(input, tag);
- else if (tag === 'objid' && state.args)
- return this._execObjid(input, state.args[0], state.args[1]);
- else if (tag === 'objid')
- return this._execObjid(input, null, null);
- else if (tag === 'gentime')
- return this._execTime(input, tag);
- else if (tag === 'null_')
- return this._execNull(input);
- else if (tag === 'bool')
- return this._execBool(input);
- else if (tag === 'int' || tag === 'enum')
- return this._execInt(input, state.args && state.args[0]);
- else if (state.use !== null)
- return this._execUse(input, state.use);
- else
- assert(false, 'unknown tag: ' + tag);
-
- return null;
};
+//
// Overrided methods
+//
+
overrided.forEach(function(method) {
Node.prototype[method] = function _overrided() {
var state = this._baseState;
@@ -228,7 +128,9 @@ overrided.forEach(function(method) {
};
});
+//
// Public methods
+//
tags.forEach(function(tag) {
Node.prototype[tag] = function _tagMethod() {
@@ -329,3 +231,229 @@ Node.prototype.choice = function choice(obj) {
return this;
};
+
+//
+// Decoding
+//
+
+Node.prototype._decode = function decode(input, obj) {
+ var state = this._baseState;
+
+ // Decode root node
+ if (state.parent === null)
+ return state.children[0]._decode(input);
+
+ var result = state['default'];
+ var present = true;
+
+ // Check if tag is there
+ if (state.optional) {
+ present = this._peekTag(
+ input,
+ state.explicit !== null ? state.explicit :
+ state.implicit !== null ? state.implicit :
+ state.tag || 0
+ );
+ }
+
+ // Push object on stack
+ if (state.obj && present) {
+ var prevObj = obj;
+ obj = {};
+ }
+
+ if (present) {
+ // Unwrap explicit values
+ if (state.explicit !== null)
+ input = this._decodeTag(input, state.explicit);
+
+ // Unwrap implicit and normal values
+ if (state.use === null && !state.any && state.choice === null) {
+ input = this._decodeTag(
+ input,
+ state.implicit !== null ? state.implicit : state.tag,
+ state.any
+ );
+ }
+
+ // Select proper method for tag
+ if (state.any)
+ result = input.raw();
+ else if (state.choice === null)
+ result = this._decodeGeneric(state.tag, input);
+ else
+ result = this._decodeChoice(input, obj);
+
+ // Decode children
+ if (!state.any && state.choice === null && state.children !== null) {
+ state.children.forEach(function decodeChildren(child) {
+ child._decode(input, obj);
+ });
+ }
+ }
+
+ // Pop object
+ if (state.obj && present) {
+ result = obj;
+ obj = prevObj;
+ }
+
+ // Set key
+ if (state.key !== null)
+ obj[state.key] = result;
+
+ return result;
+};
+
+Node.prototype._decodeGeneric = function decodeGeneric(tag, input) {
+ var state = this._baseState;
+
+ if (tag === 'seq' || tag === 'set')
+ return null;
+ if (tag === 'seqof' || tag === 'setof')
+ return this._decodeList(input, tag, state.args[0]);
+ else if (tag === 'octstr' || tag === 'bitstr')
+ return this._decodeStr(input, tag);
+ else if (tag === 'objid' && state.args)
+ return this._decodeObjid(input, state.args[0], state.args[1]);
+ else if (tag === 'objid')
+ return this._decodeObjid(input, null, null);
+ else if (tag === 'gentime')
+ return this._decodeTime(input, tag);
+ else if (tag === 'null_')
+ return this._decodeNull(input);
+ else if (tag === 'bool')
+ return this._decodeBool(input);
+ else if (tag === 'int' || tag === 'enum')
+ return this._decodeInt(input, state.args && state.args[0]);
+ else if (state.use !== null)
+ return this._use(input, state.use);
+ else
+ assert(false, 'unknown tag: ' + tag);
+
+ return null;
+};
+
+Node.prototype._decodeChoice = function decodeChoice(input, obj) {
+ var state = this._baseState;
+ var result = null;
+ var match = false;
+
+ Object.keys(state.choice).some(function(key) {
+ var save = input.save();
+ var node = state.choice[key];
+ try {
+ result = { type: key, value: node._decode(input, obj) };
+ match = true;
+ } catch (e) {
+ input.restore(save);
+ return false;
+ }
+ return true;
+ }, this);
+
+ assert(match, 'Choice not matched');
+ return result;
+};
+
+//
+// Encoding
+//
+
+Node.prototype._encode = function encode(data) {
+ var state = this._baseState;
+
+ // Decode root node
+ if (state.parent === null)
+ return state.children[0]._encode(data);
+
+ var result = null;
+ var present = true;
+
+ // Check if data is there
+ if (state.optional && data === undefined) {
+ if (state['default'] !== null)
+ data = state['default']
+ else
+ return;
+ }
+
+ // Encode children first
+ var content = null;
+ var primitive = false;
+ if (state.any) {
+ // Anything that was given is translated to buffer
+ result = new EncoderBuffer(data);
+ } else if (state.children) {
+ content = state.children.map(function(child) {
+ assert(child._baseState.key, 'Child should have a key');
+ return child._encode(data[child._baseState.key]);
+ }, this).filter(function(child) {
+ return child;
+ });
+
+ content = new EncoderBuffer(content);
+ } else {
+ if (state.choice === null) {
+ if (state.tag === 'seqof' || state.tag === 'setof') {
+ assert(state.args && state.args.length === 1);
+ assert(Array.isArray(data), 'seqof/setof, but data is not Array');
+
+ content = new EncoderBuffer(data.map(function(item) {
+ return this._use(state.args[0], item);
+ }, this));
+ } else if (state.use !== null) {
+ content = this._use(state.use, data);
+ } else {
+ content = this._encodePrimitive(state.tag, data);
+ primitive = true;
+ }
+ } else {
+ result = this._encodeChoice(data);
+ }
+ }
+
+ // Encode data itself
+ var result;
+ if (!state.any && state.choice === null) {
+ result = this._encodeComposite(
+ state.implicit !== null ? state.implicit : state.tag,
+ primitive,
+ content
+ );
+ }
+
+ // Wrap in explicit
+ if (state.explicit !== null)
+ result = this._encodeComposite(state.explicit, false, result);
+
+ return result;
+};
+
+Node.prototype._encodeChoice = function encodeChoice(data) {
+ var state = this._baseState;
+
+ var node = state.choice[data.type];
+ return node._encode(data.value);
+};
+
+Node.prototype._encodePrimitive = function encodePrimitive(tag, data) {
+ var state = this._baseState;
+
+ if (tag === 'octstr' || tag === 'bitstr')
+ return this._encodeStr(data, tag);
+ else if (tag === 'objid' && state.args)
+ return this._encodeObjid(data, state.reverseArgs[0], state.args[1]);
+ else if (tag === 'objid')
+ return this._encodeObjid(data, null, null);
+ else if (tag === 'gentime' || tag === 'utctime')
+ return this._encodeTime(data, tag);
+ else if (tag === 'null_')
+ return this._encodeNull();
+ else if (tag === 'int' || tag === 'enum')
+ return this._encodeInt(data, state.args && state.reverseArgs[0]);
+ else if (tag === 'bool')
+ return this._encodeBool(data);
+ else
+ throw new Error('Unsupported tag: ' + tag);
+};
diff --git a/lib/asn1/constants/der.js b/lib/asn1/constants/der.js
new file mode 100644
index 0000000..907dd39
--- /dev/null
+++ b/lib/asn1/constants/der.js
@@ -0,0 +1,42 @@
+var constants = require('../constants');
+
+exports.tagClass = {
+ 0: 'universal',
+ 1: 'application',
+ 2: 'context',
+ 3: 'private'
+};
+exports.tagClassByName = constants._reverse(exports.tagClass);
+
+exports.tag = {
+ 0x00: 'end',
+ 0x01: 'bool',
+ 0x02: 'int',
+ 0x03: 'bitstr',
+ 0x04: 'octstr',
+ 0x05: 'null_',
+ 0x06: 'objid',
+ 0x07: 'objDesc',
+ 0x08: 'external',
+ 0x09: 'real',
+ 0x0a: 'enum',
+ 0x0b: 'embed',
+ 0x0c: 'utf8str',
+ 0x0d: 'relativeOid',
+ 0x10: 'seq',
+ 0x11: 'set',
+ 0x12: 'numstr',
+ 0x13: 'printstr',
+ 0x14: 't61str',
+ 0x15: 'videostr',
+ 0x16: 'ia5str',
+ 0x17: 'utctime',
+ 0x18: 'gentime',
+ 0x19: 'graphstr',
+ 0x1a: 'iso646str',
+ 0x1b: 'genstr',
+ 0x1c: 'unistr',
+ 0x1d: 'charstr',
+ 0x1e: 'bmpstr'
+};
+exports.tagByName = constants._reverse(exports.tag);
diff --git a/lib/asn1/constants/index.js b/lib/asn1/constants/index.js
new file mode 100644
index 0000000..c44e325
--- /dev/null
+++ b/lib/asn1/constants/index.js
@@ -0,0 +1,19 @@
+var constants = exports;
+
+// Helper
+constants._reverse = function reverse(map) {
+ var res = {};
+
+ Object.keys(map).forEach(function(key) {
+ // Convert key to integer if it is stringified
+ if ((key | 0) == key)
+ key = key | 0;
+
+ var value = map[key];
+ res[value] = key;
+ });
+
+ return res;
+};
+
+constants.der = require('./der');
diff --git a/lib/asn1/decoders/der.js b/lib/asn1/decoders/der.js
index 5ef28e8..f21359e 100644
--- a/lib/asn1/decoders/der.js
+++ b/lib/asn1/decoders/der.js
@@ -4,6 +4,9 @@ var util = require('util');
var asn1 = require('../../asn1');
var base = asn1.base;
+// Import DER constants
+var der = asn1.constants.der;
+
function DERDecoder(entity) {
this.enc = 'der';
this.name = entity.name;
@@ -19,13 +22,14 @@ DERDecoder.prototype.decode = function decode(data) {
if (!(data instanceof base.DecoderBuffer))
data = new base.DecoderBuffer(data);
- return this.tree._exec(data);
+ debugger;
+ return this.tree._decode(data);
};
// Tree methods
function DERNode(parent) {
- base.Node.call(this, 'der', 'decoder', parent);
+ base.Node.call(this, 'der', parent);
}
util.inherits(DERNode, base.Node);
@@ -34,15 +38,15 @@ DERNode.prototype._peekTag = function peekTag(buffer, tag) {
return false;
var state = buffer.save();
- var decodedTag = decodeTag(buffer);
+ var decodedTag = derDecodeTag(buffer);
buffer.restore(state);
return decodedTag.tag === tag || decodedTag.tagStr === tag;
};
-DERNode.prototype._execTag = function execTag(buffer, tag, any) {
- var decodedTag = decodeTag(buffer);
- var len = decodeLen(buffer, decodedTag.primitive);
+DERNode.prototype._decodeTag = function decodeTag(buffer, tag, any) {
+ var decodedTag = derDecodeTag(buffer);
+ var len = derDecodeLen(buffer, decodedTag.primitive);
if (!any) {
assert(decodedTag.tag === tag ||
@@ -62,8 +66,8 @@ DERNode.prototype._execTag = function execTag(buffer, tag, any) {
DERNode.prototype._skipUntilEnd = function skipUntilEnd(buffer) {
while (true) {
- var tag = decodeTag(buffer);
- var len = decodeLen(buffer, tag.primitive);
+ var tag = derDecodeTag(buffer);
+ var len = derDecodeLen(buffer, tag.primitive);
if (tag.primitive || len !== null)
buffer.skip(len)
@@ -75,7 +79,7 @@ DERNode.prototype._skipUntilEnd = function skipUntilEnd(buffer) {
}
};
-DERNode.prototype._execOf = function execOf(buffer, tag, decoder) {
+DERNode.prototype._decodeList = function decodeList(buffer, tag, decoder) {
var result = [];
while (!buffer.isEmpty()) {
try {
@@ -89,11 +93,11 @@ DERNode.prototype._execOf = function execOf(buffer, tag, decoder) {
return result;
};
-DERNode.prototype._execStr = function execStr(buffer, tag) {
+DERNode.prototype._decodeStr = function decodeStr(buffer, tag) {
return buffer.raw();
};
-DERNode.prototype._execObjid = function execObjid(buffer, values, relative) {
+DERNode.prototype._decodeObjid = function decodeObjid(buffer, values, relative) {
var identifiers = [];
var ident = 0;
while (!buffer.isEmpty()) {
@@ -122,9 +126,10 @@ DERNode.prototype._execObjid = function execObjid(buffer, values, relative) {
return result;
};
-DERNode.prototype._execTime = function execTime(buffer, tag) {
+DERNode.prototype._decodeTime = function decodeTime(buffer, tag) {
assert.equal(tag, 'gentime');
+ // TODO(indutny): verify in spec
var str = buffer.raw().toString();
var year = str.slice(0, 4) | 0;
var mon = str.slice(4, 6) | 0;
@@ -136,15 +141,15 @@ DERNode.prototype._execTime = function execTime(buffer, tag) {
return Date.UTC(year, mon - 1, day, hour, min, sec, 0);
};
-DERNode.prototype._execNull = function execNull(buffer) {
+DERNode.prototype._decodeNull = function decodeNull(buffer) {
return null;
};
-DERNode.prototype._execBool = function execBool(buffer) {
+DERNode.prototype._decodeBool = function decodeBool(buffer) {
return buffer.readUInt8() !== 0;
};
-DERNode.prototype._execInt = function execInt(buffer, values) {
+DERNode.prototype._decodeInt = function decodeInt(buffer, values) {
var res = 0;
while (!buffer.isEmpty()) {
res <<= 8;
@@ -156,55 +161,16 @@ DERNode.prototype._execInt = function execInt(buffer, values) {
return res;
};
-DERNode.prototype._execUse = function execUse(buffer, decoder) {
+DERNode.prototype._use = function use(buffer, decoder) {
return decoder.decode(buffer, 'der');
};
// Utility methods
-var tagClasses = {
- 0: 'universal',
- 1: 'application',
- 2: 'context',
- 3: 'private'
-};
-
-var tags = {
- 0x00: 'end',
- 0x01: 'bool',
- 0x02: 'int',
- 0x03: 'bitstr',
- 0x04: 'octstr',
- 0x05: 'null',
- 0x06: 'objid',
- 0x07: 'objDesc',
- 0x08: 'external',
- 0x09: 'real',
- 0x0a: 'enum',
- 0x0b: 'embed',
- 0x0c: 'utf8str',
- 0x0d: 'relativeOid',
- 0x10: 'seq',
- 0x11: 'set',
- 0x12: 'numstr',
- 0x13: 'printstr',
- 0x14: 't61str',
- 0x15: 'videostr',
- 0x16: 'ia5str',
- 0x17: 'utctime',
- 0x18: 'gentime',
- 0x19: 'graphstr',
- 0x1a: 'iso646str',
- 0x1b: 'genstr',
- 0x1c: 'unistr',
- 0x1d: 'charstr',
- 0x1e: 'bmpstr'
-};
-
-function decodeTag(buf) {
+function derDecodeTag(buf) {
var tag = buf.readUInt8();
- var cls = tagClasses[tag >> 6];
+ var cls = der.tagClass[tag >> 6];
var primitive = (tag & 0x20) === 0;
// Multi-octet tag - load
@@ -219,7 +185,7 @@ function decodeTag(buf) {
} else {
tag &= 0x1f;
}
- var tagStr = tags[tag];
+ var tagStr = der.tag[tag];
return {
cls: cls,
@@ -229,7 +195,7 @@ function decodeTag(buf) {
};
}
-function decodeLen(buf, primitive) {
+function derDecodeLen(buf, primitive) {
var len = buf.readUInt8();
// Indefinite form
diff --git a/lib/asn1/encoders/der.js b/lib/asn1/encoders/der.js
new file mode 100644
index 0000000..cc72d4e
--- /dev/null
+++ b/lib/asn1/encoders/der.js
@@ -0,0 +1,193 @@
+var assert = require('assert');
+var util = require('util');
+var Buffer = require('buffer').Buffer;
+
+var asn1 = require('../../asn1');
+var base = asn1.base;
+
+// Import DER constants
+var der = asn1.constants.der;
+
+function DEREncoder(entity) {
+ this.enc = 'der';
+ this.name = entity.name;
+ this.entity = entity;
+
+ // Construct base tree
+ this.tree = new DERNode();
+ this.tree._init(entity.body);
+};
+module.exports = DEREncoder;
+
+DEREncoder.prototype.encode = function encode(data) {
+ return this.tree._encode(data).join();
+};
+
+// Tree methods
+
+function DERNode(parent) {
+ base.Node.call(this, 'der', parent);
+}
+util.inherits(DERNode, base.Node);
+
+DERNode.prototype._encodeComposite = function encodeComposite(tag,
+ primitive,
+ content) {
+ var encodedTag = encodeTag(tag, primitive);
+
+ // Short form
+ if (content.length < 0x80) {
+ var header = new Buffer(2);
+ header[0] = encodedTag;
+ header[1] = content.length;
+ return new base.EncoderBuffer([ header, content ]);
+ }
+
+ // Long form
+ // Count octets required to store length
+ var lenOctets = 1;
+ for (var i = content.length; i >= 0x100; i >>= 8)
+ lenOctets++;
+
+ var header = new Buffer(1 + 1 + lenOctets);
+ header[0] = encodedTag;
+ header[1] = 0x80 | lenOctets;
+
+ for (var i = 1 + lenOctets, j = content.length; j > 0; i--, j >>= 8)
+ header[i] = j & 0xff;
+
+ return new base.EncoderBuffer([ header, content ]);
+};
+
+DERNode.prototype._encodeStr = function encodeStr(str, tag) {
+ if (tag === 'octstr')
+ return new base.EncoderBuffer(str);
+
+ // TODO(indunty): support first octet
+ else if (tag === 'bitstr')
+ return new base.EncoderBuffer([ 0, str ]);
+};
+
+DERNode.prototype._encodeObjid = function encodeObjid(id, values, relative) {
+ if (typeof id === 'string') {
+ assert(values, 'string objid given, but no values map found');
+ assert(values.hasOwnProperty(id), 'objid not found in values map');
+ id = values[id].split(/\s+/g);
+ for (var i = 0; i < id.length; i++)
+ id[i] |= 0;
+ }
+
+ assert(Array.isArray(id));
+ if (!relative) {
+ assert(id[1] < 40, 'Second objid identifier OOB');
+ id.splice(0, 2, id[0] * 40 + id[1]);
+ }
+
+ // Count number of octets
+ var size = 0;
+ for (var i = 0; i < id.length; i++) {
+ var ident = id[i];
+ for (size++; ident >= 0x80; ident >>= 7)
+ size++;
+ }
+
+ var objid = new Buffer(size);
+ var offset = objid.length - 1;
+ for (var i = id.length - 1; i >= 0; i--) {
+ var ident = id[i];
+ objid[offset--] = ident & 0x7f;
+ while ((ident >>= 7) > 0)
+ objid[offset--] = 0x80 | (ident & 0x7f);
+ }
+
+ return new base.EncoderBuffer(objid);
+};
+
+function two(num) {
+ if (num <= 10)
+ return '0' + num;
+ else
+ return num;
+}
+
+DERNode.prototype._encodeTime = function encodeTime(time, tag) {
+ var str;
+
+ // TODO(indutny): verify in spec
+ if (tag === 'gentime') {
+ var date = new Date(time);
+
+ str = [
+ date.getFullYear(),
+ two(date.getUTCMonth() + 1),
+ two(date.getUTCDate()),
+ two(date.getUTCHours()),
+ two(date.getUTCMinutes()),
+ two(date.getUTCSeconds()),
+ 'Z'
+ ].join('');
+ } else {
+ assert(0, tag + ' time is not supported yet');
+ }
+
+ return this._encodeStr(str, 'octstr');
+};
+
+DERNode.prototype._encodeNull = function encodeNull() {
+ return new base.EncoderBuffer('');
+};
+
+DERNode.prototype._encodeInt = function encodeInt(num, values) {
+ if (typeof num === 'string') {
+ assert(values, 'String int or enum given, but no values map');
+ assert(values.hasOwnProperty(num), 'Values map doesn\'t contain number');
+ num = values[num];
+ }
+
+ var size = 1;
+ for (var i = num; i >= 0x100; i >>= 8)
+ size++;
+
+ var out = new Buffer(size);
+ for (var i = out.length - 1; i >= 0; i--) {
+ out[i] = num & 0xff;
+ num >>= 8;
+ }
+
+ return new base.EncoderBuffer(out);
+};
+
+DERNode.prototype._encodeBool = function encodeBool(value) {
+ return new base.EncoderBuffer(value ? 0xff : 0);
+};
+
+DERNode.prototype._use = function use(encoder, data) {
+ return encoder.encode(data, 'der');
+};
+
+// Utility methods
+
+function encodeTag(tag, primitive, cls) {
+ var res;
+
+ if (tag === 'seqof')
+ tag = 'seq';
+ else if (tag === 'setof')
+ tag = 'set';
+
+ if (der.tagByName.hasOwnProperty(tag))
+ res = der.tagByName[tag];
+ else if (typeof tag === 'number' && (tag | 0) === tag)
+ res = tag;
+ else
+ throw new Error('Unknown tag: ' + tag);
+
+ assert(res < 0x1f, 'Multi-octet tag encoding unsupported');
+
+ if (primitive)
+ res |= 0x20;
+
+ res |= der.tagClassByName[cls || 'universal'];
+
+ return res;
+}
diff --git a/lib/asn1/encoders/index.js b/lib/asn1/encoders/index.js
index e69de29..5b3cc38 100644
--- a/lib/asn1/encoders/index.js
+++ b/lib/asn1/encoders/index.js
@@ -0,0 +1,3 @@
+var encoders = exports;
+
+encoders.der = require('./der');
diff --git a/test/ping-pong-test.js b/test/ping-pong-test.js
new file mode 100644
index 0000000..fb652c7
--- /dev/null
+++ b/test/ping-pong-test.js
@@ -0,0 +1,115 @@
+var assert = require('assert');
+var asn1 = require('..');
+
+var Buffer = require('buffer').Buffer;
+
+describe('asn1.js ping/pong', function() {
+ function test(name, model, input, expected) {
+ it('should support ' + name, function() {
+ var M = asn1.define('TestModel', model);
+
+ var encoded = M.encode(input, 'der');
+ var decoded = M.decode(encoded, 'der');
+ assert.deepEqual(decoded, expected !== undefined ? expected : input);
+ });
+ }
+
+ describe('primitives', function() {
+ test('int', function() {
+ this.int();
+ }, 0);
+
+ test('enum', function() {
+ this.enum({ 0: 'hello', 1: 'world' });
+ }, 'world');
+
+ test('octstr', function() {
+ this.octstr();
+ }, new Buffer('hello'));
+
+ test('gentime', function() {
+ this.gentime();
+ }, 1385921175000);
+
+ test('null', function() {
+ this.null_();
+ }, null);
+
+ test('objid', function() {
+ this.objid({
+ '1 3 6 1 5 5 7 48 1 1': 'id-pkix-ocsp-basic'
+ });
+ }, 'id-pkix-ocsp-basic');
+
+ test('true', function() {
+ this.bool();
+ }, true);
+
+ test('false', function() {
+ this.bool();
+ }, false);
+
+ test('any', function() {
+ this.any();
+ }, new Buffer('ok any'));
+
+ test('default explicit', function() {
+ this.def('v1').explicit(0).int({
+ 0: 'v1',
+ 1: 'v2'
+ });
+ }, undefined, 'v1');
+
+ test('implicit', function() {
+ this.implicit(0).int({
+ 0: 'v1',
+ 1: 'v2'
+ });
+ }, 'v2', 'v2');
+ });
+
+ describe('composite', function() {
+ test('2x int', function() {
+ this.seq().obj(
+ this.key('hello').int(),
+ this.key('world').int()
+ );
+ }, { hello: 4, world: 2 });
+
+ test('enum', function() {
+ this.seq().obj(
+ this.key('hello').enum({ 0: 'world', 1: 'devs' })
+ );
+ }, { hello: 'devs' });
+
+ test('optionals', function() {
+ this.seq().obj(
+ this.key('hello').enum({ 0: 'world', 1: 'devs' }),
+ this.key('how').optional().def('are you').enum({
+ 0: 'are you',
+ 1: 'are we?!'
+ })
+ );
+ }, { hello: 'devs', how: 'are we?!' });
+
+ test('optionals #2', function() {
+ this.seq().obj(
+ this.key('hello').enum({ 0: 'world', 1: 'devs' }),
+ this.key('how').optional().def('are you').enum({
+ 0: 'are you',
+ 1: 'are we?!'
+ })
+ );
+ }, { hello: 'devs' }, { hello: 'devs', how: 'are you' });
+
+ test('setof', function() {
+ var S = asn1.define('S', function() {
+ this.seq().obj(
+ this.key('a').def('b').int({ 0: 'a', 1: 'b' }),
+ this.key('c').def('d').int({ 2: 'c', 3: 'd' })
+ );
+ });
+ this.seqof(S);
+ }, [{}, { a: 'a', c: 'c' }], [{ a: 'b', c: 'd' }, { a: 'a', c: 'c' }]);
+ });
+});
diff --git a/test/basic-test.js b/test/rfc2560-test.js
similarity index 90%
rename from test/basic-test.js
rename to test/rfc2560-test.js
index 5cf4029..9bea5c1 100644
--- a/test/basic-test.js
+++ b/test/rfc2560-test.js
@@ -3,7 +3,7 @@ var asn1 = require('..');
var Buffer = require('buffer').Buffer;
-describe('asn1.js', function() {
+describe('asn1.js RFC2560', function() {
it('should decode OCSP response', function() {
var data = new Buffer(
'308201d40a0100a08201cd308201c906092b0601050507300101048201ba308201b630' +
@@ -30,6 +30,7 @@ describe('asn1.js', function() {
res.responseBytes.response,
'der'
);
- console.log(require('util').inspect(basic, false, 300));
+ assert.equal(basic.tbsResponseData.version, 'v1');
+ assert.equal(basic.tbsResponseData.producedAt, 1385797510000);
});
});
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-asn1.js.git
More information about the Pkg-javascript-commits
mailing list