Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81159 views
1
// Copyright 2011 Mark Cavage <[email protected]> All rights reserved.
2
3
var assert = require('assert');
4
var ASN1 = require('./types');
5
var errors = require('./errors');
6
7
8
///--- Globals
9
10
var newInvalidAsn1Error = errors.newInvalidAsn1Error;
11
12
var DEFAULT_OPTS = {
13
size: 1024,
14
growthFactor: 8
15
};
16
17
18
///--- Helpers
19
20
function merge(from, to) {
21
assert.ok(from);
22
assert.equal(typeof(from), 'object');
23
assert.ok(to);
24
assert.equal(typeof(to), 'object');
25
26
var keys = Object.getOwnPropertyNames(from);
27
keys.forEach(function(key) {
28
if (to[key])
29
return;
30
31
var value = Object.getOwnPropertyDescriptor(from, key);
32
Object.defineProperty(to, key, value);
33
});
34
35
return to;
36
}
37
38
39
40
///--- API
41
42
function Writer(options) {
43
options = merge(DEFAULT_OPTS, options || {});
44
45
this._buf = new Buffer(options.size || 1024);
46
this._size = this._buf.length;
47
this._offset = 0;
48
this._options = options;
49
50
// A list of offsets in the buffer where we need to insert
51
// sequence tag/len pairs.
52
this._seq = [];
53
54
var self = this;
55
this.__defineGetter__('buffer', function() {
56
if (self._seq.length)
57
throw new InvalidAsn1Error(self._seq.length + ' unended sequence(s)');
58
59
return self._buf.slice(0, self._offset);
60
});
61
}
62
63
64
Writer.prototype.writeByte = function(b) {
65
if (typeof(b) !== 'number')
66
throw new TypeError('argument must be a Number');
67
68
this._ensure(1);
69
this._buf[this._offset++] = b;
70
};
71
72
73
Writer.prototype.writeInt = function(i, tag) {
74
if (typeof(i) !== 'number')
75
throw new TypeError('argument must be a Number');
76
if (typeof(tag) !== 'number')
77
tag = ASN1.Integer;
78
79
var sz = 4;
80
81
while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000)) &&
82
(sz > 1)) {
83
sz--;
84
i <<= 8;
85
}
86
87
if (sz > 4)
88
throw new InvalidAsn1Error('BER ints cannot be > 0xffffffff');
89
90
this._ensure(2 + sz);
91
this._buf[this._offset++] = tag;
92
this._buf[this._offset++] = sz;
93
94
while (sz-- > 0) {
95
this._buf[this._offset++] = ((i & 0xff000000) >> 24);
96
i <<= 8;
97
}
98
99
};
100
101
102
Writer.prototype.writeNull = function() {
103
this.writeByte(ASN1.Null);
104
this.writeByte(0x00);
105
};
106
107
108
Writer.prototype.writeEnumeration = function(i, tag) {
109
if (typeof(i) !== 'number')
110
throw new TypeError('argument must be a Number');
111
if (typeof(tag) !== 'number')
112
tag = ASN1.Enumeration;
113
114
return this.writeInt(i, tag);
115
};
116
117
118
Writer.prototype.writeBoolean = function(b, tag) {
119
if (typeof(b) !== 'boolean')
120
throw new TypeError('argument must be a Boolean');
121
if (typeof(tag) !== 'number')
122
tag = ASN1.Boolean;
123
124
this._ensure(3);
125
this._buf[this._offset++] = tag;
126
this._buf[this._offset++] = 0x01;
127
this._buf[this._offset++] = b ? 0xff : 0x00;
128
};
129
130
131
Writer.prototype.writeString = function(s, tag) {
132
if (typeof(s) !== 'string')
133
throw new TypeError('argument must be a string (was: ' + typeof(s) + ')');
134
if (typeof(tag) !== 'number')
135
tag = ASN1.OctetString;
136
137
var len = Buffer.byteLength(s);
138
this.writeByte(tag);
139
this.writeLength(len);
140
if (len) {
141
this._ensure(len);
142
this._buf.write(s, this._offset);
143
this._offset += len;
144
}
145
};
146
147
148
Writer.prototype.writeBuffer = function(buf, tag) {
149
if (typeof(tag) !== 'number')
150
throw new TypeError('tag must be a number');
151
if (!Buffer.isBuffer(buf))
152
throw new TypeError('argument must be a buffer');
153
154
this.writeByte(tag);
155
this.writeLength(buf.length);
156
this._ensure(buf.length);
157
buf.copy(this._buf, this._offset, 0, buf.length);
158
this._offset += buf.length;
159
};
160
161
162
Writer.prototype.writeStringArray = function(strings) {
163
if ((!strings instanceof Array))
164
throw new TypeError('argument must be an Array[String]');
165
166
var self = this;
167
strings.forEach(function(s) {
168
self.writeString(s);
169
});
170
};
171
172
// This is really to solve DER cases, but whatever for now
173
Writer.prototype.writeOID = function(s, tag) {
174
if (typeof(s) !== 'string')
175
throw new TypeError('argument must be a string');
176
if (typeof(tag) !== 'number')
177
tag = ASN1.OID;
178
179
if (!/^([0-9]+\.){3,}[0-9]+$/.test(s))
180
throw new Error('argument is not a valid OID string');
181
182
function encodeOctet(bytes, octet) {
183
if (octet < 128) {
184
bytes.push(octet);
185
} else if (octet < 16384) {
186
bytes.push((octet >>> 7) | 0x80);
187
bytes.push(octet & 0x7F);
188
} else if (octet < 2097152) {
189
bytes.push((octet >>> 14) | 0x80);
190
bytes.push(((octet >>> 7) | 0x80) & 0xFF);
191
bytes.push(octet & 0x7F);
192
} else if (octet < 268435456) {
193
bytes.push((octet >>> 21) | 0x80);
194
bytes.push(((octet >>> 14) | 0x80) & 0xFF);
195
bytes.push(((octet >>> 7) | 0x80) & 0xFF);
196
bytes.push(octet & 0x7F);
197
} else {
198
bytes.push(((octet >>> 28) | 0x80) & 0xFF);
199
bytes.push(((octet >>> 21) | 0x80) & 0xFF);
200
bytes.push(((octet >>> 14) | 0x80) & 0xFF);
201
bytes.push(((octet >>> 7) | 0x80) & 0xFF);
202
bytes.push(octet & 0x7F);
203
}
204
}
205
206
var tmp = s.split('.');
207
var bytes = [];
208
bytes.push(parseInt(tmp[0], 10) * 40 + parseInt(tmp[1], 10));
209
tmp.slice(2).forEach(function(b) {
210
encodeOctet(bytes, parseInt(b, 10));
211
});
212
213
var self = this;
214
this._ensure(2 + bytes.length);
215
this.writeByte(tag);
216
this.writeLength(bytes.length);
217
bytes.forEach(function(b) {
218
self.writeByte(b);
219
});
220
};
221
222
223
Writer.prototype.writeLength = function(len) {
224
if (typeof(len) !== 'number')
225
throw new TypeError('argument must be a Number');
226
227
this._ensure(4);
228
229
if (len <= 0x7f) {
230
this._buf[this._offset++] = len;
231
} else if (len <= 0xff) {
232
this._buf[this._offset++] = 0x81;
233
this._buf[this._offset++] = len;
234
} else if (len <= 0xffff) {
235
this._buf[this._offset++] = 0x82;
236
this._buf[this._offset++] = len >> 8;
237
this._buf[this._offset++] = len;
238
} else if (len <= 0xffffff) {
239
this._shift(start, len, 1);
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 new InvalidAsn1ERror('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 new InvalidAsn1Error('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 = new Buffer(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;
318
319