First commit
This commit is contained in:
24
node_modules/@ldapjs/asn1/lib/ber/index.js
generated
vendored
Normal file
24
node_modules/@ldapjs/asn1/lib/ber/index.js
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
|
||||
|
||||
const errors = require('./errors')
|
||||
const types = require('./types')
|
||||
|
||||
const Reader = require('./reader')
|
||||
const Writer = require('./writer')
|
||||
|
||||
// --- Exports
|
||||
|
||||
module.exports = {
|
||||
|
||||
Reader,
|
||||
|
||||
Writer
|
||||
|
||||
}
|
||||
|
||||
for (const t in types) {
|
||||
if (Object.prototype.hasOwnProperty.call(types, t)) { module.exports[t] = types[t] }
|
||||
}
|
||||
for (const e in errors) {
|
||||
if (Object.prototype.hasOwnProperty.call(errors, e)) { module.exports[e] = errors[e] }
|
||||
}
|
||||
502
node_modules/@ldapjs/asn1/lib/ber/reader.js
generated
vendored
Normal file
502
node_modules/@ldapjs/asn1/lib/ber/reader.js
generated
vendored
Normal file
@ -0,0 +1,502 @@
|
||||
'use strict'
|
||||
|
||||
const types = require('./types')
|
||||
const bufferToHexDump = require('../buffer-to-hex-dump')
|
||||
|
||||
/**
|
||||
* Given a buffer of ASN.1 data encoded according to Basic Encoding Rules (BER),
|
||||
* the reader provides methods for iterating that data and decoding it into
|
||||
* regular JavaScript types.
|
||||
*/
|
||||
class BerReader {
|
||||
/**
|
||||
* The source buffer as it was passed in when creating the instance.
|
||||
*
|
||||
* @type {Buffer}
|
||||
*/
|
||||
#buffer
|
||||
|
||||
/**
|
||||
* The total bytes in the backing buffer.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
#size
|
||||
|
||||
/**
|
||||
* An ASN.1 field consists of a tag, a length, and a value. This property
|
||||
* records the length of the current field.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
#currentFieldLength = 0
|
||||
|
||||
/**
|
||||
* Records the offset in the buffer where the most recent {@link readSequence}
|
||||
* was invoked. This is used to facilitate slicing of whole sequences from
|
||||
* the buffer as a new {@link BerReader} instance.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
#currentSequenceStart = 0
|
||||
|
||||
/**
|
||||
* As the BER buffer is read, this property records the current position
|
||||
* in the buffer.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
#offset = 0
|
||||
|
||||
/**
|
||||
* @param {Buffer} buffer
|
||||
*/
|
||||
constructor (buffer) {
|
||||
if (Buffer.isBuffer(buffer) === false) {
|
||||
throw TypeError('Must supply a Buffer instance to read.')
|
||||
}
|
||||
|
||||
this.#buffer = buffer.subarray(0)
|
||||
this.#size = this.#buffer.length
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag] () { return 'BerReader' }
|
||||
|
||||
/**
|
||||
* Get a buffer that represents the underlying data buffer.
|
||||
*
|
||||
* @type {Buffer}
|
||||
*/
|
||||
get buffer () {
|
||||
return this.#buffer.subarray(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* The length of the current field being read.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get length () {
|
||||
return this.#currentFieldLength
|
||||
}
|
||||
|
||||
/**
|
||||
* Current read position in the underlying data buffer.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get offset () {
|
||||
return this.#offset
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of bytes remaining in the backing buffer that have not
|
||||
* been read.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
get remain () {
|
||||
return this.#size - this.#offset
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the next byte in the buffer without advancing the offset.
|
||||
*
|
||||
* @return {number | null} The next byte or null if not enough data.
|
||||
*/
|
||||
peek () {
|
||||
return this.readByte(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a boolean from the current offset and advances the offset.
|
||||
*
|
||||
* @param {number} [tag] The tag number that is expected to be read.
|
||||
*
|
||||
* @returns {boolean} True if the tag value represents `true`, otherwise
|
||||
* `false`.
|
||||
*
|
||||
* @throws When there is an error reading the tag.
|
||||
*/
|
||||
readBoolean (tag = types.Boolean) {
|
||||
const intBuffer = this.readTag(tag)
|
||||
this.#offset += intBuffer.length
|
||||
const int = parseIntegerBuffer(intBuffer)
|
||||
|
||||
return (int !== 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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=false] `true` means don't move the offset.
|
||||
* @returns {number | null} The next byte, `null` if not enough data.
|
||||
*/
|
||||
readByte (peek = false) {
|
||||
if (this.#size - this.#offset < 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
const byte = this.#buffer[this.#offset] & 0xff
|
||||
|
||||
if (peek !== true) {
|
||||
this.#offset += 1
|
||||
}
|
||||
|
||||
return byte
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an enumeration (integer) from the current offset and advances the
|
||||
* offset.
|
||||
*
|
||||
* @returns {number} The integer represented by the next sequence of bytes
|
||||
* in the buffer from the current offset. The current offset must be at a
|
||||
* byte whose value is equal to the ASN.1 enumeration tag.
|
||||
*
|
||||
* @throws When there is an error reading the tag.
|
||||
*/
|
||||
readEnumeration () {
|
||||
const intBuffer = this.readTag(types.Enumeration)
|
||||
this.#offset += intBuffer.length
|
||||
|
||||
return parseIntegerBuffer(intBuffer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an integer from the current offset and advances the offset.
|
||||
*
|
||||
* @param {number} [tag] The tag number that is expected to be read.
|
||||
*
|
||||
* @returns {number} The integer represented by the next sequence of bytes
|
||||
* in the buffer from the current offset. The current offset must be at a
|
||||
* byte whose value is equal to the ASN.1 integer tag.
|
||||
*
|
||||
* @throws When there is an error reading the tag.
|
||||
*/
|
||||
readInt (tag = types.Integer) {
|
||||
const intBuffer = this.readTag(tag)
|
||||
this.#offset += intBuffer.length
|
||||
|
||||
return parseIntegerBuffer(intBuffer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a length value from the BER buffer at the given offset. This
|
||||
* method is not really meant to be called directly, as callers have to
|
||||
* manipulate the internal buffer afterwards.
|
||||
*
|
||||
* This method does not advance the reader offset.
|
||||
*
|
||||
* As a result of this method, the `.length` property can be read for the
|
||||
* current field until another method invokes `readLength`.
|
||||
*
|
||||
* Note: we only support up to 4 bytes to describe the length of a value.
|
||||
*
|
||||
* @param {number} [offset] Read a length value starting at the specified
|
||||
* position in the underlying buffer.
|
||||
*
|
||||
* @return {number | null} The position the buffer should be advanced to in
|
||||
* order for the reader to be at the start of the value for the field. See
|
||||
* {@link setOffset}. If the offset, or length, exceeds the size of the
|
||||
* underlying buffer, `null` will be returned.
|
||||
*
|
||||
* @throws When an unsupported length value is encountered.
|
||||
*/
|
||||
readLength (offset) {
|
||||
if (offset === undefined) { offset = this.#offset }
|
||||
|
||||
if (offset >= this.#size) { return null }
|
||||
|
||||
let lengthByte = this.#buffer[offset++] & 0xff
|
||||
// TODO: we are commenting this out because it seems to be unreachable.
|
||||
// It is not clear to me how we can ever check `lenB === null` as `null`
|
||||
// is a primitive type, and seemingly cannot be represented by a byte.
|
||||
// If we find that removal of this line does not affect the larger suite
|
||||
// of ldapjs tests, we should just completely remove it from the code.
|
||||
/* if (lenB === null) { return null } */
|
||||
|
||||
if ((lengthByte & 0x80) === 0x80) {
|
||||
lengthByte &= 0x7f
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc4511.html#section-5.1 prohibits
|
||||
// indefinite form (0x80).
|
||||
if (lengthByte === 0) { throw Error('Indefinite length not supported.') }
|
||||
|
||||
// We only support up to 4 bytes to describe encoding length. So the only
|
||||
// valid indicators are 0x81, 0x82, 0x83, and 0x84.
|
||||
if (lengthByte > 4) { throw Error('Encoding too long.') }
|
||||
|
||||
if (this.#size - offset < lengthByte) { return null }
|
||||
|
||||
this.#currentFieldLength = 0
|
||||
for (let i = 0; i < lengthByte; i++) {
|
||||
this.#currentFieldLength = (this.#currentFieldLength << 8) +
|
||||
(this.#buffer[offset++] & 0xff)
|
||||
}
|
||||
} else {
|
||||
// Wasn't a variable length
|
||||
this.#currentFieldLength = lengthByte
|
||||
}
|
||||
|
||||
return offset
|
||||
}
|
||||
|
||||
/**
|
||||
* At the current offset, read the next tag, length, and value as an
|
||||
* object identifier (OID) and return the OID string.
|
||||
*
|
||||
* @param {number} [tag] The tag number that is expected to be read.
|
||||
*
|
||||
* @returns {string | null} Will return `null` if the buffer is an invalid
|
||||
* length. Otherwise, returns the OID as a string.
|
||||
*/
|
||||
readOID (tag = types.OID) {
|
||||
// See https://web.archive.org/web/20221008202056/https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-object-identifier?redirectedfrom=MSDN
|
||||
const oidBuffer = this.readString(tag, true)
|
||||
if (oidBuffer === null) { return null }
|
||||
|
||||
const values = []
|
||||
let value = 0
|
||||
|
||||
for (let i = 0; i < oidBuffer.length; i++) {
|
||||
const byte = oidBuffer[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('.')
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new {@link Buffer} instance that represents the full set of bytes
|
||||
* for a BER representation of a specified tag. For example, this is useful
|
||||
* when construction objects from an incoming LDAP message and the object
|
||||
* constructor can read a BER representation of itself to create a new
|
||||
* instance, e.g. when reading the filter section of a "search request"
|
||||
* message.
|
||||
*
|
||||
* @param {number} tag The expected tag that starts the TLV series of bytes.
|
||||
* @param {boolean} [advanceOffset=true] Indicates if the instance's internal
|
||||
* offset should be advanced or not after reading the buffer.
|
||||
*
|
||||
* @returns {Buffer|null} If there is a problem reading the buffer, e.g.
|
||||
* the number of bytes indicated by the length do not exist in the value, then
|
||||
* `null` will be returned. Otherwise, a new {@link Buffer} of bytes that
|
||||
* represents a full TLV.
|
||||
*/
|
||||
readRawBuffer (tag, advanceOffset = true) {
|
||||
if (Number.isInteger(tag) === false) {
|
||||
throw Error('must specify an integer tag')
|
||||
}
|
||||
|
||||
const foundTag = this.peek()
|
||||
if (foundTag !== tag) {
|
||||
const expected = tag.toString(16).padStart(2, '0')
|
||||
const found = foundTag.toString(16).padStart(2, '0')
|
||||
throw Error(`Expected 0x${expected}: got 0x${found}`)
|
||||
}
|
||||
|
||||
const currentOffset = this.#offset
|
||||
const valueOffset = this.readLength(currentOffset + 1)
|
||||
if (valueOffset === null) { return null }
|
||||
const valueBytesLength = this.length
|
||||
|
||||
const numTagAndLengthBytes = valueOffset - currentOffset
|
||||
|
||||
// Buffer.subarray is not inclusive. We need to account for the
|
||||
// tag and length bytes.
|
||||
const endPos = currentOffset + valueBytesLength + numTagAndLengthBytes
|
||||
if (endPos > this.buffer.byteLength) {
|
||||
return null
|
||||
}
|
||||
const buffer = this.buffer.subarray(currentOffset, endPos)
|
||||
if (advanceOffset === true) {
|
||||
this.setOffset(currentOffset + (valueBytesLength + numTagAndLengthBytes))
|
||||
}
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
/**
|
||||
* At the current buffer offset, read the next tag as a sequence tag, and
|
||||
* advance the offset to the position of the tag of the first item in the
|
||||
* sequence.
|
||||
*
|
||||
* @param {number} [tag] The tag number that is expected to be read.
|
||||
*
|
||||
* @returns {number|null} The read sequence tag value. Should match the
|
||||
* function input parameter value.
|
||||
*
|
||||
* @throws If the `tag` does not match or if there is an error reading
|
||||
* the length of the sequence.
|
||||
*/
|
||||
readSequence (tag) {
|
||||
const foundTag = this.peek()
|
||||
if (tag !== undefined && tag !== foundTag) {
|
||||
const expected = tag.toString(16).padStart(2, '0')
|
||||
const found = foundTag.toString(16).padStart(2, '0')
|
||||
throw Error(`Expected 0x${expected}: got 0x${found}`)
|
||||
}
|
||||
|
||||
this.#currentSequenceStart = this.#offset
|
||||
const valueOffset = this.readLength(this.#offset + 1) // stored in `length`
|
||||
if (valueOffset === null) { return null }
|
||||
|
||||
this.#offset = valueOffset
|
||||
return foundTag
|
||||
}
|
||||
|
||||
/**
|
||||
* At the current buffer offset, read the next value as a string and advance
|
||||
* the offset.
|
||||
*
|
||||
* @param {number} [tag] The tag number that is expected to be read. Should
|
||||
* be `ASN1.String`.
|
||||
* @param {boolean} [asBuffer=false] When true, the raw buffer will be
|
||||
* returned. Otherwise, a native string.
|
||||
*
|
||||
* @returns {string | Buffer | null} Will return `null` if the buffer is
|
||||
* malformed.
|
||||
*
|
||||
* @throws If there is a problem reading the length.
|
||||
*/
|
||||
readString (tag = types.OctetString, asBuffer = false) {
|
||||
const tagByte = this.peek()
|
||||
|
||||
if (tagByte !== tag) {
|
||||
const expected = tag.toString(16).padStart(2, '0')
|
||||
const found = tagByte.toString(16).padStart(2, '0')
|
||||
throw Error(`Expected 0x${expected}: got 0x${found}`)
|
||||
}
|
||||
|
||||
const valueOffset = this.readLength(this.#offset + 1) // stored in `length`
|
||||
if (valueOffset === null) { return null }
|
||||
if (this.length > this.#size - valueOffset) { return null }
|
||||
|
||||
this.#offset = valueOffset
|
||||
|
||||
if (this.length === 0) { return asBuffer ? Buffer.alloc(0) : '' }
|
||||
|
||||
const str = this.#buffer.subarray(this.#offset, this.#offset + this.length)
|
||||
this.#offset += this.length
|
||||
|
||||
return asBuffer ? str : str.toString('utf8')
|
||||
}
|
||||
|
||||
/**
|
||||
* At the current buffer offset, read the next set of bytes represented
|
||||
* by the given tag, and return the resulting buffer. For example, if the
|
||||
* BER represents a sequence with a string "foo", i.e.
|
||||
* `[0x30, 0x05, 0x04, 0x03, 0x66, 0x6f, 0x6f]`, and the current offset is
|
||||
* `0`, then the result of `readTag(0x30)` is the buffer
|
||||
* `[0x04, 0x03, 0x66, 0x6f, 0x6f]`.
|
||||
*
|
||||
* @param {number} tag The tag number that is expected to be read.
|
||||
*
|
||||
* @returns {Buffer | null} The buffer representing the tag value, or null if
|
||||
* the buffer is in some way malformed.
|
||||
*
|
||||
* @throws When there is an error interpreting the buffer, or the buffer
|
||||
* is not formed correctly.
|
||||
*/
|
||||
readTag (tag) {
|
||||
if (tag == null) {
|
||||
throw Error('Must supply an ASN.1 tag to read.')
|
||||
}
|
||||
|
||||
const byte = this.peek()
|
||||
if (byte !== tag) {
|
||||
const tagString = tag.toString(16).padStart(2, '0')
|
||||
const byteString = byte.toString(16).padStart(2, '0')
|
||||
throw Error(`Expected 0x${tagString}: got 0x${byteString}`)
|
||||
}
|
||||
|
||||
const fieldOffset = this.readLength(this.#offset + 1) // stored in `length`
|
||||
if (fieldOffset === null) { return null }
|
||||
|
||||
if (this.length > this.#size - fieldOffset) { return null }
|
||||
this.#offset = fieldOffset
|
||||
|
||||
return this.#buffer.subarray(this.#offset, this.#offset + this.length)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current sequence as a new {@link BerReader} instance. This
|
||||
* method relies on {@link readSequence} having been invoked first. If it has
|
||||
* not been invoked, the returned reader will represent an undefined portion
|
||||
* of the underlying buffer.
|
||||
*
|
||||
* @returns {BerReader}
|
||||
*/
|
||||
sequenceToReader () {
|
||||
// Represents the number of bytes that constitute the "length" portion
|
||||
// of the TLV tuple.
|
||||
const lengthValueLength = this.#offset - this.#currentSequenceStart
|
||||
const buffer = this.#buffer.subarray(
|
||||
this.#currentSequenceStart,
|
||||
this.#currentSequenceStart + (lengthValueLength + this.#currentFieldLength)
|
||||
)
|
||||
return new BerReader(buffer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the internal offset to a given position in the underlying buffer.
|
||||
* This method is to support manual advancement of the reader.
|
||||
*
|
||||
* @param {number} position
|
||||
*
|
||||
* @throws If the given `position` is not an integer.
|
||||
*/
|
||||
setOffset (position) {
|
||||
if (Number.isInteger(position) === false) {
|
||||
throw Error('Must supply an integer position.')
|
||||
}
|
||||
this.#offset = position
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HexDumpParams} params The `buffer` parameter will be ignored.
|
||||
*
|
||||
* @see bufferToHexDump
|
||||
*/
|
||||
toHexDump (params) {
|
||||
bufferToHexDump({
|
||||
...params,
|
||||
buffer: this.buffer
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a buffer that represents an integer TLV, parse it and return it
|
||||
* as a decimal value. This accounts for signedness.
|
||||
*
|
||||
* @param {Buffer} integerBuffer
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
function parseIntegerBuffer (integerBuffer) {
|
||||
let value = 0
|
||||
let i
|
||||
for (i = 0; i < integerBuffer.length; i++) {
|
||||
value <<= 8
|
||||
value |= (integerBuffer[i] & 0xff)
|
||||
}
|
||||
|
||||
if ((integerBuffer[0] & 0x80) === 0x80 && i !== 4) { value -= (1 << (i * 8)) }
|
||||
|
||||
return value >> 0
|
||||
}
|
||||
|
||||
module.exports = BerReader
|
||||
671
node_modules/@ldapjs/asn1/lib/ber/reader.test.js
generated
vendored
Normal file
671
node_modules/@ldapjs/asn1/lib/ber/reader.test.js
generated
vendored
Normal file
@ -0,0 +1,671 @@
|
||||
'use strict'
|
||||
|
||||
const tap = require('tap')
|
||||
const { Writable } = require('stream')
|
||||
const BerReader = require('./reader')
|
||||
|
||||
// A sequence (0x30), 5 bytes (0x05) long, which consists of
|
||||
// a string (0x04), 3 bytes (0x03) long, representing "foo".
|
||||
const fooSequence = [0x30, 0x05, 0x04, 0x03, 0x66, 0x6f, 0x6f]
|
||||
|
||||
// ClientID certificate request example from
|
||||
// https://web.archive.org/web/20221008202056/https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-object-identifier?redirectedfrom=MSDN
|
||||
const microsoftOID = [
|
||||
0x06, 0x09, // OID; 9 bytes
|
||||
0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x14, // 1.3.6.1.4.1.311.21.20
|
||||
0x31, 0x4a, // Set; 4 bytes
|
||||
0x30, 0x48, // Sequence; 48 bytes
|
||||
0x02, 0x01, 0x09, // Integer; 1 bytes; 9
|
||||
0x0c, 0x23, // UTF8 String; 23 bytes
|
||||
0x76, 0x69, 0x63, 0x68, 0x33, 0x64, 0x2e, 0x6a, // vich3d.j
|
||||
0x64, 0x64, 0x6d, 0x63, 0x73, 0x63, 0x23, 0x6e, // domcsc.n
|
||||
0x74, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x6d, 0x69, // ttest.mi
|
||||
0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x23, // crosoft.
|
||||
0x63, 0x64, 0x6d, // com
|
||||
0x0c, 0x15, // UTF8 String; 15 bytes
|
||||
0x4a, 0x44, 0x4f, 0x4d, 0x43, 0x53, 0x43, 0x5c, // JDOMCSC\
|
||||
0x61, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, // administ
|
||||
0x72, 0x61, 0x74, 0x6f, 0x72, // rator
|
||||
0x0c, 0x07, // UTF8 String; 7 bytes
|
||||
0x63, 0x65, 0x72, 0x74, 0x72, 0x65, 0x71 // certreq
|
||||
]
|
||||
|
||||
tap.test('must supply a buffer', async t => {
|
||||
const expected = TypeError('Must supply a Buffer instance to read.')
|
||||
t.throws(
|
||||
() => new BerReader(),
|
||||
expected
|
||||
)
|
||||
t.throws(
|
||||
() => new BerReader(''),
|
||||
expected
|
||||
)
|
||||
})
|
||||
|
||||
tap.test('has toStringTag', async t => {
|
||||
const reader = new BerReader(Buffer.from('foo'))
|
||||
t.equal(Object.prototype.toString.call(reader), '[object BerReader]')
|
||||
})
|
||||
|
||||
tap.test('buffer property returns buffer', async t => {
|
||||
const fooBuffer = Buffer.from(fooSequence)
|
||||
const reader = new BerReader(fooBuffer)
|
||||
|
||||
t.equal(
|
||||
fooBuffer.compare(reader.buffer),
|
||||
0
|
||||
)
|
||||
})
|
||||
|
||||
tap.test('peek reads but does not advance', async t => {
|
||||
const reader = new BerReader(Buffer.from([0xde]))
|
||||
const byte = reader.peek()
|
||||
t.equal(byte, 0xde)
|
||||
t.equal(reader.offset, 0)
|
||||
})
|
||||
|
||||
tap.test('readBoolean', t => {
|
||||
t.test('read boolean true', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x01, 0x01, 0xff]))
|
||||
t.equal(reader.readBoolean(), true, 'wrong value')
|
||||
t.equal(reader.length, 0x01, 'wrong length')
|
||||
})
|
||||
|
||||
t.test('read boolean false', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x01, 0x01, 0x00]))
|
||||
t.equal(reader.readBoolean(), false, 'wrong value')
|
||||
t.equal(reader.length, 0x01, 'wrong length')
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('readByte', t => {
|
||||
t.test('reads a byte and advances offset', async t => {
|
||||
const reader = new BerReader(Buffer.from([0xde]))
|
||||
t.equal(reader.offset, 0)
|
||||
t.equal(reader.readByte(), 0xde)
|
||||
t.equal(reader.offset, 1)
|
||||
})
|
||||
|
||||
t.test('returns null if buffer exceeded', async t => {
|
||||
const reader = new BerReader(Buffer.from([0xde]))
|
||||
reader.readByte()
|
||||
t.equal(reader.readByte(), null)
|
||||
})
|
||||
|
||||
t.test('peek does not advance offset', async t => {
|
||||
const reader = new BerReader(Buffer.from([0xde]))
|
||||
const byte = reader.readByte(true)
|
||||
t.equal(byte, 0xde)
|
||||
t.equal(reader.offset, 0)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('readEnumeration', t => {
|
||||
t.test('read enumeration', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x0a, 0x01, 0x20]))
|
||||
t.equal(reader.readEnumeration(), 0x20, 'wrong value')
|
||||
t.equal(reader.length, 0x01, 'wrong length')
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('readInt', t => {
|
||||
t.test('read 1 byte int', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x02, 0x01, 0x03]))
|
||||
t.equal(reader.readInt(), 0x03, 'wrong value')
|
||||
t.equal(reader.length, 0x01, 'wrong length')
|
||||
})
|
||||
|
||||
t.test('read 2 byte int', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x02, 0x02, 0x7e, 0xde]))
|
||||
t.equal(reader.readInt(), 0x7ede, 'wrong value')
|
||||
t.equal(reader.length, 0x02, 'wrong length')
|
||||
})
|
||||
|
||||
t.test('read 3 byte int', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x02, 0x03, 0x7e, 0xde, 0x03]))
|
||||
t.equal(reader.readInt(), 0x7ede03, 'wrong value')
|
||||
t.equal(reader.length, 0x03, 'wrong length')
|
||||
})
|
||||
|
||||
t.test('read 4 byte int', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x02, 0x04, 0x7e, 0xde, 0x03, 0x01]))
|
||||
t.equal(reader.readInt(), 0x7ede0301, 'wrong value')
|
||||
t.equal(reader.length, 0x04, 'wrong length')
|
||||
})
|
||||
|
||||
t.test('read 1 byte negative int', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x02, 0x01, 0xdc]))
|
||||
t.equal(reader.readInt(), -36, 'wrong value')
|
||||
t.equal(reader.length, 0x01, 'wrong length')
|
||||
})
|
||||
|
||||
t.test('read 2 byte negative int', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x02, 0x02, 0xc0, 0x4e]))
|
||||
t.equal(reader.readInt(), -16306, 'wrong value')
|
||||
t.equal(reader.length, 0x02, 'wrong length')
|
||||
})
|
||||
|
||||
t.test('read 3 byte negative int', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x02, 0x03, 0xff, 0x00, 0x19]))
|
||||
t.equal(reader.readInt(), -65511, 'wrong value')
|
||||
t.equal(reader.length, 0x03, 'wrong length')
|
||||
})
|
||||
|
||||
t.test('read 4 byte negative int', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x02, 0x04, 0x91, 0x7c, 0x22, 0x1f]))
|
||||
t.equal(reader.readInt(), -1854135777, 'wrong value')
|
||||
t.equal(reader.length, 0x04, 'wrong length')
|
||||
})
|
||||
|
||||
t.test('read 4 byte negative int (abandon request tag)', async t => {
|
||||
// Technically, an abandon request shouldn't ever have a negative
|
||||
// number, but this lets us test the feature completely.
|
||||
const reader = new BerReader(Buffer.from([0x80, 0x04, 0x91, 0x7c, 0x22, 0x1f]))
|
||||
t.equal(reader.readInt(0x80), -1854135777, 'wrong value')
|
||||
t.equal(reader.length, 0x04, 'wrong length')
|
||||
})
|
||||
|
||||
t.test('correctly advances offset', async t => {
|
||||
const reader = new BerReader(Buffer.from([
|
||||
0x30, 0x06, // sequence; 6 bytes
|
||||
0x02, 0x04, 0x91, 0x7c, 0x22, 0x1f // integer; 4 bytes
|
||||
]))
|
||||
const seqBuffer = reader.readTag(0x30)
|
||||
t.equal(
|
||||
Buffer.compare(
|
||||
seqBuffer,
|
||||
Buffer.from([0x02, 0x04, 0x91, 0x7c, 0x22, 0x1f]
|
||||
)
|
||||
),
|
||||
0
|
||||
)
|
||||
|
||||
t.equal(reader.readInt(), -1854135777, 'wrong value')
|
||||
t.equal(reader.length, 0x04, 'wrong length')
|
||||
t.equal(reader.offset, 8)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('readLength', t => {
|
||||
t.test('reads from specified offset', async t => {
|
||||
const reader = new BerReader(Buffer.from(fooSequence))
|
||||
const offset = reader.readLength(1)
|
||||
t.equal(offset, 2)
|
||||
t.equal(reader.length, 5)
|
||||
})
|
||||
|
||||
t.test('returns null if offset exceeds buffer', async t => {
|
||||
const reader = new BerReader(Buffer.from(fooSequence))
|
||||
const offset = reader.readLength(10)
|
||||
t.equal(offset, null)
|
||||
t.equal(reader.offset, 0)
|
||||
})
|
||||
|
||||
t.test('reads from current offset', async t => {
|
||||
const reader = new BerReader(Buffer.from(fooSequence))
|
||||
const byte = reader.readByte()
|
||||
t.equal(byte, 0x30)
|
||||
|
||||
const offset = reader.readLength()
|
||||
t.equal(offset, 2)
|
||||
t.equal(reader.length, 5)
|
||||
})
|
||||
|
||||
t.test('throws for indefinite length', async t => {
|
||||
// Buffer would indicate a string of indefinite length.
|
||||
const reader = new BerReader(Buffer.from([0x04, 0x80]))
|
||||
t.throws(
|
||||
() => reader.readLength(1),
|
||||
Error('Indefinite length not supported.')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('throws if length too long', async t => {
|
||||
// Buffer would indicate a string whose length should be indicated
|
||||
// by the next 5 bytes (omitted).
|
||||
const reader = new BerReader(Buffer.from([0x04, 0x85]))
|
||||
t.throws(
|
||||
() => reader.readLength(1),
|
||||
Error('Encoding too long.')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('reads a long (integer) from length', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x81, 0x94]))
|
||||
const offset = reader.readLength()
|
||||
t.equal(offset, 2)
|
||||
t.equal(reader.length, 148)
|
||||
})
|
||||
|
||||
t.test(
|
||||
'returns null if long (integer) from length exceeds buffer',
|
||||
async t => {
|
||||
const reader = new BerReader(Buffer.from([0x82, 0x03]))
|
||||
const offset = reader.readLength(0)
|
||||
t.equal(offset, null)
|
||||
t.equal(reader.length, 0)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('readOID', t => {
|
||||
t.test('returns null for bad buffer', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x06, 0x03, 0x0a]))
|
||||
t.equal(reader.readOID(), null)
|
||||
})
|
||||
|
||||
t.test('reads an OID', async t => {
|
||||
const input = Buffer.from(microsoftOID.slice(0, 11))
|
||||
const reader = new BerReader(input)
|
||||
t.equal(reader.readOID(), '1.3.6.1.4.1.311.21.20')
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('readRawBuffer', t => {
|
||||
t.test('requires number tag', async t => {
|
||||
const reader = new BerReader(Buffer.from([]))
|
||||
t.throws(
|
||||
() => reader.readRawBuffer(),
|
||||
Error('must specify an integer tag')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('throws if tag does not match', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x04, 0x00]))
|
||||
t.throws(
|
||||
() => reader.readRawBuffer(0x05),
|
||||
Error('Expected 0x05: got 0x04')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('reads empty string buffer', async t => {
|
||||
const buffer = Buffer.from([0x04, 0x00])
|
||||
const reader = new BerReader(buffer)
|
||||
const readBuffer = reader.readRawBuffer(0x04)
|
||||
t.equal(buffer.compare(readBuffer), 0)
|
||||
t.equal(reader.offset, 2)
|
||||
})
|
||||
|
||||
t.test('returns null for no value byte', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x04]))
|
||||
const buffer = reader.readRawBuffer(0x04)
|
||||
t.equal(buffer, null)
|
||||
t.equal(reader.offset, 0)
|
||||
})
|
||||
|
||||
t.test('returns null if value length exceeds buffer length', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x04, 0x01]))
|
||||
const buffer = reader.readRawBuffer(0x04)
|
||||
t.equal(buffer, null)
|
||||
t.equal(reader.offset, 0)
|
||||
})
|
||||
|
||||
t.test('return only next buffer', async t => {
|
||||
const buffer = Buffer.from([
|
||||
0x04, 0x03, 0x66, 0x6f, 0x6f,
|
||||
0x04, 0x03, 0x62, 0x61, 0x72,
|
||||
0x04, 0x03, 0x62, 0x61, 0x7a
|
||||
])
|
||||
const reader = new BerReader(buffer)
|
||||
reader.readString()
|
||||
|
||||
const readBuffer = reader.readRawBuffer(0x04)
|
||||
t.equal(reader.offset, 10)
|
||||
t.equal(readBuffer.compare(buffer.subarray(5, 10)), 0)
|
||||
})
|
||||
|
||||
t.test('does not advance offset', async t => {
|
||||
const buffer = Buffer.from([
|
||||
0x04, 0x03, 0x66, 0x6f, 0x6f,
|
||||
0x04, 0x03, 0x62, 0x61, 0x72,
|
||||
0x04, 0x03, 0x62, 0x61, 0x7a
|
||||
])
|
||||
const reader = new BerReader(buffer)
|
||||
reader.readString()
|
||||
|
||||
const readBuffer = reader.readRawBuffer(0x04, false)
|
||||
t.equal(reader.offset, 5)
|
||||
t.equal(readBuffer.compare(buffer.subarray(5, 10)), 0)
|
||||
})
|
||||
|
||||
t.test('reads buffer with multi-byte length', async t => {
|
||||
// 0x01b3 => 110110011 => 00000001 + 10110011 => 0x01 + 0xb3 => 435 bytes
|
||||
const bytes = [
|
||||
0x02, 0x01, 0x00, // simple integer
|
||||
0x04, 0x82, 0x01, 0xb3 // begin string sequence
|
||||
]
|
||||
for (let i = 1; i <= 435; i += 1) {
|
||||
// Create a long string of `~` characters
|
||||
bytes.push(0x7e)
|
||||
}
|
||||
// Add a null sequence terminator
|
||||
Array.prototype.push.apply(bytes, [0x30, 0x00])
|
||||
|
||||
const buffer = Buffer.from(bytes)
|
||||
const reader = new BerReader(buffer)
|
||||
t.equal(reader.readInt(), 0)
|
||||
t.equal(reader.readString(), '~'.repeat(435))
|
||||
t.equal(reader.readSequence(0x30), 0x30)
|
||||
reader.setOffset(0)
|
||||
|
||||
// Emulate what we would do to read the filter value from an LDAP
|
||||
// search request that has a very large filter:
|
||||
reader.readInt()
|
||||
const tag = reader.peek()
|
||||
t.equal(tag, 0x04)
|
||||
const rawBuffer = reader.readRawBuffer(tag)
|
||||
t.equal(rawBuffer.compare(buffer.subarray(3, bytes.length - 2)), 0)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('readSequence', t => {
|
||||
t.test('throws for tag mismatch', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x04, 0x00]))
|
||||
t.throws(
|
||||
() => reader.readSequence(0x30),
|
||||
Error('Expected 0x30: got 0x04')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('returns null when read length is null', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x30, 0x84, 0x04, 0x03]))
|
||||
t.equal(reader.readSequence(), null)
|
||||
})
|
||||
|
||||
t.test('return read sequence and advances offset', async t => {
|
||||
const reader = new BerReader(Buffer.from(fooSequence))
|
||||
const result = reader.readSequence()
|
||||
t.equal(result, 0x30)
|
||||
t.equal(reader.offset, 2)
|
||||
})
|
||||
|
||||
// Original test
|
||||
t.test('read sequence', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x30, 0x03, 0x01, 0x01, 0xff]))
|
||||
t.ok(reader)
|
||||
t.equal(reader.readSequence(), 0x30, 'wrong value')
|
||||
t.equal(reader.length, 0x03, 'wrong length')
|
||||
t.equal(reader.readBoolean(), true, 'wrong value')
|
||||
t.equal(reader.length, 0x01, 'wrong length')
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('readString', t => {
|
||||
t.test('throws for tag mismatch', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x30, 0x00]))
|
||||
t.throws(
|
||||
() => reader.readString(),
|
||||
Error('Expected 0x04: got 0x30')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('returns null when read length is null', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x04, 0x84, 0x03, 0x0a]))
|
||||
t.equal(reader.readString(), null)
|
||||
})
|
||||
|
||||
t.test('returns null when value bytes too short', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x04, 0x03, 0x0a]))
|
||||
t.equal(reader.readString(), null)
|
||||
})
|
||||
|
||||
t.test('returns empty buffer for zero length string', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x04, 0x00]))
|
||||
const result = reader.readString(0x04, true)
|
||||
t.type(result, Buffer)
|
||||
t.equal(Buffer.compare(result, Buffer.alloc(0)), 0)
|
||||
})
|
||||
|
||||
t.test('returns empty string for zero length string', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x04, 0x00]))
|
||||
const result = reader.readString()
|
||||
t.type(result, 'string')
|
||||
t.equal(result, '')
|
||||
})
|
||||
|
||||
t.test('returns string as buffer', async t => {
|
||||
const reader = new BerReader(Buffer.from(fooSequence.slice(2)))
|
||||
const result = reader.readString(0x04, true)
|
||||
t.type(result, Buffer)
|
||||
|
||||
const expected = Buffer.from(fooSequence.slice(4))
|
||||
t.equal(Buffer.compare(result, expected), 0)
|
||||
})
|
||||
|
||||
t.test('returns string as string', async t => {
|
||||
const reader = new BerReader(Buffer.from(fooSequence.slice(2)))
|
||||
const result = reader.readString()
|
||||
t.type(result, 'string')
|
||||
t.equal(result, 'foo')
|
||||
})
|
||||
|
||||
// Original test
|
||||
t.test('read string', async t => {
|
||||
const dn = 'cn=foo,ou=unit,o=test'
|
||||
const buf = Buffer.alloc(dn.length + 2)
|
||||
buf[0] = 0x04
|
||||
buf[1] = Buffer.byteLength(dn)
|
||||
buf.write(dn, 2)
|
||||
const reader = new BerReader(buf)
|
||||
t.ok(reader)
|
||||
t.equal(reader.readString(), dn, 'wrong value')
|
||||
t.equal(reader.length, dn.length, 'wrong length')
|
||||
})
|
||||
|
||||
// Orignal test
|
||||
t.test('long string', async t => {
|
||||
const buf = Buffer.alloc(256)
|
||||
const s =
|
||||
'2;649;CN=Red Hat CS 71GA Demo,O=Red Hat CS 71GA Demo,C=US;' +
|
||||
'CN=RHCS Agent - admin01,UID=admin01,O=redhat,C=US [1] This is ' +
|
||||
'Teena Vradmin\'s description.'
|
||||
buf[0] = 0x04
|
||||
buf[1] = 0x81
|
||||
buf[2] = 0x94
|
||||
buf.write(s, 3)
|
||||
const ber = new BerReader(buf.subarray(0, 3 + s.length))
|
||||
t.equal(ber.readString(), s)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('readTag', t => {
|
||||
t.test('throws error for null tag', async t => {
|
||||
const expected = Error('Must supply an ASN.1 tag to read.')
|
||||
const reader = new BerReader(Buffer.from(fooSequence))
|
||||
|
||||
t.throws(
|
||||
() => reader.readTag(),
|
||||
expected
|
||||
)
|
||||
})
|
||||
|
||||
t.test('returns null for null byte tag', { skip: true })
|
||||
|
||||
t.test('throws error for tag mismatch', async t => {
|
||||
const expected = Error('Expected 0x40: got 0x30')
|
||||
const reader = new BerReader(Buffer.from(fooSequence))
|
||||
|
||||
t.throws(
|
||||
() => reader.readTag(0x40),
|
||||
expected
|
||||
)
|
||||
})
|
||||
|
||||
t.test('returns null if field length is null', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x05]))
|
||||
t.equal(reader.readTag(0x05), null)
|
||||
})
|
||||
|
||||
t.test('returns null if field length greater than available bytes', async t => {
|
||||
const reader = new BerReader(Buffer.from([0x30, 0x03, 0x04, 0xa0]))
|
||||
t.equal(reader.readTag(0x30), null)
|
||||
})
|
||||
|
||||
t.test('returns null if field length greater than available bytes', async t => {
|
||||
const reader = new BerReader(Buffer.from(fooSequence))
|
||||
const expected = Buffer.from([0x04, 0x03, 0x66, 0x6f, 0x6f])
|
||||
const result = reader.readTag(0x30)
|
||||
t.equal(Buffer.compare(result, expected), 0)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('remain', t => {
|
||||
t.test('returns the size of the buffer if nothing read', async t => {
|
||||
const reader = new BerReader(Buffer.from(fooSequence))
|
||||
t.equal(7, reader.remain)
|
||||
})
|
||||
|
||||
t.test('returns accurate remaining bytes', async t => {
|
||||
const reader = new BerReader(Buffer.from(fooSequence))
|
||||
t.equal(0x30, reader.readSequence())
|
||||
t.equal(5, reader.remain)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('setOffset', t => {
|
||||
t.test('throws if not an integer', async t => {
|
||||
const expected = Error('Must supply an integer position.')
|
||||
const reader = new BerReader(Buffer.from(fooSequence))
|
||||
|
||||
t.throws(
|
||||
() => reader.setOffset(1.2),
|
||||
expected
|
||||
)
|
||||
t.throws(
|
||||
() => reader.setOffset('2'),
|
||||
expected
|
||||
)
|
||||
})
|
||||
|
||||
t.test('sets offset', async t => {
|
||||
const reader = new BerReader(Buffer.from(fooSequence))
|
||||
t.equal(reader.offset, 0)
|
||||
|
||||
reader.setOffset(2)
|
||||
t.equal(reader.offset, 2)
|
||||
t.equal(reader.peek(), 0x04)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('sequenceToReader', t => {
|
||||
t.test('returns new reader with full sequence', async t => {
|
||||
const multiSequence = [
|
||||
0x30, 14,
|
||||
...fooSequence,
|
||||
...fooSequence
|
||||
]
|
||||
const reader = new BerReader(Buffer.from(multiSequence))
|
||||
|
||||
// Read the intial sequence and verify current position.
|
||||
t.equal(0x30, reader.readSequence())
|
||||
t.equal(2, reader.offset)
|
||||
|
||||
// Advance the buffer to the start of the first sub-sequence value.
|
||||
t.equal(0x30, reader.readSequence())
|
||||
t.equal(4, reader.offset)
|
||||
t.equal(12, reader.remain)
|
||||
|
||||
// Get a new reader the consists of the first sub-sequence and verify
|
||||
// that the original reader's position has not changed.
|
||||
const fooReader = reader.sequenceToReader()
|
||||
t.equal(fooReader.remain, 7)
|
||||
t.equal(fooReader.offset, 0)
|
||||
t.equal(reader.offset, 4)
|
||||
t.equal(0x30, fooReader.readSequence())
|
||||
t.equal('foo', fooReader.readString())
|
||||
|
||||
// The original reader should advance like normal.
|
||||
t.equal('foo', reader.readString())
|
||||
t.equal(0x30, reader.readSequence())
|
||||
t.equal('foo', reader.readString())
|
||||
t.equal(0, reader.remain)
|
||||
t.equal(16, reader.offset)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('toHexDump', t => {
|
||||
t.test('dumps buffer', t => {
|
||||
const reader = new BerReader(
|
||||
Buffer.from([0x00, 0x01, 0x02, 0x03])
|
||||
)
|
||||
const expected = '00010203'
|
||||
|
||||
let found = ''
|
||||
const destination = new Writable({
|
||||
write (chunk, encoding, callback) {
|
||||
found += chunk.toString()
|
||||
callback()
|
||||
}
|
||||
})
|
||||
|
||||
destination.on('finish', () => {
|
||||
t.equal(found, expected)
|
||||
t.end()
|
||||
})
|
||||
|
||||
reader.toHexDump({
|
||||
destination,
|
||||
closeDestination: true
|
||||
})
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
// Original test
|
||||
tap.test('anonymous LDAPv3 bind', async t => {
|
||||
const BIND = Buffer.alloc(14)
|
||||
BIND[0] = 0x30 // Sequence
|
||||
BIND[1] = 12 // len
|
||||
BIND[2] = 0x02 // ASN.1 Integer
|
||||
BIND[3] = 1 // len
|
||||
BIND[4] = 0x04 // msgid (make up 4)
|
||||
BIND[5] = 0x60 // Bind Request
|
||||
BIND[6] = 7 // len
|
||||
BIND[7] = 0x02 // ASN.1 Integer
|
||||
BIND[8] = 1 // len
|
||||
BIND[9] = 0x03 // v3
|
||||
BIND[10] = 0x04 // String (bind dn)
|
||||
BIND[11] = 0 // len
|
||||
BIND[12] = 0x80 // ContextSpecific (choice)
|
||||
BIND[13] = 0 // simple bind
|
||||
|
||||
// Start testing ^^
|
||||
const ber = new BerReader(BIND)
|
||||
t.equal(ber.readSequence(), 48, 'Not an ASN.1 Sequence')
|
||||
t.equal(ber.length, 12, 'Message length should be 12')
|
||||
t.equal(ber.readInt(), 4, 'Message id should have been 4')
|
||||
t.equal(ber.readSequence(), 96, 'Bind Request should have been 96')
|
||||
t.equal(ber.length, 7, 'Bind length should have been 7')
|
||||
t.equal(ber.readInt(), 3, 'LDAP version should have been 3')
|
||||
t.equal(ber.readString(), '', 'Bind DN should have been empty')
|
||||
t.equal(ber.length, 0, 'string length should have been 0')
|
||||
t.equal(ber.readByte(), 0x80, 'Should have been ContextSpecific (choice)')
|
||||
t.equal(ber.readByte(), 0, 'Should have been simple bind')
|
||||
t.equal(null, ber.readByte(), 'Should be out of data')
|
||||
})
|
||||
36
node_modules/@ldapjs/asn1/lib/ber/types.js
generated
vendored
Normal file
36
node_modules/@ldapjs/asn1/lib/ber/types.js
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
EOC: 0x0,
|
||||
Boolean: 0x01,
|
||||
Integer: 0x02,
|
||||
BitString: 0x03,
|
||||
OctetString: 0x04,
|
||||
Null: 0x05,
|
||||
OID: 0x06,
|
||||
ObjectDescriptor: 0x07,
|
||||
External: 0x08,
|
||||
Real: 0x09, // float
|
||||
Enumeration: 0x0a,
|
||||
PDV: 0x0b,
|
||||
Utf8String: 0x0c,
|
||||
RelativeOID: 0x0d,
|
||||
Sequence: 0x10,
|
||||
Set: 0x11,
|
||||
NumericString: 0x12,
|
||||
PrintableString: 0x13,
|
||||
T61String: 0x14,
|
||||
VideotexString: 0x15,
|
||||
IA5String: 0x16,
|
||||
UTCTime: 0x17,
|
||||
GeneralizedTime: 0x18,
|
||||
GraphicString: 0x19,
|
||||
VisibleString: 0x1a,
|
||||
GeneralString: 0x1c,
|
||||
UniversalString: 0x1d,
|
||||
CharacterString: 0x1e,
|
||||
BMPString: 0x1f,
|
||||
Constructor: 0x20,
|
||||
LDAPSequence: 0x30,
|
||||
Context: 0x80
|
||||
}
|
||||
466
node_modules/@ldapjs/asn1/lib/ber/writer.js
generated
vendored
Normal file
466
node_modules/@ldapjs/asn1/lib/ber/writer.js
generated
vendored
Normal file
@ -0,0 +1,466 @@
|
||||
'use strict'
|
||||
|
||||
const types = require('./types')
|
||||
const bufferToHexDump = require('../buffer-to-hex-dump')
|
||||
|
||||
class BerWriter {
|
||||
/**
|
||||
* The source buffer as it was passed in when creating the instance.
|
||||
*
|
||||
* @type {Buffer}
|
||||
*/
|
||||
#buffer
|
||||
|
||||
/**
|
||||
* The total bytes in the backing buffer.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
#size
|
||||
|
||||
/**
|
||||
* As the BER buffer is written, this property records the current position
|
||||
* in the buffer.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
#offset = 0
|
||||
|
||||
/**
|
||||
* A list of offsets in the buffer where we need to insert sequence tag and
|
||||
* length pairs.
|
||||
*/
|
||||
#sequenceOffsets = []
|
||||
|
||||
/**
|
||||
* Coeffecient used when increasing the buffer to accomodate writes that
|
||||
* exceed the available space left in the buffer.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
#growthFactor
|
||||
|
||||
constructor ({ size = 1024, growthFactor = 8 } = {}) {
|
||||
this.#buffer = Buffer.alloc(size)
|
||||
this.#size = this.#buffer.length
|
||||
this.#offset = 0
|
||||
this.#growthFactor = growthFactor
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag] () { return 'BerWriter' }
|
||||
|
||||
get buffer () {
|
||||
// TODO: handle sequence check
|
||||
|
||||
return this.#buffer.subarray(0, this.#offset)
|
||||
}
|
||||
|
||||
/**
|
||||
* The size of the backing buffer.
|
||||
*
|
||||
* @return {number}
|
||||
*/
|
||||
get size () {
|
||||
return this.#size
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a raw buffer to the current writer instance. No validation to
|
||||
* determine if the buffer represents a valid BER encoding is performed.
|
||||
*
|
||||
* @param {Buffer} buffer The buffer to append. If this is not a valid BER
|
||||
* sequence of data, it will invalidate the BER represented by the `BerWriter`.
|
||||
*
|
||||
* @throws If the input is not an instance of Buffer.
|
||||
*/
|
||||
appendBuffer (buffer) {
|
||||
if (Buffer.isBuffer(buffer) === false) {
|
||||
throw Error('buffer must be an instance of Buffer')
|
||||
}
|
||||
this.#ensureBufferCapacity(buffer.length)
|
||||
buffer.copy(this.#buffer, this.#offset, 0, buffer.length)
|
||||
this.#offset += buffer.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a sequence started with {@link startSequence}.
|
||||
*
|
||||
* @throws When the sequence is too long and would exceed the 4 byte
|
||||
* length descriptor limitation.
|
||||
*/
|
||||
endSequence () {
|
||||
const sequenceStartOffset = this.#sequenceOffsets.pop()
|
||||
const start = sequenceStartOffset + 3
|
||||
const length = this.#offset - start
|
||||
|
||||
if (length <= 0x7f) {
|
||||
this.#shift(start, length, -2)
|
||||
this.#buffer[sequenceStartOffset] = length
|
||||
} else if (length <= 0xff) {
|
||||
this.#shift(start, length, -1)
|
||||
this.#buffer[sequenceStartOffset] = 0x81
|
||||
this.#buffer[sequenceStartOffset + 1] = length
|
||||
} else if (length <= 0xffff) {
|
||||
this.#buffer[sequenceStartOffset] = 0x82
|
||||
this.#buffer[sequenceStartOffset + 1] = length >> 8
|
||||
this.#buffer[sequenceStartOffset + 2] = length
|
||||
} else if (length <= 0xffffff) {
|
||||
this.#shift(start, length, 1)
|
||||
this.#buffer[sequenceStartOffset] = 0x83
|
||||
this.#buffer[sequenceStartOffset + 1] = length >> 16
|
||||
this.#buffer[sequenceStartOffset + 2] = length >> 8
|
||||
this.#buffer[sequenceStartOffset + 3] = length
|
||||
} else {
|
||||
throw Error('sequence too long')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a sequence tag to the buffer and advance the offset to the starting
|
||||
* position of the value. Sequences must be completed with a subsequent
|
||||
* invocation of {@link endSequence}.
|
||||
*
|
||||
* @param {number} [tag=0x30] The tag to use for the sequence.
|
||||
*
|
||||
* @throws When the tag is not a number.
|
||||
*/
|
||||
startSequence (tag = (types.Sequence | types.Constructor)) {
|
||||
if (typeof tag !== 'number') {
|
||||
throw TypeError('tag must be a Number')
|
||||
}
|
||||
|
||||
this.writeByte(tag)
|
||||
this.#sequenceOffsets.push(this.#offset)
|
||||
this.#ensureBufferCapacity(3)
|
||||
this.#offset += 3
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HexDumpParams} params The `buffer` parameter will be ignored.
|
||||
*
|
||||
* @see bufferToHexDump
|
||||
*/
|
||||
toHexDump (params) {
|
||||
bufferToHexDump({
|
||||
...params,
|
||||
buffer: this.buffer
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a boolean TLV to the buffer.
|
||||
*
|
||||
* @param {boolean} boolValue
|
||||
* @param {tag} [number=0x01] A custom tag for the boolean.
|
||||
*
|
||||
* @throws When a parameter is of the wrong type.
|
||||
*/
|
||||
writeBoolean (boolValue, tag = types.Boolean) {
|
||||
if (typeof boolValue !== 'boolean') {
|
||||
throw TypeError('boolValue must be a Boolean')
|
||||
}
|
||||
if (typeof tag !== 'number') {
|
||||
throw TypeError('tag must be a Number')
|
||||
}
|
||||
|
||||
this.#ensureBufferCapacity(3)
|
||||
this.#buffer[this.#offset++] = tag
|
||||
this.#buffer[this.#offset++] = 0x01
|
||||
this.#buffer[this.#offset++] = boolValue === true ? 0xff : 0x00
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an arbitrary buffer of data to the backing buffer using the given
|
||||
* tag.
|
||||
*
|
||||
* @param {Buffer} buffer
|
||||
* @param {number} tag The tag to use for the ASN.1 TLV sequence.
|
||||
*
|
||||
* @throws When either input parameter is of the wrong type.
|
||||
*/
|
||||
writeBuffer (buffer, tag) {
|
||||
if (typeof tag !== 'number') {
|
||||
throw TypeError('tag must be a Number')
|
||||
}
|
||||
if (Buffer.isBuffer(buffer) === false) {
|
||||
throw TypeError('buffer must be an instance of Buffer')
|
||||
}
|
||||
|
||||
this.writeByte(tag)
|
||||
this.writeLength(buffer.length)
|
||||
this.#ensureBufferCapacity(buffer.length)
|
||||
buffer.copy(this.#buffer, this.#offset, 0, buffer.length)
|
||||
this.#offset += buffer.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a single byte to the backing buffer and advance the offset. The
|
||||
* backing buffer will be automatically expanded to accomodate the new byte
|
||||
* if no room in the buffer remains.
|
||||
*
|
||||
* @param {number} byte The byte to be written.
|
||||
*
|
||||
* @throws When the passed in parameter is not a `Number` (aka a byte).
|
||||
*/
|
||||
writeByte (byte) {
|
||||
if (typeof byte !== 'number') {
|
||||
throw TypeError('argument must be a Number')
|
||||
}
|
||||
|
||||
this.#ensureBufferCapacity(1)
|
||||
this.#buffer[this.#offset++] = byte
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an enumeration TLV to the buffer.
|
||||
*
|
||||
* @param {number} value
|
||||
* @param {number} [tag=0x0a] A custom tag for the enumeration.
|
||||
*
|
||||
* @throws When a passed in parameter is not of the correct type, or the
|
||||
* value requires too many bytes (must be <= 4).
|
||||
*/
|
||||
writeEnumeration (value, tag = types.Enumeration) {
|
||||
if (typeof value !== 'number') {
|
||||
throw TypeError('value must be a Number')
|
||||
}
|
||||
if (typeof tag !== 'number') {
|
||||
throw TypeError('tag must be a Number')
|
||||
}
|
||||
this.writeInt(value, tag)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an, up to 4 byte, integer TLV to the buffer.
|
||||
*
|
||||
* @param {number} intToWrite
|
||||
* @param {number} [tag=0x02]
|
||||
*
|
||||
* @throws When either parameter is not of the write type, or if the
|
||||
* integer consists of too many bytes.
|
||||
*/
|
||||
writeInt (intToWrite, tag = types.Integer) {
|
||||
if (typeof intToWrite !== 'number') {
|
||||
throw TypeError('intToWrite must be a Number')
|
||||
}
|
||||
if (typeof tag !== 'number') {
|
||||
throw TypeError('tag must be a Number')
|
||||
}
|
||||
|
||||
let intSize = 4
|
||||
while (
|
||||
(
|
||||
((intToWrite & 0xff800000) === 0) ||
|
||||
((intToWrite & 0xff800000) === (0xff800000 >> 0))
|
||||
) && (intSize > 1)
|
||||
) {
|
||||
intSize--
|
||||
intToWrite <<= 8
|
||||
}
|
||||
|
||||
// TODO: figure out how to cover this in a test.
|
||||
/* istanbul ignore if: needs test */
|
||||
if (intSize > 4) {
|
||||
throw Error('BER ints cannot be > 0xffffffff')
|
||||
}
|
||||
|
||||
this.#ensureBufferCapacity(2 + intSize)
|
||||
this.#buffer[this.#offset++] = tag
|
||||
this.#buffer[this.#offset++] = intSize
|
||||
|
||||
while (intSize-- > 0) {
|
||||
this.#buffer[this.#offset++] = ((intToWrite & 0xff000000) >>> 24)
|
||||
intToWrite <<= 8
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a set of length bytes to the backing buffer. Per
|
||||
* https://www.rfc-editor.org/rfc/rfc4511.html#section-5.1, LDAP message
|
||||
* BERs prohibit greater than 4 byte lengths. Given we are supporing
|
||||
* the `ldapjs` module, we limit ourselves to 4 byte lengths.
|
||||
*
|
||||
* @param {number} len The length value to write to the buffer.
|
||||
*
|
||||
* @throws When the length is not a number or requires too many bytes.
|
||||
*/
|
||||
writeLength (len) {
|
||||
if (typeof len !== 'number') {
|
||||
throw TypeError('argument must be a Number')
|
||||
}
|
||||
|
||||
this.#ensureBufferCapacity(4)
|
||||
|
||||
if (len <= 0x7f) {
|
||||
this.#buffer[this.#offset++] = len
|
||||
} else if (len <= 0xff) {
|
||||
this.#buffer[this.#offset++] = 0x81
|
||||
this.#buffer[this.#offset++] = len
|
||||
} else if (len <= 0xffff) {
|
||||
this.#buffer[this.#offset++] = 0x82
|
||||
this.#buffer[this.#offset++] = len >> 8
|
||||
this.#buffer[this.#offset++] = len
|
||||
} else if (len <= 0xffffff) {
|
||||
this.#buffer[this.#offset++] = 0x83
|
||||
this.#buffer[this.#offset++] = len >> 16
|
||||
this.#buffer[this.#offset++] = len >> 8
|
||||
this.#buffer[this.#offset++] = len
|
||||
} else {
|
||||
throw Error('length too long (> 4 bytes)')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a NULL tag and value to the buffer.
|
||||
*/
|
||||
writeNull () {
|
||||
this.writeByte(types.Null)
|
||||
this.writeByte(0x00)
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an OID string, e.g. `1.2.840.113549.1.1.1`, split it into
|
||||
* octets, encode the octets, and write it to the backing buffer.
|
||||
*
|
||||
* @param {string} oidString
|
||||
* @param {number} [tag=0x06] A custom tag to use for the OID.
|
||||
*
|
||||
* @throws When the parameters are not of the correct types, or if the
|
||||
* OID is not in the correct format.
|
||||
*/
|
||||
writeOID (oidString, tag = types.OID) {
|
||||
if (typeof oidString !== 'string') {
|
||||
throw TypeError('oidString must be a string')
|
||||
}
|
||||
if (typeof tag !== 'number') {
|
||||
throw TypeError('tag must be a Number')
|
||||
}
|
||||
|
||||
if (/^([0-9]+\.){3,}[0-9]+$/.test(oidString) === false) {
|
||||
throw Error('oidString is not a valid OID string')
|
||||
}
|
||||
|
||||
const parts = oidString.split('.')
|
||||
const bytes = []
|
||||
bytes.push(parseInt(parts[0], 10) * 40 + parseInt(parts[1], 10))
|
||||
for (const part of parts.slice(2)) {
|
||||
encodeOctet(bytes, parseInt(part, 10))
|
||||
}
|
||||
|
||||
this.#ensureBufferCapacity(2 + bytes.length)
|
||||
this.writeByte(tag)
|
||||
this.writeLength(bytes.length)
|
||||
this.appendBuffer(Buffer.from(bytes))
|
||||
|
||||
function encodeOctet (bytes, octet) {
|
||||
if (octet < 128) {
|
||||
bytes.push(octet)
|
||||
} else if (octet < 16_384) {
|
||||
bytes.push((octet >>> 7) | 0x80)
|
||||
bytes.push(octet & 0x7F)
|
||||
} else if (octet < 2_097_152) {
|
||||
bytes.push((octet >>> 14) | 0x80)
|
||||
bytes.push(((octet >>> 7) | 0x80) & 0xFF)
|
||||
bytes.push(octet & 0x7F)
|
||||
} else if (octet < 268_435_456) {
|
||||
bytes.push((octet >>> 21) | 0x80)
|
||||
bytes.push(((octet >>> 14) | 0x80) & 0xFF)
|
||||
bytes.push(((octet >>> 7) | 0x80) & 0xFF)
|
||||
bytes.push(octet & 0x7F)
|
||||
} else {
|
||||
bytes.push(((octet >>> 28) | 0x80) & 0xFF)
|
||||
bytes.push(((octet >>> 21) | 0x80) & 0xFF)
|
||||
bytes.push(((octet >>> 14) | 0x80) & 0xFF)
|
||||
bytes.push(((octet >>> 7) | 0x80) & 0xFF)
|
||||
bytes.push(octet & 0x7F)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a string TLV to the buffer.
|
||||
*
|
||||
* @param {string} stringToWrite
|
||||
* @param {number} [tag=0x04] The tag to use.
|
||||
*
|
||||
* @throws When either input parameter is of the wrong type.
|
||||
*/
|
||||
writeString (stringToWrite, tag = types.OctetString) {
|
||||
if (typeof stringToWrite !== 'string') {
|
||||
throw TypeError('stringToWrite must be a string')
|
||||
}
|
||||
if (typeof tag !== 'number') {
|
||||
throw TypeError('tag must be a number')
|
||||
}
|
||||
|
||||
const toWriteLength = Buffer.byteLength(stringToWrite)
|
||||
this.writeByte(tag)
|
||||
this.writeLength(toWriteLength)
|
||||
if (toWriteLength > 0) {
|
||||
this.#ensureBufferCapacity(toWriteLength)
|
||||
this.#buffer.write(stringToWrite, this.#offset)
|
||||
this.#offset += toWriteLength
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a set of strings, write each as a string TLV to the buffer.
|
||||
*
|
||||
* @param {string[]} strings
|
||||
*
|
||||
* @throws When the input is not an array.
|
||||
*/
|
||||
writeStringArray (strings) {
|
||||
if (Array.isArray(strings) === false) {
|
||||
throw TypeError('strings must be an instance of Array')
|
||||
}
|
||||
for (const string of strings) {
|
||||
this.writeString(string)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a number of bytes to be written into the buffer, verify the buffer
|
||||
* has enough free space. If not, allocate a new buffer, copy the current
|
||||
* backing buffer into the new buffer, and promote the new buffer to be the
|
||||
* current backing buffer.
|
||||
*
|
||||
* @param {number} numberOfBytesToWrite How many bytes are required to be
|
||||
* available for writing in the backing buffer.
|
||||
*/
|
||||
#ensureBufferCapacity (numberOfBytesToWrite) {
|
||||
if (this.#size - this.#offset < numberOfBytesToWrite) {
|
||||
let newSize = this.#size * this.#growthFactor
|
||||
if (newSize - this.#offset < numberOfBytesToWrite) {
|
||||
newSize += numberOfBytesToWrite
|
||||
}
|
||||
|
||||
const newBuffer = Buffer.alloc(newSize)
|
||||
|
||||
this.#buffer.copy(newBuffer, 0, 0, this.#offset)
|
||||
this.#buffer = newBuffer
|
||||
this.#size = newSize
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift a region of the buffer indicated by `start` and `length` a number
|
||||
* of bytes indicated by `shiftAmount`.
|
||||
*
|
||||
* @param {number} start The starting position in the buffer for the region
|
||||
* of bytes to be shifted.
|
||||
* @param {number} length The number of bytes that constitutes the region
|
||||
* of the buffer to be shifted.
|
||||
* @param {number} shiftAmount The number of bytes to shift the region by.
|
||||
* This may be negative.
|
||||
*/
|
||||
#shift (start, length, shiftAmount) {
|
||||
// TODO: this leaves garbage behind. We should either zero out the bytes
|
||||
// left behind, or device a better algorightm that generates a clean
|
||||
// buffer.
|
||||
this.#buffer.copy(this.#buffer, start + shiftAmount, start, start + length)
|
||||
this.#offset += shiftAmount
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BerWriter
|
||||
749
node_modules/@ldapjs/asn1/lib/ber/writer.test.js
generated
vendored
Normal file
749
node_modules/@ldapjs/asn1/lib/ber/writer.test.js
generated
vendored
Normal file
@ -0,0 +1,749 @@
|
||||
'use strict'
|
||||
|
||||
const tap = require('tap')
|
||||
const { Writable } = require('stream')
|
||||
const BerWriter = require('./writer')
|
||||
|
||||
tap.test('has toStringTag', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.equal(Object.prototype.toString.call(writer), '[object BerWriter]')
|
||||
})
|
||||
|
||||
tap.test('#ensureBufferCapacity', t => {
|
||||
t.test('does not change buffer size if unnecessary', async t => {
|
||||
const writer = new BerWriter({ size: 1 })
|
||||
t.equal(writer.size, 1)
|
||||
|
||||
writer.writeByte(0x01)
|
||||
t.equal(writer.size, 1)
|
||||
})
|
||||
|
||||
t.test('expands buffer to accomodate write skipping growth factor', async t => {
|
||||
const writer = new BerWriter({ size: 0 })
|
||||
t.equal(writer.size, 0)
|
||||
|
||||
writer.writeByte(0x01)
|
||||
t.equal(writer.size, 1)
|
||||
t.equal(Buffer.compare(writer.buffer, Buffer.from([0x01])), 0)
|
||||
})
|
||||
|
||||
t.test('expands buffer to accomodate write with growth factor', async t => {
|
||||
const writer = new BerWriter({ size: 1 })
|
||||
t.equal(writer.size, 1)
|
||||
|
||||
writer.writeByte(0x01)
|
||||
writer.writeByte(0x02)
|
||||
t.equal(writer.size, 8)
|
||||
t.equal(Buffer.compare(writer.buffer, Buffer.from([0x01, 0x02])), 0)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('appendBuffer', t => {
|
||||
t.test('throws if input not a buffer', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.appendBuffer('foo'),
|
||||
Error('buffer must be an instance of Buffer')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('appendBuffer appends a buffer', async t => {
|
||||
const expected = Buffer.from([0x04, 0x03, 0x66, 0x6f, 0x6f, 0x66, 0x6f, 0x6f])
|
||||
const writer = new BerWriter()
|
||||
writer.writeString('foo')
|
||||
writer.appendBuffer(Buffer.from('foo'))
|
||||
t.equal(Buffer.compare(writer.buffer, expected), 0)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('endSequence', t => {
|
||||
t.test('ends a sequence', async t => {
|
||||
const writer = new BerWriter({ size: 25 })
|
||||
writer.startSequence()
|
||||
writer.writeString('hello world')
|
||||
writer.endSequence()
|
||||
|
||||
const ber = writer.buffer
|
||||
const expected = Buffer.from([
|
||||
0x30, 0x0d, // sequence; 13 bytes
|
||||
0x04, 0x0b, // string; 11 bytes
|
||||
0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, // 'hello '
|
||||
0x77, 0x6f, 0x72, 0x6c, 0x64 // 'world'
|
||||
])
|
||||
t.equal(Buffer.compare(ber, expected), 0)
|
||||
})
|
||||
|
||||
t.test('ends sequence of two byte length', async t => {
|
||||
const value = Buffer.alloc(0x81, 0x01)
|
||||
const writer = new BerWriter()
|
||||
|
||||
writer.startSequence()
|
||||
writer.writeBuffer(value, 0x04)
|
||||
writer.endSequence()
|
||||
|
||||
const ber = writer.buffer
|
||||
t.equal(
|
||||
Buffer.from([0x30, 0x81, 0x84, 0x04, 0x81, value.length])
|
||||
.compare(ber.subarray(0, 6)),
|
||||
0
|
||||
)
|
||||
})
|
||||
|
||||
t.test('ends sequence of three byte length', async t => {
|
||||
const value = Buffer.alloc(0xfe, 0x01)
|
||||
const writer = new BerWriter()
|
||||
|
||||
writer.startSequence()
|
||||
writer.writeBuffer(value, 0x04)
|
||||
writer.endSequence()
|
||||
|
||||
const ber = writer.buffer
|
||||
t.equal(
|
||||
Buffer.from([0x30, 0x82, 0x01, 0x01, 0x04, 0x81, value.length])
|
||||
.compare(ber.subarray(0, 7)),
|
||||
0
|
||||
)
|
||||
})
|
||||
|
||||
t.test('ends sequence of four byte length', async t => {
|
||||
const value = Buffer.alloc(0xaaaaaa, 0x01)
|
||||
const writer = new BerWriter()
|
||||
|
||||
writer.startSequence()
|
||||
writer.writeBuffer(value, 0x04)
|
||||
writer.endSequence()
|
||||
|
||||
const ber = writer.buffer
|
||||
t.equal(
|
||||
Buffer.from([0x30, 0x83, 0xaa, 0xaa, 0xaf, 0x04, 0x83, value.length])
|
||||
.compare(ber.subarray(0, 8)),
|
||||
0
|
||||
)
|
||||
})
|
||||
|
||||
t.test('throws if sequence too long', async t => {
|
||||
const value = Buffer.alloc(0xaffffff, 0x01)
|
||||
const writer = new BerWriter()
|
||||
|
||||
writer.startSequence()
|
||||
writer.writeByte(0x04)
|
||||
// We can't write the length because it is too long. However, this
|
||||
// still gives us enough data to generate the error we want to generate.
|
||||
writer.appendBuffer(value)
|
||||
t.throws(
|
||||
() => writer.endSequence(),
|
||||
Error('sequence too long')
|
||||
)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('startSequence', t => {
|
||||
t.test('throws if tag not a number', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.startSequence('30'),
|
||||
Error('tag must be a Number')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('starts a sequence', async t => {
|
||||
const writer = new BerWriter({ size: 1 })
|
||||
writer.startSequence()
|
||||
t.equal(writer.size, 8)
|
||||
|
||||
const expected = Buffer.from([0x30, 0x00, 0x00, 0x00])
|
||||
t.equal(Buffer.compare(writer.buffer, expected), 0)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('toHexDump', t => {
|
||||
t.test('dumps buffer', t => {
|
||||
const writer = new BerWriter()
|
||||
writer.appendBuffer(Buffer.from([0x00, 0x01, 0x02, 0x03]))
|
||||
const expected = '00010203'
|
||||
|
||||
let found = ''
|
||||
const destination = new Writable({
|
||||
write (chunk, encoding, callback) {
|
||||
found += chunk.toString()
|
||||
callback()
|
||||
}
|
||||
})
|
||||
|
||||
destination.on('finish', () => {
|
||||
t.equal(found, expected)
|
||||
t.end()
|
||||
})
|
||||
|
||||
writer.toHexDump({
|
||||
destination,
|
||||
closeDestination: true
|
||||
})
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('writeBoolean', t => {
|
||||
t.test('throws if input not a boolean', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeBoolean(1),
|
||||
Error('boolValue must be a Boolean')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('throws if tag not a number', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeBoolean(true, '5'),
|
||||
Error('tag must be a Number')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('writes true', async t => {
|
||||
const writer = new BerWriter({ size: 1 })
|
||||
writer.writeBoolean(true)
|
||||
t.equal(writer.size, 8)
|
||||
t.equal(Buffer.compare(writer.buffer, Buffer.from([0x01, 0x01, 0xff])), 0)
|
||||
})
|
||||
|
||||
t.test('writes false', async t => {
|
||||
const writer = new BerWriter({ size: 1 })
|
||||
writer.writeBoolean(false)
|
||||
t.equal(writer.size, 8)
|
||||
t.equal(Buffer.compare(writer.buffer, Buffer.from([0x01, 0x01, 0x00])), 0)
|
||||
})
|
||||
|
||||
t.test('writes with custom tag', async t => {
|
||||
const writer = new BerWriter({ size: 1 })
|
||||
writer.writeBoolean(true, 0xff)
|
||||
t.equal(writer.size, 8)
|
||||
t.equal(Buffer.compare(writer.buffer, Buffer.from([0xff, 0x01, 0xff])), 0)
|
||||
})
|
||||
|
||||
// Original test
|
||||
t.test('write boolean', async t => {
|
||||
const writer = new BerWriter()
|
||||
|
||||
writer.writeBoolean(true)
|
||||
writer.writeBoolean(false)
|
||||
const ber = writer.buffer
|
||||
|
||||
t.equal(ber.length, 6, 'Wrong length')
|
||||
t.equal(ber[0], 0x01, 'tag wrong')
|
||||
t.equal(ber[1], 0x01, 'length wrong')
|
||||
t.equal(ber[2], 0xff, 'value wrong')
|
||||
t.equal(ber[3], 0x01, 'tag wrong')
|
||||
t.equal(ber[4], 0x01, 'length wrong')
|
||||
t.equal(ber[5], 0x00, 'value wrong')
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('writeBuffer', t => {
|
||||
t.test('throws if tag not a number', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeBuffer(Buffer.alloc(0), '1'),
|
||||
Error('tag must be a Number')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('throws if buffer not a Buffer', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeBuffer([0x00], 0x01),
|
||||
Error('buffer must be an instance of Buffer')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('write buffer', async t => {
|
||||
const writer = new BerWriter()
|
||||
// write some stuff to start with
|
||||
writer.writeString('hello world')
|
||||
let ber = writer.buffer
|
||||
const buf = Buffer.from([0x04, 0x0b, 0x30, 0x09, 0x02, 0x01, 0x0f, 0x01, 0x01,
|
||||
0xff, 0x01, 0x01, 0xff])
|
||||
writer.writeBuffer(buf.subarray(2, buf.length), 0x04)
|
||||
ber = writer.buffer
|
||||
|
||||
t.equal(ber.length, 26, 'wrong length')
|
||||
t.equal(ber[0], 0x04, 'wrong tag')
|
||||
t.equal(ber[1], 11, 'wrong length')
|
||||
t.equal(ber.slice(2, 13).toString('utf8'), 'hello world', 'wrong value')
|
||||
t.equal(ber[13], buf[0], 'wrong tag')
|
||||
t.equal(ber[14], buf[1], 'wrong length')
|
||||
for (let i = 13, j = 0; i < ber.length && j < buf.length; i++, j++) {
|
||||
t.equal(ber[i], buf[j], 'buffer contents not identical')
|
||||
}
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('writeByte', t => {
|
||||
t.test('throws if input not a number', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.equal(writer.size, 1024)
|
||||
|
||||
t.throws(
|
||||
() => writer.writeByte('1'),
|
||||
Error('argument must be a Number')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('writes a byte to the backing buffer', async t => {
|
||||
const writer = new BerWriter()
|
||||
writer.writeByte(0x01)
|
||||
|
||||
const buffer = writer.buffer
|
||||
t.equal(buffer.length, 1)
|
||||
t.equal(Buffer.compare(buffer, Buffer.from([0x01])), 0)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('writeEnumeration', async t => {
|
||||
t.test('throws if value not a number', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeEnumeration('1'),
|
||||
Error('value must be a Number')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('throws if tag not a number', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeEnumeration(1, '1'),
|
||||
Error('tag must be a Number')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('writes an enumeration', async t => {
|
||||
const writer = new BerWriter({ size: 1 })
|
||||
writer.writeEnumeration(0x01)
|
||||
t.equal(writer.size, 8)
|
||||
t.equal(Buffer.compare(writer.buffer, Buffer.from([0x0a, 0x01, 0x01])), 0)
|
||||
})
|
||||
|
||||
t.test('writes an enumeration with custom tag', async t => {
|
||||
const writer = new BerWriter({ size: 1 })
|
||||
writer.writeEnumeration(0x01, 0xff)
|
||||
t.equal(writer.size, 8)
|
||||
t.equal(Buffer.compare(writer.buffer, Buffer.from([0xff, 0x01, 0x01])), 0)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('writeInt', t => {
|
||||
t.test('throws if int not a number', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeInt('1'),
|
||||
Error('intToWrite must be a Number')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('throws if tag not a number', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeInt(1, '1'),
|
||||
Error('tag must be a Number')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('write 1 byte int', async t => {
|
||||
const writer = new BerWriter()
|
||||
|
||||
writer.writeInt(0x7f)
|
||||
const ber = writer.buffer
|
||||
|
||||
t.equal(ber.length, 3, 'Wrong length for an int: ' + ber.length)
|
||||
t.equal(ber[0], 0x02, 'ASN.1 tag wrong (2) -> ' + ber[0])
|
||||
t.equal(ber[1], 0x01, 'length wrong(1) -> ' + ber[1])
|
||||
t.equal(ber[2], 0x7f, 'value wrong(3) -> ' + ber[2])
|
||||
})
|
||||
|
||||
t.test('write 2 byte int', async t => {
|
||||
const writer = new BerWriter()
|
||||
|
||||
writer.writeInt(0x7ffe)
|
||||
const ber = writer.buffer
|
||||
|
||||
t.equal(ber.length, 4, 'Wrong length for an int')
|
||||
t.equal(ber[0], 0x02, 'ASN.1 tag wrong')
|
||||
t.equal(ber[1], 0x02, 'length wrong')
|
||||
t.equal(ber[2], 0x7f, 'value wrong (byte 1)')
|
||||
t.equal(ber[3], 0xfe, 'value wrong (byte 2)')
|
||||
})
|
||||
|
||||
t.test('write 3 byte int', async t => {
|
||||
const writer = new BerWriter()
|
||||
|
||||
writer.writeInt(0x7ffffe)
|
||||
const ber = writer.buffer
|
||||
|
||||
t.equal(ber.length, 5, 'Wrong length for an int')
|
||||
t.equal(ber[0], 0x02, 'ASN.1 tag wrong')
|
||||
t.equal(ber[1], 0x03, 'length wrong')
|
||||
t.equal(ber[2], 0x7f, 'value wrong (byte 1)')
|
||||
t.equal(ber[3], 0xff, 'value wrong (byte 2)')
|
||||
t.equal(ber[4], 0xfe, 'value wrong (byte 3)')
|
||||
})
|
||||
|
||||
t.test('write 4 byte int', async t => {
|
||||
const writer = new BerWriter()
|
||||
|
||||
writer.writeInt(0x7ffffffe)
|
||||
const ber = writer.buffer
|
||||
|
||||
t.equal(ber.length, 6, 'Wrong length for an int')
|
||||
t.equal(ber[0], 0x02, 'ASN.1 tag wrong')
|
||||
t.equal(ber[1], 0x04, 'length wrong')
|
||||
t.equal(ber[2], 0x7f, 'value wrong (byte 1)')
|
||||
t.equal(ber[3], 0xff, 'value wrong (byte 2)')
|
||||
t.equal(ber[4], 0xff, 'value wrong (byte 3)')
|
||||
t.equal(ber[5], 0xfe, 'value wrong (byte 4)')
|
||||
})
|
||||
|
||||
t.test('write 1 byte negative int', async t => {
|
||||
const writer = new BerWriter()
|
||||
|
||||
writer.writeInt(-128)
|
||||
const ber = writer.buffer
|
||||
|
||||
t.equal(ber.length, 3, 'Wrong length for an int')
|
||||
t.equal(ber[0], 0x02, 'ASN.1 tag wrong')
|
||||
t.equal(ber[1], 0x01, 'length wrong')
|
||||
t.equal(ber[2], 0x80, 'value wrong (byte 1)')
|
||||
})
|
||||
|
||||
t.test('write 2 byte negative int', async t => {
|
||||
const writer = new BerWriter()
|
||||
|
||||
writer.writeInt(-22400)
|
||||
const ber = writer.buffer
|
||||
|
||||
t.equal(ber.length, 4, 'Wrong length for an int')
|
||||
t.equal(ber[0], 0x02, 'ASN.1 tag wrong')
|
||||
t.equal(ber[1], 0x02, 'length wrong')
|
||||
t.equal(ber[2], 0xa8, 'value wrong (byte 1)')
|
||||
t.equal(ber[3], 0x80, 'value wrong (byte 2)')
|
||||
})
|
||||
|
||||
t.test('write 3 byte negative int', async t => {
|
||||
const writer = new BerWriter()
|
||||
|
||||
writer.writeInt(-481653)
|
||||
const ber = writer.buffer
|
||||
|
||||
t.equal(ber.length, 5, 'Wrong length for an int')
|
||||
t.equal(ber[0], 0x02, 'ASN.1 tag wrong')
|
||||
t.equal(ber[1], 0x03, 'length wrong')
|
||||
t.equal(ber[2], 0xf8, 'value wrong (byte 1)')
|
||||
t.equal(ber[3], 0xa6, 'value wrong (byte 2)')
|
||||
t.equal(ber[4], 0x8b, 'value wrong (byte 3)')
|
||||
})
|
||||
|
||||
t.test('write 4 byte negative int', async t => {
|
||||
const writer = new BerWriter()
|
||||
|
||||
writer.writeInt(-1522904131)
|
||||
const ber = writer.buffer
|
||||
|
||||
t.equal(ber.length, 6, 'Wrong length for an int')
|
||||
t.equal(ber[0], 0x02, 'ASN.1 tag wrong')
|
||||
t.equal(ber[1], 0x04, 'length wrong')
|
||||
t.equal(ber[2], 0xa5, 'value wrong (byte 1)')
|
||||
t.equal(ber[3], 0x3a, 'value wrong (byte 2)')
|
||||
t.equal(ber[4], 0x53, 'value wrong (byte 3)')
|
||||
t.equal(ber[5], 0xbd, 'value wrong (byte 4)')
|
||||
})
|
||||
|
||||
t.test('throws for > 4 byte integer', { skip: true }, async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeInt(0xffffffffff),
|
||||
Error('BER ints cannot be > 0xffffffff')
|
||||
)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('writeLength', t => {
|
||||
t.test('throws if length not a number', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeLength('1'),
|
||||
Error('argument must be a Number')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('writes a single byte length', async t => {
|
||||
const writer = new BerWriter({ size: 4 })
|
||||
writer.writeLength(0x7f)
|
||||
t.equal(writer.buffer.length, 1)
|
||||
t.equal(Buffer.compare(writer.buffer, Buffer.from([0x7f])), 0)
|
||||
})
|
||||
|
||||
t.test('writes a two byte length', async t => {
|
||||
const writer = new BerWriter({ size: 4 })
|
||||
writer.writeLength(0xff)
|
||||
t.equal(writer.buffer.length, 2)
|
||||
t.equal(Buffer.compare(writer.buffer, Buffer.from([0x81, 0xff])), 0)
|
||||
})
|
||||
|
||||
t.test('writes a three byte length', async t => {
|
||||
const writer = new BerWriter({ size: 4 })
|
||||
writer.writeLength(0xffff)
|
||||
t.equal(writer.buffer.length, 3)
|
||||
t.equal(Buffer.compare(writer.buffer, Buffer.from([0x82, 0xff, 0xff])), 0)
|
||||
})
|
||||
|
||||
t.test('writes a four byte length', async t => {
|
||||
const writer = new BerWriter({ size: 4 })
|
||||
writer.writeLength(0xffffff)
|
||||
t.equal(writer.buffer.length, 4)
|
||||
t.equal(Buffer.compare(writer.buffer, Buffer.from([0x83, 0xff, 0xff, 0xff])), 0)
|
||||
})
|
||||
|
||||
t.test('throw if byte length is too long', async t => {
|
||||
const writer = new BerWriter({ size: 4 })
|
||||
t.throws(
|
||||
() => writer.writeLength(0xffffffffff),
|
||||
Error('length too long (> 4 bytes)')
|
||||
)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('writeNull', t => {
|
||||
t.test('writeNull', async t => {
|
||||
const writer = new BerWriter({ size: 2 })
|
||||
writer.writeNull()
|
||||
t.equal(writer.size, 2)
|
||||
t.equal(Buffer.compare(writer.buffer, Buffer.from([0x05, 0x00])), 0)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('writeOID', t => {
|
||||
t.test('throws if OID not a string', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeOID(42),
|
||||
Error('oidString must be a string')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('throws if tag not a number', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeOID('1.2.3', '1'),
|
||||
Error('tag must be a Number')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('throws if OID not a valid OID string', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeOID('foo'),
|
||||
Error('oidString is not a valid OID string')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('writes an OID', async t => {
|
||||
const oid = '1.2.840.113549.1.1.1'
|
||||
const writer = new BerWriter()
|
||||
writer.writeOID(oid)
|
||||
|
||||
const expected = Buffer.from([0x06, 0x09, 0x2a, 0x86,
|
||||
0x48, 0x86, 0xf7, 0x0d,
|
||||
0x01, 0x01, 0x01])
|
||||
const ber = writer.buffer
|
||||
t.equal(ber.compare(expected), 0)
|
||||
})
|
||||
|
||||
t.test('writes OID covering all octet encodings', async t => {
|
||||
const oid = '1.2.200.17000.2100100.270100100'
|
||||
const writer = new BerWriter()
|
||||
writer.writeOID(oid)
|
||||
|
||||
const expected = Buffer.from([
|
||||
0x06, 0x0f,
|
||||
0x2a, 0x81, 0x48, 0x81,
|
||||
0x84, 0x68, 0x81, 0x80,
|
||||
0x97, 0x04, 0x81, 0x80,
|
||||
0xe5, 0xcd, 0x04
|
||||
])
|
||||
const ber = writer.buffer
|
||||
t.equal(ber.compare(expected), 0)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('writeString', t => {
|
||||
t.test('throws if non-string supplied', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeString(42),
|
||||
Error('stringToWrite must be a string')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('throws if tag not a number', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeString('foo', '1'),
|
||||
Error('tag must be a number')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('writes an empty string', async t => {
|
||||
const writer = new BerWriter()
|
||||
writer.writeString('')
|
||||
|
||||
const expected = Buffer.from([0x04, 0x00])
|
||||
t.equal(Buffer.compare(writer.buffer, expected), 0)
|
||||
})
|
||||
|
||||
t.test('writes a string', async t => {
|
||||
const writer = new BerWriter({ size: 1 })
|
||||
writer.writeString('foo')
|
||||
|
||||
const expected = Buffer.from([0x04, 0x03, 0x66, 0x6f, 0x6f])
|
||||
t.equal(Buffer.compare(writer.buffer, expected), 0)
|
||||
t.equal(writer.size, 8)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('writeString', t => {
|
||||
t.test('throws if non-array supplied', async t => {
|
||||
const writer = new BerWriter()
|
||||
t.throws(
|
||||
() => writer.writeStringArray(42),
|
||||
Error('strings must be an instance of Array')
|
||||
)
|
||||
})
|
||||
|
||||
t.test('write string array', async t => {
|
||||
const writer = new BerWriter()
|
||||
writer.writeStringArray(['hello world', 'fubar!'])
|
||||
const ber = writer.buffer
|
||||
|
||||
t.equal(ber.length, 21, 'wrong length')
|
||||
t.equal(ber[0], 0x04, 'wrong tag')
|
||||
t.equal(ber[1], 11, 'wrong length')
|
||||
t.equal(ber.subarray(2, 13).toString('utf8'), 'hello world', 'wrong value')
|
||||
|
||||
t.equal(ber[13], 0x04, 'wrong tag')
|
||||
t.equal(ber[14], 6, 'wrong length')
|
||||
t.equal(ber.subarray(15).toString('utf8'), 'fubar!', 'wrong value')
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
tap.test('original tests', t => {
|
||||
t.test('resize internal buffer', async t => {
|
||||
const writer = new BerWriter({ size: 2 })
|
||||
writer.writeString('hello world')
|
||||
const ber = writer.buffer
|
||||
|
||||
t.equal(ber.length, 13, 'wrong length')
|
||||
t.equal(ber[0], 0x04, 'wrong tag')
|
||||
t.equal(ber[1], 11, 'wrong length')
|
||||
t.equal(ber.subarray(2).toString('utf8'), 'hello world', 'wrong value')
|
||||
})
|
||||
|
||||
t.test('sequence', async t => {
|
||||
const writer = new BerWriter({ size: 25 })
|
||||
writer.startSequence()
|
||||
writer.writeString('hello world')
|
||||
writer.endSequence()
|
||||
const ber = writer.buffer
|
||||
|
||||
t.equal(ber.length, 15, 'wrong length')
|
||||
t.equal(ber[0], 0x30, 'wrong tag')
|
||||
t.equal(ber[1], 13, 'wrong length')
|
||||
t.equal(ber[2], 0x04, 'wrong tag')
|
||||
t.equal(ber[3], 11, 'wrong length')
|
||||
t.equal(ber.subarray(4).toString('utf8'), 'hello world', 'wrong value')
|
||||
})
|
||||
|
||||
t.test('nested sequence', async t => {
|
||||
const writer = new BerWriter({ size: 25 })
|
||||
writer.startSequence()
|
||||
writer.writeString('hello world')
|
||||
writer.startSequence()
|
||||
writer.writeString('hello world')
|
||||
writer.endSequence()
|
||||
writer.endSequence()
|
||||
const ber = writer.buffer
|
||||
|
||||
t.equal(ber.length, 30, 'wrong length')
|
||||
t.equal(ber[0], 0x30, 'wrong tag')
|
||||
t.equal(ber[1], 28, 'wrong length')
|
||||
t.equal(ber[2], 0x04, 'wrong tag')
|
||||
t.equal(ber[3], 11, 'wrong length')
|
||||
t.equal(ber.subarray(4, 15).toString('utf8'), 'hello world', 'wrong value')
|
||||
t.equal(ber[15], 0x30, 'wrong tag')
|
||||
t.equal(ber[16], 13, 'wrong length')
|
||||
t.equal(ber[17], 0x04, 'wrong tag')
|
||||
t.equal(ber[18], 11, 'wrong length')
|
||||
t.equal(ber.subarray(19, 30).toString('utf8'), 'hello world', 'wrong value')
|
||||
})
|
||||
|
||||
t.test('LDAP bind message', async t => {
|
||||
const dn = 'cn=foo,ou=unit,o=test'
|
||||
const writer = new BerWriter()
|
||||
writer.startSequence()
|
||||
writer.writeInt(3) // msgid = 3
|
||||
writer.startSequence(0x60) // ldap bind
|
||||
writer.writeInt(3) // ldap v3
|
||||
writer.writeString(dn)
|
||||
writer.writeByte(0x80)
|
||||
writer.writeByte(0x00)
|
||||
writer.endSequence()
|
||||
writer.endSequence()
|
||||
const ber = writer.buffer
|
||||
|
||||
t.equal(ber.length, 35, 'wrong length (buffer)')
|
||||
t.equal(ber[0], 0x30, 'wrong tag')
|
||||
t.equal(ber[1], 33, 'wrong length')
|
||||
t.equal(ber[2], 0x02, 'wrong tag')
|
||||
t.equal(ber[3], 1, 'wrong length')
|
||||
t.equal(ber[4], 0x03, 'wrong value')
|
||||
t.equal(ber[5], 0x60, 'wrong tag')
|
||||
t.equal(ber[6], 28, 'wrong length')
|
||||
t.equal(ber[7], 0x02, 'wrong tag')
|
||||
t.equal(ber[8], 1, 'wrong length')
|
||||
t.equal(ber[9], 0x03, 'wrong value')
|
||||
t.equal(ber[10], 0x04, 'wrong tag')
|
||||
t.equal(ber[11], dn.length, 'wrong length')
|
||||
t.equal(ber.subarray(12, 33).toString('utf8'), dn, 'wrong value')
|
||||
t.equal(ber[33], 0x80, 'wrong tag')
|
||||
t.equal(ber[34], 0x00, 'wrong len')
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
74
node_modules/@ldapjs/asn1/lib/buffer-to-hex-dump.js
generated
vendored
Normal file
74
node_modules/@ldapjs/asn1/lib/buffer-to-hex-dump.js
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
'use strict'
|
||||
|
||||
const { createWriteStream } = require('fs')
|
||||
|
||||
/**
|
||||
* @typedef {object} HexDumpParams
|
||||
* @property {Buffer} buffer The buffer instance to serialize into a hex dump.
|
||||
* @property {string} [prefix=''] A string to prefix each byte with, e.g.
|
||||
* `0x`.
|
||||
* @property {string} [separator=''] A string to separate each byte with, e.g.
|
||||
* `, '.
|
||||
* @property {string[]} [wrapCharacters=[]] A set of characters to wrap the
|
||||
* output with. For example, `wrapCharacters=['[', ']']` will start the hex
|
||||
* dump with `[` and end it with `]`.
|
||||
* @property {number} [width=10] How many bytes to write per line.
|
||||
* @property {WriteStream | string} [destination=process.stdout] Where to
|
||||
* write the serialized data. If a string is provided, it is assumed to be
|
||||
* the path to a file. This file will be completely overwritten.
|
||||
* @property {boolean} [closeDestination=false] Indicates whether the
|
||||
* `destination` should be closed when done. This _should_ be `true` when the
|
||||
* passed in `destination` is a stream that you control. If a string path is
|
||||
* supplied for the `destination`, this will automatically be handled.
|
||||
*/
|
||||
|
||||
// We'd like to put this coverage directive after the doc block,
|
||||
// but that confuses doc tooling (e.g. WebStorm).
|
||||
/* istanbul ignore next: defaults don't need 100% coverage */
|
||||
/**
|
||||
* Given a buffer of bytes, generate a hex dump that can be loaded later
|
||||
* or viewed in a hex editor (e.g. [Hex Fiend](https://hexfiend.com)).
|
||||
*
|
||||
* @param {HexDumpParams} params
|
||||
*
|
||||
* @throws When the destination cannot be accessed.
|
||||
*/
|
||||
module.exports = function bufferToHexDump ({
|
||||
buffer,
|
||||
prefix = '',
|
||||
separator = '',
|
||||
wrapCharacters = [],
|
||||
width = 10,
|
||||
destination = process.stdout,
|
||||
closeDestination = false
|
||||
}) {
|
||||
let closeStream = closeDestination
|
||||
if (typeof destination === 'string') {
|
||||
destination = createWriteStream(destination)
|
||||
closeStream = true
|
||||
}
|
||||
|
||||
if (wrapCharacters[0]) {
|
||||
destination.write(wrapCharacters[0])
|
||||
}
|
||||
|
||||
for (const [i, byte] of buffer.entries()) {
|
||||
const outByte = Number(byte).toString(16).padStart(2, '0')
|
||||
destination.write(prefix + outByte)
|
||||
if (i !== buffer.byteLength - 1) {
|
||||
destination.write(separator)
|
||||
}
|
||||
if ((i + 1) % width === 0) {
|
||||
destination.write('\n')
|
||||
}
|
||||
}
|
||||
|
||||
if (wrapCharacters[1]) {
|
||||
destination.write(wrapCharacters[1])
|
||||
}
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (closeStream === true) {
|
||||
destination.end()
|
||||
}
|
||||
}
|
||||
75
node_modules/@ldapjs/asn1/lib/buffer-to-hex-dump.test.js
generated
vendored
Normal file
75
node_modules/@ldapjs/asn1/lib/buffer-to-hex-dump.test.js
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
'use strict'
|
||||
|
||||
const tap = require('tap')
|
||||
const path = require('path')
|
||||
const { Writable } = require('stream')
|
||||
const { tmpdir } = require('os')
|
||||
const { randomUUID } = require('crypto')
|
||||
const { readFile, rm } = require('fs/promises')
|
||||
const { setTimeout } = require('timers/promises')
|
||||
const bufferToHexDump = require('./buffer-to-hex-dump')
|
||||
|
||||
const input = Buffer.from([
|
||||
0x00, 0x01, 0x02, 0x03, 0x04,
|
||||
0x05, 0x06, 0x07, 0x08, 0x09,
|
||||
0x0a, 0x0b, 0x0c
|
||||
])
|
||||
|
||||
tap.test('writes to stream', t => {
|
||||
const expected = [
|
||||
'[0x00, 0x01, 0x02, 0x03, \n',
|
||||
'0x04, 0x05, 0x06, 0x07, \n',
|
||||
'0x08, 0x09, 0x0a, 0x0b, \n',
|
||||
'0x0c]'
|
||||
].join('')
|
||||
|
||||
let found = ''
|
||||
const destination = new Writable({
|
||||
write (chunk, encoding, callback) {
|
||||
found += chunk.toString()
|
||||
callback()
|
||||
}
|
||||
})
|
||||
|
||||
destination.on('finish', () => {
|
||||
t.equal(found, expected)
|
||||
t.end()
|
||||
})
|
||||
|
||||
bufferToHexDump({
|
||||
buffer: input,
|
||||
prefix: '0x',
|
||||
separator: ', ',
|
||||
wrapCharacters: ['[', ']'],
|
||||
width: 4,
|
||||
closeDestination: true,
|
||||
destination
|
||||
})
|
||||
})
|
||||
|
||||
tap.test('writes to file', async t => {
|
||||
const expected = [
|
||||
'00010203\n',
|
||||
'04050607\n',
|
||||
'08090a0b\n',
|
||||
'0c'
|
||||
].join('')
|
||||
const destination = path.join(tmpdir(), randomUUID())
|
||||
|
||||
t.teardown(async () => {
|
||||
await rm(destination)
|
||||
})
|
||||
|
||||
bufferToHexDump({
|
||||
buffer: input,
|
||||
width: 4,
|
||||
destination
|
||||
})
|
||||
|
||||
// Give a little time for the write stream to create and
|
||||
// close the file.
|
||||
await setTimeout(100)
|
||||
|
||||
const contents = await readFile(destination)
|
||||
t.equal(contents.toString(), expected)
|
||||
})
|
||||
Reference in New Issue
Block a user