First commit

This commit is contained in:
2025-10-08 11:12:59 -04:00
commit b0605a28a9
820 changed files with 100317 additions and 0 deletions

9
node_modules/@ldapjs/asn1/.eslintrc generated vendored Normal file
View File

@ -0,0 +1,9 @@
{
"parserOptions": {
"ecmaVersion": "latest"
},
"extends": [
"standard"
]
}

10
node_modules/@ldapjs/asn1/.github/workflows/main.yml generated vendored Normal file
View File

@ -0,0 +1,10 @@
name: "CI"
on:
pull_request:
push:
branches:
- master
jobs:
call-core-ci:
uses: ldapjs/.github/.github/workflows/node-ci.yml@main

6
node_modules/@ldapjs/asn1/.taprc.yml generated vendored Normal file
View File

@ -0,0 +1,6 @@
reporter: terse
coverage-map: coverage-map.js
files:
- 'index.test.js'
- 'lib/**/*.test.js'

9
node_modules/@ldapjs/asn1/CONTRIBUTING.md generated vendored Normal file
View File

@ -0,0 +1,9 @@
# Contributing
This repository uses GitHub pull requests for code review.
See [this primer](https://jrfom.com/posts/2017/03/08/a-primer-on-contributing-to-projects-with-git/)
for instructions on how to make contributions to the project.
If you're changing something non-trivial or user-facing, you may want to submit
an issue first.

22
node_modules/@ldapjs/asn1/LICENSE generated vendored Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2011 Mark Cavage, All rights reserved.
Copyright (c) 2022 The LDAPJS Collaborators.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE

43
node_modules/@ldapjs/asn1/README.md generated vendored Normal file
View File

@ -0,0 +1,43 @@
# `@ldapjs/asn1`
`@ldapjs/asn1` is a library for encoding and decoding ASN.1 datatypes in pure
JS. Currently BER encoding is supported.
### Decoding
The following reads an ASN.1 sequence with a boolean.
```js
const { BerReader, BerTypes } = require('@ldapjs/asn1')
const reader = new BerReader(Buffer.from([0x30, 0x03, 0x01, 0x01, 0xff]))
reader.readSequence()
console.log('Sequence len: ' + reader.length)
if (reader.peek() === BerTypes.Boolean)
console.log(reader.readBoolean())
```
### Encoding
The following generates the same payload as above.
```js
const { BerWriter } = require('@ldapjs/asn1');
const writer = new BerWriter();
writer.startSequence();
writer.writeBoolean(true);
writer.endSequence();
console.log(writer.buffer);
```
## Installation
```sh
npm install @ldapjs/asn1
```
## Bugs
See <https://github.com/ldapjs/asn1/issues>.

3
node_modules/@ldapjs/asn1/coverage-map.js generated vendored Normal file
View File

@ -0,0 +1,3 @@
'use strict'
module.exports = testFile => testFile.replace(/\.test\.js$/, '.js')

13
node_modules/@ldapjs/asn1/index.js generated vendored Normal file
View File

@ -0,0 +1,13 @@
'use strict'
const BerReader = require('./lib/ber/reader')
const BerWriter = require('./lib/ber/writer')
const BerTypes = require('./lib/ber/types')
const bufferToHexDump = require('./lib/buffer-to-hex-dump')
module.exports = {
BerReader,
BerTypes,
BerWriter,
bufferToHexDump
}

28
node_modules/@ldapjs/asn1/index.test.js generated vendored Normal file
View File

@ -0,0 +1,28 @@
'use strict'
const tap = require('tap')
const asn1 = require('./index')
tap.test('exports BerReader', async t => {
const { BerReader } = asn1
t.ok(BerReader)
const reader = new BerReader(Buffer.from([0x00]))
t.type(reader, BerReader)
t.equal(Object.prototype.toString.call(reader), '[object BerReader]')
})
tap.test('exports BerTypes', async t => {
const { BerTypes } = asn1
t.type(BerTypes, Object)
t.equal(BerTypes.LDAPSequence, 0x30)
})
tap.test('exports BerWriter', async t => {
const { BerWriter } = asn1
t.ok(BerWriter)
const writer = new BerWriter()
t.type(writer, BerWriter)
t.equal(Object.prototype.toString.call(writer), '[object BerWriter]')
})

24
node_modules/@ldapjs/asn1/lib/ber/index.js generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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()
}
}

View 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)
})

40
node_modules/@ldapjs/asn1/package.json generated vendored Normal file
View File

@ -0,0 +1,40 @@
{
"originalAuthor": "Joyent (joyent.com)",
"contributors": [
"Mark Cavage <mcavage@gmail.com>",
"David Gwynne <loki@animata.net>",
"Yunong Xiao <yunong@joyent.com>",
"Alex Wilson <alex.wilson@joyent.com>"
],
"name": "@ldapjs/asn1",
"description": "Contains parsers and serializers for ASN.1 (currently BER only)",
"version": "2.0.0",
"repository": {
"type": "git",
"url": "git://github.com/ldapjs/asn1.git"
},
"main": "index.js",
"devDependencies": {
"@fastify/pre-commit": "^2.0.2",
"eslint": "^8.34.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.6.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.1.1",
"tap": "^16.3.4"
},
"scripts": {
"lint": "eslint .",
"lint:ci": "eslint .",
"test": "tap --no-coverage-report",
"test:cov": "tap",
"test:cov:html": "tap --coverage-report=html",
"test:watch": "tap -w --no-coverage-report"
},
"license": "MIT",
"pre-commit": [
"lint",
"test"
]
}