You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					318 lines
				
				7.5 KiB
			
		
		
			
		
	
	
					318 lines
				
				7.5 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var assert = require('assert');
							 | 
						||
| 
								 | 
							
								var Buffer = require('safer-buffer').Buffer;
							 | 
						||
| 
								 | 
							
								var ASN1 = require('./types');
							 | 
						||
| 
								 | 
							
								var errors = require('./errors');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// --- Globals
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var newInvalidAsn1Error = errors.newInvalidAsn1Error;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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 newInvalidAsn1Error(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 (typeof (i) !== 'number')
							 | 
						||
| 
								 | 
							
								    throw new TypeError('argument must be a Number');
							 | 
						||
| 
								 | 
							
								  if (typeof (tag) !== 'number')
							 | 
						||
| 
								 | 
							
								    tag = ASN1.Integer;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var sz = 4;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000 >> 0)) &&
							 | 
						||
| 
								 | 
							
								        (sz > 1)) {
							 | 
						||
| 
								 | 
							
								    sz--;
							 | 
						||
| 
								 | 
							
								    i <<= 8;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (sz > 4)
							 | 
						||
| 
								 | 
							
								    throw newInvalidAsn1Error('BER ints cannot be > 0xffffffff');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this._ensure(2 + sz);
							 | 
						||
| 
								 | 
							
								  this._buf[this._offset++] = tag;
							 | 
						||
| 
								 | 
							
								  this._buf[this._offset++] = sz;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while (sz-- > 0) {
							 | 
						||
| 
								 | 
							
								    this._buf[this._offset++] = ((i & 0xff000000) >>> 24);
							 | 
						||
| 
								 | 
							
								    i <<= 8;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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 (typeof (tag) !== 'number')
							 | 
						||
| 
								 | 
							
								    throw new TypeError('tag must be a number');
							 | 
						||
| 
								 | 
							
								  if (!Buffer.isBuffer(buf))
							 | 
						||
| 
								 | 
							
								    throw new TypeError('argument must be a buffer');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this.writeByte(tag);
							 | 
						||
| 
								 | 
							
								  this.writeLength(buf.length);
							 | 
						||
| 
								 | 
							
								  this._ensure(buf.length);
							 | 
						||
| 
								 | 
							
								  buf.copy(this._buf, this._offset, 0, buf.length);
							 | 
						||
| 
								 | 
							
								  this._offset += buf.length;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Writer.prototype.writeStringArray = function (strings) {
							 | 
						||
| 
								 | 
							
								  if ((!strings instanceof Array))
							 | 
						||
| 
								 | 
							
								    throw new TypeError('argument must be an Array[String]');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var self = this;
							 | 
						||
| 
								 | 
							
								  strings.forEach(function (s) {
							 | 
						||
| 
								 | 
							
								    self.writeString(s);
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// 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]+\.){3,}[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 newInvalidAsn1Error('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 newInvalidAsn1Error('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;
							 |