UNPKG

7.67 kBJavaScriptView Raw
1// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
2
3var assert = require('assert');
4var Buffer = require('safer-buffer').Buffer;
5var ASN1 = require('./types');
6var errors = require('./errors');
7
8
9// --- Globals
10
11var newInvalidAsn1Error = errors.newInvalidAsn1Error;
12
13var DEFAULT_OPTS = {
14 size: 1024,
15 growthFactor: 8
16};
17
18
19// --- Helpers
20
21function 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
43function 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
56Object.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
65Writer.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
74Writer.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
103Writer.prototype.writeNull = function () {
104 this.writeByte(ASN1.Null);
105 this.writeByte(0x00);
106};
107
108
109Writer.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
119Writer.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
132Writer.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
149Writer.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
163Writer.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
174Writer.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
224Writer.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
249Writer.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
260Writer.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
288Writer.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
297Writer.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
317module.exports = Writer;