• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
263