[6a3a178] | 1 | // Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
|
---|
| 2 |
|
---|
| 3 | var assert = require('assert');
|
---|
| 4 | var Buffer = require('safer-buffer').Buffer;
|
---|
| 5 | var ASN1 = require('./types');
|
---|
| 6 | var errors = require('./errors');
|
---|
| 7 |
|
---|
| 8 |
|
---|
| 9 | // --- Globals
|
---|
| 10 |
|
---|
| 11 | var newInvalidAsn1Error = errors.newInvalidAsn1Error;
|
---|
| 12 |
|
---|
| 13 | var DEFAULT_OPTS = {
|
---|
| 14 | size: 1024,
|
---|
| 15 | growthFactor: 8
|
---|
| 16 | };
|
---|
| 17 |
|
---|
| 18 |
|
---|
| 19 | // --- Helpers
|
---|
| 20 |
|
---|
| 21 | function merge(from, to) {
|
---|
| 22 | assert.ok(from);
|
---|
| 23 | assert.equal(typeof (from), 'object');
|
---|
| 24 | assert.ok(to);
|
---|
| 25 | assert.equal(typeof (to), 'object');
|
---|
| 26 |
|
---|
| 27 | var keys = Object.getOwnPropertyNames(from);
|
---|
| 28 | keys.forEach(function (key) {
|
---|
| 29 | if (to[key])
|
---|
| 30 | return;
|
---|
| 31 |
|
---|
| 32 | var value = Object.getOwnPropertyDescriptor(from, key);
|
---|
| 33 | Object.defineProperty(to, key, value);
|
---|
| 34 | });
|
---|
| 35 |
|
---|
| 36 | return to;
|
---|
| 37 | }
|
---|
| 38 |
|
---|
| 39 |
|
---|
| 40 |
|
---|
| 41 | // --- API
|
---|
| 42 |
|
---|
| 43 | function Writer(options) {
|
---|
| 44 | options = merge(DEFAULT_OPTS, options || {});
|
---|
| 45 |
|
---|
| 46 | this._buf = Buffer.alloc(options.size || 1024);
|
---|
| 47 | this._size = this._buf.length;
|
---|
| 48 | this._offset = 0;
|
---|
| 49 | this._options = options;
|
---|
| 50 |
|
---|
| 51 | // A list of offsets in the buffer where we need to insert
|
---|
| 52 | // sequence tag/len pairs.
|
---|
| 53 | this._seq = [];
|
---|
| 54 | }
|
---|
| 55 |
|
---|
| 56 | Object.defineProperty(Writer.prototype, 'buffer', {
|
---|
| 57 | get: function () {
|
---|
| 58 | if (this._seq.length)
|
---|
| 59 | throw newInvalidAsn1Error(this._seq.length + ' unended sequence(s)');
|
---|
| 60 |
|
---|
| 61 | return (this._buf.slice(0, this._offset));
|
---|
| 62 | }
|
---|
| 63 | });
|
---|
| 64 |
|
---|
| 65 | Writer.prototype.writeByte = function (b) {
|
---|
| 66 | if (typeof (b) !== 'number')
|
---|
| 67 | throw new TypeError('argument must be a Number');
|
---|
| 68 |
|
---|
| 69 | this._ensure(1);
|
---|
| 70 | this._buf[this._offset++] = b;
|
---|
| 71 | };
|
---|
| 72 |
|
---|
| 73 |
|
---|
| 74 | Writer.prototype.writeInt = function (i, tag) {
|
---|
| 75 | if (typeof (i) !== 'number')
|
---|
| 76 | throw new TypeError('argument must be a Number');
|
---|
| 77 | if (typeof (tag) !== 'number')
|
---|
| 78 | tag = ASN1.Integer;
|
---|
| 79 |
|
---|
| 80 | var sz = 4;
|
---|
| 81 |
|
---|
| 82 | while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000 >> 0)) &&
|
---|
| 83 | (sz > 1)) {
|
---|
| 84 | sz--;
|
---|
| 85 | i <<= 8;
|
---|
| 86 | }
|
---|
| 87 |
|
---|
| 88 | if (sz > 4)
|
---|
| 89 | throw newInvalidAsn1Error('BER ints cannot be > 0xffffffff');
|
---|
| 90 |
|
---|
| 91 | this._ensure(2 + sz);
|
---|
| 92 | this._buf[this._offset++] = tag;
|
---|
| 93 | this._buf[this._offset++] = sz;
|
---|
| 94 |
|
---|
| 95 | while (sz-- > 0) {
|
---|
| 96 | this._buf[this._offset++] = ((i & 0xff000000) >>> 24);
|
---|
| 97 | i <<= 8;
|
---|
| 98 | }
|
---|
| 99 |
|
---|
| 100 | };
|
---|
| 101 |
|
---|
| 102 |
|
---|
| 103 | Writer.prototype.writeNull = function () {
|
---|
| 104 | this.writeByte(ASN1.Null);
|
---|
| 105 | this.writeByte(0x00);
|
---|
| 106 | };
|
---|
| 107 |
|
---|
| 108 |
|
---|
| 109 | Writer.prototype.writeEnumeration = function (i, tag) {
|
---|
| 110 | if (typeof (i) !== 'number')
|
---|
| 111 | throw new TypeError('argument must be a Number');
|
---|
| 112 | if (typeof (tag) !== 'number')
|
---|
| 113 | tag = ASN1.Enumeration;
|
---|
| 114 |
|
---|
| 115 | return this.writeInt(i, tag);
|
---|
| 116 | };
|
---|
| 117 |
|
---|
| 118 |
|
---|
| 119 | Writer.prototype.writeBoolean = function (b, tag) {
|
---|
| 120 | if (typeof (b) !== 'boolean')
|
---|
| 121 | throw new TypeError('argument must be a Boolean');
|
---|
| 122 | if (typeof (tag) !== 'number')
|
---|
| 123 | tag = ASN1.Boolean;
|
---|
| 124 |
|
---|
| 125 | this._ensure(3);
|
---|
| 126 | this._buf[this._offset++] = tag;
|
---|
| 127 | this._buf[this._offset++] = 0x01;
|
---|
| 128 | this._buf[this._offset++] = b ? 0xff : 0x00;
|
---|
| 129 | };
|
---|
| 130 |
|
---|
| 131 |
|
---|
| 132 | Writer.prototype.writeString = function (s, tag) {
|
---|
| 133 | if (typeof (s) !== 'string')
|
---|
| 134 | throw new TypeError('argument must be a string (was: ' + typeof (s) + ')');
|
---|
| 135 | if (typeof (tag) !== 'number')
|
---|
| 136 | tag = ASN1.OctetString;
|
---|
| 137 |
|
---|
| 138 | var len = Buffer.byteLength(s);
|
---|
| 139 | this.writeByte(tag);
|
---|
| 140 | this.writeLength(len);
|
---|
| 141 | if (len) {
|
---|
| 142 | this._ensure(len);
|
---|
| 143 | this._buf.write(s, this._offset);
|
---|
| 144 | this._offset += len;
|
---|
| 145 | }
|
---|
| 146 | };
|
---|
| 147 |
|
---|
| 148 |
|
---|
| 149 | Writer.prototype.writeBuffer = function (buf, tag) {
|
---|
| 150 | if (typeof (tag) !== 'number')
|
---|
| 151 | throw new TypeError('tag must be a number');
|
---|
| 152 | if (!Buffer.isBuffer(buf))
|
---|
| 153 | throw new TypeError('argument must be a buffer');
|
---|
| 154 |
|
---|
| 155 | this.writeByte(tag);
|
---|
| 156 | this.writeLength(buf.length);
|
---|
| 157 | this._ensure(buf.length);
|
---|
| 158 | buf.copy(this._buf, this._offset, 0, buf.length);
|
---|
| 159 | this._offset += buf.length;
|
---|
| 160 | };
|
---|
| 161 |
|
---|
| 162 |
|
---|
| 163 | Writer.prototype.writeStringArray = function (strings) {
|
---|
| 164 | if ((!strings instanceof Array))
|
---|
| 165 | throw new TypeError('argument must be an Array[String]');
|
---|
| 166 |
|
---|
| 167 | var self = this;
|
---|
| 168 | strings.forEach(function (s) {
|
---|
| 169 | self.writeString(s);
|
---|
| 170 | });
|
---|
| 171 | };
|
---|
| 172 |
|
---|
| 173 | // This is really to solve DER cases, but whatever for now
|
---|
| 174 | Writer.prototype.writeOID = function (s, tag) {
|
---|
| 175 | if (typeof (s) !== 'string')
|
---|
| 176 | throw new TypeError('argument must be a string');
|
---|
| 177 | if (typeof (tag) !== 'number')
|
---|
| 178 | tag = ASN1.OID;
|
---|
| 179 |
|
---|
| 180 | if (!/^([0-9]+\.){3,}[0-9]+$/.test(s))
|
---|
| 181 | throw new Error('argument is not a valid OID string');
|
---|
| 182 |
|
---|
| 183 | function encodeOctet(bytes, octet) {
|
---|
| 184 | if (octet < 128) {
|
---|
| 185 | bytes.push(octet);
|
---|
| 186 | } else if (octet < 16384) {
|
---|
| 187 | bytes.push((octet >>> 7) | 0x80);
|
---|
| 188 | bytes.push(octet & 0x7F);
|
---|
| 189 | } else if (octet < 2097152) {
|
---|
| 190 | bytes.push((octet >>> 14) | 0x80);
|
---|
| 191 | bytes.push(((octet >>> 7) | 0x80) & 0xFF);
|
---|
| 192 | bytes.push(octet & 0x7F);
|
---|
| 193 | } else if (octet < 268435456) {
|
---|
| 194 | bytes.push((octet >>> 21) | 0x80);
|
---|
| 195 | bytes.push(((octet >>> 14) | 0x80) & 0xFF);
|
---|
| 196 | bytes.push(((octet >>> 7) | 0x80) & 0xFF);
|
---|
| 197 | bytes.push(octet & 0x7F);
|
---|
| 198 | } else {
|
---|
| 199 | bytes.push(((octet >>> 28) | 0x80) & 0xFF);
|
---|
| 200 | bytes.push(((octet >>> 21) | 0x80) & 0xFF);
|
---|
| 201 | bytes.push(((octet >>> 14) | 0x80) & 0xFF);
|
---|
| 202 | bytes.push(((octet >>> 7) | 0x80) & 0xFF);
|
---|
| 203 | bytes.push(octet & 0x7F);
|
---|
| 204 | }
|
---|
| 205 | }
|
---|
| 206 |
|
---|
| 207 | var tmp = s.split('.');
|
---|
| 208 | var bytes = [];
|
---|
| 209 | bytes.push(parseInt(tmp[0], 10) * 40 + parseInt(tmp[1], 10));
|
---|
| 210 | tmp.slice(2).forEach(function (b) {
|
---|
| 211 | encodeOctet(bytes, parseInt(b, 10));
|
---|
| 212 | });
|
---|
| 213 |
|
---|
| 214 | var self = this;
|
---|
| 215 | this._ensure(2 + bytes.length);
|
---|
| 216 | this.writeByte(tag);
|
---|
| 217 | this.writeLength(bytes.length);
|
---|
| 218 | bytes.forEach(function (b) {
|
---|
| 219 | self.writeByte(b);
|
---|
| 220 | });
|
---|
| 221 | };
|
---|
| 222 |
|
---|
| 223 |
|
---|
| 224 | Writer.prototype.writeLength = function (len) {
|
---|
| 225 | if (typeof (len) !== 'number')
|
---|
| 226 | throw new TypeError('argument must be a Number');
|
---|
| 227 |
|
---|
| 228 | this._ensure(4);
|
---|
| 229 |
|
---|
| 230 | if (len <= 0x7f) {
|
---|
| 231 | this._buf[this._offset++] = len;
|
---|
| 232 | } else if (len <= 0xff) {
|
---|
| 233 | this._buf[this._offset++] = 0x81;
|
---|
| 234 | this._buf[this._offset++] = len;
|
---|
| 235 | } else if (len <= 0xffff) {
|
---|
| 236 | this._buf[this._offset++] = 0x82;
|
---|
| 237 | this._buf[this._offset++] = len >> 8;
|
---|
| 238 | this._buf[this._offset++] = len;
|
---|
| 239 | } else if (len <= 0xffffff) {
|
---|
| 240 | this._buf[this._offset++] = 0x83;
|
---|
| 241 | this._buf[this._offset++] = len >> 16;
|
---|
| 242 | this._buf[this._offset++] = len >> 8;
|
---|
| 243 | this._buf[this._offset++] = len;
|
---|
| 244 | } else {
|
---|
| 245 | throw newInvalidAsn1Error('Length too long (> 4 bytes)');
|
---|
| 246 | }
|
---|
| 247 | };
|
---|
| 248 |
|
---|
| 249 | Writer.prototype.startSequence = function (tag) {
|
---|
| 250 | if (typeof (tag) !== 'number')
|
---|
| 251 | tag = ASN1.Sequence | ASN1.Constructor;
|
---|
| 252 |
|
---|
| 253 | this.writeByte(tag);
|
---|
| 254 | this._seq.push(this._offset);
|
---|
| 255 | this._ensure(3);
|
---|
| 256 | this._offset += 3;
|
---|
| 257 | };
|
---|
| 258 |
|
---|
| 259 |
|
---|
| 260 | Writer.prototype.endSequence = function () {
|
---|
| 261 | var seq = this._seq.pop();
|
---|
| 262 | var start = seq + 3;
|
---|
| 263 | var len = this._offset - start;
|
---|
| 264 |
|
---|
| 265 | if (len <= 0x7f) {
|
---|
| 266 | this._shift(start, len, -2);
|
---|
| 267 | this._buf[seq] = len;
|
---|
| 268 | } else if (len <= 0xff) {
|
---|
| 269 | this._shift(start, len, -1);
|
---|
| 270 | this._buf[seq] = 0x81;
|
---|
| 271 | this._buf[seq + 1] = len;
|
---|
| 272 | } else if (len <= 0xffff) {
|
---|
| 273 | this._buf[seq] = 0x82;
|
---|
| 274 | this._buf[seq + 1] = len >> 8;
|
---|
| 275 | this._buf[seq + 2] = len;
|
---|
| 276 | } else if (len <= 0xffffff) {
|
---|
| 277 | this._shift(start, len, 1);
|
---|
| 278 | this._buf[seq] = 0x83;
|
---|
| 279 | this._buf[seq + 1] = len >> 16;
|
---|
| 280 | this._buf[seq + 2] = len >> 8;
|
---|
| 281 | this._buf[seq + 3] = len;
|
---|
| 282 | } else {
|
---|
| 283 | throw newInvalidAsn1Error('Sequence too long');
|
---|
| 284 | }
|
---|
| 285 | };
|
---|
| 286 |
|
---|
| 287 |
|
---|
| 288 | Writer.prototype._shift = function (start, len, shift) {
|
---|
| 289 | assert.ok(start !== undefined);
|
---|
| 290 | assert.ok(len !== undefined);
|
---|
| 291 | assert.ok(shift);
|
---|
| 292 |
|
---|
| 293 | this._buf.copy(this._buf, start + shift, start, start + len);
|
---|
| 294 | this._offset += shift;
|
---|
| 295 | };
|
---|
| 296 |
|
---|
| 297 | Writer.prototype._ensure = function (len) {
|
---|
| 298 | assert.ok(len);
|
---|
| 299 |
|
---|
| 300 | if (this._size - this._offset < len) {
|
---|
| 301 | var sz = this._size * this._options.growthFactor;
|
---|
| 302 | if (sz - this._offset < len)
|
---|
| 303 | sz += len;
|
---|
| 304 |
|
---|
| 305 | var buf = Buffer.alloc(sz);
|
---|
| 306 |
|
---|
| 307 | this._buf.copy(buf, 0, 0, this._offset);
|
---|
| 308 | this._buf = buf;
|
---|
| 309 | this._size = sz;
|
---|
| 310 | }
|
---|
| 311 | };
|
---|
| 312 |
|
---|
| 313 |
|
---|
| 314 |
|
---|
| 315 | // --- Exported API
|
---|
| 316 |
|
---|
| 317 | module.exports = Writer;
|
---|