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;
|
---|