123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
-
- var assert = require('assert');
- var ASN1 = require('./types');
- var errors = require('./errors');
-
-
- ///--- Globals
-
- var InvalidAsn1Error = errors.InvalidAsn1Error;
-
- var DEFAULT_OPTS = {
- size: 1024,
- growthFactor: 8
- };
-
-
- ///--- Helpers
-
- function merge(from, to) {
- assert.ok(from);
- assert.equal(typeof(from), 'object');
- assert.ok(to);
- assert.equal(typeof(to), 'object');
-
- var keys = Object.getOwnPropertyNames(from);
- keys.forEach(function(key) {
- if (to[key])
- return;
-
- var value = Object.getOwnPropertyDescriptor(from, key);
- Object.defineProperty(to, key, value);
- });
-
- return to;
- }
-
-
-
- ///--- API
-
- function Writer(options) {
- options = merge(DEFAULT_OPTS, options || {});
-
- this._buf = Buffer.alloc(options.size || 1024);
- this._size = this._buf.length;
- this._offset = 0;
- this._options = options;
-
- // A list of offsets in the buffer where we need to insert
- // sequence tag/len pairs.
- this._seq = [];
- }
-
- Object.defineProperty(Writer.prototype, 'buffer', {
- get: function () {
- if (this._seq.length)
- throw new InvalidAsn1Error(this._seq.length + ' unended sequence(s)');
-
- return (this._buf.slice(0, this._offset));
- }
- });
-
- Writer.prototype.writeByte = function(b) {
- if (typeof(b) !== 'number')
- throw new TypeError('argument must be a Number');
-
- this._ensure(1);
- this._buf[this._offset++] = b;
- };
-
- Writer.prototype.writeInt = function(i, tag) {
- if (!Number.isInteger(i))
- throw new TypeError('argument must be an integer');
- if (typeof(tag) !== 'number')
- tag = ASN1.Integer;
-
- let bytes = [];
- while (i < -0x80 || i >= 0x80) {
- bytes.push(i & 0xff);
- i = Math.floor(i / 0x100);
- }
- bytes.push(i & 0xff);
-
- this._ensure(2 + bytes.length);
- this._buf[this._offset++] = tag;
- this._buf[this._offset++] = bytes.length;
-
- while (bytes.length) {
- this._buf[this._offset++] = bytes.pop();
- }
- };
-
- Writer.prototype.writeNull = function() {
- this.writeByte(ASN1.Null);
- this.writeByte(0x00);
- };
-
-
- Writer.prototype.writeEnumeration = function(i, tag) {
- if (typeof(i) !== 'number')
- throw new TypeError('argument must be a Number');
- if (typeof(tag) !== 'number')
- tag = ASN1.Enumeration;
-
- return this.writeInt(i, tag);
- };
-
-
- Writer.prototype.writeBoolean = function(b, tag) {
- if (typeof(b) !== 'boolean')
- throw new TypeError('argument must be a Boolean');
- if (typeof(tag) !== 'number')
- tag = ASN1.Boolean;
-
- this._ensure(3);
- this._buf[this._offset++] = tag;
- this._buf[this._offset++] = 0x01;
- this._buf[this._offset++] = b ? 0xff : 0x00;
- };
-
-
- Writer.prototype.writeString = function(s, tag) {
- if (typeof(s) !== 'string')
- throw new TypeError('argument must be a string (was: ' + typeof(s) + ')');
- if (typeof(tag) !== 'number')
- tag = ASN1.OctetString;
-
- var len = Buffer.byteLength(s);
- this.writeByte(tag);
- this.writeLength(len);
- if (len) {
- this._ensure(len);
- this._buf.write(s, this._offset);
- this._offset += len;
- }
- };
-
-
- Writer.prototype.writeBuffer = function(buf, tag) {
- if (!Buffer.isBuffer(buf))
- throw new TypeError('argument must be a buffer');
-
- // If no tag is specified we will assume `buf` already contains tag and length
- if (typeof(tag) === 'number') {
- this.writeByte(tag);
- this.writeLength(buf.length);
- }
-
- if ( buf.length > 0 ) {
- this._ensure(buf.length);
- buf.copy(this._buf, this._offset, 0, buf.length);
- this._offset += buf.length;
- }
- };
-
-
- Writer.prototype.writeStringArray = function(strings, tag) {
- if (! (strings instanceof Array))
- throw new TypeError('argument must be an Array[String]');
-
- var self = this;
- strings.forEach(function(s) {
- self.writeString(s, tag);
- });
- };
-
- // This is really to solve DER cases, but whatever for now
- Writer.prototype.writeOID = function(s, tag) {
- if (typeof(s) !== 'string')
- throw new TypeError('argument must be a string');
- if (typeof(tag) !== 'number')
- tag = ASN1.OID;
-
- if (!/^([0-9]+\.){0,}[0-9]+$/.test(s))
- throw new Error('argument is not a valid OID string');
-
- function encodeOctet(bytes, octet) {
- if (octet < 128) {
- bytes.push(octet);
- } else if (octet < 16384) {
- bytes.push((octet >>> 7) | 0x80);
- bytes.push(octet & 0x7F);
- } else if (octet < 2097152) {
- bytes.push((octet >>> 14) | 0x80);
- bytes.push(((octet >>> 7) | 0x80) & 0xFF);
- bytes.push(octet & 0x7F);
- } else if (octet < 268435456) {
- bytes.push((octet >>> 21) | 0x80);
- bytes.push(((octet >>> 14) | 0x80) & 0xFF);
- bytes.push(((octet >>> 7) | 0x80) & 0xFF);
- bytes.push(octet & 0x7F);
- } else {
- bytes.push(((octet >>> 28) | 0x80) & 0xFF);
- bytes.push(((octet >>> 21) | 0x80) & 0xFF);
- bytes.push(((octet >>> 14) | 0x80) & 0xFF);
- bytes.push(((octet >>> 7) | 0x80) & 0xFF);
- bytes.push(octet & 0x7F);
- }
- }
-
- var tmp = s.split('.');
- var bytes = [];
- bytes.push(parseInt(tmp[0], 10) * 40 + parseInt(tmp[1], 10));
- tmp.slice(2).forEach(function(b) {
- encodeOctet(bytes, parseInt(b, 10));
- });
-
- var self = this;
- this._ensure(2 + bytes.length);
- this.writeByte(tag);
- this.writeLength(bytes.length);
- bytes.forEach(function(b) {
- self.writeByte(b);
- });
- };
-
-
- Writer.prototype.writeLength = function(len) {
- if (typeof(len) !== 'number')
- throw new TypeError('argument must be a Number');
-
- this._ensure(4);
-
- if (len <= 0x7f) {
- this._buf[this._offset++] = len;
- } else if (len <= 0xff) {
- this._buf[this._offset++] = 0x81;
- this._buf[this._offset++] = len;
- } else if (len <= 0xffff) {
- this._buf[this._offset++] = 0x82;
- this._buf[this._offset++] = len >> 8;
- this._buf[this._offset++] = len;
- } else if (len <= 0xffffff) {
- this._buf[this._offset++] = 0x83;
- this._buf[this._offset++] = len >> 16;
- this._buf[this._offset++] = len >> 8;
- this._buf[this._offset++] = len;
- } else {
- throw new InvalidAsn1Error('Length too long (> 4 bytes)');
- }
- };
-
- Writer.prototype.startSequence = function(tag) {
- if (typeof(tag) !== 'number')
- tag = ASN1.Sequence | ASN1.Constructor;
-
- this.writeByte(tag);
- this._seq.push(this._offset);
- this._ensure(3);
- this._offset += 3;
- };
-
-
- Writer.prototype.endSequence = function() {
- var seq = this._seq.pop();
- var start = seq + 3;
- var len = this._offset - start;
-
- if (len <= 0x7f) {
- this._shift(start, len, -2);
- this._buf[seq] = len;
- } else if (len <= 0xff) {
- this._shift(start, len, -1);
- this._buf[seq] = 0x81;
- this._buf[seq + 1] = len;
- } else if (len <= 0xffff) {
- this._buf[seq] = 0x82;
- this._buf[seq + 1] = len >> 8;
- this._buf[seq + 2] = len;
- } else if (len <= 0xffffff) {
- this._shift(start, len, 1);
- this._buf[seq] = 0x83;
- this._buf[seq + 1] = len >> 16;
- this._buf[seq + 2] = len >> 8;
- this._buf[seq + 3] = len;
- } else {
- throw new InvalidAsn1Error('Sequence too long');
- }
- };
-
-
- Writer.prototype._shift = function(start, len, shift) {
- assert.ok(start !== undefined);
- assert.ok(len !== undefined);
- assert.ok(shift);
-
- this._buf.copy(this._buf, start + shift, start, start + len);
- this._offset += shift;
- };
-
- Writer.prototype._ensure = function(len) {
- assert.ok(len);
-
- if (this._size - this._offset < len) {
- var sz = this._size * this._options.growthFactor;
- if (sz - this._offset < len)
- sz += len;
-
- var buf = Buffer.alloc(sz);
-
- this._buf.copy(buf, 0, 0, this._offset);
- this._buf = buf;
- this._size = sz;
- }
- };
-
-
-
- ///--- Exported API
-
- module.exports = Writer;
|