From d41fedbc52a9fc942515668060944d05a84d4218 Mon Sep 17 00:00:00 2001
From: Ashraful Islam <4134005+ashraful-islam@users.noreply.github.com>
Date: Mon, 29 May 2023 19:59:57 +0200
Subject: [PATCH 1/4] refactor: convert to typescript
---
.gitignore | 2 +
index.js | 6 -
lib/ber/errors.js | 9 -
lib/ber/index.js | 17 -
lib/ber/reader.d.ts | 25 ++
lib/ber/reader.js | 470 +++++++++----------------
lib/ber/types.js | 34 --
lib/ber/writer.d.ts | 30 ++
lib/ber/writer.js | 513 +++++++++++----------------
lib/common/asn1.types.d.ts | 33 ++
lib/common/asn1.types.js | 37 ++
lib/common/errors.d.ts | 4 +
lib/common/errors.js | 10 +
lib/common/util.d.ts | 2 +
lib/common/util.js | 42 +++
lib/common/validation.d.ts | 6 +
lib/common/validation.js | 27 ++
lib/index.d.ts | 13 +
lib/index.js | 19 +
package.json | 18 +-
src/ber/reader.spec.ts | 296 ++++++++++++++++
src/ber/reader.ts | 205 +++++++++++
src/ber/writer.spec.ts | 584 +++++++++++++++++++++++++++++++
src/ber/writer.ts | 237 +++++++++++++
src/common/asn1.types.ts | 42 +++
src/common/errors.ts | 6 +
src/common/util.ts | 40 +++
src/common/validation.ts | 23 ++
src/index.ts | 15 +
test/unittests_lib-ber-reader.js | 4 +-
test/unittests_lib-ber-writer.js | 4 +-
tsconfig.json | 30 ++
32 files changed, 2117 insertions(+), 686 deletions(-)
delete mode 100644 index.js
delete mode 100644 lib/ber/errors.js
delete mode 100644 lib/ber/index.js
create mode 100644 lib/ber/reader.d.ts
delete mode 100644 lib/ber/types.js
create mode 100644 lib/ber/writer.d.ts
create mode 100644 lib/common/asn1.types.d.ts
create mode 100644 lib/common/asn1.types.js
create mode 100644 lib/common/errors.d.ts
create mode 100644 lib/common/errors.js
create mode 100644 lib/common/util.d.ts
create mode 100644 lib/common/util.js
create mode 100644 lib/common/validation.d.ts
create mode 100644 lib/common/validation.js
create mode 100644 lib/index.d.ts
create mode 100644 lib/index.js
create mode 100644 src/ber/reader.spec.ts
create mode 100644 src/ber/reader.ts
create mode 100644 src/ber/writer.spec.ts
create mode 100644 src/ber/writer.ts
create mode 100644 src/common/asn1.types.ts
create mode 100644 src/common/errors.ts
create mode 100644 src/common/util.ts
create mode 100644 src/common/validation.ts
create mode 100644 src/index.ts
create mode 100644 tsconfig.json
diff --git a/.gitignore b/.gitignore
index 740a0a4..1c047ea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
node_modules
*.log
package-lock.json
+.tsbuildinfo
+.vscode/**
\ No newline at end of file
diff --git a/index.js b/index.js
deleted file mode 100644
index 57809f4..0000000
--- a/index.js
+++ /dev/null
@@ -1,6 +0,0 @@
-
-var Ber = require('./lib/ber/index')
-
-exports.Ber = Ber
-exports.BerReader = Ber.Reader
-exports.BerWriter = Ber.Writer
diff --git a/lib/ber/errors.js b/lib/ber/errors.js
deleted file mode 100644
index 0106747..0000000
--- a/lib/ber/errors.js
+++ /dev/null
@@ -1,9 +0,0 @@
-
-module.exports = {
- InvalidAsn1Error: function(msg) {
- var e = new Error()
- e.name = 'InvalidAsn1Error'
- e.message = msg || ''
- return e
- }
-}
diff --git a/lib/ber/index.js b/lib/ber/index.js
deleted file mode 100644
index 65985c1..0000000
--- a/lib/ber/index.js
+++ /dev/null
@@ -1,17 +0,0 @@
-
-var errors = require('./errors')
-var types = require('./types')
-
-var Reader = require('./reader')
-var Writer = require('./writer')
-
-for (var t in types)
- if (types.hasOwnProperty(t))
- exports[t] = types[t]
-
-for (var e in errors)
- if (errors.hasOwnProperty(e))
- exports[e] = errors[e]
-
-exports.Reader = Reader
-exports.Writer = Writer
diff --git a/lib/ber/reader.d.ts b/lib/ber/reader.d.ts
new file mode 100644
index 0000000..6629e9a
--- /dev/null
+++ b/lib/ber/reader.d.ts
@@ -0,0 +1,25 @@
+///
+export type NullableNumber = number | null;
+export type NullableString = string | null;
+export default class Reader {
+ private _buffer;
+ private _size;
+ private _length;
+ private _offset;
+ constructor(data: Buffer);
+ get length(): number;
+ get offset(): number;
+ get remain(): number;
+ get buffer(): Buffer;
+ private readTag;
+ readByte: (peek?: boolean) => NullableNumber;
+ peek: () => NullableNumber;
+ readLength: (offset: number) => NullableNumber;
+ readSequence: (tag?: number) => NullableNumber;
+ readInt: (tag?: number) => NullableNumber;
+ readBoolean: (tag?: number) => boolean;
+ readEnumeration: (tag?: number) => NullableNumber;
+ readString: (tag?: number, returnBuffer?: boolean) => NullableString | Buffer;
+ readOID: (tag?: number) => NullableString;
+ readBitString: (tag?: number) => NullableString;
+}
diff --git a/lib/ber/reader.js b/lib/ber/reader.js
index 4a08a0b..945c8d8 100644
--- a/lib/ber/reader.js
+++ b/lib/ber/reader.js
@@ -1,303 +1,169 @@
-
-var assert = require('assert');
-
-var ASN1 = require('./types');
-var errors = require('./errors');
-
-
-///--- Globals
-
-var InvalidAsn1Error = errors.InvalidAsn1Error;
-
-
-
-///--- API
-
-function Reader(data) {
- if (!data || !Buffer.isBuffer(data))
- throw new TypeError('data must be a node Buffer');
-
- this._buf = data;
- this._size = data.length;
-
- // These hold the "current" state
- this._len = 0;
- this._offset = 0;
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+const asn1_types_1 = require("../common/asn1.types");
+const errors_1 = require("../common/errors");
+const validation_1 = require("../common/validation");
+class Reader {
+ constructor(data) {
+ this.readTag = (tag) => {
+ const byte = this.peek();
+ if ((0, validation_1.isNil)(byte))
+ return null;
+ if (!(0, validation_1.isNil)(tag) && byte !== tag) {
+ throw new errors_1.InvalidAsn1Error(`Expected 0x${tag === null || tag === void 0 ? void 0 : tag.toString(16)}, got 0x${byte === null || byte === void 0 ? void 0 : byte.toString(16)}`);
+ }
+ const expectedSize = this.readLength(this._offset + 1);
+ if ((0, validation_1.isNil)(expectedSize))
+ return null;
+ if (this._length === 0) {
+ throw new errors_1.InvalidAsn1Error('Zero-length integer not supported');
+ }
+ if (this.length > this._size - expectedSize)
+ return null;
+ this._offset = expectedSize;
+ let value = this._buffer.readInt8(this._offset++);
+ for (let i = 1; i < this.length; i++) {
+ value *= 256;
+ value += this._buffer[this._offset++];
+ }
+ if (!Number.isSafeInteger(value)) {
+ throw new errors_1.InvalidAsn1Error('Integer not respresentable as Javascript number.');
+ }
+ return value;
+ };
+ this.readByte = (peek) => {
+ if (this.remain < 1)
+ return null;
+ const byte = this._buffer[this.offset] & 0xff;
+ if (!peek)
+ this._offset++;
+ return byte;
+ };
+ this.peek = () => this.readByte(true);
+ this.readLength = (offset) => {
+ offset = (0, validation_1.isNil)(offset) ? this._offset : offset;
+ if (offset >= this._size)
+ return null;
+ let size = this._buffer[offset++] & 0xff;
+ if ((0, validation_1.isNil)(size))
+ return null;
+ if ((size & 0x80) === 0x80) {
+ size &= 0x7f;
+ if (size === 0) {
+ throw new errors_1.InvalidAsn1Error('Indefinite length not supported');
+ }
+ if (this._size - offset < size)
+ return null;
+ this._length = 0;
+ for (let i = 0; i < size; i++) {
+ this._length *= 256;
+ this._length += (this._buffer[offset++] & 0xff);
+ }
+ }
+ else {
+ this._length = size;
+ }
+ return offset;
+ };
+ this.readSequence = (tag) => {
+ const sequence = this.peek();
+ if ((0, validation_1.isNil)(sequence))
+ return null;
+ if (!(0, validation_1.isNil)(tag) && tag !== sequence) {
+ throw new errors_1.InvalidAsn1Error(`Expected 0x${tag === null || tag === void 0 ? void 0 : tag.toString(16)}, got 0x${sequence === null || sequence === void 0 ? void 0 : sequence.toString(16)}`);
+ }
+ const size = this.readLength(this._offset + 1);
+ if ((0, validation_1.isNil)(size))
+ return null;
+ this._offset = size;
+ return sequence;
+ };
+ this.readInt = (tag) => this.readTag(tag);
+ this.readBoolean = (tag) => {
+ const tagValue = (0, validation_1.isNumber)(tag) ? tag : asn1_types_1.E_ASN1_TYPES.BOOLEAN;
+ return (this.readTag(tagValue) === 0 ? false : true);
+ };
+ this.readEnumeration = (tag) => {
+ return !(0, validation_1.isNumber)(tag)
+ ? this.readTag(asn1_types_1.E_ASN1_TYPES.ENUMERATION)
+ : this.readTag(tag);
+ };
+ this.readString = (tag, returnBuffer) => {
+ const tagValue = (0, validation_1.isNumber)(tag) ? tag : asn1_types_1.E_ASN1_TYPES.OCTET_STRING;
+ const byte = this.peek();
+ if ((0, validation_1.isNil)(byte))
+ return null;
+ if (byte !== tagValue) {
+ throw new errors_1.InvalidAsn1Error(`Expected 0x${tagValue === null || tagValue === void 0 ? void 0 : tagValue.toString(16)}, got 0x${byte === null || byte === void 0 ? void 0 : byte.toString(16)}`);
+ }
+ const expectedSize = this.readLength(this._offset + 1);
+ if ((0, validation_1.isNil)(expectedSize))
+ return null;
+ if (this.length > this._size - expectedSize)
+ return null;
+ this._offset = expectedSize;
+ if (this.length === 0) {
+ return returnBuffer ? Buffer.alloc(0) : '';
+ }
+ const str = this._buffer.subarray(this.offset, this.offset + this.length);
+ this._offset += this.length;
+ return returnBuffer ? str : str.toString('utf-8');
+ };
+ this.readOID = (tag) => {
+ const tagValue = (0, validation_1.isNumber)(tag) ? tag : asn1_types_1.E_ASN1_TYPES.OID;
+ const stringOrBuffer = this.readString(tagValue, true);
+ if ((0, validation_1.isNil)(stringOrBuffer))
+ return null;
+ const values = [];
+ let value = 0;
+ let byte;
+ for (let i = 0; i < stringOrBuffer.length; i++) {
+ byte = stringOrBuffer[i] & 0xff;
+ value <<= 7;
+ value += byte & 0x7f;
+ if ((byte & 0x80) === 0) {
+ values.push(value >>> 0);
+ value = 0;
+ }
+ }
+ value = values.shift();
+ values.unshift(value % 40);
+ values.unshift((value / 40) >> 0);
+ return values.join('.');
+ };
+ this.readBitString = (tag) => {
+ let tagValue = (0, validation_1.isNumber)(tag) ? tag : asn1_types_1.E_ASN1_TYPES.BIT_STRING;
+ const byte = this.peek();
+ if ((0, validation_1.isNil)(byte))
+ return null;
+ if (byte !== tagValue) {
+ throw new errors_1.InvalidAsn1Error(`Expected 0x${tagValue === null || tagValue === void 0 ? void 0 : tagValue.toString(16)}, got 0x${byte === null || byte === void 0 ? void 0 : byte.toString(16)}`);
+ }
+ const expectedSize = this.readLength(this._offset + 1);
+ if ((0, validation_1.isNil)(expectedSize))
+ return null;
+ if (this.length > this._size - expectedSize)
+ return null;
+ this._offset = expectedSize;
+ if (this.length === 0)
+ return '';
+ const ignoredBits = this._buffer[this._offset++];
+ const bitStringOctets = this._buffer.subarray(this._offset, this._offset + this.length - 1);
+ const bitString = (Number.parseInt(bitStringOctets.toString('hex'), 16).toString(2)).padStart(bitStringOctets.length * 8, '0');
+ this._offset += this.length - 1;
+ return bitString.substring(0, bitString.length - ignoredBits);
+ };
+ if (!(0, validation_1.isBuffer)(data)) {
+ throw new TypeError('data must be a node buffer');
+ }
+ this._buffer = data;
+ this._size = data.length;
+ this._length = 0;
+ this._offset = 0;
+ }
+ get length() { return this._length; }
+ get offset() { return this._offset; }
+ get remain() { return this._size - this._offset; }
+ get buffer() { return this._buffer.subarray(this.offset); }
}
-
-Object.defineProperty(Reader.prototype, 'length', {
- enumerable: true,
- get: function () { return (this._len); }
-});
-
-Object.defineProperty(Reader.prototype, 'offset', {
- enumerable: true,
- get: function () { return (this._offset); }
-});
-
-Object.defineProperty(Reader.prototype, 'remain', {
- get: function () { return (this._size - this._offset); }
-});
-
-Object.defineProperty(Reader.prototype, 'buffer', {
- get: function () { return (this._buf.slice(this._offset)); }
-});
-
-
-/**
- * Reads a single byte and advances offset; you can pass in `true` to make this
- * a "peek" operation (i.e., get the byte, but don't advance the offset).
- *
- * @param {Boolean} peek true means don't move offset.
- * @return {Number} the next byte, null if not enough data.
- */
-Reader.prototype.readByte = function(peek) {
- if (this._size - this._offset < 1)
- return null;
-
- var b = this._buf[this._offset] & 0xff;
-
- if (!peek)
- this._offset += 1;
-
- return b;
-};
-
-
-Reader.prototype.peek = function() {
- return this.readByte(true);
-};
-
-
-/**
- * Reads a (potentially) variable length off the BER buffer. This call is
- * not really meant to be called directly, as callers have to manipulate
- * the internal buffer afterwards.
- *
- * As a result of this call, you can call `Reader.length`, until the
- * next thing called that does a readLength.
- *
- * @return {Number} the amount of offset to advance the buffer.
- * @throws {InvalidAsn1Error} on bad ASN.1
- */
-Reader.prototype.readLength = function(offset) {
- if (offset === undefined)
- offset = this._offset;
-
- if (offset >= this._size)
- return null;
-
- var lenB = this._buf[offset++] & 0xff;
- if (lenB === null)
- return null;
-
- if ((lenB & 0x80) == 0x80) {
- lenB &= 0x7f;
-
- if (lenB == 0)
- throw InvalidAsn1Error('Indefinite length not supported');
-
- // Caused problems for node-net-snmp issue #172
- // if (lenB > 4)
- // throw InvalidAsn1Error('encoding too long');
-
- if (this._size - offset < lenB)
- return null;
-
- this._len = 0;
- for (var i = 0; i < lenB; i++) {
- this._len *= 256;
- this._len += (this._buf[offset++] & 0xff);
- }
-
- } else {
- // Wasn't a variable length
- this._len = lenB;
- }
-
- return offset;
-};
-
-
-/**
- * Parses the next sequence in this BER buffer.
- *
- * To get the length of the sequence, call `Reader.length`.
- *
- * @return {Number} the sequence's tag.
- */
-Reader.prototype.readSequence = function(tag) {
- var seq = this.peek();
- if (seq === null)
- return null;
- if (tag !== undefined && tag !== seq)
- throw InvalidAsn1Error('Expected 0x' + tag.toString(16) +
- ': got 0x' + seq.toString(16));
-
- var o = this.readLength(this._offset + 1); // stored in `length`
- if (o === null)
- return null;
-
- this._offset = o;
- return seq;
-};
-
-
-Reader.prototype.readInt = function(tag) {
- // if (typeof(tag) !== 'number')
- // tag = ASN1.Integer;
-
- return this._readTag(tag);
-};
-
-
-Reader.prototype.readBoolean = function(tag) {
- if (typeof(tag) !== 'number')
- tag = ASN1.Boolean;
-
- return (this._readTag(tag) === 0 ? false : true);
-};
-
-
-Reader.prototype.readEnumeration = function(tag) {
- if (typeof(tag) !== 'number')
- tag = ASN1.Enumeration;
-
- return this._readTag(tag);
-};
-
-
-Reader.prototype.readString = function(tag, retbuf) {
- if (!tag)
- tag = ASN1.OctetString;
-
- var b = this.peek();
- if (b === null)
- return null;
-
- if (b !== tag)
- throw InvalidAsn1Error('Expected 0x' + tag.toString(16) +
- ': got 0x' + b.toString(16));
-
- var o = this.readLength(this._offset + 1); // stored in `length`
-
- if (o === null)
- return null;
-
- if (this.length > this._size - o)
- return null;
-
- this._offset = o;
-
- if (this.length === 0)
- return retbuf ? Buffer.alloc(0) : '';
-
- var str = this._buf.slice(this._offset, this._offset + this.length);
- this._offset += this.length;
-
- return retbuf ? str : str.toString('utf8');
-};
-
-Reader.prototype.readOID = function(tag) {
- if (!tag)
- tag = ASN1.OID;
-
- var b = this.readString(tag, true);
- if (b === null)
- return null;
-
- var values = [];
- var value = 0;
-
- for (var i = 0; i < b.length; i++) {
- var byte = b[i] & 0xff;
-
- value <<= 7;
- value += byte & 0x7f;
- if ((byte & 0x80) == 0) {
- values.push(value >>> 0);
- value = 0;
- }
- }
-
- value = values.shift();
- values.unshift(value % 40);
- values.unshift((value / 40) >> 0);
-
- return values.join('.');
-};
-
-Reader.prototype.readBitString = function(tag) {
- if (!tag)
- tag = ASN1.BitString;
-
- var b = this.peek();
- if (b === null)
- return null;
-
- if (b !== tag)
- throw InvalidAsn1Error('Expected 0x' + tag.toString(16) +
- ': got 0x' + b.toString(16));
-
- var o = this.readLength(this._offset + 1);
-
- if (o === null)
- return null;
-
- if (this.length > this._size - o)
- return null;
-
- this._offset = o;
-
- if (this.length === 0)
- return '';
-
- var ignoredBits = this._buf[this._offset++];
-
- var bitStringOctets = this._buf.slice(this._offset, this._offset + this.length - 1);
- var bitString = (parseInt(bitStringOctets.toString('hex'), 16).toString(2)).padStart(bitStringOctets.length * 8, '0');
- this._offset += this.length - 1;
-
- return bitString.substring(0, bitString.length - ignoredBits);
-};
-
-Reader.prototype._readTag = function(tag) {
- // assert.ok(tag !== undefined);
-
- var b = this.peek();
-
- if (b === null)
- return null;
-
- if (tag !== undefined && b !== tag)
- throw InvalidAsn1Error('Expected 0x' + tag.toString(16) +
- ': got 0x' + b.toString(16));
-
- var o = this.readLength(this._offset + 1); // stored in `length`
- if (o === null)
- return null;
-
- if (this.length === 0)
- throw InvalidAsn1Error('Zero-length integer');
-
- if (this.length > this._size - o)
- return null;
- this._offset = o;
-
- var value = this._buf.readInt8(this._offset++);
- for (var i = 1; i < this.length; i++) {
- value *= 256;
- value += this._buf[this._offset++];
- }
-
- if ( ! Number.isSafeInteger(value) )
- throw InvalidAsn1Error('Integer not representable as javascript number');
-
- return value;
-};
-
-
-
-///--- Exported API
-
-module.exports = Reader;
+exports.default = Reader;
diff --git a/lib/ber/types.js b/lib/ber/types.js
deleted file mode 100644
index 345824b..0000000
--- a/lib/ber/types.js
+++ /dev/null
@@ -1,34 +0,0 @@
-
-module.exports = {
- EOC: 0,
- Boolean: 1,
- Integer: 2,
- BitString: 3,
- OctetString: 4,
- Null: 5,
- OID: 6,
- ObjectDescriptor: 7,
- External: 8,
- Real: 9,
- 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
-}
diff --git a/lib/ber/writer.d.ts b/lib/ber/writer.d.ts
new file mode 100644
index 0000000..10594eb
--- /dev/null
+++ b/lib/ber/writer.d.ts
@@ -0,0 +1,30 @@
+///
+export type NullableNumber = number | null;
+export type NullableString = string | null;
+export type WriterOptions = {
+ size?: number;
+ growthFactor?: number;
+};
+export default class Writer {
+ private _buffer;
+ private size;
+ private offset;
+ private sequence;
+ private opts;
+ constructor(options?: WriterOptions);
+ get buffer(): Buffer;
+ private ensure;
+ private shift;
+ writeByte: (byte?: number) => void;
+ writeInt: (val?: number, tag?: number) => void;
+ writeNull: () => void;
+ writeEnumeration: (e: number, tag: number) => void;
+ writeBoolean: (b?: boolean, tag?: number) => void;
+ writeString: (s?: string, tag?: number) => void;
+ writeBuffer: (buffer: Buffer, tag?: number) => void;
+ writeStringArray: (string: string[], tag?: number) => void;
+ writeOID: (oid: string, tag?: number) => void;
+ writeLength: (length: number) => void;
+ startSequence: (tag?: number) => void;
+ endSequence: () => void;
+}
diff --git a/lib/ber/writer.js b/lib/ber/writer.js
index 75a538a..1f9f94b 100644
--- a/lib/ber/writer.js
+++ b/lib/ber/writer.js
@@ -1,311 +1,206 @@
-
-var assert = require('assert');
-var ASN1 = require('./types');
-var errors = require('./errors');
-
-
-///--- Globals
-
-var InvalidAsn1Error = errors.InvalidAsn1Error;
-
-var DEFAULT_OPTS = {
- size: 1024,
- growthFactor: 8
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+const asn1_types_1 = require("../common/asn1.types");
+const errors_1 = require("../common/errors");
+const util_1 = require("../common/util");
+const validation_1 = require("../common/validation");
+const DEFAULT_OPTIONS = {
+ size: 1024,
+ growthFactor: 8,
};
-
-
-///--- Helpers
-
-function merge(from, to) {
- assert.ok(from);
- assert.equal(typeof(from), 'object');
- assert.ok(to);
- assert.equal(typeof(to), 'object');
-
- var keys = Object.getOwnPropertyNames(from);
- keys.forEach(function(key) {
- if (to[key])
- return;
-
- var value = Object.getOwnPropertyDescriptor(from, key);
- Object.defineProperty(to, key, value);
- });
-
- return to;
-}
-
-
-
-///--- API
-
-function Writer(options) {
- options = merge(DEFAULT_OPTS, options || {});
-
- this._buf = Buffer.alloc(options.size || 1024);
- this._size = this._buf.length;
- this._offset = 0;
- this._options = options;
-
- // A list of offsets in the buffer where we need to insert
- // sequence tag/len pairs.
- this._seq = [];
-}
-
-Object.defineProperty(Writer.prototype, 'buffer', {
- get: function () {
- if (this._seq.length)
- throw new InvalidAsn1Error(this._seq.length + ' unended sequence(s)');
-
- return (this._buf.slice(0, this._offset));
- }
-});
-
-Writer.prototype.writeByte = function(b) {
- if (typeof(b) !== 'number')
- throw new TypeError('argument must be a Number');
-
- this._ensure(1);
- this._buf[this._offset++] = b;
-};
-
-Writer.prototype.writeInt = function(i, tag) {
- if (!Number.isInteger(i))
- throw new TypeError('argument must be an integer');
- if (typeof(tag) !== 'number')
- tag = ASN1.Integer;
-
- let bytes = [];
- while (i < -0x80 || i >= 0x80) {
- bytes.push(i & 0xff);
- i = Math.floor(i / 0x100);
- }
- bytes.push(i & 0xff);
-
- this._ensure(2 + bytes.length);
- this._buf[this._offset++] = tag;
- this._buf[this._offset++] = bytes.length;
-
- while (bytes.length) {
- this._buf[this._offset++] = bytes.pop();
- }
-};
-
-Writer.prototype.writeNull = function() {
- this.writeByte(ASN1.Null);
- this.writeByte(0x00);
-};
-
-
-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;
-
- return this.writeInt(i, tag);
-};
-
-
-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._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;
-
- var len = Buffer.byteLength(s);
- this.writeByte(tag);
- this.writeLength(len);
- if (len) {
- this._ensure(len);
- this._buf.write(s, this._offset);
- this._offset += len;
- }
-};
-
-
-Writer.prototype.writeBuffer = function(buf, tag) {
- if (!Buffer.isBuffer(buf))
- throw new TypeError('argument must be a buffer');
-
- // If no tag is specified we will assume `buf` already contains tag and length
- if (typeof(tag) === 'number') {
- this.writeByte(tag);
- this.writeLength(buf.length);
- }
-
- if ( buf.length > 0 ) {
- this._ensure(buf.length);
- buf.copy(this._buf, this._offset, 0, buf.length);
- this._offset += buf.length;
+class Writer {
+ constructor(options = DEFAULT_OPTIONS) {
+ this.ensure = (length) => {
+ if (this.size - this.offset >= length)
+ return;
+ let size = this.size * this.opts.growthFactor;
+ if (size - this.offset < length)
+ size += length;
+ const buffer = Buffer.alloc(size);
+ this._buffer.copy(buffer, 0, 0, this.offset);
+ this._buffer = buffer;
+ this.size = size;
+ };
+ this.shift = (start, length, shift) => {
+ if (!(0, validation_1.isNumber)(start) || !(0, validation_1.isNumber)(length) || !(0, validation_1.isNumber)(shift)) {
+ throw new errors_1.InvalidAsn1Error(`Invalid shift parameters.`);
+ }
+ this._buffer.copy(this._buffer, start + shift, start, start + length);
+ this.offset += shift;
+ };
+ this.writeByte = (byte) => {
+ if (!(0, validation_1.isNumber)(byte)) {
+ throw new errors_1.InvalidAsn1Error('Argument must be a number.');
+ }
+ this.ensure(1);
+ this._buffer[this.offset++] = byte;
+ };
+ this.writeInt = (val, tag) => {
+ if (!Number.isInteger(val)) {
+ throw new TypeError('Argument must be a valid integer');
+ }
+ const tagVal = (0, validation_1.isNumber)(tag) ? tag : asn1_types_1.E_ASN1_TYPES.INTEGER;
+ let bytes = [];
+ let i = val;
+ while (i < -0x80 || i >= 0x80) {
+ bytes.push(i & 0xff);
+ i = Math.floor(i / 0x100);
+ }
+ bytes.push(i & 0xff);
+ this.ensure(2 + bytes.length);
+ this._buffer[this.offset++] = tagVal;
+ this._buffer[this.offset++] = bytes.length;
+ for (let i = bytes.length - 1; i >= 0; i--) {
+ this._buffer[this.offset++] = bytes[i];
+ }
+ };
+ this.writeNull = () => {
+ this.writeByte(asn1_types_1.E_ASN1_TYPES.NULL);
+ this.writeByte(0x00);
+ };
+ this.writeEnumeration = (e, tag) => {
+ if (!(0, validation_1.isNumber)(e)) {
+ throw new TypeError('Argument must be a number');
+ }
+ const tagVal = (0, validation_1.isNumber)(tag) ? tag : asn1_types_1.E_ASN1_TYPES.ENUMERATION;
+ return this.writeInt(e, tagVal);
+ };
+ this.writeBoolean = (b, tag) => {
+ if (!(0, validation_1.isBoolean)(b)) {
+ throw new TypeError('Argument must be boolean');
+ }
+ const tagVal = (0, validation_1.isNumber)(tag) ? tag : asn1_types_1.E_ASN1_TYPES.BOOLEAN;
+ this.ensure(3);
+ this._buffer[this.offset++] = tagVal;
+ this._buffer[this.offset++] = 0x01;
+ this._buffer[this.offset++] = b ? 0xff : 0x00;
+ };
+ this.writeString = (s, tag) => {
+ if (!(0, validation_1.isString)(s)) {
+ throw new TypeError('Argument must be a valid string.');
+ }
+ const tagVal = (0, validation_1.isNumber)(tag) ? tag : asn1_types_1.E_ASN1_TYPES.OCTET_STRING;
+ const length = Buffer.byteLength(s);
+ this.writeByte(tagVal);
+ this.writeLength(length);
+ if (length) {
+ this.ensure(length);
+ this._buffer.write(s, this.offset);
+ this.offset += length;
+ }
+ };
+ this.writeBuffer = (buffer, tag) => {
+ if (!(0, validation_1.isBuffer)(buffer)) {
+ throw new TypeError('Argument must be valid buffer');
+ }
+ if ((0, validation_1.isNumber)(tag)) {
+ this.writeByte(tag);
+ this.writeLength(buffer.length);
+ }
+ if (buffer.length > 0) {
+ this.ensure(buffer.length);
+ buffer.copy(this._buffer, this.offset, 0, buffer.length);
+ this.offset += buffer.length;
+ }
+ };
+ this.writeStringArray = (string, tag) => {
+ if (!(string instanceof Array)) {
+ throw new TypeError('Argument must be an Array[String]');
+ }
+ string.forEach((s) => this.writeString(s, tag));
+ };
+ this.writeOID = (oid, tag) => {
+ if (!(0, validation_1.isString)(oid)) {
+ throw new TypeError(`Argument must be of type string`);
+ }
+ const tagVal = (0, validation_1.isNumber)(tag) ? tag : asn1_types_1.E_ASN1_TYPES.OID;
+ if (!(0, validation_1.isOID)(oid)) {
+ throw new Error('Argument is not a valid OID string');
+ }
+ const oidNodes = oid.split('.');
+ const bytes = [
+ (Number.parseInt(oidNodes[0], 10) * 40 + Number.parseInt(oidNodes[1], 10)),
+ ];
+ oidNodes.slice(2).forEach((b) => (0, util_1.encodeOctet)(bytes, Number.parseInt(b, 10)));
+ this.ensure(2 + bytes.length);
+ this.writeByte(tagVal);
+ this.writeLength(bytes.length);
+ bytes.forEach((v) => this.writeByte(v));
+ };
+ this.writeLength = (length) => {
+ if (!(0, validation_1.isNumber)(length)) {
+ throw new TypeError(`Length must be a number`);
+ }
+ this.ensure(4);
+ if (length <= 0x7f) {
+ this._buffer[this.offset++] = length;
+ }
+ else if (length <= 0xff) {
+ this._buffer[this.offset++] = 0x81;
+ this._buffer[this.offset++] = length;
+ }
+ else if (length <= 0xffff) {
+ this._buffer[this.offset++] = 0x82;
+ this._buffer[this.offset++] = length >> 8;
+ this._buffer[this.offset++] = length;
+ }
+ else if (length <= 0xffffff) {
+ this._buffer[this.offset++] = 0x83;
+ this._buffer[this.offset++] = length >> 16;
+ this._buffer[this.offset++] = length >> 8;
+ this._buffer[this.offset++] = length;
+ }
+ else {
+ throw new errors_1.InvalidAsn1Error('Length too long (> 4 bytes)');
+ }
+ };
+ this.startSequence = (tag) => {
+ const tagVal = (0, validation_1.isNumber)(tag) ? tag : asn1_types_1.E_ASN1_TYPES.SEQUENCE | asn1_types_1.E_ASN1_TYPES.CONSTRUCTOR;
+ this.writeByte(tagVal);
+ this.sequence.push(this.offset);
+ this.ensure(3);
+ this.offset += 3;
+ };
+ this.endSequence = () => {
+ const seq = this.sequence.pop();
+ const start = seq + 3;
+ const length = this.offset - start;
+ if (length <= 0x7f) {
+ this.shift(start, length, -2);
+ this._buffer[seq] = length;
+ }
+ else if (length <= 0xff) {
+ this.shift(start, length, -1);
+ this._buffer[seq] = 0x81;
+ this._buffer[seq + 1] = length;
+ }
+ else if (length <= 0xffff) {
+ this._buffer[seq] = 0x82;
+ this._buffer[seq + 1] = length >> 8;
+ this._buffer[seq + 2] = length;
+ }
+ else if (length <= 0xffffff) {
+ this.shift(start, length, 1);
+ this._buffer[seq] = 0x83;
+ this._buffer[seq + 1] = length >> 16;
+ this._buffer[seq + 2] = length >> 8;
+ this._buffer[seq + 3] = length;
+ }
+ else {
+ throw new errors_1.InvalidAsn1Error('Sequence too long');
+ }
+ };
+ this.opts = (0, util_1.mergeObject)(DEFAULT_OPTIONS, options || {});
+ this._buffer = Buffer.alloc(this.opts.size || DEFAULT_OPTIONS.size);
+ this.size = this._buffer.length;
+ this.offset = 0;
+ this.sequence = [];
}
-};
-
-
-Writer.prototype.writeStringArray = function(strings, tag) {
- if (! (strings instanceof Array))
- throw new TypeError('argument must be an Array[String]');
-
- var self = this;
- strings.forEach(function(s) {
- self.writeString(s, tag);
- });
-};
-
-// 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]+\.){0,}[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);
- } 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);
- }
- }
-
- var tmp = s.split('.');
- var bytes = [];
- bytes.push(parseInt(tmp[0], 10) * 40 + parseInt(tmp[1], 10));
- tmp.slice(2).forEach(function(b) {
- encodeOctet(bytes, parseInt(b, 10));
- });
-
- var self = this;
- this._ensure(2 + bytes.length);
- this.writeByte(tag);
- this.writeLength(bytes.length);
- bytes.forEach(function(b) {
- self.writeByte(b);
- });
-};
-
-
-Writer.prototype.writeLength = function(len) {
- if (typeof(len) !== 'number')
- throw new TypeError('argument must be a Number');
-
- this._ensure(4);
-
- 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 new InvalidAsn1Error('Length too long (> 4 bytes)');
- }
-};
-
-Writer.prototype.startSequence = function(tag) {
- if (typeof(tag) !== 'number')
- tag = ASN1.Sequence | ASN1.Constructor;
-
- this.writeByte(tag);
- this._seq.push(this._offset);
- this._ensure(3);
- this._offset += 3;
-};
-
-
-Writer.prototype.endSequence = function() {
- var seq = this._seq.pop();
- var start = seq + 3;
- var 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 new InvalidAsn1Error('Sequence too long');
- }
-};
-
-
-Writer.prototype._shift = function(start, len, shift) {
- assert.ok(start !== undefined);
- assert.ok(len !== undefined);
- assert.ok(shift);
-
- this._buf.copy(this._buf, start + shift, start, start + len);
- this._offset += shift;
-};
-
-Writer.prototype._ensure = function(len) {
- assert.ok(len);
-
- if (this._size - this._offset < len) {
- var sz = this._size * this._options.growthFactor;
- if (sz - this._offset < len)
- sz += len;
-
- var buf = Buffer.alloc(sz);
-
- this._buf.copy(buf, 0, 0, this._offset);
- this._buf = buf;
- this._size = sz;
- }
-};
-
-
-
-///--- Exported API
-
-module.exports = Writer;
+ get buffer() {
+ if (this.sequence.length) {
+ throw new errors_1.InvalidAsn1Error(`${this.sequence.length} unedned sequence(s)`);
+ }
+ return this._buffer.subarray(0, this.offset);
+ }
+}
+exports.default = Writer;
diff --git a/lib/common/asn1.types.d.ts b/lib/common/asn1.types.d.ts
new file mode 100644
index 0000000..af658fb
--- /dev/null
+++ b/lib/common/asn1.types.d.ts
@@ -0,0 +1,33 @@
+export declare enum E_ASN1_TYPES {
+ EOC = 0,
+ BOOLEAN = 1,
+ INTEGER = 2,
+ BIT_STRING = 3,
+ OCTET_STRING = 4,
+ NULL = 5,
+ OID = 6,
+ OBJECT_DESCRIPTOR = 7,
+ EXTERNAL = 8,
+ REAL = 9,
+ ENUMERATION = 10,
+ PDV = 11,
+ UTF8_STRING = 12,
+ RELATIVE_OID = 13,
+ SEQUENCE = 16,
+ SET = 17,
+ NUMERIC_STRING = 18,
+ PRINTABLE_STRING = 19,
+ T61_STRING = 20,
+ VIDEO_TEXT_STRING = 21,
+ IA5_STRING = 22,
+ UTC_TIME = 23,
+ GENERALIZED_TIME = 24,
+ GRAPHIC_STRING = 25,
+ VISIBLE_STRING = 26,
+ GENERAL_STRING = 28,
+ UNIVERSAL_STRING = 29,
+ CHARATER_STRING = 30,
+ BMP_STRING = 31,
+ CONSTRUCTOR = 32,
+ CONTEXT = 128
+}
diff --git a/lib/common/asn1.types.js b/lib/common/asn1.types.js
new file mode 100644
index 0000000..a859513
--- /dev/null
+++ b/lib/common/asn1.types.js
@@ -0,0 +1,37 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.E_ASN1_TYPES = void 0;
+var E_ASN1_TYPES;
+(function (E_ASN1_TYPES) {
+ E_ASN1_TYPES[E_ASN1_TYPES["EOC"] = 0] = "EOC";
+ E_ASN1_TYPES[E_ASN1_TYPES["BOOLEAN"] = 1] = "BOOLEAN";
+ E_ASN1_TYPES[E_ASN1_TYPES["INTEGER"] = 2] = "INTEGER";
+ E_ASN1_TYPES[E_ASN1_TYPES["BIT_STRING"] = 3] = "BIT_STRING";
+ E_ASN1_TYPES[E_ASN1_TYPES["OCTET_STRING"] = 4] = "OCTET_STRING";
+ E_ASN1_TYPES[E_ASN1_TYPES["NULL"] = 5] = "NULL";
+ E_ASN1_TYPES[E_ASN1_TYPES["OID"] = 6] = "OID";
+ E_ASN1_TYPES[E_ASN1_TYPES["OBJECT_DESCRIPTOR"] = 7] = "OBJECT_DESCRIPTOR";
+ E_ASN1_TYPES[E_ASN1_TYPES["EXTERNAL"] = 8] = "EXTERNAL";
+ E_ASN1_TYPES[E_ASN1_TYPES["REAL"] = 9] = "REAL";
+ E_ASN1_TYPES[E_ASN1_TYPES["ENUMERATION"] = 10] = "ENUMERATION";
+ E_ASN1_TYPES[E_ASN1_TYPES["PDV"] = 11] = "PDV";
+ E_ASN1_TYPES[E_ASN1_TYPES["UTF8_STRING"] = 12] = "UTF8_STRING";
+ E_ASN1_TYPES[E_ASN1_TYPES["RELATIVE_OID"] = 13] = "RELATIVE_OID";
+ E_ASN1_TYPES[E_ASN1_TYPES["SEQUENCE"] = 16] = "SEQUENCE";
+ E_ASN1_TYPES[E_ASN1_TYPES["SET"] = 17] = "SET";
+ E_ASN1_TYPES[E_ASN1_TYPES["NUMERIC_STRING"] = 18] = "NUMERIC_STRING";
+ E_ASN1_TYPES[E_ASN1_TYPES["PRINTABLE_STRING"] = 19] = "PRINTABLE_STRING";
+ E_ASN1_TYPES[E_ASN1_TYPES["T61_STRING"] = 20] = "T61_STRING";
+ E_ASN1_TYPES[E_ASN1_TYPES["VIDEO_TEXT_STRING"] = 21] = "VIDEO_TEXT_STRING";
+ E_ASN1_TYPES[E_ASN1_TYPES["IA5_STRING"] = 22] = "IA5_STRING";
+ E_ASN1_TYPES[E_ASN1_TYPES["UTC_TIME"] = 23] = "UTC_TIME";
+ E_ASN1_TYPES[E_ASN1_TYPES["GENERALIZED_TIME"] = 24] = "GENERALIZED_TIME";
+ E_ASN1_TYPES[E_ASN1_TYPES["GRAPHIC_STRING"] = 25] = "GRAPHIC_STRING";
+ E_ASN1_TYPES[E_ASN1_TYPES["VISIBLE_STRING"] = 26] = "VISIBLE_STRING";
+ E_ASN1_TYPES[E_ASN1_TYPES["GENERAL_STRING"] = 28] = "GENERAL_STRING";
+ E_ASN1_TYPES[E_ASN1_TYPES["UNIVERSAL_STRING"] = 29] = "UNIVERSAL_STRING";
+ E_ASN1_TYPES[E_ASN1_TYPES["CHARATER_STRING"] = 30] = "CHARATER_STRING";
+ E_ASN1_TYPES[E_ASN1_TYPES["BMP_STRING"] = 31] = "BMP_STRING";
+ E_ASN1_TYPES[E_ASN1_TYPES["CONSTRUCTOR"] = 32] = "CONSTRUCTOR";
+ E_ASN1_TYPES[E_ASN1_TYPES["CONTEXT"] = 128] = "CONTEXT";
+})(E_ASN1_TYPES = exports.E_ASN1_TYPES || (exports.E_ASN1_TYPES = {}));
diff --git a/lib/common/errors.d.ts b/lib/common/errors.d.ts
new file mode 100644
index 0000000..ad13e11
--- /dev/null
+++ b/lib/common/errors.d.ts
@@ -0,0 +1,4 @@
+export declare class InvalidAsn1Error extends Error {
+ readonly name: string;
+ constructor(message: string);
+}
diff --git a/lib/common/errors.js b/lib/common/errors.js
new file mode 100644
index 0000000..69e22fc
--- /dev/null
+++ b/lib/common/errors.js
@@ -0,0 +1,10 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.InvalidAsn1Error = void 0;
+class InvalidAsn1Error extends Error {
+ constructor(message) {
+ super(message);
+ this.name = InvalidAsn1Error.name;
+ }
+}
+exports.InvalidAsn1Error = InvalidAsn1Error;
diff --git a/lib/common/util.d.ts b/lib/common/util.d.ts
new file mode 100644
index 0000000..aa9b953
--- /dev/null
+++ b/lib/common/util.d.ts
@@ -0,0 +1,2 @@
+export declare const mergeObject: (from: T, to: T) => T;
+export declare const encodeOctet: (bytes: number[], octet: number) => void;
diff --git a/lib/common/util.js b/lib/common/util.js
new file mode 100644
index 0000000..96098f7
--- /dev/null
+++ b/lib/common/util.js
@@ -0,0 +1,42 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.encodeOctet = exports.mergeObject = void 0;
+const mergeObject = (from, to) => {
+ const keys = Object.getOwnPropertyNames(from);
+ keys.forEach((key) => {
+ if (Object.prototype.hasOwnProperty.call(to, key))
+ return;
+ const val = Object.getOwnPropertyDescriptor(from, key);
+ Object.defineProperty(to, key, val);
+ });
+ return to;
+};
+exports.mergeObject = mergeObject;
+const 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);
+ }
+ 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);
+ }
+};
+exports.encodeOctet = encodeOctet;
diff --git a/lib/common/validation.d.ts b/lib/common/validation.d.ts
new file mode 100644
index 0000000..136b057
--- /dev/null
+++ b/lib/common/validation.d.ts
@@ -0,0 +1,6 @@
+export declare const isNil: (data: T) => boolean;
+export declare const isBuffer: (data: T) => boolean;
+export declare const isNumber: (data: T) => boolean;
+export declare const isBoolean: (data: T) => boolean;
+export declare const isString: (data: T) => boolean;
+export declare const isOID: (data: string) => boolean;
diff --git a/lib/common/validation.js b/lib/common/validation.js
new file mode 100644
index 0000000..e6274c1
--- /dev/null
+++ b/lib/common/validation.js
@@ -0,0 +1,27 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.isOID = exports.isString = exports.isBoolean = exports.isNumber = exports.isBuffer = exports.isNil = void 0;
+const isNil = (data) => {
+ return data === null || data === undefined;
+};
+exports.isNil = isNil;
+const isBuffer = (data) => {
+ return !(0, exports.isNil)(data) && Buffer.isBuffer(data);
+};
+exports.isBuffer = isBuffer;
+const isNumber = (data) => {
+ return typeof data === 'number' && Number.isFinite(data);
+};
+exports.isNumber = isNumber;
+const isBoolean = (data) => {
+ return typeof data === 'boolean';
+};
+exports.isBoolean = isBoolean;
+const isString = (data) => {
+ return typeof data === 'string';
+};
+exports.isString = isString;
+const isOID = (data) => {
+ return /^([0-9]+\.){0,}[0-9]+$/.test(data);
+};
+exports.isOID = isOID;
diff --git a/lib/index.d.ts b/lib/index.d.ts
new file mode 100644
index 0000000..0a4a82c
--- /dev/null
+++ b/lib/index.d.ts
@@ -0,0 +1,13 @@
+import Reader from "./ber/reader";
+import Writer from "./ber/writer";
+import { InvalidAsn1Error } from "./common/errors";
+import { E_ASN1_TYPES } from "./common/asn1.types";
+export declare const Ber: {
+ InvalidAsn1Error: typeof InvalidAsn1Error;
+ E_ASN1_TYPES: typeof E_ASN1_TYPES;
+ BerReader: typeof Reader;
+ BerWriter: typeof Writer;
+};
+export default Ber;
+export declare const BerReader: typeof Reader;
+export declare const BerWriter: typeof Writer;
diff --git a/lib/index.js b/lib/index.js
new file mode 100644
index 0000000..47e9856
--- /dev/null
+++ b/lib/index.js
@@ -0,0 +1,19 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.BerWriter = exports.BerReader = exports.Ber = void 0;
+const reader_1 = __importDefault(require("./ber/reader"));
+const writer_1 = __importDefault(require("./ber/writer"));
+const errors_1 = require("./common/errors");
+const asn1_types_1 = require("./common/asn1.types");
+exports.Ber = {
+ InvalidAsn1Error: errors_1.InvalidAsn1Error,
+ E_ASN1_TYPES: asn1_types_1.E_ASN1_TYPES,
+ BerReader: reader_1.default,
+ BerWriter: writer_1.default,
+};
+exports.default = exports.Ber;
+exports.BerReader = reader_1.default;
+exports.BerWriter = writer_1.default;
diff --git a/package.json b/package.json
index ef99b42..7a634b4 100644
--- a/package.json
+++ b/package.json
@@ -1,15 +1,23 @@
{
"name": "asn1-ber",
- "version": "1.2.2",
+ "version": "1.2.3",
"description": "Generate and parse ASN1.BER objects",
- "main": "index.js",
+ "main": "./lib/index.js",
+ "types":"./lib/index.d.ts",
"scripts": {
- "test": "mocha"
+ "test:ts": "mocha --bail --require ts-node/register src/**/*.spec.ts",
+ "test": "npm run build && mocha --bail",
+ "build": "tsc"
},
"directories": {},
- "dependencies": {},
"devDependencies": {
- "mocha": "^10.0.0"
+ "@types/chai": "^4.3.5",
+ "@types/mocha": "^10.0.1",
+ "@types/node": "^20.2.5",
+ "chai": "^4.3.7",
+ "mocha": "^10.0.0",
+ "ts-node": "^10.9.1",
+ "typescript": "^5.0.4"
},
"contributors": [
{
diff --git a/src/ber/reader.spec.ts b/src/ber/reader.spec.ts
new file mode 100644
index 0000000..e40bb9b
--- /dev/null
+++ b/src/ber/reader.spec.ts
@@ -0,0 +1,296 @@
+import { assert } from 'chai';
+import Reader from './reader';
+
+describe("lib/ber/reader.js", function() {
+ describe("readByte()", function() {
+ it("can read a value", function() {
+ const reader = new Reader(Buffer.from([0xde]))
+ assert.equal(reader.readByte(), 0xde)
+ })
+ })
+
+ describe("readInt()", function() {
+ it("can read zero", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x01, 0x00]))
+ assert.equal(reader.readInt(), 0)
+ assert.equal(reader.length, 1)
+ })
+
+ it("can read a 1 byte positive integer - lowest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x01, 0x01]))
+ assert.equal(reader.readInt(), 1)
+ assert.equal(reader.length, 1)
+ })
+
+ it("can read a 1 byte positive integer - middle", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x01, 0x34]))
+ assert.equal(reader.readInt(), 52)
+ assert.equal(reader.length, 1)
+ })
+
+ it("can read a 1 byte positive integer - highest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x01, 0x7f]))
+ assert.equal(reader.readInt(), 127)
+ assert.equal(reader.length, 1)
+ })
+
+ it("can read a 2 byte positive integer - lowest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x02, 0x00, 0x80]))
+ assert.equal(reader.readInt(), 128)
+ assert.equal(reader.length, 2)
+ })
+
+ it("can read a 2 byte positive integer - middle", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x02, 0x7e, 0xde]))
+ assert.equal(reader.readInt(), 0x7ede)
+ assert.equal(reader.length, 2)
+ })
+
+ it("can read a 2 byte positive integer - highest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x02, 0x7f, 0xff]))
+ assert.equal(reader.readInt(), 32767)
+ assert.equal(reader.length, 2)
+ })
+
+ it("can read a 3 byte positive integer - lowest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x03, 0x00, 0x80, 0x00]))
+ assert.equal(reader.readInt(), 32768)
+ assert.equal(reader.length, 3)
+ })
+
+ it("can read a 3 byte positive integer - middle", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x03, 0x7e, 0xde, 0x03]))
+ assert.equal(reader.readInt(), 8314371)
+ assert.equal(reader.length, 3)
+ })
+
+ it("can read a 3 byte positive integer - highest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x03, 0x7e, 0xde, 0x03]))
+ assert.equal(reader.readInt(), 0x7ede03)
+ assert.equal(reader.length, 0x03)
+ })
+
+ it("can read a 4 byte positive integer - lowest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x04, 0x00, 0x80, 0x00, 0x00]))
+ assert.equal(reader.readInt(), 8388608)
+ assert.equal(reader.length, 4)
+ })
+
+ it("can read a 4 byte positive integer - middle", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x04, 0x7e, 0xde, 0x03, 0x01]))
+ assert.equal(reader.readInt(), 2128478977)
+ assert.equal(reader.length, 4)
+ })
+
+ it("can read a 4 byte positive integer - highest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x04, 0x7f, 0xff, 0xff, 0xff]))
+ assert.equal(reader.readInt(), 2147483647)
+ assert.equal(reader.length, 4)
+ })
+
+ it("can read a 5 byte positive integer - lowest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x05, 0x00, 0x80, 0x00, 0x00, 0x00]))
+ assert.equal(reader.readInt(), 2147483648)
+ assert.equal(reader.length, 5)
+ })
+
+ it("can read a 5 byte positive integer - middle", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x05, 0x00, 0x8b, 0xde, 0x03, 0x01]))
+ assert.equal(reader.readInt(), 2346582785)
+ assert.equal(reader.length, 5)
+ })
+
+ it("can read a 5 byte positive integer - highest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff]))
+ assert.equal(reader.readInt(), 4294967295)
+ assert.equal(reader.length, 5)
+ })
+
+ it("can read a 1 byte negative integer - lowest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x01, 0x80]))
+ assert.equal(reader.readInt(), -128)
+ assert.equal(reader.length, 1)
+ })
+
+ it("can read a 1 byte negative integer - middle", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x01, 0xdc]))
+ assert.equal(reader.readInt(), -36)
+ assert.equal(reader.length, 1)
+ })
+
+ it("can read a 1 byte negative integer - highest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x01, 0xff]))
+ assert.equal(reader.readInt(), -1)
+ assert.equal(reader.length, 1)
+ })
+
+ it("can read a 2 byte negative integer - lowest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x02, 0x80, 0x00]))
+ assert.equal(reader.readInt(), -32768)
+ assert.equal(reader.length, 2)
+ })
+
+ it("can read a 2 byte negative integer - middle", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x02, 0xc0, 0x4e]))
+ assert.equal(reader.readInt(), -16306)
+ assert.equal(reader.length, 2)
+ })
+
+ it("can read a 2 byte negative integer - highest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x02, 0xff, 0x7f]))
+ assert.equal(reader.readInt(), -129)
+ assert.equal(reader.length, 2)
+ })
+
+ it("can read a 3 byte negative integer - lowest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x03, 0x80, 0x00, 0x00]))
+ assert.equal(reader.readInt(), -8388608)
+ assert.equal(reader.length, 3)
+ })
+
+ it("can read a 3 byte negative integer - middle", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x03, 0xff, 0x00, 0x19]))
+ assert.equal(reader.readInt(), -65511)
+ assert.equal(reader.length, 3)
+ })
+
+ it("can read a 3 byte negative integer - highest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x03, 0xff, 0x7f, 0xff]))
+ assert.equal(reader.readInt(), -32769)
+ assert.equal(reader.length, 3)
+ })
+
+ it("can read a 4 byte negative integer - lowest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x04, 0x80, 0x00, 0x00, 0x00]))
+ assert.equal(reader.readInt(), -2147483648)
+ assert.equal(reader.length, 4)
+ })
+
+ it("can read a 4 byte negative integer - middle", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x04, 0x91, 0x7c, 0x22, 0x1f]))
+ assert.equal(reader.readInt(), -1854135777)
+ assert.equal(reader.length, 4)
+ })
+
+ it("can read a 4 byte negative integer - highest", function() {
+ const reader = new Reader(Buffer.from([0x02, 0x04, 0xff, 0x7f, 0xff, 0xff]))
+ assert.equal(reader.readInt(), -8388609)
+ assert.equal(reader.length, 4)
+ })
+ })
+
+ describe("readBoolean()", function() {
+ it("can read a true value", function() {
+ const reader = new Reader(Buffer.from([0x01, 0x01, 0xff]))
+ assert.equal(reader.readBoolean(), true)
+ assert.equal(reader.length, 0x01)
+ })
+
+ it("can read a false value", function() {
+ const reader = new Reader(Buffer.from([0x01, 0x01, 0x00]))
+ assert.equal(reader.readBoolean(), false)
+ assert.equal(reader.length, 0x01)
+ })
+ })
+
+ describe("readEnumeration()", function() {
+ it("can read a value", function() {
+ const reader = new Reader(Buffer.from([0x0a, 0x01, 0x20]))
+ assert.equal(reader.readEnumeration(), 0x20, 'wrong value')
+ assert.equal(reader.length, 0x01, 'wrong length')
+ })
+ })
+
+ describe("readOID()", function() {
+ it("does not convert to unsigned", function() {
+ // Make sure 2887117176 is NOT converted to -1407850120
+ const buffer = Buffer.from([6, 18, 43, 6, 1, 4, 1, 245, 12, 1, 1, 5, 1, 1, 19, 138, 224, 215, 210, 120])
+ const reader = new Reader(buffer)
+ assert.equal(reader.readOID(), "1.3.6.1.4.1.14988.1.1.5.1.1.19.2887117176")
+ assert.equal(reader.length, 18)
+ })
+ })
+
+ describe("readString()", function() {
+ it("can read a value", function() {
+ const string = 'cn=foo,ou=unit,o=test'
+ const buffer = Buffer.alloc(string.length + 2)
+ buffer[0] = 0x04
+ buffer[1] = Buffer.byteLength(string)
+ buffer.write(string, 2)
+
+ const reader = new Reader(buffer)
+ assert.equal(reader.readString(), string)
+ assert.equal(reader.length, string.length)
+ })
+ })
+
+ describe("readSequence()", function() {
+ it("can read a sequence", function() {
+ const reader = new Reader(Buffer.from([0x30, 0x03, 0x01, 0x01, 0xff]))
+ assert.equal(reader.readSequence(), 0x30)
+ assert.equal(reader.length, 0x03)
+ assert.equal(reader.readBoolean(), true)
+ assert.equal(reader.length, 0x01)
+ })
+ })
+
+ describe("readBitString()", function() {
+ it("can read a bit string", function() {
+ const reader = new Reader(Buffer.from([0x03, 0x07, 0x04, 0x0a, 0x3b, 0x5f, 0x29, 0x1c, 0xd0]))
+ assert.equal(reader.readBitString(), '00001010001110110101111100101001000111001101')
+ assert.equal(reader.length, 7)
+ })
+ })
+
+ describe("complex sequences", function() {
+ it("are processed correctly", function() {
+ const buffer = Buffer.alloc(14);
+
+ // An anonymous LDAP v3 BIND request
+ buffer[0] = 0x30 // Sequence
+ buffer[1] = 12 // len
+ buffer[2] = 0x02 // ASN.1 Integer
+ buffer[3] = 1 // len
+ buffer[4] = 0x04 // msgid (make up 4)
+ buffer[5] = 0x60 // Bind Request
+ buffer[6] = 7 // len
+ buffer[7] = 0x02 // ASN.1 Integer
+ buffer[8] = 1 // len
+ buffer[9] = 0x03 // v3
+ buffer[10] = 0x04 // String (bind dn)
+ buffer[11] = 0 // len
+ buffer[12] = 0x80 // ContextSpecific (choice)
+ buffer[13] = 0 // simple bind
+
+ const reader = new Reader(buffer)
+ assert.equal(reader.readSequence(), 48)
+ assert.equal(reader.length, 12)
+ assert.equal(reader.readInt(), 4)
+ assert.equal(reader.readSequence(), 96)
+ assert.equal(reader.length, 7)
+ assert.equal(reader.readInt(), 3)
+ assert.equal(reader.readString(), "")
+ assert.equal(reader.length, 0)
+ assert.equal(reader.readByte(), 0x80)
+ assert.equal(reader.readByte(), 0)
+ assert.equal(null, reader.readByte())
+ })
+ })
+
+ describe("long strings", function() {
+ it("can be parsed", function() {
+ const buffer = Buffer.alloc(256)
+ const string = "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."
+ buffer[0] = 0x04
+ buffer[1] = 0x81
+ buffer[2] = 0x94
+ buffer.write(string, 3)
+
+ const reader = new Reader(buffer.slice(0, 3 + string.length));
+ assert.equal(reader.readString(), string)
+ })
+ })
+})
diff --git a/src/ber/reader.ts b/src/ber/reader.ts
new file mode 100644
index 0000000..469333b
--- /dev/null
+++ b/src/ber/reader.ts
@@ -0,0 +1,205 @@
+import { E_ASN1_TYPES } from '../common/asn1.types';
+import { InvalidAsn1Error } from "../common/errors";
+import { isBuffer, isNil, isNumber } from '../common/validation';
+
+export type NullableNumber = number|null;
+export type NullableString = string|null;
+
+export default class Reader {
+ private _buffer: Buffer;
+ private _size: number;
+ // @NOTE: holders current state
+ private _length: number;
+ private _offset: number;
+ constructor(data: Buffer) {
+ if (!isBuffer(data)) {
+ throw new TypeError('data must be a node buffer');
+ }
+ this._buffer = data;
+ this._size = data.length;
+ this._length = 0;
+ this._offset = 0;
+ }
+
+ get length(): number { return this._length; }
+ get offset(): number { return this._offset; }
+ get remain(): number { return this._size - this._offset; }
+ get buffer(): Buffer { return this._buffer.subarray(this.offset); }
+
+ private readTag = (tag?: number): NullableNumber => {
+ const byte = this.peek();
+
+ if (isNil(byte)) return null;
+
+ if (!isNil(tag) && byte !== tag) {
+ throw new InvalidAsn1Error(`Expected 0x${tag?.toString(16)}, got 0x${byte?.toString(16)}`);
+ }
+
+ const expectedSize = this.readLength(this._offset + 1);
+
+ if (isNil(expectedSize)) return null;
+
+ if (this._length === 0) {
+ throw new InvalidAsn1Error('Zero-length integer not supported');
+ }
+
+ if (this.length > this._size - expectedSize!) return null;
+
+ this._offset = expectedSize!;
+
+ let value = this._buffer.readInt8(this._offset++);
+
+ for (let i = 1; i < this.length; i++) {
+ value *= 256;
+ value += this._buffer[this._offset++];
+ }
+
+ if (!Number.isSafeInteger(value)) {
+ throw new InvalidAsn1Error('Integer not respresentable as Javascript number.');
+ }
+ return value;
+ }
+
+ public readByte = (peek?: boolean): NullableNumber => {
+ if (this.remain < 1) return null;
+ const byte = this._buffer[this.offset] & 0xff;
+ if (!peek) this._offset++;
+ return byte;
+ }
+
+ public peek = (): NullableNumber => this.readByte(true);
+
+ public readLength = (offset: number): NullableNumber => {
+ offset = isNil(offset) ? this._offset : offset;
+
+ if (offset >= this._size) return null;
+
+ let size = this._buffer[offset++] & 0xff;
+
+ if (isNil(size)) return null;
+
+ if ((size & 0x80) === 0x80) {
+ size &= 0x7f;
+
+ if (size === 0) {
+ throw new InvalidAsn1Error('Indefinite length not supported');
+ }
+
+ if (this._size - offset < size) return null;
+
+ this._length = 0;
+
+ for (let i = 0; i < size; i++) {
+ this._length *= 256;
+ this._length += (this._buffer[offset++] & 0xff);
+ }
+ } else {
+ this._length = size;
+ }
+
+ return offset;
+ }
+
+ public readSequence = (tag?: number): NullableNumber => {
+ const sequence = this.peek();
+
+ if (isNil(sequence)) return null;
+
+ if (!isNil(tag) && tag !== sequence) {
+ throw new InvalidAsn1Error(`Expected 0x${tag?.toString(16)}, got 0x${sequence?.toString(16)}`);
+ }
+
+ const size = this.readLength(this._offset + 1);
+
+ if (isNil(size)) return null;
+
+ this._offset = (size as number);
+
+ return sequence;
+ }
+
+ public readInt = (tag?: number): NullableNumber => this.readTag(tag);
+
+ public readBoolean = (tag?: number): boolean => {
+ const tagValue = isNumber(tag) ? tag : E_ASN1_TYPES.BOOLEAN;
+ return (this.readTag(tagValue) === 0 ? false : true);
+ }
+
+ public readEnumeration = (tag?: number): NullableNumber => {
+ return !isNumber(tag)
+ ? this.readTag(E_ASN1_TYPES.ENUMERATION)
+ : this.readTag(tag);
+ }
+
+ public readString = (tag?: number, returnBuffer?: boolean): NullableString|Buffer => {
+ const tagValue = isNumber(tag) ? tag : E_ASN1_TYPES.OCTET_STRING;
+ const byte = this.peek();
+ if (isNil(byte)) return null;
+ if (byte !== tagValue) {
+ throw new InvalidAsn1Error(`Expected 0x${tagValue?.toString(16)}, got 0x${byte?.toString(16)}`);
+ }
+
+ const expectedSize = this.readLength(this._offset + 1);
+ if (isNil(expectedSize)) return null;
+
+ if (this.length > this._size - expectedSize!) return null;
+
+ this._offset = expectedSize!;
+
+ if (this.length === 0) {
+ return returnBuffer ? Buffer.alloc(0): '';
+ }
+ const str = this._buffer.subarray(this.offset, this.offset + this.length);
+ this._offset += this.length;
+ return returnBuffer ? str : str.toString('utf-8');
+ }
+
+ public readOID = (tag?: number): NullableString => {
+ const tagValue = isNumber(tag) ? tag : E_ASN1_TYPES.OID;
+ const stringOrBuffer = this.readString(tagValue!, true);
+ if (isNil(stringOrBuffer)) return null;
+ const values = [];
+ let value = 0;
+ let byte: number;
+ for (let i = 0; i < stringOrBuffer!.length; i++) {
+ byte = (stringOrBuffer as Buffer)[i] & 0xff;
+ value <<= 7;
+ value += byte & 0x7f;
+ if ((byte & 0x80) === 0) {
+ values.push(value >>> 0);
+ value = 0;
+ }
+ }
+
+ value = values.shift()!;
+ values.unshift(value % 40);
+ values.unshift((value / 40) >> 0);
+ return values.join('.');
+ }
+
+ public readBitString = (tag?: number): NullableString => {
+ let tagValue = isNumber(tag) ? tag : E_ASN1_TYPES.BIT_STRING;
+
+ const byte = this.peek();
+
+ if (isNil(byte)) return null;
+
+ if (byte !== tagValue) {
+ throw new InvalidAsn1Error(`Expected 0x${tagValue?.toString(16)}, got 0x${byte?.toString(16)}`);
+ }
+ const expectedSize = this.readLength(this._offset + 1);
+ if (isNil(expectedSize)) return null;
+ if (this.length > this._size - expectedSize!) return null;
+
+ this._offset = expectedSize!;
+
+ if (this.length === 0) return '';
+
+ const ignoredBits = this._buffer[this._offset++];
+ const bitStringOctets = this._buffer.subarray(this._offset, this._offset + this.length - 1);
+ const bitString = (Number.parseInt( bitStringOctets.toString('hex'), 16).toString(2)).padStart(bitStringOctets.length * 8, '0');
+ this._offset += this.length - 1;
+
+ return bitString.substring(0, bitString.length - ignoredBits);
+ }
+}
\ No newline at end of file
diff --git a/src/ber/writer.spec.ts b/src/ber/writer.spec.ts
new file mode 100644
index 0000000..ff597a4
--- /dev/null
+++ b/src/ber/writer.spec.ts
@@ -0,0 +1,584 @@
+import { assert } from 'chai';
+import Writer from './writer';
+
+describe("lib/ber/writer.js", function() {
+ describe("writeByte()", function() {
+ it("can write a value", function() {
+ const writer = new Writer()
+ writer.writeByte(0xC2)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 1)
+ assert.equal(buffer[0], 0xc2)
+ })
+ })
+
+ describe("writeInt()", function() {
+ it("can write zero", function() {
+ const writer = new Writer()
+ writer.writeInt(0)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 3)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x01)
+ assert.equal(buffer[2], 0x00)
+ })
+
+ it("can write 1 byte positive integers - lowest", function() {
+ const writer = new Writer()
+ writer.writeInt(1)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 3)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x01)
+ assert.equal(buffer[2], 0x01)
+ })
+
+ it("can write 1 byte positive integers - middle", function() {
+ const writer = new Writer()
+ writer.writeInt(101)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 3)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x01)
+ assert.equal(buffer[2], 0x65)
+ })
+
+ it("can write 1 byte positive integers - highest", function() {
+ const writer = new Writer()
+ writer.writeInt(127)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 3)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x01)
+ assert.equal(buffer[2], 0x7f)
+ })
+
+ it("can write 2 byte positive integers - lowest", function() {
+ const writer = new Writer()
+ writer.writeInt(128)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 4)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x02)
+ assert.equal(buffer[2], 0x00)
+ assert.equal(buffer[3], 0x80)
+ })
+
+ it("can write 2 byte positive integers - middle", function() {
+ const writer = new Writer()
+ writer.writeInt(9374)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 4)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x02)
+ assert.equal(buffer[2], 0x24)
+ assert.equal(buffer[3], 0x9e)
+ })
+
+ it("can write 2 byte positive integers - highest", function() {
+ const writer = new Writer()
+ writer.writeInt(32767)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 4)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x02)
+ assert.equal(buffer[2], 0x7f)
+ assert.equal(buffer[3], 0xff)
+ })
+
+ it("can write 3 byte positive integers - lowest", function() {
+ const writer = new Writer()
+ writer.writeInt(32768)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 5)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x03)
+ assert.equal(buffer[2], 0x00)
+ assert.equal(buffer[3], 0x80)
+ assert.equal(buffer[4], 0x00)
+ })
+
+ it("can write 3 byte positive integers - middle", function() {
+ const writer = new Writer()
+ writer.writeInt(5938243)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 5)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x03)
+ assert.equal(buffer[2], 0x5a)
+ assert.equal(buffer[3], 0x9c)
+ assert.equal(buffer[4], 0x43)
+ })
+
+ it("can write 3 byte positive integers - highest", function() {
+ const writer = new Writer()
+ writer.writeInt(8388607)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 5)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x03)
+ assert.equal(buffer[2], 0x7f)
+ assert.equal(buffer[3], 0xff)
+ assert.equal(buffer[4], 0xff)
+ })
+
+ it("can write 4 byte positive integers - lowest", function() {
+ const writer = new Writer()
+ writer.writeInt(8388608)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 6)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x04)
+ assert.equal(buffer[2], 0x00)
+ assert.equal(buffer[3], 0x80)
+ assert.equal(buffer[4], 0x00)
+ assert.equal(buffer[5], 0x00)
+ })
+
+ it("can write 4 byte positive integers - middle", function() {
+ const writer = new Writer()
+ writer.writeInt(1483722690)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 6)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x04)
+ assert.equal(buffer[2], 0x58)
+ assert.equal(buffer[3], 0x6f)
+ assert.equal(buffer[4], 0xcf)
+ assert.equal(buffer[5], 0xc2)
+ })
+
+ it("can write 4 byte positive integers - highest", function() {
+ const writer = new Writer()
+ writer.writeInt(2147483647)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 6)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x04)
+ assert.equal(buffer[2], 0x7f)
+ assert.equal(buffer[3], 0xff)
+ assert.equal(buffer[4], 0xff)
+ assert.equal(buffer[5], 0xff)
+ })
+
+ it("can write 5 byte positive integers - lowest", function() {
+ const writer = new Writer()
+ writer.writeInt(2147483648)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 7)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x05)
+ assert.equal(buffer[2], 0x00)
+ assert.equal(buffer[3], 0x80)
+ assert.equal(buffer[4], 0x00)
+ assert.equal(buffer[5], 0x00)
+ assert.equal(buffer[6], 0x00)
+ })
+
+ it("can write 5 byte positive integers - middle", function() {
+ const writer = new Writer()
+ writer.writeInt(3843548325)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 7)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x05)
+ assert.equal(buffer[2], 0x00)
+ assert.equal(buffer[3], 0xe5)
+ assert.equal(buffer[4], 0x17)
+ assert.equal(buffer[5], 0xe4)
+ assert.equal(buffer[6], 0xa5)
+ })
+
+ it("can write 5 byte positive integers - highest", function() {
+ const writer = new Writer()
+ writer.writeInt(4294967295)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 7)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x05)
+ assert.equal(buffer[2], 0x00)
+ assert.equal(buffer[3], 0xff)
+ assert.equal(buffer[4], 0xff)
+ assert.equal(buffer[5], 0xff)
+ assert.equal(buffer[6], 0xff)
+ })
+
+ it("can write 1 byte negative integers - lowest", function() {
+ const writer = new Writer()
+ writer.writeInt(-128)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 3)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x01)
+ assert.equal(buffer[2], 0x80)
+ })
+
+ it("can write 1 byte negative integers - middle", function() {
+ const writer = new Writer()
+ writer.writeInt(-73)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 3)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x01)
+ assert.equal(buffer[2], 0xb7)
+ })
+
+ it("can write 1 byte negative integers - highest", function() {
+ const writer = new Writer()
+ writer.writeInt(-1)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 3)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x01)
+ assert.equal(buffer[2], 0xff)
+ })
+
+ it("can write 2 byte negative integers - lowest", function() {
+ const writer = new Writer()
+ writer.writeInt(-32768)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 4)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x02)
+ assert.equal(buffer[2], 0x80)
+ assert.equal(buffer[3], 0x00)
+ })
+
+ it("can write 2 byte negative integers - middle", function() {
+ const writer = new Writer()
+ writer.writeInt(-22400)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 4)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x02)
+ assert.equal(buffer[2], 0xa8)
+ assert.equal(buffer[3], 0x80)
+ })
+
+ it("can write 2 byte negative integers - highest", function() {
+ const writer = new Writer()
+ writer.writeInt(-129)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 4)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x02)
+ assert.equal(buffer[2], 0xff)
+ assert.equal(buffer[3], 0x7f)
+ })
+
+ it("can write 3 byte negative integers - lowest", function() {
+ const writer = new Writer()
+ writer.writeInt(-8388608)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 5)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x03)
+ assert.equal(buffer[2], 0x80)
+ assert.equal(buffer[3], 0x00)
+ assert.equal(buffer[4], 0x00)
+ })
+
+ it("can write 3 byte negative integers - middle", function() {
+ const writer = new Writer()
+ writer.writeInt(-481653)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 5)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x03)
+ assert.equal(buffer[2], 0xf8)
+ assert.equal(buffer[3], 0xa6)
+ assert.equal(buffer[4], 0x8b)
+ })
+
+ it("can write 3 byte negative integers - highest", function() {
+ const writer = new Writer()
+ writer.writeInt(-32769)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 5)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x03)
+ assert.equal(buffer[2], 0xff)
+ assert.equal(buffer[3], 0x7f)
+ assert.equal(buffer[4], 0xff)
+ })
+
+ it("can write 4 byte negative integers - lowest", function() {
+ const writer = new Writer()
+ writer.writeInt(-2147483648)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 6)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x04)
+ assert.equal(buffer[2], 0x80)
+ assert.equal(buffer[3], 0x00)
+ assert.equal(buffer[4], 0x00)
+ assert.equal(buffer[5], 0x00)
+ })
+
+ it("can write 4 byte negative integers - middle", function() {
+ const writer = new Writer()
+ writer.writeInt(-1522904131)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 6)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x04)
+ assert.equal(buffer[2], 0xa5)
+ assert.equal(buffer[3], 0x3a)
+ assert.equal(buffer[4], 0x53)
+ assert.equal(buffer[5], 0xbd)
+ })
+
+ it("can write 4 byte negative integers - highest", function() {
+ const writer = new Writer()
+ writer.writeInt(-8388609)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 6)
+ assert.equal(buffer[0], 0x02)
+ assert.equal(buffer[1], 0x04)
+ assert.equal(buffer[2], 0xff)
+ assert.equal(buffer[3], 0x7f)
+ assert.equal(buffer[4], 0xff)
+ assert.equal(buffer[5], 0xff)
+ })
+ })
+
+ describe("writeBoolean()", function() {
+ it("can write a true and false value", function() {
+ const writer = new Writer()
+ writer.writeBoolean(true)
+ writer.writeBoolean(false)
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 6)
+ assert.equal(buffer[0], 0x01)
+ assert.equal(buffer[1], 0x01)
+ assert.equal(buffer[2], 0xff)
+ assert.equal(buffer[3], 0x01)
+ assert.equal(buffer[4], 0x01)
+ assert.equal(buffer[5], 0x00)
+ })
+ })
+
+ describe("writeString()", function() {
+ it("can write a value", function() {
+ const writer = new Writer()
+ writer.writeString("hello world")
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 13)
+ assert.equal(buffer[0], 0x04)
+ assert.equal(buffer[1], 11)
+ assert.equal(buffer.slice(2).toString("utf8"), "hello world")
+ })
+ })
+
+ describe("writeBuffer()", function() {
+ it("can write a value", function() {
+ const writer = new Writer()
+ writer.writeString("hello world")
+
+ const expected = Buffer.from([
+ 0x04, 0x0b, 0x30, 0x09, 0x02, 0x01, 0x0f, 0x01,
+ 0x01, 0xff, 0x01, 0x01, 0xff
+ ])
+ writer.writeBuffer(expected.slice(2, expected.length), 0x04)
+
+ const buffer = writer.buffer;
+
+ assert.equal(buffer.length, 26)
+ assert.equal(buffer[0], 0x04)
+ assert.equal(buffer[1], 11)
+ assert.equal(buffer.subarray(2, 13).toString("utf8"), "hello world")
+ assert.equal(buffer[13], expected[0])
+ assert.equal(buffer[14], expected[1])
+
+ for (let i = 13, j = 0; i < buffer.length && j < expected.length; i++, j++)
+ assert.equal(buffer[i], expected[j])
+ })
+ })
+
+ describe("writeStringArray()", function() {
+ it("can write an array of strings", function() {
+ const writer = new Writer()
+ writer.writeStringArray(["hello world", "fubar!"])
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 21)
+ assert.equal(buffer[0], 0x04)
+ assert.equal(buffer[1], 11)
+ assert.equal(buffer.subarray(2, 13).toString("utf8"), "hello world")
+
+ assert.equal(buffer[13], 0x04)
+ assert.equal(buffer[14], 6)
+ assert.equal(buffer.subarray(15).toString("utf8"), "fubar!")
+ })
+ })
+
+ describe("oversized data", function() {
+ it("results in a buffer resize", function() {
+ const writer = new Writer({size: 2})
+ writer.writeString("hello world")
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 13)
+ assert.equal(buffer[0], 0x04)
+ assert.equal(buffer[1], 11)
+ assert.equal(buffer.subarray(2).toString("utf8"), "hello world")
+ })
+ })
+
+ describe("complex sequences", function() {
+ it("are processed correctly", function() {
+ const writer = new Writer({size: 25})
+ writer.startSequence()
+ writer.writeString("hello world")
+ writer.endSequence()
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 15)
+ assert.equal(buffer[0], 0x30)
+ assert.equal(buffer[1], 13)
+ assert.equal(buffer[2], 0x04)
+ assert.equal(buffer[3], 11)
+ assert.equal(buffer.subarray(4).toString("utf8"), "hello world")
+ })
+ })
+
+ describe("nested sequences", function() {
+ it("are processed correctly", function() {
+ const writer = new Writer({size: 25})
+ writer.startSequence()
+ writer.writeString("hello world")
+ writer.startSequence()
+ writer.writeString("hello world")
+ writer.endSequence()
+ writer.endSequence()
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 30)
+ assert.equal(buffer[0], 0x30)
+ assert.equal(buffer[1], 28)
+ assert.equal(buffer[2], 0x04)
+ assert.equal(buffer[3], 11)
+ assert.equal(buffer.subarray(4, 15).toString("utf8"), "hello world")
+
+ assert.equal(buffer[15], 0x30)
+ assert.equal(buffer[16], 13)
+ assert.equal(buffer[17], 0x04)
+ assert.equal(buffer[18], 11)
+ assert.equal(buffer.subarray(19, 30).toString("utf8"), "hello world")
+ })
+ })
+
+ describe("multiple sequences", function() {
+ it("are processed correctly", function() {
+ // An anonymous LDAP v3 BIND request
+ const dn = "cn=foo,ou=unit,o=test"
+
+ const writer = new Writer()
+ writer.startSequence()
+ writer.writeInt(3)
+ writer.startSequence(0x60)
+ writer.writeInt(3)
+ writer.writeString(dn)
+ writer.writeByte(0x80)
+ writer.writeByte(0x00)
+ writer.endSequence()
+ writer.endSequence()
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.length, 35)
+ assert.equal(buffer[0], 0x30)
+ assert.equal(buffer[1], 33)
+ assert.equal(buffer[2], 0x02)
+ assert.equal(buffer[3], 1)
+ assert.equal(buffer[4], 0x03)
+ assert.equal(buffer[5], 0x60)
+ assert.equal(buffer[6], 28)
+ assert.equal(buffer[7], 0x02)
+ assert.equal(buffer[8], 1)
+ assert.equal(buffer[9], 0x03)
+ assert.equal(buffer[10], 0x04)
+ assert.equal(buffer[11], dn.length)
+ assert.equal(buffer.subarray(12, 33).toString("utf8"), dn)
+ assert.equal(buffer[33], 0x80)
+ assert.equal(buffer[34], 0x00)
+ })
+ })
+
+ describe("writeOID()", function() {
+ it("can write a value", function() {
+ const writer = new Writer()
+ writer.writeOID("1.2.840.113549.1.1.1")
+
+ const buffer = writer.buffer
+
+ assert.equal(buffer.toString("hex"), "06092a864886f70d010101")
+ })
+ })
+})
diff --git a/src/ber/writer.ts b/src/ber/writer.ts
new file mode 100644
index 0000000..e354505
--- /dev/null
+++ b/src/ber/writer.ts
@@ -0,0 +1,237 @@
+import { E_ASN1_TYPES } from '../common/asn1.types';
+import { InvalidAsn1Error } from '../common/errors';
+import { encodeOctet, mergeObject } from '../common/util';
+import { isBoolean, isBuffer, isNumber, isOID, isString } from '../common/validation';
+
+export type NullableNumber = number|null;
+export type NullableString = string|null;
+
+export type WriterOptions = {
+ size?: number;
+ growthFactor?: number;
+};
+
+const DEFAULT_OPTIONS: WriterOptions = {
+ size: 1024,
+ growthFactor: 8,
+};
+
+export default class Writer {
+ private _buffer: Buffer;
+ private size: number;
+ private offset: number;
+ private sequence: number[];
+ private opts: WriterOptions;
+ constructor(options: WriterOptions = DEFAULT_OPTIONS) {
+ this.opts = mergeObject(DEFAULT_OPTIONS, options || {});
+ this._buffer = Buffer.alloc(this.opts.size! || DEFAULT_OPTIONS.size!);
+ this.size = this._buffer.length;
+ this.offset = 0;
+ this.sequence = [];
+ }
+
+ get buffer():Buffer {
+ if (this.sequence.length) {
+ throw new InvalidAsn1Error(`${this.sequence.length} unedned sequence(s)`);
+ }
+ return this._buffer.subarray(0, this.offset);
+ }
+
+ private ensure = (length: number): void => {
+ if (this.size - this.offset >= length) return;
+ let size = this.size * this.opts.growthFactor!;
+ if (size - this.offset < length) size += length;
+ const buffer = Buffer.alloc(size);
+ this._buffer.copy(buffer, 0, 0, this.offset);
+ this._buffer = buffer;
+ this.size = size;
+ }
+
+ private shift = (start: number, length: number, shift: number): void => {
+ if (!isNumber(start) || !isNumber(length) || !isNumber(shift)) {
+ throw new InvalidAsn1Error(`Invalid shift parameters.`);
+ }
+ this._buffer.copy(this._buffer, start + shift, start, start + length);
+ this.offset += shift;
+ }
+
+ public writeByte = (byte?: number): void => {
+ if (!isNumber(byte)) {
+ throw new InvalidAsn1Error('Argument must be a number.');
+ }
+ this.ensure(1);
+ this._buffer[this.offset++] = byte!;
+ }
+
+ public writeInt = (val?: number, tag?: number): void => {
+ if (!Number.isInteger(val)) {
+ throw new TypeError('Argument must be a valid integer');
+ }
+ const tagVal = isNumber(tag) ? tag : E_ASN1_TYPES.INTEGER;
+ let bytes = [];
+ let i = val!;
+ while (i < -0x80 || i >= 0x80) {
+ bytes.push(i & 0xff);
+ i = Math.floor(i / 0x100);
+ }
+ bytes.push(i & 0xff);
+
+ this.ensure(2 + bytes.length);
+ this._buffer[this.offset++] = tagVal!;
+ this._buffer[this.offset++] = bytes.length;
+
+ for (let i = bytes.length - 1; i >= 0; i--) {
+ this._buffer[this.offset++] = bytes[i];
+ }
+ }
+
+ public writeNull = (): void => {
+ this.writeByte(E_ASN1_TYPES.NULL);
+ this.writeByte(0x00);
+ }
+
+ public writeEnumeration = (e: number, tag: number): void => {
+ if (!isNumber(e)) {
+ throw new TypeError('Argument must be a number');
+ }
+ const tagVal = isNumber(tag) ? tag: E_ASN1_TYPES.ENUMERATION;
+ return this.writeInt(e, tagVal);
+ }
+
+ public writeBoolean = (b?: boolean, tag?: number): void => {
+ if (!isBoolean(b)) {
+ throw new TypeError('Argument must be boolean');
+ }
+ const tagVal = isNumber(tag) ? tag : E_ASN1_TYPES.BOOLEAN;
+ this.ensure(3);
+ this._buffer[this.offset++] = tagVal!;
+ this._buffer[this.offset++] = 0x01;
+ this._buffer[this.offset++] = b ? 0xff : 0x00;
+ }
+
+ public writeString = (s?: string, tag?: number): void => {
+ if (!isString(s)) {
+ throw new TypeError('Argument must be a valid string.');
+ }
+ const tagVal = isNumber(tag) ? tag : E_ASN1_TYPES.OCTET_STRING;
+ const length = Buffer.byteLength(s!);
+ this.writeByte(tagVal);
+ this.writeLength(length);
+ if (length) {
+ this.ensure(length);
+ this._buffer.write(s!, this.offset);
+ this.offset += length;
+ }
+ }
+
+ public writeBuffer = (buffer: Buffer, tag?: number): void => {
+ if (!isBuffer(buffer)) {
+ throw new TypeError('Argument must be valid buffer');
+ }
+ if (isNumber(tag)) {
+ this.writeByte(tag);
+ this.writeLength(buffer.length);
+ }
+
+ if (buffer.length > 0) {
+ this.ensure(buffer.length);
+ buffer.copy(this._buffer, this.offset, 0, buffer.length);
+ this.offset += buffer.length;
+ }
+ }
+
+ public writeStringArray = (string: string[], tag?: number): void => {
+ if (!(string instanceof Array)) {
+ throw new TypeError('Argument must be an Array[String]');
+ }
+ string.forEach((s: string) => this.writeString(s, tag));
+ }
+
+ public writeOID = (oid: string, tag?: number): void => {
+ if (!isString(oid)) {
+ throw new TypeError(`Argument must be of type string`);
+ }
+
+ const tagVal = isNumber(tag) ? tag : E_ASN1_TYPES.OID;
+
+ if (!isOID(oid)) {
+ throw new Error('Argument is not a valid OID string');
+ }
+
+ const oidNodes: string[] = oid.split('.');
+
+ const bytes: number[] = [
+ (Number.parseInt(oidNodes[0], 10) * 40 + Number.parseInt(oidNodes[1], 10)),
+ ];
+
+ oidNodes.slice(2).forEach((b: string) => encodeOctet(bytes, Number.parseInt(b, 10)));
+
+ this.ensure(2 + bytes.length);
+
+ this.writeByte(tagVal);
+
+ this.writeLength(bytes.length);
+
+ bytes.forEach((v: number) => this.writeByte(v));
+ }
+
+ public writeLength = (length: number): void => {
+ if (!isNumber(length)) {
+ throw new TypeError(`Length must be a number`);
+ }
+ this.ensure(4);
+ if (length <= 0x7f) {
+ this._buffer[this.offset++] = length;
+ } else if (length <= 0xff) {
+ this._buffer[this.offset++] = 0x81;
+ this._buffer[this.offset++] = length;
+ } else if (length <= 0xffff) {
+ this._buffer[this.offset++] = 0x82;
+ this._buffer[this.offset++] = length >> 8;
+ this._buffer[this.offset++] = length;
+ } else if (length <= 0xffffff) {
+ this._buffer[this.offset++] = 0x83;
+ this._buffer[this.offset++] = length >> 16;
+ this._buffer[this.offset++] = length >> 8;
+ this._buffer[this.offset++] = length;
+ } else {
+ throw new InvalidAsn1Error('Length too long (> 4 bytes)');
+ }
+ }
+
+ public startSequence = (tag?: number): void => {
+ const tagVal = isNumber(tag) ? tag : E_ASN1_TYPES.SEQUENCE | E_ASN1_TYPES.CONSTRUCTOR;
+ this.writeByte(tagVal);
+ this.sequence.push(this.offset);
+ this.ensure(3);
+ this.offset += 3;
+ }
+
+ public endSequence = (): void => {
+ const seq = this.sequence.pop();
+ const start = seq! + 3;
+ const length = this.offset - start;
+
+ if (length <= 0x7f) {
+ this.shift(start, length, -2);
+ this._buffer[seq!] = length;
+ } else if (length <= 0xff) {
+ this.shift(start, length, -1);
+ this._buffer[seq!] = 0x81;
+ this._buffer[seq! + 1] = length;
+ } else if (length <= 0xffff) {
+ this._buffer[seq!] = 0x82;
+ this._buffer[seq! + 1] = length >> 8;
+ this._buffer[seq! + 2] = length;
+ } else if (length <= 0xffffff) {
+ this.shift(start, length, 1);
+ this._buffer[seq!] = 0x83;
+ this._buffer[seq! + 1] = length >> 16;
+ this._buffer[seq! + 2] = length >> 8;
+ this._buffer[seq! + 3] = length;
+ } else {
+ throw new InvalidAsn1Error('Sequence too long');
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/common/asn1.types.ts b/src/common/asn1.types.ts
new file mode 100644
index 0000000..4c87d28
--- /dev/null
+++ b/src/common/asn1.types.ts
@@ -0,0 +1,42 @@
+/**
+ * -- A List of ASN1 Data Types --
+ * Any value with out-of-order OR new order is marked with a note comment
+ * @WARN: Before chaning or re-ordering, keep the values in mind
+ */
+export enum E_ASN1_TYPES {
+ EOC = 0,
+ BOOLEAN,
+ INTEGER,
+ BIT_STRING,
+ OCTET_STRING,
+ NULL,
+ OID,
+ OBJECT_DESCRIPTOR,
+ EXTERNAL,
+ REAL,
+ ENUMERATION,
+ PDV,
+ UTF8_STRING,
+ RELATIVE_OID,
+ // @NOTE: Order change
+ SEQUENCE = 16,
+ SET,
+ NUMERIC_STRING,
+ PRINTABLE_STRING,
+ T61_STRING,
+ VIDEO_TEXT_STRING,
+ IA5_STRING,
+ UTC_TIME,
+ GENERALIZED_TIME,
+ GRAPHIC_STRING,
+ VISIBLE_STRING,
+ // @NOTE: Order change
+ GENERAL_STRING = 28,
+ UNIVERSAL_STRING,
+ CHARATER_STRING,
+ BMP_STRING,
+ CONSTRUCTOR,
+ // @NOTE: order change
+ CONTEXT = 128,
+
+}
\ No newline at end of file
diff --git a/src/common/errors.ts b/src/common/errors.ts
new file mode 100644
index 0000000..5c56837
--- /dev/null
+++ b/src/common/errors.ts
@@ -0,0 +1,6 @@
+export class InvalidAsn1Error extends Error {
+ public readonly name: string = InvalidAsn1Error.name;
+ constructor(message: string) {
+ super(message)
+ }
+}
\ No newline at end of file
diff --git a/src/common/util.ts b/src/common/util.ts
new file mode 100644
index 0000000..f494c7f
--- /dev/null
+++ b/src/common/util.ts
@@ -0,0 +1,40 @@
+/**
+ * Merges properties from one object to another
+ * @WARN: will modify the `to` parameter in-place
+ * @param {T} from - source object to inherit from
+ * @param {T} to - destination object to embed properties into
+ * @returns {T} - modified `to` parameter
+ */
+export const mergeObject = (from: T, to: T): T => {
+ const keys = Object.getOwnPropertyNames(from);
+ keys.forEach((key: string) => {
+ if (Object.prototype.hasOwnProperty.call(to, key)) return;
+ const val = Object.getOwnPropertyDescriptor(from, key);
+ Object.defineProperty(to, key, val!);
+ });
+ return to;
+}
+
+export const encodeOctet = (bytes: number[], octet: number): void => {
+ 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);
+ } 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);
+ }
+}
\ No newline at end of file
diff --git a/src/common/validation.ts b/src/common/validation.ts
new file mode 100644
index 0000000..842910d
--- /dev/null
+++ b/src/common/validation.ts
@@ -0,0 +1,23 @@
+export const isNil = (data: T): boolean => {
+ return data === null || data === undefined;
+};
+
+export const isBuffer = (data: T): boolean => {
+ return !isNil(data) && Buffer.isBuffer(data);
+};
+
+export const isNumber = (data: T): boolean => {
+ return typeof data === 'number' && Number.isFinite(data);
+};
+
+export const isBoolean = (data: T): boolean => {
+ return typeof data === 'boolean';
+}
+
+export const isString = (data: T): boolean => {
+ return typeof data === 'string';
+}
+
+export const isOID = (data: string): boolean => {
+ return /^([0-9]+\.){0,}[0-9]+$/.test(data);
+}
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..37823ab
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,15 @@
+import Reader from "./ber/reader";
+import Writer from "./ber/writer";
+import { InvalidAsn1Error } from "./common/errors";
+import { E_ASN1_TYPES } from "./common/asn1.types";
+
+export const Ber = {
+ InvalidAsn1Error,
+ E_ASN1_TYPES,
+ BerReader: Reader,
+ BerWriter: Writer,
+};
+
+export default Ber;
+export const BerReader = Reader;
+export const BerWriter = Writer;
diff --git a/test/unittests_lib-ber-reader.js b/test/unittests_lib-ber-reader.js
index 93aa115..7a6221c 100644
--- a/test/unittests_lib-ber-reader.js
+++ b/test/unittests_lib-ber-reader.js
@@ -1,6 +1,6 @@
-var asn1 = require("../")
-var assert = require("assert")
+var asn1 = require("../lib")
+var assert = require("chai").assert;
var BerReader = asn1.BerReader
diff --git a/test/unittests_lib-ber-writer.js b/test/unittests_lib-ber-writer.js
index 2b09c09..faef064 100644
--- a/test/unittests_lib-ber-writer.js
+++ b/test/unittests_lib-ber-writer.js
@@ -1,6 +1,6 @@
-var asn1 = require("../")
-var assert = require("assert")
+var asn1 = require("../lib")
+var assert = require("chai").assert;
var BerWriter = asn1.BerWriter
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..d4b2dec
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,30 @@
+{
+ "compilerOptions": {
+ "target": "es6",
+ "lib": ["es2015"],
+ "module": "commonjs",
+ "rootDir": "./src",
+ "declaration": true,
+
+ "outDir": "./lib/", /* Specify an output folder for all emitted files. */
+ "removeComments": true,
+ "newLine": "lf",
+ "stripInternal": true,
+ "noEmitOnError": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "strictFunctionTypes": true,
+ "alwaysStrict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "allowUnreachableCode": true,
+ "skipLibCheck": true,
+ },
+ "include": ["./src/**/*.ts"],
+ "exclude": ["./src/**/*.spec.ts"]
+}
From a6788154d0c73434a05ea6d4d23fc30648b43fd4 Mon Sep 17 00:00:00 2001
From: Ashraful Islam <4134005+ashraful-islam@users.noreply.github.com>
Date: Mon, 29 May 2023 20:10:22 +0200
Subject: [PATCH 2/4] chore: add ASN1 types to avoid breakage in other packages
---
src/common/asn1.types.ts | 36 ++++++++++++++++++++++++++++++++++++
src/index.ts | 4 ++--
2 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/src/common/asn1.types.ts b/src/common/asn1.types.ts
index 4c87d28..b943ea3 100644
--- a/src/common/asn1.types.ts
+++ b/src/common/asn1.types.ts
@@ -39,4 +39,40 @@ export enum E_ASN1_TYPES {
// @NOTE: order change
CONTEXT = 128,
+}
+
+export type BER_ASN1_TYPES = {[key: string]: E_ASN1_TYPES };
+
+export const BER_ASN1_TYPES: BER_ASN1_TYPES = {
+ EOC: 0,
+ Boolean: 1,
+ Integer: 2,
+ BitString: 3,
+ OctetString: 4,
+ Null: 5,
+ OID: 6,
+ ObjectDescriptor: 7,
+ External: 8,
+ Real: 9,
+ 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,
}
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index 37823ab..322f32e 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,11 +1,11 @@
import Reader from "./ber/reader";
import Writer from "./ber/writer";
import { InvalidAsn1Error } from "./common/errors";
-import { E_ASN1_TYPES } from "./common/asn1.types";
+import { BER_ASN1_TYPES } from "./common/asn1.types";
export const Ber = {
+ ...BER_ASN1_TYPES,
InvalidAsn1Error,
- E_ASN1_TYPES,
BerReader: Reader,
BerWriter: Writer,
};
From 25dd5553356ce6df1336bf70b873145f488905c9 Mon Sep 17 00:00:00 2001
From: Ashraful Islam <4134005+ashraful-islam@users.noreply.github.com>
Date: Mon, 29 May 2023 20:22:16 +0200
Subject: [PATCH 3/4] chore: add asn1 types to export and rebuild
---
lib/common/asn1.types.d.ts | 4 ++++
lib/common/asn1.types.js | 35 ++++++++++++++++++++++++++++++++++-
lib/index.d.ts | 14 +-------------
lib/index.js | 11 +----------
src/index.ts | 8 ++------
5 files changed, 42 insertions(+), 30 deletions(-)
diff --git a/lib/common/asn1.types.d.ts b/lib/common/asn1.types.d.ts
index af658fb..6e44e58 100644
--- a/lib/common/asn1.types.d.ts
+++ b/lib/common/asn1.types.d.ts
@@ -31,3 +31,7 @@ export declare enum E_ASN1_TYPES {
CONSTRUCTOR = 32,
CONTEXT = 128
}
+export type BER_ASN1_TYPES = {
+ [key: string]: E_ASN1_TYPES;
+};
+export declare const BER_ASN1_TYPES: BER_ASN1_TYPES;
diff --git a/lib/common/asn1.types.js b/lib/common/asn1.types.js
index a859513..643713f 100644
--- a/lib/common/asn1.types.js
+++ b/lib/common/asn1.types.js
@@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-exports.E_ASN1_TYPES = void 0;
+exports.BER_ASN1_TYPES = exports.E_ASN1_TYPES = void 0;
var E_ASN1_TYPES;
(function (E_ASN1_TYPES) {
E_ASN1_TYPES[E_ASN1_TYPES["EOC"] = 0] = "EOC";
@@ -35,3 +35,36 @@ var E_ASN1_TYPES;
E_ASN1_TYPES[E_ASN1_TYPES["CONSTRUCTOR"] = 32] = "CONSTRUCTOR";
E_ASN1_TYPES[E_ASN1_TYPES["CONTEXT"] = 128] = "CONTEXT";
})(E_ASN1_TYPES = exports.E_ASN1_TYPES || (exports.E_ASN1_TYPES = {}));
+exports.BER_ASN1_TYPES = {
+ EOC: 0,
+ Boolean: 1,
+ Integer: 2,
+ BitString: 3,
+ OctetString: 4,
+ Null: 5,
+ OID: 6,
+ ObjectDescriptor: 7,
+ External: 8,
+ Real: 9,
+ 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,
+};
diff --git a/lib/index.d.ts b/lib/index.d.ts
index 0a4a82c..cb0ff5c 100644
--- a/lib/index.d.ts
+++ b/lib/index.d.ts
@@ -1,13 +1 @@
-import Reader from "./ber/reader";
-import Writer from "./ber/writer";
-import { InvalidAsn1Error } from "./common/errors";
-import { E_ASN1_TYPES } from "./common/asn1.types";
-export declare const Ber: {
- InvalidAsn1Error: typeof InvalidAsn1Error;
- E_ASN1_TYPES: typeof E_ASN1_TYPES;
- BerReader: typeof Reader;
- BerWriter: typeof Writer;
-};
-export default Ber;
-export declare const BerReader: typeof Reader;
-export declare const BerWriter: typeof Writer;
+export {};
diff --git a/lib/index.js b/lib/index.js
index 47e9856..8f1053c 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -3,17 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
-exports.BerWriter = exports.BerReader = exports.Ber = void 0;
const reader_1 = __importDefault(require("./ber/reader"));
const writer_1 = __importDefault(require("./ber/writer"));
const errors_1 = require("./common/errors");
const asn1_types_1 = require("./common/asn1.types");
-exports.Ber = {
- InvalidAsn1Error: errors_1.InvalidAsn1Error,
- E_ASN1_TYPES: asn1_types_1.E_ASN1_TYPES,
- BerReader: reader_1.default,
- BerWriter: writer_1.default,
-};
-exports.default = exports.Ber;
-exports.BerReader = reader_1.default;
-exports.BerWriter = writer_1.default;
+module.exports = Object.assign(Object.assign({}, asn1_types_1.BER_ASN1_TYPES), { BerReader: reader_1.default, BerWriter: writer_1.default, InvalidAsn1Error: errors_1.InvalidAsn1Error });
diff --git a/src/index.ts b/src/index.ts
index 322f32e..116f897 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -3,13 +3,9 @@ import Writer from "./ber/writer";
import { InvalidAsn1Error } from "./common/errors";
import { BER_ASN1_TYPES } from "./common/asn1.types";
-export const Ber = {
+module.exports = {
...BER_ASN1_TYPES,
- InvalidAsn1Error,
BerReader: Reader,
BerWriter: Writer,
+ InvalidAsn1Error,
};
-
-export default Ber;
-export const BerReader = Reader;
-export const BerWriter = Writer;
From 7c034effcdda761ac925ef7163324b061832eed8 Mon Sep 17 00:00:00 2001
From: Ashraful Islam <4134005+ashraful-islam@users.noreply.github.com>
Date: Mon, 29 May 2023 20:25:32 +0200
Subject: [PATCH 4/4] docs: update README.md with change logs
---
README.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/README.md b/README.md
index 2bd8984..d05da5e 100644
--- a/README.md
+++ b/README.md
@@ -580,6 +580,12 @@ instance:
* Improve writeInt() function
+## Version 1.2.3 - 29/05/2023
+
+ * Refactor into Typescript
+ * Add chai for assert functionality
+ * Add typescript types
+
# License
Copyright (c) 2020 Mark Abrahams