Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@ldapjs/asn1

Package Overview
Dependencies
Maintainers
4
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ldapjs/asn1 - npm Package Compare versions

Comparing version 1.2.0 to 2.0.0-rc.1

.eslintrc

4

lib/ber/index.js

@@ -13,5 +13,5 @@ // Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.

Reader: Reader,
Reader,
Writer: Writer
Writer

@@ -18,0 +18,0 @@ }

@@ -1,221 +0,435 @@

// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
'use strict'
const assert = require('assert')
const ASN1 = require('./types')
const errors = require('./errors')
const types = require('./types')
// --- Globals
/**
* 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
const newInvalidAsn1Error = errors.newInvalidAsn1Error
/**
* The total bytes in the backing buffer.
*
* @type {number}
*/
#size
// --- API
/**
* 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
function Reader (data) {
if (!data || !Buffer.isBuffer(data)) { throw new TypeError('data must be a node Buffer') }
/**
* 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
this._buf = data
this._size = data.length
/**
* As the BER buffer is read, this property records the current position
* in the buffer.
*
* @type {number}
*/
#offset = 0
// These hold the "current" state
this._len = 0
this._offset = 0
}
/**
* @param {Buffer} buffer
*/
constructor (buffer) {
if (Buffer.isBuffer(buffer) === false) {
throw TypeError('Must supply a Buffer instance to read.')
}
Object.defineProperty(Reader.prototype, Symbol.toStringTag, { value: 'BerReader' })
this.#buffer = buffer.subarray(0)
this.#size = this.#buffer.length
}
Object.defineProperty(Reader.prototype, 'length', {
enumerable: true,
get: function () { return (this._len) }
})
get [Symbol.toStringTag] () { return 'BerReader' }
Object.defineProperty(Reader.prototype, 'offset', {
enumerable: true,
get: function () { return (this._offset) }
})
/**
* Get a buffer that represents the underlying data buffer.
*
* @type {Buffer}
*/
get buffer () {
return this.#buffer.subarray(0)
}
Object.defineProperty(Reader.prototype, 'remain', {
get: function () { return (this._size - this._offset) }
})
/**
* The length of the current field being read.
*
* @type {number}
*/
get length () {
return this.#currentFieldLength
}
Object.defineProperty(Reader.prototype, 'buffer', {
get: function () { return (this._buf.slice(this._offset)) }
})
/**
* Current read position in the underlying data buffer.
*
* @type {number}
*/
get offset () {
return this.#offset
}
/**
* Reads a single byte and advances offset; you can pass in `true` to make this
* a "peek" operation (i.e., get the byte, but don't advance the offset).
*
* @param {Boolean} peek true means don't move offset.
* @return {Number} the next byte, null if not enough data.
*/
Reader.prototype.readByte = function (peek) {
if (this._size - this._offset < 1) { return null }
/**
* The number of bytes remaining in the backing buffer that have not
* been read.
*
* @type {number}
*/
get remain () {
return this.#size - this.#offset
}
const b = this._buf[this._offset] & 0xff
/**
* 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)
}
if (!peek) { this._offset += 1 }
/**
* 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 b
}
return (int !== 0)
}
Reader.prototype.peek = function () {
return this.readByte(true)
}
/**
* 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
}
/**
* Reads a (potentially) variable length off the BER buffer. This call is
* not really meant to be called directly, as callers have to manipulate
* the internal buffer afterwards.
*
* As a result of this call, you can call `Reader.length`, until the
* next thing called that does a readLength.
*
* @return {Number} the amount of offset to advance the buffer.
* @throws {InvalidAsn1Error} on bad ASN.1
*/
Reader.prototype.readLength = function (offset) {
if (offset === undefined) { offset = this._offset }
const byte = this.#buffer[this.#offset] & 0xff
if (offset >= this._size) { return null }
if (peek !== true) {
this.#offset += 1
}
let lenB = this._buf[offset++] & 0xff
if (lenB === null) { return null }
return byte
}
if ((lenB & 0x80) === 0x80) {
lenB &= 0x7f
/**
* 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 who's 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
if (lenB === 0) { throw newInvalidAsn1Error('Indefinite length not supported') }
return parseIntegerBuffer(intBuffer)
}
if (lenB > 4) { throw newInvalidAsn1Error('encoding too long') }
/**
* 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 who's 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
if (this._size - offset < lenB) { return null }
this._len = 0
for (let i = 0; i < lenB; i++) { this._len = (this._len << 8) + (this._buf[offset++] & 0xff) }
} else {
// Wasn't a variable length
this._len = lenB
return parseIntegerBuffer(intBuffer)
}
return offset
}
/**
* 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 }
/**
* Parses the next sequence in this BER buffer.
*
* To get the length of the sequence, call `Reader.length`.
*
* @return {Number} the sequence's tag.
*/
Reader.prototype.readSequence = function (tag) {
const seq = this.peek()
if (seq === null) { return null }
if (tag !== undefined && tag !== seq) {
throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
': got 0x' + seq.toString(16))
}
if (offset >= this.#size) { return null }
const o = this.readLength(this._offset + 1) // stored in `length`
if (o === null) { 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 } */
this._offset = o
return seq
}
if ((lengthByte & 0x80) === 0x80) {
lengthByte &= 0x7f
Reader.prototype.readInt = function () {
return this._readTag(ASN1.Integer)
}
// https://www.rfc-editor.org/rfc/rfc4511.html#section-5.1 prohibits
// indefinite form (0x80).
if (lengthByte === 0) { throw Error('Indefinite length not supported.') }
Reader.prototype.readBoolean = function (tag) {
return (this._readTag(tag || ASN1.Boolean) !== 0)
}
// 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.') }
Reader.prototype.readEnumeration = function () {
return this._readTag(ASN1.Enumeration)
}
if (this.#size - offset < lengthByte) { return null }
Reader.prototype.readString = function (tag, retbuf) {
if (!tag) { tag = ASN1.OctetString }
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
}
const b = this.peek()
if (b === null) { return null }
if (b !== tag) {
throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
': got 0x' + b.toString(16))
return offset
}
const o = this.readLength(this._offset + 1) // stored in `length`
/**
* 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 }
if (o === null) { return null }
const values = []
let value = 0
if (this.length > this._size - o) { return null }
for (let i = 0; i < oidBuffer.length; i++) {
const byte = oidBuffer[i] & 0xff
this._offset = o
value <<= 7
value += byte & 0x7f
if ((byte & 0x80) === 0) {
values.push(value)
value = 0
}
}
if (this.length === 0) { return retbuf ? Buffer.alloc(0) : '' }
value = values.shift()
values.unshift(value % 40)
values.unshift((value / 40) >> 0)
const str = this._buf.slice(this._offset, this._offset + this.length)
this._offset += this.length
return values.join('.')
}
return retbuf ? str : str.toString('utf8')
}
/**
* 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} 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}`)
}
Reader.prototype.readOID = function (tag) {
if (!tag) { tag = ASN1.OID }
this.#currentSequenceStart = this.#offset
const valueOffset = this.readLength(this.#offset + 1) // stored in `length`
if (valueOffset === null) { return null }
const b = this.readString(tag, true)
if (b === null) { return null }
this.#offset = valueOffset
return foundTag
}
const values = []
let value = 0
/**
* 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()
for (let i = 0; i < b.length; i++) {
const byte = b[i] & 0xff
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}`)
}
value <<= 7
value += byte & 0x7f
if ((byte & 0x80) === 0) {
values.push(value)
value = 0
}
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')
}
value = values.shift()
values.unshift(value % 40)
values.unshift((value / 40) >> 0)
/**
* 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.')
}
return values.join('.')
}
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}`)
}
Reader.prototype._readTag = function (tag) {
assert.ok(tag !== undefined)
const fieldOffset = this.readLength(this.#offset + 1) // stored in `length`
if (fieldOffset === null) { return null }
const b = this.peek()
if (this.length > this.#size - fieldOffset) { return null }
this.#offset = fieldOffset
if (b === null) { return null }
return this.#buffer.subarray(this.#offset, this.#offset + this.length)
}
if (b !== tag) {
throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) +
': got 0x' + b.toString(16))
/**
* 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)
}
const o = this.readLength(this._offset + 1) // stored in `length`
if (o === null) { return null }
/**
* 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
}
}
if (this.length > 4) { throw newInvalidAsn1Error('Integer too long: ' + this.length) }
if (this.length > this._size - o) { return null }
this._offset = o
const fb = this._buf[this._offset]
/**
* Given a buffer that represents an ingeter TLV, parse it and return it
* as a decimal value. This accounts for signedness.
*
* @param {Buffer}
*
* @returns {number}
*/
function parseIntegerBuffer (integerBuffer) {
let value = 0
let i
for (i = 0; i < this.length; i++) {
for (i = 0; i < integerBuffer.length; i++) {
value <<= 8
value |= (this._buf[this._offset++] & 0xff)
value |= (integerBuffer[i] & 0xff)
}
if ((fb & 0x80) === 0x80 && i !== 4) { value -= (1 << (i * 8)) }
if ((integerBuffer[0] & 0x80) === 0x80 && i !== 4) { value -= (1 << (i * 8)) }

@@ -225,4 +439,2 @@ return value >> 0

// --- Exported API
module.exports = Reader
module.exports = BerReader

@@ -1,137 +0,513 @@

// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
'use strict'
const { test } = require('tap')
const tap = require('tap')
const BerReader = require('./reader')
test('load library', function (t) {
t.ok(BerReader)
try {
const reader = new BerReader()
t.equal(reader, null, 'reader')
t.fail('Should have thrown')
} catch (e) {
t.ok(e instanceof TypeError, 'Should have been a type error')
}
t.end()
// 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
)
})
test('read byte', function (t) {
const reader = new BerReader(Buffer.from([0xde]))
t.ok(reader)
t.equal(reader.readByte(), 0xde, 'wrong value')
t.end()
tap.test('has toStringTag', async t => {
const reader = new BerReader(Buffer.from('foo'))
t.equal(Object.prototype.toString.call(reader), '[object BerReader]')
})
test('read 1 byte int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x01, 0x03]))
t.ok(reader)
t.equal(reader.readInt(), 0x03, 'wrong value')
t.equal(reader.length, 0x01, 'wrong length')
t.end()
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
)
})
test('read 2 byte int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x02, 0x7e, 0xde]))
t.ok(reader)
t.equal(reader.readInt(), 0x7ede, 'wrong value')
t.equal(reader.length, 0x02, 'wrong length')
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()
})
test('read 3 byte int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x03, 0x7e, 0xde, 0x03]))
t.ok(reader)
t.equal(reader.readInt(), 0x7ede03, 'wrong value')
t.equal(reader.length, 0x03, 'wrong length')
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()
})
test('read 4 byte int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x04, 0x7e, 0xde, 0x03, 0x01]))
t.ok(reader)
t.equal(reader.readInt(), 0x7ede0301, 'wrong value')
t.equal(reader.length, 0x04, 'wrong length')
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()
})
test('read 1 byte negative int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x01, 0xdc]))
t.ok(reader)
t.equal(reader.readInt(), -36, 'wrong value')
t.equal(reader.length, 0x01, 'wrong length')
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()
})
test('read 2 byte negative int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x02, 0xc0, 0x4e]))
t.ok(reader)
t.equal(reader.readInt(), -16306, 'wrong value')
t.equal(reader.length, 0x02, 'wrong length')
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 who's 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()
})
test('read 3 byte negative int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x03, 0xff, 0x00, 0x19]))
t.ok(reader)
t.equal(reader.readInt(), -65511, 'wrong value')
t.equal(reader.length, 0x03, 'wrong length')
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()
})
test('read 4 byte negative int', function (t) {
const reader = new BerReader(Buffer.from([0x02, 0x04, 0x91, 0x7c, 0x22, 0x1f]))
t.ok(reader)
t.equal(reader.readInt(), -1854135777, 'wrong value')
t.equal(reader.length, 0x04, 'wrong length')
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()
})
test('read boolean true', function (t) {
const reader = new BerReader(Buffer.from([0x01, 0x01, 0xff]))
t.ok(reader)
t.equal(reader.readBoolean(), true, 'wrong value')
t.equal(reader.length, 0x01, 'wrong length')
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()
})
test('read boolean false', function (t) {
const reader = new BerReader(Buffer.from([0x01, 0x01, 0x00]))
t.ok(reader)
t.equal(reader.readBoolean(), false, 'wrong value')
t.equal(reader.length, 0x01, 'wrong length')
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()
})
test('read enumeration', function (t) {
const reader = new BerReader(Buffer.from([0x0a, 0x01, 0x20]))
t.ok(reader)
t.equal(reader.readEnumeration(), 0x20, 'wrong value')
t.equal(reader.length, 0x01, 'wrong length')
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()
})
test('read string', function (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')
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()
})
test('read sequence', function (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')
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()
})
test('anonymous LDAPv3 bind', function (t) {
// Original test
tap.test('anonymous LDAPv3 bind', async t => {
const BIND = Buffer.alloc(14)

@@ -166,18 +542,2 @@ BIND[0] = 0x30 // Sequence

t.equal(null, ber.readByte(), 'Should be out of data')
t.end()
})
test('long string', function (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.slice(0, 3 + s.length))
t.equal(ber.readString(), s)
t.end()
})

@@ -1,35 +0,36 @@

// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
'use strict'
module.exports = {
EOC: 0,
Boolean: 1,
Integer: 2,
BitString: 3,
OctetString: 4,
Null: 5,
OID: 6,
ObjectDescriptor: 7,
External: 8,
Real: 9, // float
Enumeration: 10,
PDV: 11,
Utf8String: 12,
RelativeOID: 13,
Sequence: 16,
Set: 17,
NumericString: 18,
PrintableString: 19,
T61String: 20,
VideotexString: 21,
IA5String: 22,
UTCTime: 23,
GeneralizedTime: 24,
GraphicString: 25,
VisibleString: 26,
GeneralString: 28,
UniversalString: 29,
CharacterString: 30,
BMPString: 31,
Constructor: 32,
Context: 128
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
}

@@ -1,298 +0,453 @@

// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
'use strict'
const assert = require('assert')
const ASN1 = require('./types')
const errors = require('./errors')
const types = require('./types')
// --- Globals
class BerWriter {
/**
* The source buffer as it was passed in when creating the instance.
*
* @type {Buffer}
*/
#buffer
const newInvalidAsn1Error = errors.newInvalidAsn1Error
/**
* The total bytes in the backing buffer.
*
* @type {number}
*/
#size
const DEFAULT_OPTS = {
size: 1024,
growthFactor: 8
}
/**
* As the BER buffer is written, this property records the current position
* in the buffer.
*
* @type {number}
*/
#offset = 0
// --- Helpers
/**
* A list of offsets in the buffer where we need to insert sequence tag and
* length pairs.
*/
#sequenceOffsets = []
function merge (from, to) {
assert.ok(from)
assert.equal(typeof (from), 'object')
assert.ok(to)
assert.equal(typeof (to), 'object')
/**
* Coeffecient used when increasing the buffer to accomodate writes that
* exceed the available space left in the buffer.
*
* @type {number}
*/
#growthFactor
const keys = Object.getOwnPropertyNames(from)
keys.forEach(function (key) {
if (to[key]) { return }
constructor ({ size = 1024, growthFactor = 8 } = {}) {
this.#buffer = Buffer.alloc(size)
this.#size = this.#buffer.length
this.#offset = 0
this.#growthFactor = growthFactor
}
const value = Object.getOwnPropertyDescriptor(from, key)
Object.defineProperty(to, key, value)
})
get [Symbol.toStringTag] () { return 'BerWriter' }
return to
}
get buffer () {
// TODO: handle sequence check
// --- API
return this.#buffer.subarray(0, this.#offset)
}
function Writer (options) {
options = merge(DEFAULT_OPTS, options || {})
/**
* The size of the backing buffer.
*
* @return {number}
*/
get size () {
return this.#size
}
this._buf = Buffer.alloc(options.size || 1024)
this._size = this._buf.length
this._offset = 0
this._options = options
/**
* 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
}
// A list of offsets in the buffer where we need to insert
// sequence tag/len pairs.
this._seq = []
}
/**
* 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
Object.defineProperty(Writer.prototype, Symbol.toStringTag, { value: 'BerWriter' })
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')
}
}
Object.defineProperty(Writer.prototype, 'buffer', {
get: function () {
if (this._seq.length) { throw newInvalidAsn1Error(this._seq.length + ' unended sequence(s)') }
/**
* 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')
}
return (this._buf.slice(0, this._offset))
this.writeByte(tag)
this.#sequenceOffsets.push(this.#offset)
this.#ensureBufferCapacity(3)
this.#offset += 3
}
})
/**
* 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.
*/
Writer.prototype.appendBuffer = function appendBuffer (buffer) {
if (Buffer.isBuffer(buffer) === false) {
throw Error('buffer must be an instance of 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
}
for (const b of buffer.values()) {
this.writeByte(b)
}
}
Writer.prototype.writeByte = function (b) {
if (typeof (b) !== 'number') { throw new TypeError('argument must be a Number') }
/**
* 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._ensure(1)
this._buf[this._offset++] = b
}
this.writeByte(tag)
this.writeLength(buffer.length)
this.#ensureBufferCapacity(buffer.length)
buffer.copy(this.#buffer, this.#offset, 0, buffer.length)
this.#offset += buffer.length
}
Writer.prototype.writeInt = function (i, tag) {
if (typeof (i) !== 'number') { throw new TypeError('argument must be a Number') }
if (typeof (tag) !== 'number') { tag = ASN1.Integer }
/**
* 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')
}
let sz = 4
while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000 >> 0)) &&
(sz > 1)) {
sz--
i <<= 8
this.#ensureBufferCapacity(1)
this.#buffer[this.#offset++] = byte
}
if (sz > 4) { throw newInvalidAsn1Error('BER ints cannot be > 0xffffffff') }
this._ensure(2 + sz)
this._buf[this._offset++] = tag
this._buf[this._offset++] = sz
while (sz-- > 0) {
this._buf[this._offset++] = ((i & 0xff000000) >>> 24)
i <<= 8
/**
* 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)
}
}
Writer.prototype.writeNull = function () {
this.writeByte(ASN1.Null)
this.writeByte(0x00)
}
/**
* 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')
}
Writer.prototype.writeEnumeration = function (i, tag) {
if (typeof (i) !== 'number') { throw new TypeError('argument must be a Number') }
if (typeof (tag) !== 'number') { tag = ASN1.Enumeration }
let intSize = 4
while (
(
((intToWrite & 0xff800000) === 0) ||
((intToWrite & 0xff800000) === (0xff800000 >> 0))
) && (intSize > 1)
) {
intSize--
intToWrite <<= 8
}
return this.writeInt(i, tag)
}
// 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')
}
Writer.prototype.writeBoolean = function (b, tag) {
if (typeof (b) !== 'boolean') { throw new TypeError('argument must be a Boolean') }
if (typeof (tag) !== 'number') { tag = ASN1.Boolean }
this.#ensureBufferCapacity(2 + intSize)
this.#buffer[this.#offset++] = tag
this.#buffer[this.#offset++] = intSize
this._ensure(3)
this._buf[this._offset++] = tag
this._buf[this._offset++] = 0x01
this._buf[this._offset++] = b ? 0xff : 0x00
}
Writer.prototype.writeString = function (s, tag) {
if (typeof (s) !== 'string') { throw new TypeError('argument must be a string (was: ' + typeof (s) + ')') }
if (typeof (tag) !== 'number') { tag = ASN1.OctetString }
const len = Buffer.byteLength(s)
this.writeByte(tag)
this.writeLength(len)
if (len) {
this._ensure(len)
this._buf.write(s, this._offset)
this._offset += len
while (intSize-- > 0) {
this.#buffer[this.#offset++] = ((intToWrite & 0xff000000) >>> 24)
intToWrite <<= 8
}
}
}
Writer.prototype.writeBuffer = function (buf, tag) {
if (typeof (tag) !== 'number') { throw new TypeError('tag must be a number') }
if (!Buffer.isBuffer(buf)) { throw new TypeError('argument must be a buffer') }
/**
* 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.writeByte(tag)
this.writeLength(buf.length)
this._ensure(buf.length)
buf.copy(this._buf, this._offset, 0, buf.length)
this._offset += buf.length
}
this.#ensureBufferCapacity(4)
Writer.prototype.writeStringArray = function (strings) {
if (Array.isArray(strings) === false) { throw new TypeError('argument must be an Array[String]') }
const self = this
strings.forEach(function (s) {
self.writeString(s)
})
}
// This is really to solve DER cases, but whatever for now
Writer.prototype.writeOID = function (s, tag) {
if (typeof (s) !== 'string') { throw new TypeError('argument must be a string') }
if (typeof (tag) !== 'number') { tag = ASN1.OID }
if (!/^([0-9]+\.){3,}[0-9]+$/.test(s)) { throw new Error('argument is not a valid OID string') }
function encodeOctet (bytes, octet) {
if (octet < 128) {
bytes.push(octet)
} else if (octet < 16384) {
bytes.push((octet >>> 7) | 0x80)
bytes.push(octet & 0x7F)
} else if (octet < 2097152) {
bytes.push((octet >>> 14) | 0x80)
bytes.push(((octet >>> 7) | 0x80) & 0xFF)
bytes.push(octet & 0x7F)
} else if (octet < 268435456) {
bytes.push((octet >>> 21) | 0x80)
bytes.push(((octet >>> 14) | 0x80) & 0xFF)
bytes.push(((octet >>> 7) | 0x80) & 0xFF)
bytes.push(octet & 0x7F)
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 {
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)
throw Error('length too long (> 4 bytes)')
}
}
const tmp = s.split('.')
const bytes = []
bytes.push(parseInt(tmp[0], 10) * 40 + parseInt(tmp[1], 10))
tmp.slice(2).forEach(function (b) {
encodeOctet(bytes, parseInt(b, 10))
})
/**
* Write a NULL tag and value to the buffer.
*/
writeNull () {
this.writeByte(types.Null)
this.writeByte(0x00)
}
const self = this
this._ensure(2 + bytes.length)
this.writeByte(tag)
this.writeLength(bytes.length)
bytes.forEach(function (b) {
self.writeByte(b)
})
}
/**
* 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')
}
Writer.prototype.writeLength = function (len) {
if (typeof (len) !== 'number') { throw new TypeError('argument must be a Number') }
if (/^([0-9]+\.){3,}[0-9]+$/.test(oidString) === false) {
throw Error('oidString is not a valid OID string')
}
this._ensure(4)
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))
}
if (len <= 0x7f) {
this._buf[this._offset++] = len
} else if (len <= 0xff) {
this._buf[this._offset++] = 0x81
this._buf[this._offset++] = len
} else if (len <= 0xffff) {
this._buf[this._offset++] = 0x82
this._buf[this._offset++] = len >> 8
this._buf[this._offset++] = len
} else if (len <= 0xffffff) {
this._buf[this._offset++] = 0x83
this._buf[this._offset++] = len >> 16
this._buf[this._offset++] = len >> 8
this._buf[this._offset++] = len
} else {
throw newInvalidAsn1Error('Length too long (> 4 bytes)')
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)
}
}
}
}
Writer.prototype.startSequence = function (tag) {
if (typeof (tag) !== 'number') { tag = ASN1.Sequence | ASN1.Constructor }
/**
* 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')
}
this.writeByte(tag)
this._seq.push(this._offset)
this._ensure(3)
this._offset += 3
}
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
}
}
Writer.prototype.endSequence = function () {
const seq = this._seq.pop()
const start = seq + 3
const len = this._offset - start
if (len <= 0x7f) {
this._shift(start, len, -2)
this._buf[seq] = len
} else if (len <= 0xff) {
this._shift(start, len, -1)
this._buf[seq] = 0x81
this._buf[seq + 1] = len
} else if (len <= 0xffff) {
this._buf[seq] = 0x82
this._buf[seq + 1] = len >> 8
this._buf[seq + 2] = len
} else if (len <= 0xffffff) {
this._shift(start, len, 1)
this._buf[seq] = 0x83
this._buf[seq + 1] = len >> 16
this._buf[seq + 2] = len >> 8
this._buf[seq + 3] = len
} else {
throw newInvalidAsn1Error('Sequence too long')
/**
* 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)
}
}
}
Writer.prototype._shift = function (start, len, shift) {
assert.ok(start !== undefined)
assert.ok(len !== undefined)
assert.ok(shift)
/**
* 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
}
this._buf.copy(this._buf, start + shift, start, start + len)
this._offset += shift
}
const newBuffer = Buffer.alloc(newSize)
Writer.prototype._ensure = function (len) {
assert.ok(len)
this.#buffer.copy(newBuffer, 0, 0, this.#offset)
this.#buffer = newBuffer
this.#size = newSize
}
}
if (this._size - this._offset < len) {
let sz = this._size * this._options.growthFactor
if (sz - this._offset < len) { sz += len }
const buf = Buffer.alloc(sz)
this._buf.copy(buf, 0, 0, this._offset)
this._buf = buf
this._size = sz
/**
* 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
}
}
// --- Exported API
module.exports = Writer
module.exports = BerWriter

@@ -1,30 +0,38 @@

// Copyright 2011 Mark Cavage <mcavage@gmail.com> All rights reserved.
'use strict'
const { test } = require('tap')
const tap = require('tap')
const BerWriter = require('./writer')
test('write byte', function (t) {
tap.test('has toStringTag', async t => {
const writer = new BerWriter()
t.equal(Object.prototype.toString.call(writer), '[object BerWriter]')
})
writer.writeByte(0xC2)
const ber = writer.buffer
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)
t.ok(ber)
t.equal(ber.length, 1, 'Wrong length')
t.equal(ber[0], 0xC2, 'value wrong')
writer.writeByte(0x01)
t.equal(writer.size, 1)
})
t.end()
})
t.test('expands buffer to accomodate write skipping growth factor', async t => {
const writer = new BerWriter({ size: 0 })
t.equal(writer.size, 0)
test('write 1 byte int', function (t) {
const writer = new BerWriter()
writer.writeByte(0x01)
t.equal(writer.size, 1)
t.equal(Buffer.compare(writer.buffer, Buffer.from([0x01])), 0)
})
writer.writeInt(0x7f)
const ber = writer.buffer
t.test('expands buffer to accomodate write with growth factor', async t => {
const writer = new BerWriter({ size: 1 })
t.equal(writer.size, 1)
t.ok(ber)
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])
writer.writeByte(0x01)
writer.writeByte(0x02)
t.equal(writer.size, 8)
t.equal(Buffer.compare(writer.buffer, Buffer.from([0x01, 0x02])), 0)
})

@@ -34,31 +42,39 @@ t.end()

test('write 2 byte int', function (t) {
const writer = new BerWriter()
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')
)
})
writer.writeInt(0x7ffe)
const ber = writer.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.ok(ber)
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.end()
})
test('write 3 byte int', function (t) {
const writer = new BerWriter()
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')
)
})
writer.writeInt(0x7ffffe)
const ber = writer.buffer
t.test('starts a sequence', async t => {
const writer = new BerWriter({ size: 1 })
writer.startSequence()
t.equal(writer.size, 8)
t.ok(ber)
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)')
const expected = Buffer.from([0x30, 0x00, 0x00, 0x00])
t.equal(Buffer.compare(writer.buffer, expected), 0)
})

@@ -68,50 +84,180 @@ t.end()

test('write 4 byte int', function (t) {
const writer = new BerWriter()
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()
writer.writeInt(0x7ffffffe)
const ber = writer.buffer
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.ok(ber)
t.test('ends sequence of two byte length', async t => {
const value = Buffer.alloc(0x81, 0x01)
const writer = new BerWriter()
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)')
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()
})
test('write 1 byte negative int', function (t) {
const writer = new BerWriter()
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')
)
})
writer.writeInt(-128)
const ber = writer.buffer
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.ok(ber)
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.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('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()
})
test('write 2 byte negative int', function (t) {
const writer = new BerWriter()
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')
)
})
writer.writeInt(-22400)
const ber = writer.buffer
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.ok(ber)
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, 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.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')
}
})

@@ -121,16 +267,21 @@ t.end()

test('write 3 byte negative int', function (t) {
const writer = new BerWriter()
tap.test('writeByte', t => {
t.test('throws if input not a number', async t => {
const writer = new BerWriter()
t.equal(writer.size, 1024)
writer.writeInt(-481653)
const ber = writer.buffer
t.throws(
() => writer.writeByte('1'),
Error('argument must be a Number')
)
})
t.ok(ber)
t.test('writes a byte to the backing buffer', async t => {
const writer = new BerWriter()
writer.writeByte(0x01)
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)')
const buffer = writer.buffer
t.equal(buffer.length, 1)
t.equal(Buffer.compare(buffer, Buffer.from([0x01])), 0)
})

@@ -140,17 +291,32 @@ t.end()

test('write 4 byte negative int', function (t) {
const writer = new BerWriter()
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')
)
})
writer.writeInt(-1522904131)
const ber = writer.buffer
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.ok(ber)
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.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('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)
})

@@ -160,87 +326,249 @@ t.end()

test('write boolean', function (t) {
const writer = new BerWriter()
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')
)
})
writer.writeBoolean(true)
writer.writeBoolean(false)
const ber = writer.buffer
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.ok(ber)
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.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()
})
test('write string', function (t) {
const writer = new BerWriter()
writer.writeString('hello world')
const ber = writer.buffer
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.ok(ber)
t.equal(ber.length, 13, 'wrong length')
t.equal(ber[0], 0x04, 'wrong tag')
t.equal(ber[1], 11, 'wrong length')
t.equal(ber.slice(2).toString('utf8'), 'hello world', 'wrong value')
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()
})
test('write buffer', function (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.slice(2, buf.length), 0x04)
ber = writer.buffer
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.ok(ber)
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()
})
test('write string array', function (t) {
const writer = new BerWriter()
writer.writeStringArray(['hello world', 'fubar!'])
const ber = writer.buffer
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.ok(ber)
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.equal(ber.length, 21, '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.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.equal(ber[13], 0x04, 'wrong tag')
t.equal(ber[14], 6, 'wrong length')
t.equal(ber.slice(15).toString('utf8'), 'fubar!', 'wrong value')
t.test('writes an OID', async t => {
const oid = '1.2.840.113549.1.1.1'
const writer = new BerWriter()
writer.writeOID(oid)
t.end()
})
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)
})
test('resize internal buffer', function (t) {
const writer = new BerWriter({ size: 2 })
writer.writeString('hello world')
const ber = writer.buffer
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)
t.ok(ber)
t.equal(ber.length, 13, 'wrong length')
t.equal(ber[0], 0x04, 'wrong tag')
t.equal(ber[1], 11, 'wrong length')
t.equal(ber.slice(2).toString('utf8'), 'hello world', 'wrong value')
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)
})

@@ -250,101 +578,149 @@ t.end()

test('sequence', function (t) {
const writer = new BerWriter({ size: 25 })
writer.startSequence()
writer.writeString('hello world')
writer.endSequence()
const ber = writer.buffer
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.ok(ber)
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.slice(4).toString('utf8'), 'hello world', 'wrong value')
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.end()
})
t.test('writes an empty string', async t => {
const writer = new BerWriter()
writer.writeString('')
test('nested sequence', function (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
const expected = Buffer.from([0x04, 0x00])
t.equal(Buffer.compare(writer.buffer, expected), 0)
})
t.ok(ber)
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.slice(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.slice(19, 30).toString('utf8'), 'hello world', 'wrong value')
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()
})
test('LDAP bind message', function (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
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.ok(ber)
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.slice(12, 33).toString('utf8'), dn, 'wrong value')
t.equal(ber[33], 0x80, 'wrong tag')
t.equal(ber[34], 0x00, 'wrong len')
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()
})
test('Write OID', function (t) {
const oid = '1.2.840.113549.1.1.1'
const writer = new BerWriter()
writer.writeOID(oid)
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
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.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()
})
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)
})

@@ -11,3 +11,3 @@ {

"description": "Contains parsers and serializers for ASN.1 (currently BER only)",
"version": "1.2.0",
"version": "2.0.0-rc.1",
"repository": {

@@ -17,14 +17,20 @@ "type": "git",

},
"main": "lib/index.js",
"main": "index.js",
"devDependencies": {
"@fastify/pre-commit": "^2.0.2",
"standard": "^16.0.4",
"tap": "^16.0.1"
"eslint": "^8.25.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.3.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.0.1",
"tap": "^16.3.0"
},
"scripts": {
"test": "tap --no-coverage-report -R terse",
"test:cov": "tap -R terse",
"test:cov:html": "tap -R terse --coverage-report=html",
"test:watch": "tap -w --no-coverage-report -R terse",
"lint": "standard"
"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"
},

@@ -31,0 +37,0 @@ "license": "MIT",

@@ -10,11 +10,12 @@ # `@ldapjs/asn1`

var Ber = require('@ldapjs/asn1').Ber;
```js
const { BerReader, BerTypes } = require('@ldapjs/asn1')
const reader = new BerReader(Buffer.from([0x30, 0x03, 0x01, 0x01, 0xff]))
var reader = new Ber.Reader(Buffer.from([0x30, 0x03, 0x01, 0x01, 0xff]));
reader.readSequence()
console.log('Sequence len: ' + reader.length)
if (reader.peek() === BerTypes.Boolean)
console.log(reader.readBoolean())
```
reader.readSequence();
console.log('Sequence len: ' + reader.length);
if (reader.peek() === Ber.Boolean)
console.log(reader.readBoolean());
### Encoding

@@ -24,15 +25,18 @@

var Ber = require('@ldapjs/asn1').Ber;
```js
const { BerWriter } = require('@ldapjs/asn1');
const writer = new BerWriter();
var writer = new Ber.Writer();
writer.startSequence();
writer.writeBoolean(true);
writer.endSequence();
writer.startSequence();
writer.writeBoolean(true);
writer.endSequence();
console.log(writer.buffer);
```
console.log(writer.buffer);
## Installation
npm install asn1
```sh
npm install @ldapjs/asn1
```

@@ -39,0 +43,0 @@ ## Bugs

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc