1 |
|
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 |
|
10 |
|
11 | var newInvalidAsn1Error = errors.newInvalidAsn1Error;
|
12 |
|
13 | var DEFAULT_OPTS = {
|
14 | size: 1024,
|
15 | growthFactor: 8
|
16 | };
|
17 |
|
18 |
|
19 |
|
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 |
|
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 |
|
52 |
|
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 |
|
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 |
|
316 |
|
317 | module.exports = Writer;
|