reader.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. 'use strict'
  2. const types = require('./types')
  3. const bufferToHexDump = require('../buffer-to-hex-dump')
  4. /**
  5. * Given a buffer of ASN.1 data encoded according to Basic Encoding Rules (BER),
  6. * the reader provides methods for iterating that data and decoding it into
  7. * regular JavaScript types.
  8. */
  9. class BerReader {
  10. /**
  11. * The source buffer as it was passed in when creating the instance.
  12. *
  13. * @type {Buffer}
  14. */
  15. #buffer
  16. /**
  17. * The total bytes in the backing buffer.
  18. *
  19. * @type {number}
  20. */
  21. #size
  22. /**
  23. * An ASN.1 field consists of a tag, a length, and a value. This property
  24. * records the length of the current field.
  25. *
  26. * @type {number}
  27. */
  28. #currentFieldLength = 0
  29. /**
  30. * Records the offset in the buffer where the most recent {@link readSequence}
  31. * was invoked. This is used to facilitate slicing of whole sequences from
  32. * the buffer as a new {@link BerReader} instance.
  33. *
  34. * @type {number}
  35. */
  36. #currentSequenceStart = 0
  37. /**
  38. * As the BER buffer is read, this property records the current position
  39. * in the buffer.
  40. *
  41. * @type {number}
  42. */
  43. #offset = 0
  44. /**
  45. * @param {Buffer} buffer
  46. */
  47. constructor (buffer) {
  48. if (Buffer.isBuffer(buffer) === false) {
  49. throw TypeError('Must supply a Buffer instance to read.')
  50. }
  51. this.#buffer = buffer.subarray(0)
  52. this.#size = this.#buffer.length
  53. }
  54. get [Symbol.toStringTag] () { return 'BerReader' }
  55. /**
  56. * Get a buffer that represents the underlying data buffer.
  57. *
  58. * @type {Buffer}
  59. */
  60. get buffer () {
  61. return this.#buffer.subarray(0)
  62. }
  63. /**
  64. * The length of the current field being read.
  65. *
  66. * @type {number}
  67. */
  68. get length () {
  69. return this.#currentFieldLength
  70. }
  71. /**
  72. * Current read position in the underlying data buffer.
  73. *
  74. * @type {number}
  75. */
  76. get offset () {
  77. return this.#offset
  78. }
  79. /**
  80. * The number of bytes remaining in the backing buffer that have not
  81. * been read.
  82. *
  83. * @type {number}
  84. */
  85. get remain () {
  86. return this.#size - this.#offset
  87. }
  88. /**
  89. * Read the next byte in the buffer without advancing the offset.
  90. *
  91. * @return {number | null} The next byte or null if not enough data.
  92. */
  93. peek () {
  94. return this.readByte(true)
  95. }
  96. /**
  97. * Reads a boolean from the current offset and advances the offset.
  98. *
  99. * @param {number} [tag] The tag number that is expected to be read.
  100. *
  101. * @returns {boolean} True if the tag value represents `true`, otherwise
  102. * `false`.
  103. *
  104. * @throws When there is an error reading the tag.
  105. */
  106. readBoolean (tag = types.Boolean) {
  107. const intBuffer = this.readTag(tag)
  108. this.#offset += intBuffer.length
  109. const int = parseIntegerBuffer(intBuffer)
  110. return (int !== 0)
  111. }
  112. /**
  113. * Reads a single byte and advances offset; you can pass in `true` to make
  114. * this a "peek" operation (i.e. get the byte, but don't advance the offset).
  115. *
  116. * @param {boolean} [peek=false] `true` means don't move the offset.
  117. * @returns {number | null} The next byte, `null` if not enough data.
  118. */
  119. readByte (peek = false) {
  120. if (this.#size - this.#offset < 1) {
  121. return null
  122. }
  123. const byte = this.#buffer[this.#offset] & 0xff
  124. if (peek !== true) {
  125. this.#offset += 1
  126. }
  127. return byte
  128. }
  129. /**
  130. * Reads an enumeration (integer) from the current offset and advances the
  131. * offset.
  132. *
  133. * @returns {number} The integer represented by the next sequence of bytes
  134. * in the buffer from the current offset. The current offset must be at a
  135. * byte whose value is equal to the ASN.1 enumeration tag.
  136. *
  137. * @throws When there is an error reading the tag.
  138. */
  139. readEnumeration () {
  140. const intBuffer = this.readTag(types.Enumeration)
  141. this.#offset += intBuffer.length
  142. return parseIntegerBuffer(intBuffer)
  143. }
  144. /**
  145. * Reads an integer from the current offset and advances the offset.
  146. *
  147. * @param {number} [tag] The tag number that is expected to be read.
  148. *
  149. * @returns {number} The integer represented by the next sequence of bytes
  150. * in the buffer from the current offset. The current offset must be at a
  151. * byte whose value is equal to the ASN.1 integer tag.
  152. *
  153. * @throws When there is an error reading the tag.
  154. */
  155. readInt (tag = types.Integer) {
  156. const intBuffer = this.readTag(tag)
  157. this.#offset += intBuffer.length
  158. return parseIntegerBuffer(intBuffer)
  159. }
  160. /**
  161. * Reads a length value from the BER buffer at the given offset. This
  162. * method is not really meant to be called directly, as callers have to
  163. * manipulate the internal buffer afterwards.
  164. *
  165. * This method does not advance the reader offset.
  166. *
  167. * As a result of this method, the `.length` property can be read for the
  168. * current field until another method invokes `readLength`.
  169. *
  170. * Note: we only support up to 4 bytes to describe the length of a value.
  171. *
  172. * @param {number} [offset] Read a length value starting at the specified
  173. * position in the underlying buffer.
  174. *
  175. * @return {number | null} The position the buffer should be advanced to in
  176. * order for the reader to be at the start of the value for the field. See
  177. * {@link setOffset}. If the offset, or length, exceeds the size of the
  178. * underlying buffer, `null` will be returned.
  179. *
  180. * @throws When an unsupported length value is encountered.
  181. */
  182. readLength (offset) {
  183. if (offset === undefined) { offset = this.#offset }
  184. if (offset >= this.#size) { return null }
  185. let lengthByte = this.#buffer[offset++] & 0xff
  186. // TODO: we are commenting this out because it seems to be unreachable.
  187. // It is not clear to me how we can ever check `lenB === null` as `null`
  188. // is a primitive type, and seemingly cannot be represented by a byte.
  189. // If we find that removal of this line does not affect the larger suite
  190. // of ldapjs tests, we should just completely remove it from the code.
  191. /* if (lenB === null) { return null } */
  192. if ((lengthByte & 0x80) === 0x80) {
  193. lengthByte &= 0x7f
  194. // https://www.rfc-editor.org/rfc/rfc4511.html#section-5.1 prohibits
  195. // indefinite form (0x80).
  196. if (lengthByte === 0) { throw Error('Indefinite length not supported.') }
  197. // We only support up to 4 bytes to describe encoding length. So the only
  198. // valid indicators are 0x81, 0x82, 0x83, and 0x84.
  199. if (lengthByte > 4) { throw Error('Encoding too long.') }
  200. if (this.#size - offset < lengthByte) { return null }
  201. this.#currentFieldLength = 0
  202. for (let i = 0; i < lengthByte; i++) {
  203. this.#currentFieldLength = (this.#currentFieldLength << 8) +
  204. (this.#buffer[offset++] & 0xff)
  205. }
  206. } else {
  207. // Wasn't a variable length
  208. this.#currentFieldLength = lengthByte
  209. }
  210. return offset
  211. }
  212. /**
  213. * At the current offset, read the next tag, length, and value as an
  214. * object identifier (OID) and return the OID string.
  215. *
  216. * @param {number} [tag] The tag number that is expected to be read.
  217. *
  218. * @returns {string | null} Will return `null` if the buffer is an invalid
  219. * length. Otherwise, returns the OID as a string.
  220. */
  221. readOID (tag = types.OID) {
  222. // See https://web.archive.org/web/20221008202056/https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-object-identifier?redirectedfrom=MSDN
  223. const oidBuffer = this.readString(tag, true)
  224. if (oidBuffer === null) { return null }
  225. const values = []
  226. let value = 0
  227. for (let i = 0; i < oidBuffer.length; i++) {
  228. const byte = oidBuffer[i] & 0xff
  229. value <<= 7
  230. value += byte & 0x7f
  231. if ((byte & 0x80) === 0) {
  232. values.push(value)
  233. value = 0
  234. }
  235. }
  236. value = values.shift()
  237. values.unshift(value % 40)
  238. values.unshift((value / 40) >> 0)
  239. return values.join('.')
  240. }
  241. /**
  242. * Get a new {@link Buffer} instance that represents the full set of bytes
  243. * for a BER representation of a specified tag. For example, this is useful
  244. * when construction objects from an incoming LDAP message and the object
  245. * constructor can read a BER representation of itself to create a new
  246. * instance, e.g. when reading the filter section of a "search request"
  247. * message.
  248. *
  249. * @param {number} tag The expected tag that starts the TLV series of bytes.
  250. * @param {boolean} [advanceOffset=true] Indicates if the instance's internal
  251. * offset should be advanced or not after reading the buffer.
  252. *
  253. * @returns {Buffer|null} If there is a problem reading the buffer, e.g.
  254. * the number of bytes indicated by the length do not exist in the value, then
  255. * `null` will be returned. Otherwise, a new {@link Buffer} of bytes that
  256. * represents a full TLV.
  257. */
  258. readRawBuffer (tag, advanceOffset = true) {
  259. if (Number.isInteger(tag) === false) {
  260. throw Error('must specify an integer tag')
  261. }
  262. const foundTag = this.peek()
  263. if (foundTag !== tag) {
  264. const expected = tag.toString(16).padStart(2, '0')
  265. const found = foundTag.toString(16).padStart(2, '0')
  266. throw Error(`Expected 0x${expected}: got 0x${found}`)
  267. }
  268. const currentOffset = this.#offset
  269. const valueOffset = this.readLength(currentOffset + 1)
  270. if (valueOffset === null) { return null }
  271. const valueBytesLength = this.length
  272. const numTagAndLengthBytes = valueOffset - currentOffset
  273. // Buffer.subarray is not inclusive. We need to account for the
  274. // tag and length bytes.
  275. const endPos = currentOffset + valueBytesLength + numTagAndLengthBytes
  276. if (endPos > this.buffer.byteLength) {
  277. return null
  278. }
  279. const buffer = this.buffer.subarray(currentOffset, endPos)
  280. if (advanceOffset === true) {
  281. this.setOffset(currentOffset + (valueBytesLength + numTagAndLengthBytes))
  282. }
  283. return buffer
  284. }
  285. /**
  286. * At the current buffer offset, read the next tag as a sequence tag, and
  287. * advance the offset to the position of the tag of the first item in the
  288. * sequence.
  289. *
  290. * @param {number} [tag] The tag number that is expected to be read.
  291. *
  292. * @returns {number|null} The read sequence tag value. Should match the
  293. * function input parameter value.
  294. *
  295. * @throws If the `tag` does not match or if there is an error reading
  296. * the length of the sequence.
  297. */
  298. readSequence (tag) {
  299. const foundTag = this.peek()
  300. if (tag !== undefined && tag !== foundTag) {
  301. const expected = tag.toString(16).padStart(2, '0')
  302. const found = foundTag.toString(16).padStart(2, '0')
  303. throw Error(`Expected 0x${expected}: got 0x${found}`)
  304. }
  305. this.#currentSequenceStart = this.#offset
  306. const valueOffset = this.readLength(this.#offset + 1) // stored in `length`
  307. if (valueOffset === null) { return null }
  308. this.#offset = valueOffset
  309. return foundTag
  310. }
  311. /**
  312. * At the current buffer offset, read the next value as a string and advance
  313. * the offset.
  314. *
  315. * @param {number} [tag] The tag number that is expected to be read. Should
  316. * be `ASN1.String`.
  317. * @param {boolean} [asBuffer=false] When true, the raw buffer will be
  318. * returned. Otherwise, a native string.
  319. *
  320. * @returns {string | Buffer | null} Will return `null` if the buffer is
  321. * malformed.
  322. *
  323. * @throws If there is a problem reading the length.
  324. */
  325. readString (tag = types.OctetString, asBuffer = false) {
  326. const tagByte = this.peek()
  327. if (tagByte !== tag) {
  328. const expected = tag.toString(16).padStart(2, '0')
  329. const found = tagByte.toString(16).padStart(2, '0')
  330. throw Error(`Expected 0x${expected}: got 0x${found}`)
  331. }
  332. const valueOffset = this.readLength(this.#offset + 1) // stored in `length`
  333. if (valueOffset === null) { return null }
  334. if (this.length > this.#size - valueOffset) { return null }
  335. this.#offset = valueOffset
  336. if (this.length === 0) { return asBuffer ? Buffer.alloc(0) : '' }
  337. const str = this.#buffer.subarray(this.#offset, this.#offset + this.length)
  338. this.#offset += this.length
  339. return asBuffer ? str : str.toString('utf8')
  340. }
  341. /**
  342. * At the current buffer offset, read the next set of bytes represented
  343. * by the given tag, and return the resulting buffer. For example, if the
  344. * BER represents a sequence with a string "foo", i.e.
  345. * `[0x30, 0x05, 0x04, 0x03, 0x66, 0x6f, 0x6f]`, and the current offset is
  346. * `0`, then the result of `readTag(0x30)` is the buffer
  347. * `[0x04, 0x03, 0x66, 0x6f, 0x6f]`.
  348. *
  349. * @param {number} tag The tag number that is expected to be read.
  350. *
  351. * @returns {Buffer | null} The buffer representing the tag value, or null if
  352. * the buffer is in some way malformed.
  353. *
  354. * @throws When there is an error interpreting the buffer, or the buffer
  355. * is not formed correctly.
  356. */
  357. readTag (tag) {
  358. if (tag == null) {
  359. throw Error('Must supply an ASN.1 tag to read.')
  360. }
  361. const byte = this.peek()
  362. if (byte !== tag) {
  363. const tagString = tag.toString(16).padStart(2, '0')
  364. const byteString = byte.toString(16).padStart(2, '0')
  365. throw Error(`Expected 0x${tagString}: got 0x${byteString}`)
  366. }
  367. const fieldOffset = this.readLength(this.#offset + 1) // stored in `length`
  368. if (fieldOffset === null) { return null }
  369. if (this.length > this.#size - fieldOffset) { return null }
  370. this.#offset = fieldOffset
  371. return this.#buffer.subarray(this.#offset, this.#offset + this.length)
  372. }
  373. /**
  374. * Returns the current sequence as a new {@link BerReader} instance. This
  375. * method relies on {@link readSequence} having been invoked first. If it has
  376. * not been invoked, the returned reader will represent an undefined portion
  377. * of the underlying buffer.
  378. *
  379. * @returns {BerReader}
  380. */
  381. sequenceToReader () {
  382. // Represents the number of bytes that constitute the "length" portion
  383. // of the TLV tuple.
  384. const lengthValueLength = this.#offset - this.#currentSequenceStart
  385. const buffer = this.#buffer.subarray(
  386. this.#currentSequenceStart,
  387. this.#currentSequenceStart + (lengthValueLength + this.#currentFieldLength)
  388. )
  389. return new BerReader(buffer)
  390. }
  391. /**
  392. * Set the internal offset to a given position in the underlying buffer.
  393. * This method is to support manual advancement of the reader.
  394. *
  395. * @param {number} position
  396. *
  397. * @throws If the given `position` is not an integer.
  398. */
  399. setOffset (position) {
  400. if (Number.isInteger(position) === false) {
  401. throw Error('Must supply an integer position.')
  402. }
  403. this.#offset = position
  404. }
  405. /**
  406. * @param {HexDumpParams} params The `buffer` parameter will be ignored.
  407. *
  408. * @see bufferToHexDump
  409. */
  410. toHexDump (params) {
  411. bufferToHexDump({
  412. ...params,
  413. buffer: this.buffer
  414. })
  415. }
  416. }
  417. /**
  418. * Given a buffer that represents an integer TLV, parse it and return it
  419. * as a decimal value. This accounts for signedness.
  420. *
  421. * @param {Buffer} integerBuffer
  422. *
  423. * @returns {number}
  424. */
  425. function parseIntegerBuffer (integerBuffer) {
  426. let value = 0
  427. let i
  428. for (i = 0; i < integerBuffer.length; i++) {
  429. value <<= 8
  430. value |= (integerBuffer[i] & 0xff)
  431. }
  432. if ((integerBuffer[0] & 0x80) === 0x80 && i !== 4) { value -= (1 << (i * 8)) }
  433. return value >> 0
  434. }
  435. module.exports = BerReader