// Copyright 2011 Mark Cavage All rights reserved. const assert = require('assert') const ASN1 = require('./types') const errors = require('./errors') // --- Globals const 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, Symbol.toStringTag, { value: 'BerReader' }) 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 } const 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 } let 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 (let 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) { const 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)) } const 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 (tag) { return (this._readTag(tag || ASN1.Boolean) !== 0) } Reader.prototype.readEnumeration = function () { return this._readTag(ASN1.Enumeration) } Reader.prototype.readString = function (tag, retbuf) { if (!tag) { tag = ASN1.OctetString } const b = this.peek() if (b === null) { return null } if (b !== tag) { throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + ': got 0x' + b.toString(16)) } const 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) : '' } const 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 } const b = this.readString(tag, true) if (b === null) { return null } const values = [] let value = 0 for (let i = 0; i < b.length; i++) { const 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) const b = this.peek() if (b === null) { return null } if (b !== tag) { throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + ': got 0x' + b.toString(16)) } const 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 const fb = this._buf[this._offset] let value = 0 let i for (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