UNPKG

5.65 kBJavaScriptView Raw
1// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
2
3var assert = require('assert');
4var Buffer = require('safer-buffer').Buffer;
5
6var ASN1 = require('./types');
7var errors = require('./errors');
8
9
10// --- Globals
11
12var newInvalidAsn1Error = errors.newInvalidAsn1Error;
13
14
15
16// --- API
17
18function Reader(data) {
19 if (!data || !Buffer.isBuffer(data))
20 throw new TypeError('data must be a node Buffer');
21
22 this._buf = data;
23 this._size = data.length;
24
25 // These hold the "current" state
26 this._len = 0;
27 this._offset = 0;
28}
29
30Object.defineProperty(Reader.prototype, 'length', {
31 enumerable: true,
32 get: function () { return (this._len); }
33});
34
35Object.defineProperty(Reader.prototype, 'offset', {
36 enumerable: true,
37 get: function () { return (this._offset); }
38});
39
40Object.defineProperty(Reader.prototype, 'remain', {
41 get: function () { return (this._size - this._offset); }
42});
43
44Object.defineProperty(Reader.prototype, 'buffer', {
45 get: function () { return (this._buf.slice(this._offset)); }
46});
47
48
49/**
50 * Reads a single byte and advances offset; you can pass in `true` to make this
51 * a "peek" operation (i.e., get the byte, but don't advance the offset).
52 *
53 * @param {Boolean} peek true means don't move offset.
54 * @return {Number} the next byte, null if not enough data.
55 */
56Reader.prototype.readByte = function (peek) {
57 if (this._size - this._offset < 1)
58 return null;
59
60 var b = this._buf[this._offset] & 0xff;
61
62 if (!peek)
63 this._offset += 1;
64
65 return b;
66};
67
68
69Reader.prototype.peek = function () {
70 return this.readByte(true);
71};
72
73
74/**
75 * Reads a (potentially) variable length off the BER buffer. This call is
76 * not really meant to be called directly, as callers have to manipulate
77 * the internal buffer afterwards.
78 *
79 * As a result of this call, you can call `Reader.length`, until the
80 * next thing called that does a readLength.
81 *
82 * @return {Number} the amount of offset to advance the buffer.
83 * @throws {InvalidAsn1Error} on bad ASN.1
84 */
85Reader.prototype.readLength = function (offset) {
86 if (offset === undefined)
87 offset = this._offset;
88
89 if (offset >= this._size)
90 return null;
91
92 var lenB = this._buf[offset++] & 0xff;
93 if (lenB === null)
94 return null;
95
96 if ((lenB & 0x80) === 0x80) {
97 lenB &= 0x7f;
98
99 if (lenB === 0)
100 throw newInvalidAsn1Error('Indefinite length not supported');
101
102 if (lenB > 4)
103 throw newInvalidAsn1Error('encoding too long');
104
105 if (this._size - offset < lenB)
106 return null;
107
108 this._len = 0;
109 for (var i = 0; i < lenB; i++)
110 this._len = (this._len << 8) + (this._buf[offset++] & 0xff);
111
112 } else {
113 // Wasn't a variable length
114 this._len = lenB;
115 }
116
117 return offset;
118};
119
120
121/**
122 * Parses the next sequence in this BER buffer.
123 *
124 * To get the length of the sequence, call `Reader.length`.
125 *
126 * @return {Number} the sequence's tag.
127 */
128Reader.prototype.readSequence = function (tag) {
129 var seq = this.peek();
130 if (seq === null)
131 return null;
132 if (tag !== undefined && tag !== seq)
133 throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
134 ': got 0x' + seq.toString(16));
135
136 var o = this.readLength(this._offset + 1); // stored in `length`
137 if (o === null)
138 return null;
139
140 this._offset = o;
141 return seq;
142};
143
144
145Reader.prototype.readInt = function () {
146 return this._readTag(ASN1.Integer);
147};
148
149
150Reader.prototype.readBoolean = function () {
151 return (this._readTag(ASN1.Boolean) === 0 ? false : true);
152};
153
154
155Reader.prototype.readEnumeration = function () {
156 return this._readTag(ASN1.Enumeration);
157};
158
159
160Reader.prototype.readString = function (tag, retbuf) {
161 if (!tag)
162 tag = ASN1.OctetString;
163
164 var b = this.peek();
165 if (b === null)
166 return null;
167
168 if (b !== tag)
169 throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
170 ': got 0x' + b.toString(16));
171
172 var o = this.readLength(this._offset + 1); // stored in `length`
173
174 if (o === null)
175 return null;
176
177 if (this.length > this._size - o)
178 return null;
179
180 this._offset = o;
181
182 if (this.length === 0)
183 return retbuf ? Buffer.alloc(0) : '';
184
185 var str = this._buf.slice(this._offset, this._offset + this.length);
186 this._offset += this.length;
187
188 return retbuf ? str : str.toString('utf8');
189};
190
191Reader.prototype.readOID = function (tag) {
192 if (!tag)
193 tag = ASN1.OID;
194
195 var b = this.readString(tag, true);
196 if (b === null)
197 return null;
198
199 var values = [];
200 var value = 0;
201
202 for (var i = 0; i < b.length; i++) {
203 var byte = b[i] & 0xff;
204
205 value <<= 7;
206 value += byte & 0x7f;
207 if ((byte & 0x80) === 0) {
208 values.push(value);
209 value = 0;
210 }
211 }
212
213 value = values.shift();
214 values.unshift(value % 40);
215 values.unshift((value / 40) >> 0);
216
217 return values.join('.');
218};
219
220
221Reader.prototype._readTag = function (tag) {
222 assert.ok(tag !== undefined);
223
224 var b = this.peek();
225
226 if (b === null)
227 return null;
228
229 if (b !== tag)
230 throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
231 ': got 0x' + b.toString(16));
232
233 var o = this.readLength(this._offset + 1); // stored in `length`
234 if (o === null)
235 return null;
236
237 if (this.length > 4)
238 throw newInvalidAsn1Error('Integer too long: ' + this.length);
239
240 if (this.length > this._size - o)
241 return null;
242 this._offset = o;
243
244 var fb = this._buf[this._offset];
245 var value = 0;
246
247 for (var i = 0; i < this.length; i++) {
248 value <<= 8;
249 value |= (this._buf[this._offset++] & 0xff);
250 }
251
252 if ((fb & 0x80) === 0x80 && i !== 4)
253 value -= (1 << (i * 8));
254
255 return value >> 0;
256};
257
258
259
260// --- Exported API
261
262module.exports = Reader;