You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
262 lines
5.5 KiB
262 lines
5.5 KiB
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved. |
|
|
|
var assert = require('assert'); |
|
var Buffer = require('safer-buffer').Buffer; |
|
|
|
var ASN1 = require('./types'); |
|
var errors = require('./errors'); |
|
|
|
|
|
// --- Globals |
|
|
|
var newInvalidAsn1Error = errors.newInvalidAsn1Error; |
|
|
|
|
|
|
|
// --- API |
|
|
|
function Reader(data) { |
|
if (!data || !Buffer.isBuffer(data)) |
|
throw new TypeError('data must be a node Buffer'); |
|
|
|
this._buf = data; |
|
this._size = data.length; |
|
|
|
// These hold the "current" state |
|
this._len = 0; |
|
this._offset = 0; |
|
} |
|
|
|
Object.defineProperty(Reader.prototype, 'length', { |
|
enumerable: true, |
|
get: function () { return (this._len); } |
|
}); |
|
|
|
Object.defineProperty(Reader.prototype, 'offset', { |
|
enumerable: true, |
|
get: function () { return (this._offset); } |
|
}); |
|
|
|
Object.defineProperty(Reader.prototype, 'remain', { |
|
get: function () { return (this._size - this._offset); } |
|
}); |
|
|
|
Object.defineProperty(Reader.prototype, 'buffer', { |
|
get: function () { return (this._buf.slice(this._offset)); } |
|
}); |
|
|
|
|
|
/** |
|
* Reads a single byte and advances offset; you can pass in `true` to make this |
|
* a "peek" operation (i.e., get the byte, but don't advance the offset). |
|
* |
|
* @param {Boolean} peek true means don't move offset. |
|
* @return {Number} the next byte, null if not enough data. |
|
*/ |
|
Reader.prototype.readByte = function (peek) { |
|
if (this._size - this._offset < 1) |
|
return null; |
|
|
|
var b = this._buf[this._offset] & 0xff; |
|
|
|
if (!peek) |
|
this._offset += 1; |
|
|
|
return b; |
|
}; |
|
|
|
|
|
Reader.prototype.peek = function () { |
|
return this.readByte(true); |
|
}; |
|
|
|
|
|
/** |
|
* Reads a (potentially) variable length off the BER buffer. This call is |
|
* not really meant to be called directly, as callers have to manipulate |
|
* the internal buffer afterwards. |
|
* |
|
* As a result of this call, you can call `Reader.length`, until the |
|
* next thing called that does a readLength. |
|
* |
|
* @return {Number} the amount of offset to advance the buffer. |
|
* @throws {InvalidAsn1Error} on bad ASN.1 |
|
*/ |
|
Reader.prototype.readLength = function (offset) { |
|
if (offset === undefined) |
|
offset = this._offset; |
|
|
|
if (offset >= this._size) |
|
return null; |
|
|
|
var lenB = this._buf[offset++] & 0xff; |
|
if (lenB === null) |
|
return null; |
|
|
|
if ((lenB & 0x80) === 0x80) { |
|
lenB &= 0x7f; |
|
|
|
if (lenB === 0) |
|
throw newInvalidAsn1Error('Indefinite length not supported'); |
|
|
|
if (lenB > 4) |
|
throw newInvalidAsn1Error('encoding too long'); |
|
|
|
if (this._size - offset < lenB) |
|
return null; |
|
|
|
this._len = 0; |
|
for (var i = 0; i < lenB; i++) |
|
this._len = (this._len << 8) + (this._buf[offset++] & 0xff); |
|
|
|
} else { |
|
// Wasn't a variable length |
|
this._len = lenB; |
|
} |
|
|
|
return offset; |
|
}; |
|
|
|
|
|
/** |
|
* Parses the next sequence in this BER buffer. |
|
* |
|
* To get the length of the sequence, call `Reader.length`. |
|
* |
|
* @return {Number} the sequence's tag. |
|
*/ |
|
Reader.prototype.readSequence = function (tag) { |
|
var seq = this.peek(); |
|
if (seq === null) |
|
return null; |
|
if (tag !== undefined && tag !== seq) |
|
throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + |
|
': got 0x' + seq.toString(16)); |
|
|
|
var o = this.readLength(this._offset + 1); // stored in `length` |
|
if (o === null) |
|
return null; |
|
|
|
this._offset = o; |
|
return seq; |
|
}; |
|
|
|
|
|
Reader.prototype.readInt = function () { |
|
return this._readTag(ASN1.Integer); |
|
}; |
|
|
|
|
|
Reader.prototype.readBoolean = function () { |
|
return (this._readTag(ASN1.Boolean) === 0 ? false : true); |
|
}; |
|
|
|
|
|
Reader.prototype.readEnumeration = function () { |
|
return this._readTag(ASN1.Enumeration); |
|
}; |
|
|
|
|
|
Reader.prototype.readString = function (tag, retbuf) { |
|
if (!tag) |
|
tag = ASN1.OctetString; |
|
|
|
var b = this.peek(); |
|
if (b === null) |
|
return null; |
|
|
|
if (b !== tag) |
|
throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + |
|
': got 0x' + b.toString(16)); |
|
|
|
var o = this.readLength(this._offset + 1); // stored in `length` |
|
|
|
if (o === null) |
|
return null; |
|
|
|
if (this.length > this._size - o) |
|
return null; |
|
|
|
this._offset = o; |
|
|
|
if (this.length === 0) |
|
return retbuf ? Buffer.alloc(0) : ''; |
|
|
|
var str = this._buf.slice(this._offset, this._offset + this.length); |
|
this._offset += this.length; |
|
|
|
return retbuf ? str : str.toString('utf8'); |
|
}; |
|
|
|
Reader.prototype.readOID = function (tag) { |
|
if (!tag) |
|
tag = ASN1.OID; |
|
|
|
var b = this.readString(tag, true); |
|
if (b === null) |
|
return null; |
|
|
|
var values = []; |
|
var value = 0; |
|
|
|
for (var i = 0; i < b.length; i++) { |
|
var byte = b[i] & 0xff; |
|
|
|
value <<= 7; |
|
value += byte & 0x7f; |
|
if ((byte & 0x80) === 0) { |
|
values.push(value); |
|
value = 0; |
|
} |
|
} |
|
|
|
value = values.shift(); |
|
values.unshift(value % 40); |
|
values.unshift((value / 40) >> 0); |
|
|
|
return values.join('.'); |
|
}; |
|
|
|
|
|
Reader.prototype._readTag = function (tag) { |
|
assert.ok(tag !== undefined); |
|
|
|
var b = this.peek(); |
|
|
|
if (b === null) |
|
return null; |
|
|
|
if (b !== tag) |
|
throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + |
|
': got 0x' + b.toString(16)); |
|
|
|
var o = this.readLength(this._offset + 1); // stored in `length` |
|
if (o === null) |
|
return null; |
|
|
|
if (this.length > 4) |
|
throw newInvalidAsn1Error('Integer too long: ' + this.length); |
|
|
|
if (this.length > this._size - o) |
|
return null; |
|
this._offset = o; |
|
|
|
var fb = this._buf[this._offset]; |
|
var value = 0; |
|
|
|
for (var i = 0; i < this.length; i++) { |
|
value <<= 8; |
|
value |= (this._buf[this._offset++] & 0xff); |
|
} |
|
|
|
if ((fb & 0x80) === 0x80 && i !== 4) |
|
value -= (1 << (i * 8)); |
|
|
|
return value >> 0; |
|
}; |
|
|
|
|
|
|
|
// --- Exported API |
|
|
|
module.exports = Reader;
|
|
|