Path: blob/master/node_modules/asn1/lib/ber/writer.js
2593 views
// Copyright 2011 Mark Cavage <[email protected]> All rights reserved.12var assert = require('assert');3var Buffer = require('safer-buffer').Buffer;4var ASN1 = require('./types');5var errors = require('./errors');678// --- Globals910var newInvalidAsn1Error = errors.newInvalidAsn1Error;1112var DEFAULT_OPTS = {13size: 1024,14growthFactor: 815};161718// --- Helpers1920function merge(from, to) {21assert.ok(from);22assert.equal(typeof (from), 'object');23assert.ok(to);24assert.equal(typeof (to), 'object');2526var keys = Object.getOwnPropertyNames(from);27keys.forEach(function (key) {28if (to[key])29return;3031var value = Object.getOwnPropertyDescriptor(from, key);32Object.defineProperty(to, key, value);33});3435return to;36}37383940// --- API4142function Writer(options) {43options = merge(DEFAULT_OPTS, options || {});4445this._buf = Buffer.alloc(options.size || 1024);46this._size = this._buf.length;47this._offset = 0;48this._options = options;4950// A list of offsets in the buffer where we need to insert51// sequence tag/len pairs.52this._seq = [];53}5455Object.defineProperty(Writer.prototype, 'buffer', {56get: function () {57if (this._seq.length)58throw newInvalidAsn1Error(this._seq.length + ' unended sequence(s)');5960return (this._buf.slice(0, this._offset));61}62});6364Writer.prototype.writeByte = function (b) {65if (typeof (b) !== 'number')66throw new TypeError('argument must be a Number');6768this._ensure(1);69this._buf[this._offset++] = b;70};717273Writer.prototype.writeInt = function (i, tag) {74if (typeof (i) !== 'number')75throw new TypeError('argument must be a Number');76if (typeof (tag) !== 'number')77tag = ASN1.Integer;7879var sz = 4;8081while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000 >> 0)) &&82(sz > 1)) {83sz--;84i <<= 8;85}8687if (sz > 4)88throw newInvalidAsn1Error('BER ints cannot be > 0xffffffff');8990this._ensure(2 + sz);91this._buf[this._offset++] = tag;92this._buf[this._offset++] = sz;9394while (sz-- > 0) {95this._buf[this._offset++] = ((i & 0xff000000) >>> 24);96i <<= 8;97}9899};100101102Writer.prototype.writeNull = function () {103this.writeByte(ASN1.Null);104this.writeByte(0x00);105};106107108Writer.prototype.writeEnumeration = function (i, tag) {109if (typeof (i) !== 'number')110throw new TypeError('argument must be a Number');111if (typeof (tag) !== 'number')112tag = ASN1.Enumeration;113114return this.writeInt(i, tag);115};116117118Writer.prototype.writeBoolean = function (b, tag) {119if (typeof (b) !== 'boolean')120throw new TypeError('argument must be a Boolean');121if (typeof (tag) !== 'number')122tag = ASN1.Boolean;123124this._ensure(3);125this._buf[this._offset++] = tag;126this._buf[this._offset++] = 0x01;127this._buf[this._offset++] = b ? 0xff : 0x00;128};129130131Writer.prototype.writeString = function (s, tag) {132if (typeof (s) !== 'string')133throw new TypeError('argument must be a string (was: ' + typeof (s) + ')');134if (typeof (tag) !== 'number')135tag = ASN1.OctetString;136137var len = Buffer.byteLength(s);138this.writeByte(tag);139this.writeLength(len);140if (len) {141this._ensure(len);142this._buf.write(s, this._offset);143this._offset += len;144}145};146147148Writer.prototype.writeBuffer = function (buf, tag) {149if (typeof (tag) !== 'number')150throw new TypeError('tag must be a number');151if (!Buffer.isBuffer(buf))152throw new TypeError('argument must be a buffer');153154this.writeByte(tag);155this.writeLength(buf.length);156this._ensure(buf.length);157buf.copy(this._buf, this._offset, 0, buf.length);158this._offset += buf.length;159};160161162Writer.prototype.writeStringArray = function (strings) {163if ((!strings instanceof Array))164throw new TypeError('argument must be an Array[String]');165166var self = this;167strings.forEach(function (s) {168self.writeString(s);169});170};171172// This is really to solve DER cases, but whatever for now173Writer.prototype.writeOID = function (s, tag) {174if (typeof (s) !== 'string')175throw new TypeError('argument must be a string');176if (typeof (tag) !== 'number')177tag = ASN1.OID;178179if (!/^([0-9]+\.){3,}[0-9]+$/.test(s))180throw new Error('argument is not a valid OID string');181182function encodeOctet(bytes, octet) {183if (octet < 128) {184bytes.push(octet);185} else if (octet < 16384) {186bytes.push((octet >>> 7) | 0x80);187bytes.push(octet & 0x7F);188} else if (octet < 2097152) {189bytes.push((octet >>> 14) | 0x80);190bytes.push(((octet >>> 7) | 0x80) & 0xFF);191bytes.push(octet & 0x7F);192} else if (octet < 268435456) {193bytes.push((octet >>> 21) | 0x80);194bytes.push(((octet >>> 14) | 0x80) & 0xFF);195bytes.push(((octet >>> 7) | 0x80) & 0xFF);196bytes.push(octet & 0x7F);197} else {198bytes.push(((octet >>> 28) | 0x80) & 0xFF);199bytes.push(((octet >>> 21) | 0x80) & 0xFF);200bytes.push(((octet >>> 14) | 0x80) & 0xFF);201bytes.push(((octet >>> 7) | 0x80) & 0xFF);202bytes.push(octet & 0x7F);203}204}205206var tmp = s.split('.');207var bytes = [];208bytes.push(parseInt(tmp[0], 10) * 40 + parseInt(tmp[1], 10));209tmp.slice(2).forEach(function (b) {210encodeOctet(bytes, parseInt(b, 10));211});212213var self = this;214this._ensure(2 + bytes.length);215this.writeByte(tag);216this.writeLength(bytes.length);217bytes.forEach(function (b) {218self.writeByte(b);219});220};221222223Writer.prototype.writeLength = function (len) {224if (typeof (len) !== 'number')225throw new TypeError('argument must be a Number');226227this._ensure(4);228229if (len <= 0x7f) {230this._buf[this._offset++] = len;231} else if (len <= 0xff) {232this._buf[this._offset++] = 0x81;233this._buf[this._offset++] = len;234} else if (len <= 0xffff) {235this._buf[this._offset++] = 0x82;236this._buf[this._offset++] = len >> 8;237this._buf[this._offset++] = len;238} else if (len <= 0xffffff) {239this._buf[this._offset++] = 0x83;240this._buf[this._offset++] = len >> 16;241this._buf[this._offset++] = len >> 8;242this._buf[this._offset++] = len;243} else {244throw newInvalidAsn1Error('Length too long (> 4 bytes)');245}246};247248Writer.prototype.startSequence = function (tag) {249if (typeof (tag) !== 'number')250tag = ASN1.Sequence | ASN1.Constructor;251252this.writeByte(tag);253this._seq.push(this._offset);254this._ensure(3);255this._offset += 3;256};257258259Writer.prototype.endSequence = function () {260var seq = this._seq.pop();261var start = seq + 3;262var len = this._offset - start;263264if (len <= 0x7f) {265this._shift(start, len, -2);266this._buf[seq] = len;267} else if (len <= 0xff) {268this._shift(start, len, -1);269this._buf[seq] = 0x81;270this._buf[seq + 1] = len;271} else if (len <= 0xffff) {272this._buf[seq] = 0x82;273this._buf[seq + 1] = len >> 8;274this._buf[seq + 2] = len;275} else if (len <= 0xffffff) {276this._shift(start, len, 1);277this._buf[seq] = 0x83;278this._buf[seq + 1] = len >> 16;279this._buf[seq + 2] = len >> 8;280this._buf[seq + 3] = len;281} else {282throw newInvalidAsn1Error('Sequence too long');283}284};285286287Writer.prototype._shift = function (start, len, shift) {288assert.ok(start !== undefined);289assert.ok(len !== undefined);290assert.ok(shift);291292this._buf.copy(this._buf, start + shift, start, start + len);293this._offset += shift;294};295296Writer.prototype._ensure = function (len) {297assert.ok(len);298299if (this._size - this._offset < len) {300var sz = this._size * this._options.growthFactor;301if (sz - this._offset < len)302sz += len;303304var buf = Buffer.alloc(sz);305306this._buf.copy(buf, 0, 0, this._offset);307this._buf = buf;308this._size = sz;309}310};311312313314// --- Exported API315316module.exports = Writer;317318319