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