From 83f4e93ecfbd6423d5c116872ab0423576317d9a Mon Sep 17 00:00:00 2001 From: James Sumners Date: Sun, 27 Mar 2022 11:35:21 -0400 Subject: [PATCH] Org version (#4) * Convert to org standards * Remove safer-buffer dependency * Configure pnpm * Move and clean up writer tests * Move and clean up reader tests * Add more test scripts * Update package.json * Fix test glob pattern * Project details cleanup * Update CI config * Use reusable workflow * Bump to initial version --- .eslintrc | 18 +- .github/workflows/main.yml | 42 +---- .gitignore | 1 + .npmrc | 5 + .taprc.yml | 4 + CONTRIBUTING.md | 8 +- LICENSE | 3 + lib/ber/errors.js | 11 +- lib/ber/index.js | 23 +-- lib/ber/reader.js | 235 +++++++++++------------ lib/ber/reader.test.js | 182 ++++++++++++++++++ lib/ber/types.js | 3 +- lib/ber/writer.js | 376 ++++++++++++++++--------------------- lib/ber/writer.test.js | 336 +++++++++++++++++++++++++++++++++ lib/index.js | 6 +- package.json | 33 ++-- test/ber/reader.test.js | 209 --------------------- test/ber/writer.test.js | 369 ------------------------------------ test/run.js | 16 -- 19 files changed, 843 insertions(+), 1037 deletions(-) create mode 100644 .npmrc create mode 100644 .taprc.yml create mode 100644 lib/ber/reader.test.js create mode 100644 lib/ber/writer.test.js delete mode 100644 test/ber/reader.test.js delete mode 100644 test/ber/writer.test.js delete mode 100644 test/run.js diff --git a/.eslintrc b/.eslintrc index ccc6f09..2267ece 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,19 +1,5 @@ { - "plugins": [ "joyent" ], "extends": [ - "eslint:recommended", - "plugin:joyent/style", - "plugin:joyent/lint" - ], - "parserOptions": { - "ecmaVersion": 5, - "sourceType": "script", - "ecmaFeatures": { - } - }, - "env": { - "node": true - }, - "rules": { - } + "standard" + ] } diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ca8a7b6..60030ee 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,43 +6,5 @@ on: - master jobs: - - Test: - if: "!contains(github.event.head_commit.message, '[skip ci]')" - strategy: - matrix: - node_version: ['lts/*', node] - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v2 - - name: Install Node - uses: dcodeIO/setup-node-nvm@master - with: - node-version: ${{ matrix.node_version }} - - name: Install Dependencies - run: npm install - - name: Run Tests 👩🏽‍💻 - run: npm run test - - Lint: - if: "!contains(github.event.head_commit.message, '[skip ci]')" - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v2 - - name: Install Node - uses: dcodeIO/setup-node-nvm@master - with: - node-version: 'lts/*' - - name: Install Dependencies - run: npm install - - name: Lint ✨ - run: npm run lint - - Skip: - if: contains(github.event.head_commit.message, '[skip ci]') - runs-on: ubuntu-latest - steps: - - name: Skip CI 🚫 - run: echo skip ci + call-core-ci: + uses: ldapjs/.github/.github/workflows/node-ci.yml@main diff --git a/.gitignore b/.gitignore index e3236a8..22cd8a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/build /coverage /node_modules *.log diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..e881611 --- /dev/null +++ b/.npmrc @@ -0,0 +1,5 @@ +# npm general settings + +# pnpm specific settings +hoist=false +public-hoist-pattern[]=*eslint* diff --git a/.taprc.yml b/.taprc.yml new file mode 100644 index 0000000..6054aa4 --- /dev/null +++ b/.taprc.yml @@ -0,0 +1,4 @@ +check-coverage: false + +files: + - 'lib/**/*.test.js' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c13e1a8..79e8ce2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,12 +2,8 @@ This repository uses GitHub pull requests for code review. -See the [Joyent Engineering -Guidelines](https://github.com/joyent/eng/blob/master/docs/index.md) for general -best practices expected in this repository. - -Contributions should be "make prepush" clean. The "prepush" target runs the -"check" target, which will check for linting and style errors. +See [this primer](https://jrfom.com/posts/2017/03/08/a-primer-on-contributing-to-projects-with-git/) +for instructions on how to make contributions to the project. If you're changing something non-trivial or user-facing, you may want to submit an issue first. diff --git a/LICENSE b/LICENSE index 9b5dcdb..1913201 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,7 @@ +The MIT License (MIT) + Copyright (c) 2011 Mark Cavage, All rights reserved. +Copyright (c) 2022 The LDAPJS Collaborators. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/ber/errors.js b/lib/ber/errors.js index 4557b8a..f08f259 100644 --- a/lib/ber/errors.js +++ b/lib/ber/errors.js @@ -1,13 +1,12 @@ // Copyright 2011 Mark Cavage All rights reserved. - module.exports = { newInvalidAsn1Error: function (msg) { - var e = new Error(); - e.name = 'InvalidAsn1Error'; - e.message = msg || ''; - return e; + const e = new Error() + e.name = 'InvalidAsn1Error' + e.message = msg || '' + return e } -}; +} diff --git a/lib/ber/index.js b/lib/ber/index.js index cb67d88..f9c6f31 100644 --- a/lib/ber/index.js +++ b/lib/ber/index.js @@ -1,11 +1,10 @@ // Copyright 2011 Mark Cavage All rights reserved. -var errors = require('./errors'); -var types = require('./types'); - -var Reader = require('./reader'); -var Writer = require('./writer'); +const errors = require('./errors') +const types = require('./types') +const Reader = require('./reader') +const Writer = require('./writer') // --- Exports @@ -15,15 +14,11 @@ module.exports = { Writer: Writer -}; +} -for (var t in types) { - if (Object.prototype.hasOwnProperty.call(types, t)) { - module.exports[t] = types[t]; - } +for (const t in types) { + if (Object.prototype.hasOwnProperty.call(types, t)) { module.exports[t] = types[t] } } -for (var e in errors) { - if (Object.prototype.hasOwnProperty.call(errors, e)) { - module.exports[e] = errors[e]; - } +for (const e in errors) { + if (Object.prototype.hasOwnProperty.call(errors, e)) { module.exports[e] = errors[e] } } diff --git a/lib/ber/reader.js b/lib/ber/reader.js index fc46987..7dd4aaa 100644 --- a/lib/ber/reader.js +++ b/lib/ber/reader.js @@ -1,51 +1,43 @@ // Copyright 2011 Mark Cavage All rights reserved. -var assert = require('assert'); -var Buffer = require('safer-buffer').Buffer; - -var ASN1 = require('./types'); -var errors = require('./errors'); - +const assert = require('assert') +const ASN1 = require('./types') +const errors = require('./errors') // --- Globals -var newInvalidAsn1Error = errors.newInvalidAsn1Error; - - +const newInvalidAsn1Error = errors.newInvalidAsn1Error // --- API -function Reader(data) { - if (!data || !Buffer.isBuffer(data)) { - throw new TypeError('data must be a node Buffer'); - } +function Reader (data) { + if (!data || !Buffer.isBuffer(data)) { throw new TypeError('data must be a node Buffer') } - this._buf = data; - this._size = data.length; + this._buf = data + this._size = data.length // These hold the "current" state - this._len = 0; - this._offset = 0; + this._len = 0 + this._offset = 0 } Object.defineProperty(Reader.prototype, 'length', { enumerable: true, - get: function () { return (this._len); } -}); + get: function () { return (this._len) } +}) Object.defineProperty(Reader.prototype, 'offset', { enumerable: true, - get: function () { return (this._offset); } -}); + get: function () { return (this._offset) } +}) Object.defineProperty(Reader.prototype, 'remain', { - get: function () { return (this._size - this._offset); } -}); + get: function () { return (this._size - this._offset) } +}) Object.defineProperty(Reader.prototype, 'buffer', { - get: function () { return (this._buf.slice(this._offset)); } -}); - + get: function () { return (this._buf.slice(this._offset)) } +}) /** * Reads a single byte and advances offset; you can pass in `true` to make this @@ -55,20 +47,18 @@ Object.defineProperty(Reader.prototype, 'buffer', { * @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 (this._size - this._offset < 1) { return null } - if (!peek) { this._offset += 1; } + const b = this._buf[this._offset] & 0xff - return b; -}; + if (!peek) { this._offset += 1 } + return b +} Reader.prototype.peek = function () { - return this.readByte(true); -}; - + return this.readByte(true) +} /** * Reads a (potentially) variable length off the BER buffer. This call is @@ -82,36 +72,31 @@ Reader.prototype.peek = function () { * @throws {InvalidAsn1Error} on bad ASN.1 */ Reader.prototype.readLength = function (offset) { - if (offset === undefined) { offset = this._offset; } + if (offset === undefined) { offset = this._offset } - if (offset >= this._size) { return null; } + if (offset >= this._size) { return null } - var lenB = this._buf[offset++] & 0xff; - if (lenB === null) { return null; } + let lenB = this._buf[offset++] & 0xff + if (lenB === null) { return null } if ((lenB & 0x80) === 0x80) { - lenB &= 0x7f; + lenB &= 0x7f - if (lenB === 0) { - throw newInvalidAsn1Error('Indefinite length not supported'); - } + if (lenB === 0) { throw newInvalidAsn1Error('Indefinite length not supported') } - if (lenB > 4) { throw newInvalidAsn1Error('encoding too long'); } + if (lenB > 4) { throw newInvalidAsn1Error('encoding too long') } - if (this._size - offset < lenB) { return null; } + if (this._size - offset < lenB) { return null } - this._len = 0; - for (var i = 0; i < lenB; i++) { - this._len = (this._len << 8) + (this._buf[offset++] & 0xff); - } + this._len = 0 + for (let i = 0; i < lenB; i++) { this._len = (this._len << 8) + (this._buf[offset++] & 0xff) } } else { // Wasn't a variable length - this._len = lenB; + this._len = lenB } - return offset; -}; - + return offset +} /** * Parses the next sequence in this BER buffer. @@ -121,128 +106,120 @@ Reader.prototype.readLength = function (offset) { * @return {Number} the sequence's tag. */ Reader.prototype.readSequence = function (tag) { - var seq = this.peek(); - if (seq === null) { return null; } + const seq = this.peek() + if (seq === null) { return null } if (tag !== undefined && tag !== seq) { - throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + - ': got 0x' + seq.toString(16)); -} - - var o = this.readLength(this._offset + 1); // stored in `length` - if (o === null) { return null; } + throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + + ': got 0x' + seq.toString(16)) + } - this._offset = o; - return seq; -}; + const o = this.readLength(this._offset + 1) // stored in `length` + if (o === null) { return null } + this._offset = o + return seq +} Reader.prototype.readInt = function () { - return this._readTag(ASN1.Integer); -}; - + return this._readTag(ASN1.Integer) +} Reader.prototype.readBoolean = function () { - return (this._readTag(ASN1.Boolean) !== 0); -}; - + return (this._readTag(ASN1.Boolean) !== 0) +} Reader.prototype.readEnumeration = function () { - return this._readTag(ASN1.Enumeration); -}; - + return this._readTag(ASN1.Enumeration) +} Reader.prototype.readString = function (tag, retbuf) { - if (!tag) { tag = ASN1.OctetString; } + if (!tag) { tag = ASN1.OctetString } - var b = this.peek(); - if (b === null) { return null; } + const b = this.peek() + if (b === null) { return null } if (b !== tag) { - throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + - ': got 0x' + b.toString(16)); -} + throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + + ': got 0x' + b.toString(16)) + } - var o = this.readLength(this._offset + 1); // stored in `length` + const o = this.readLength(this._offset + 1) // stored in `length` - if (o === null) { return null; } + if (o === null) { return null } - if (this.length > this._size - o) { return null; } + if (this.length > this._size - o) { return null } - this._offset = o; + this._offset = o - if (this.length === 0) { return retbuf ? Buffer.alloc(0) : ''; } + if (this.length === 0) { return retbuf ? Buffer.alloc(0) : '' } - var str = this._buf.slice(this._offset, this._offset + this.length); - this._offset += this.length; + const str = this._buf.slice(this._offset, this._offset + this.length) + this._offset += this.length - return retbuf ? str : str.toString('utf8'); -}; + return retbuf ? str : str.toString('utf8') +} Reader.prototype.readOID = function (tag) { - if (!tag) { tag = ASN1.OID; } + if (!tag) { tag = ASN1.OID } - var b = this.readString(tag, true); - if (b === null) { return null; } + const b = this.readString(tag, true) + if (b === null) { return null } - var values = []; - var value = 0; + const values = [] + let value = 0 - for (var i = 0; i < b.length; i++) { - var byte = b[i] & 0xff; + for (let i = 0; i < b.length; i++) { + const byte = b[i] & 0xff - value <<= 7; - value += byte & 0x7f; + value <<= 7 + value += byte & 0x7f if ((byte & 0x80) === 0) { - values.push(value); - value = 0; + values.push(value) + value = 0 } } - value = values.shift(); - values.unshift(value % 40); - values.unshift((value / 40) >> 0); - - return values.join('.'); -}; + value = values.shift() + values.unshift(value % 40) + values.unshift((value / 40) >> 0) + return values.join('.') +} Reader.prototype._readTag = function (tag) { - assert.ok(tag !== undefined); + assert.ok(tag !== undefined) - var b = this.peek(); + const b = this.peek() - if (b === null) { return null; } + if (b === null) { return null } if (b !== tag) { - throw newInvalidAsn1Error('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 > 4) { - throw newInvalidAsn1Error('Integer too long: ' + this.length); + throw newInvalidAsn1Error('Expected 0x' + tag.toString(16) + + ': got 0x' + b.toString(16)) } - if (this.length > this._size - o) { return null; } - this._offset = o; + const o = this.readLength(this._offset + 1) // stored in `length` + if (o === null) { return null } - var fb = this._buf[this._offset]; - var value = 0; + if (this.length > 4) { throw newInvalidAsn1Error('Integer too long: ' + this.length) } - for (var i = 0; i < this.length; i++) { - value <<= 8; - value |= (this._buf[this._offset++] & 0xff); - } + if (this.length > this._size - o) { return null } + this._offset = o - if ((fb & 0x80) === 0x80 && i !== 4) { value -= (1 << (i * 8)); } + const fb = this._buf[this._offset] + let value = 0 - return value >> 0; -}; + let i + for (i = 0; i < this.length; i++) { + value <<= 8 + value |= (this._buf[this._offset++] & 0xff) + } + if ((fb & 0x80) === 0x80 && i !== 4) { value -= (1 << (i * 8)) } + return value >> 0 +} // --- Exported API -module.exports = Reader; +module.exports = Reader diff --git a/lib/ber/reader.test.js b/lib/ber/reader.test.js new file mode 100644 index 0000000..1d94775 --- /dev/null +++ b/lib/ber/reader.test.js @@ -0,0 +1,182 @@ +// Copyright 2011 Mark Cavage All rights reserved. + +const { test } = require('tap') +const BerReader = require('./reader') + +test('load library', function (t) { + t.ok(BerReader) + try { + const reader = new BerReader() + t.equal(reader, null, 'reader') + t.fail('Should have thrown') + } catch (e) { + t.ok(e instanceof TypeError, 'Should have been a type error') + } + t.end() +}) + +test('read byte', function (t) { + const reader = new BerReader(Buffer.from([0xde])) + t.ok(reader) + t.equal(reader.readByte(), 0xde, 'wrong value') + t.end() +}) + +test('read 1 byte int', function (t) { + const reader = new BerReader(Buffer.from([0x02, 0x01, 0x03])) + t.ok(reader) + t.equal(reader.readInt(), 0x03, 'wrong value') + t.equal(reader.length, 0x01, 'wrong length') + t.end() +}) + +test('read 2 byte int', function (t) { + const reader = new BerReader(Buffer.from([0x02, 0x02, 0x7e, 0xde])) + t.ok(reader) + t.equal(reader.readInt(), 0x7ede, 'wrong value') + t.equal(reader.length, 0x02, 'wrong length') + t.end() +}) + +test('read 3 byte int', function (t) { + const reader = new BerReader(Buffer.from([0x02, 0x03, 0x7e, 0xde, 0x03])) + t.ok(reader) + t.equal(reader.readInt(), 0x7ede03, 'wrong value') + t.equal(reader.length, 0x03, 'wrong length') + t.end() +}) + +test('read 4 byte int', function (t) { + const reader = new BerReader(Buffer.from([0x02, 0x04, 0x7e, 0xde, 0x03, 0x01])) + t.ok(reader) + t.equal(reader.readInt(), 0x7ede0301, 'wrong value') + t.equal(reader.length, 0x04, 'wrong length') + t.end() +}) + +test('read 1 byte negative int', function (t) { + const reader = new BerReader(Buffer.from([0x02, 0x01, 0xdc])) + t.ok(reader) + t.equal(reader.readInt(), -36, 'wrong value') + t.equal(reader.length, 0x01, 'wrong length') + t.end() +}) + +test('read 2 byte negative int', function (t) { + const reader = new BerReader(Buffer.from([0x02, 0x02, 0xc0, 0x4e])) + t.ok(reader) + t.equal(reader.readInt(), -16306, 'wrong value') + t.equal(reader.length, 0x02, 'wrong length') + t.end() +}) + +test('read 3 byte negative int', function (t) { + const reader = new BerReader(Buffer.from([0x02, 0x03, 0xff, 0x00, 0x19])) + t.ok(reader) + t.equal(reader.readInt(), -65511, 'wrong value') + t.equal(reader.length, 0x03, 'wrong length') + t.end() +}) + +test('read 4 byte negative int', function (t) { + const reader = new BerReader(Buffer.from([0x02, 0x04, 0x91, 0x7c, 0x22, 0x1f])) + t.ok(reader) + t.equal(reader.readInt(), -1854135777, 'wrong value') + t.equal(reader.length, 0x04, 'wrong length') + t.end() +}) + +test('read boolean true', function (t) { + const reader = new BerReader(Buffer.from([0x01, 0x01, 0xff])) + t.ok(reader) + t.equal(reader.readBoolean(), true, 'wrong value') + t.equal(reader.length, 0x01, 'wrong length') + t.end() +}) + +test('read boolean false', function (t) { + const reader = new BerReader(Buffer.from([0x01, 0x01, 0x00])) + t.ok(reader) + t.equal(reader.readBoolean(), false, 'wrong value') + t.equal(reader.length, 0x01, 'wrong length') + t.end() +}) + +test('read enumeration', function (t) { + const reader = new BerReader(Buffer.from([0x0a, 0x01, 0x20])) + t.ok(reader) + t.equal(reader.readEnumeration(), 0x20, 'wrong value') + t.equal(reader.length, 0x01, 'wrong length') + t.end() +}) + +test('read string', function (t) { + const dn = 'cn=foo,ou=unit,o=test' + const buf = Buffer.alloc(dn.length + 2) + buf[0] = 0x04 + buf[1] = Buffer.byteLength(dn) + buf.write(dn, 2) + const reader = new BerReader(buf) + t.ok(reader) + t.equal(reader.readString(), dn, 'wrong value') + t.equal(reader.length, dn.length, 'wrong length') + t.end() +}) + +test('read sequence', function (t) { + const reader = new BerReader(Buffer.from([0x30, 0x03, 0x01, 0x01, 0xff])) + t.ok(reader) + t.equal(reader.readSequence(), 0x30, 'wrong value') + t.equal(reader.length, 0x03, 'wrong length') + t.equal(reader.readBoolean(), true, 'wrong value') + t.equal(reader.length, 0x01, 'wrong length') + t.end() +}) + +test('anonymous LDAPv3 bind', function (t) { + const BIND = Buffer.alloc(14) + BIND[0] = 0x30 // Sequence + BIND[1] = 12 // len + BIND[2] = 0x02 // ASN.1 Integer + BIND[3] = 1 // len + BIND[4] = 0x04 // msgid (make up 4) + BIND[5] = 0x60 // Bind Request + BIND[6] = 7 // len + BIND[7] = 0x02 // ASN.1 Integer + BIND[8] = 1 // len + BIND[9] = 0x03 // v3 + BIND[10] = 0x04 // String (bind dn) + BIND[11] = 0 // len + BIND[12] = 0x80 // ContextSpecific (choice) + BIND[13] = 0 // simple bind + + // Start testing ^^ + const ber = new BerReader(BIND) + t.equal(ber.readSequence(), 48, 'Not an ASN.1 Sequence') + t.equal(ber.length, 12, 'Message length should be 12') + t.equal(ber.readInt(), 4, 'Message id should have been 4') + t.equal(ber.readSequence(), 96, 'Bind Request should have been 96') + t.equal(ber.length, 7, 'Bind length should have been 7') + t.equal(ber.readInt(), 3, 'LDAP version should have been 3') + t.equal(ber.readString(), '', 'Bind DN should have been empty') + t.equal(ber.length, 0, 'string length should have been 0') + t.equal(ber.readByte(), 0x80, 'Should have been ContextSpecific (choice)') + t.equal(ber.readByte(), 0, 'Should have been simple bind') + t.equal(null, ber.readByte(), 'Should be out of data') + t.end() +}) + +test('long string', function (t) { + const buf = Buffer.alloc(256) + const s = + '2;649;CN=Red Hat CS 71GA Demo,O=Red Hat CS 71GA Demo,C=US;' + + 'CN=RHCS Agent - admin01,UID=admin01,O=redhat,C=US [1] This is ' + + 'Teena Vradmin\'s description.' + buf[0] = 0x04 + buf[1] = 0x81 + buf[2] = 0x94 + buf.write(s, 3) + const ber = new BerReader(buf.slice(0, 3 + s.length)) + t.equal(ber.readString(), s) + t.end() +}) diff --git a/lib/ber/types.js b/lib/ber/types.js index 8aea000..d4d094e 100644 --- a/lib/ber/types.js +++ b/lib/ber/types.js @@ -1,6 +1,5 @@ // Copyright 2011 Mark Cavage All rights reserved. - module.exports = { EOC: 0, Boolean: 1, @@ -33,4 +32,4 @@ module.exports = { BMPString: 31, Constructor: 32, Context: 128 -}; +} diff --git a/lib/ber/writer.js b/lib/ber/writer.js index 4d4cf2c..ff15f13 100644 --- a/lib/ber/writer.js +++ b/lib/ber/writer.js @@ -1,322 +1,278 @@ // Copyright 2011 Mark Cavage All rights reserved. -var assert = require('assert'); -var Buffer = require('safer-buffer').Buffer; -var ASN1 = require('./types'); -var errors = require('./errors'); - +const assert = require('assert') +const ASN1 = require('./types') +const errors = require('./errors') // --- Globals -var newInvalidAsn1Error = errors.newInvalidAsn1Error; +const newInvalidAsn1Error = errors.newInvalidAsn1Error -var DEFAULT_OPTS = { +const DEFAULT_OPTS = { 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'); +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); + const keys = Object.getOwnPropertyNames(from) keys.forEach(function (key) { - if (to[key]) { return; } + if (to[key]) { return } - var value = Object.getOwnPropertyDescriptor(from, key); - Object.defineProperty(to, key, value); - }); + const value = Object.getOwnPropertyDescriptor(from, key) + Object.defineProperty(to, key, value) + }) - return to; + return to } - - // --- API -function Writer(options) { - options = merge(DEFAULT_OPTS, options || {}); +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; + 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 = []; + this._seq = [] } Object.defineProperty(Writer.prototype, 'buffer', { get: function () { - if (this._seq.length) { - throw newInvalidAsn1Error(this._seq.length + ' unended sequence(s)'); - } + if (this._seq.length) { throw newInvalidAsn1Error(this._seq.length + ' unended sequence(s)') } - return (this._buf.slice(0, this._offset)); + 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; -}; + 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 (typeof (i) !== 'number') { - throw new TypeError('argument must be a Number'); - } - if (typeof (tag) !== 'number') { tag = ASN1.Integer; } + if (typeof (i) !== 'number') { throw new TypeError('argument must be a Number') } + if (typeof (tag) !== 'number') { tag = ASN1.Integer } - var sz = 4; + let sz = 4 - while ((( - (i & 0xff800000) === 0) || - ((i & 0xff800000) === 0xff800000 >> 0) - ) && - (sz > 1)) { - sz--; - i <<= 8; + while ((((i & 0xff800000) === 0) || ((i & 0xff800000) === 0xff800000 >> 0)) && + (sz > 1)) { + sz-- + i <<= 8 } - if (sz > 4) { throw newInvalidAsn1Error('BER ints cannot be > 0xffffffff'); } + if (sz > 4) { throw newInvalidAsn1Error('BER ints cannot be > 0xffffffff') } - this._ensure(2 + sz); - this._buf[this._offset++] = tag; - this._buf[this._offset++] = sz; + this._ensure(2 + sz) + this._buf[this._offset++] = tag + this._buf[this._offset++] = sz while (sz-- > 0) { - this._buf[this._offset++] = ((i & 0xff000000) >>> 24); - i <<= 8; + this._buf[this._offset++] = ((i & 0xff000000) >>> 24) + i <<= 8 } -}; - +} Writer.prototype.writeNull = function () { - this.writeByte(ASN1.Null); - this.writeByte(0x00); -}; - + 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); -}; + 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; -}; + 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; } + 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); + const len = Buffer.byteLength(s) + this.writeByte(tag) + this.writeLength(len) if (len) { - this._ensure(len); - this._buf.write(s, this._offset); - this._offset += len; + this._ensure(len) + this._buf.write(s, this._offset) + this._offset += len } -}; - +} Writer.prototype.writeBuffer = function (buf, tag) { - if (typeof (tag) !== 'number') { - throw new TypeError('tag must be a number'); - } - if (!Buffer.isBuffer(buf)) { - throw new TypeError('argument must be a buffer'); - } - - this.writeByte(tag); - this.writeLength(buf.length); - this._ensure(buf.length); - buf.copy(this._buf, this._offset, 0, buf.length); - this._offset += buf.length; -}; - + if (typeof (tag) !== 'number') { throw new TypeError('tag must be a number') } + if (!Buffer.isBuffer(buf)) { throw new TypeError('argument must be a buffer') } + + this.writeByte(tag) + this.writeLength(buf.length) + this._ensure(buf.length) + buf.copy(this._buf, this._offset, 0, buf.length) + this._offset += buf.length +} Writer.prototype.writeStringArray = function (strings) { - if (!Array.isArray(strings)) { - throw new TypeError('argument must be an Array[String]'); - } + if (Array.isArray(strings) === false) { throw new TypeError('argument must be an Array[String]') } - var self = this; + const self = this strings.forEach(function (s) { - self.writeString(s); - }); -}; + self.writeString(s) + }) +} // This is really to solve DER cases, but whatever for now Writer.prototype.writeOID = function (s, tag) { - if (typeof (s) !== 'string') { - throw new TypeError('argument must be a string'); - } - if (typeof (tag) !== 'number') { tag = ASN1.OID; } + if (typeof (s) !== 'string') { throw new TypeError('argument must be a string') } + if (typeof (tag) !== 'number') { tag = ASN1.OID } - if (!/^([0-9]+\.){3,}[0-9]+$/.test(s)) { - throw new Error('argument is not a valid OID string'); - } + if (!/^([0-9]+\.){3,}[0-9]+$/.test(s)) { throw new Error('argument is not a valid OID string') } - function encodeOctet(bytes, octet) { + function encodeOctet (bytes, octet) { if (octet < 128) { - bytes.push(octet); + bytes.push(octet) } else if (octet < 16384) { - bytes.push((octet >>> 7) | 0x80); - bytes.push(octet & 0x7F); + 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); + 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); + 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); + 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)); + const tmp = s.split('.') + const bytes = [] + bytes.push(parseInt(tmp[0], 10) * 40 + parseInt(tmp[1], 10)) tmp.slice(2).forEach(function (b) { - encodeOctet(bytes, parseInt(b, 10)); - }); + encodeOctet(bytes, parseInt(b, 10)) + }) - var self = this; - this._ensure(2 + bytes.length); - this.writeByte(tag); - this.writeLength(bytes.length); + const self = this + this._ensure(2 + bytes.length) + this.writeByte(tag) + this.writeLength(bytes.length) bytes.forEach(function (b) { - self.writeByte(b); - }); -}; - + self.writeByte(b) + }) +} Writer.prototype.writeLength = function (len) { - if (typeof (len) !== 'number') { - throw new TypeError('argument must be a Number'); - } + if (typeof (len) !== 'number') { throw new TypeError('argument must be a Number') } - this._ensure(4); + this._ensure(4) if (len <= 0x7f) { - this._buf[this._offset++] = len; + this._buf[this._offset++] = len } else if (len <= 0xff) { - this._buf[this._offset++] = 0x81; - this._buf[this._offset++] = len; + 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; + 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; + this._buf[this._offset++] = 0x83 + this._buf[this._offset++] = len >> 16 + this._buf[this._offset++] = len >> 8 + this._buf[this._offset++] = len } else { - throw newInvalidAsn1Error('Length too long (> 4 bytes)'); + throw newInvalidAsn1Error('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; -}; + 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; + const seq = this._seq.pop() + const start = seq + 3 + const len = this._offset - start if (len <= 0x7f) { - this._shift(start, len, -2); - this._buf[seq] = len; + 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; + 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; + 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; + this._shift(start, len, 1) + this._buf[seq] = 0x83 + this._buf[seq + 1] = len >> 16 + this._buf[seq + 2] = len >> 8 + this._buf[seq + 3] = len } else { - throw newInvalidAsn1Error('Sequence too long'); + throw newInvalidAsn1Error('Sequence too long') } -}; - +} Writer.prototype._shift = function (start, len, shift) { - assert.ok(start !== undefined); - assert.ok(len !== undefined); - assert.ok(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; -}; + this._buf.copy(this._buf, start + shift, start, start + len) + this._offset += shift +} Writer.prototype._ensure = function (len) { - assert.ok(len); + assert.ok(len) if (this._size - this._offset < len) { - var sz = this._size * this._options.growthFactor; - if (sz - this._offset < len) { sz += len; } + let sz = this._size * this._options.growthFactor + if (sz - this._offset < len) { sz += len } - var buf = Buffer.alloc(sz); + const buf = Buffer.alloc(sz) - this._buf.copy(buf, 0, 0, this._offset); - this._buf = buf; - this._size = sz; + this._buf.copy(buf, 0, 0, this._offset) + this._buf = buf + this._size = sz } -}; - - +} // --- Exported API -module.exports = Writer; +module.exports = Writer diff --git a/lib/ber/writer.test.js b/lib/ber/writer.test.js new file mode 100644 index 0000000..8a405c4 --- /dev/null +++ b/lib/ber/writer.test.js @@ -0,0 +1,336 @@ +// Copyright 2011 Mark Cavage All rights reserved. + +const { test } = require('tap') +const BerWriter = require('./writer') + +test('write byte', function (t) { + const writer = new BerWriter() + + writer.writeByte(0xC2) + const ber = writer.buffer + + t.ok(ber) + t.equal(ber.length, 1, 'Wrong length') + t.equal(ber[0], 0xC2, 'value wrong') + + t.end() +}) + +test('write 1 byte int', function (t) { + const writer = new BerWriter() + + writer.writeInt(0x7f) + const ber = writer.buffer + + t.ok(ber) + t.equal(ber.length, 3, 'Wrong length for an int: ' + ber.length) + t.equal(ber[0], 0x02, 'ASN.1 tag wrong (2) -> ' + ber[0]) + t.equal(ber[1], 0x01, 'length wrong(1) -> ' + ber[1]) + t.equal(ber[2], 0x7f, 'value wrong(3) -> ' + ber[2]) + + t.end() +}) + +test('write 2 byte int', function (t) { + const writer = new BerWriter() + + writer.writeInt(0x7ffe) + const ber = writer.buffer + + t.ok(ber) + t.equal(ber.length, 4, 'Wrong length for an int') + t.equal(ber[0], 0x02, 'ASN.1 tag wrong') + t.equal(ber[1], 0x02, 'length wrong') + t.equal(ber[2], 0x7f, 'value wrong (byte 1)') + t.equal(ber[3], 0xfe, 'value wrong (byte 2)') + + t.end() +}) + +test('write 3 byte int', function (t) { + const writer = new BerWriter() + + writer.writeInt(0x7ffffe) + const ber = writer.buffer + + t.ok(ber) + t.equal(ber.length, 5, 'Wrong length for an int') + t.equal(ber[0], 0x02, 'ASN.1 tag wrong') + t.equal(ber[1], 0x03, 'length wrong') + t.equal(ber[2], 0x7f, 'value wrong (byte 1)') + t.equal(ber[3], 0xff, 'value wrong (byte 2)') + t.equal(ber[4], 0xfe, 'value wrong (byte 3)') + + t.end() +}) + +test('write 4 byte int', function (t) { + const writer = new BerWriter() + + writer.writeInt(0x7ffffffe) + const ber = writer.buffer + + t.ok(ber) + + t.equal(ber.length, 6, 'Wrong length for an int') + t.equal(ber[0], 0x02, 'ASN.1 tag wrong') + t.equal(ber[1], 0x04, 'length wrong') + t.equal(ber[2], 0x7f, 'value wrong (byte 1)') + t.equal(ber[3], 0xff, 'value wrong (byte 2)') + t.equal(ber[4], 0xff, 'value wrong (byte 3)') + t.equal(ber[5], 0xfe, 'value wrong (byte 4)') + + t.end() +}) + +test('write 1 byte negative int', function (t) { + const writer = new BerWriter() + + writer.writeInt(-128) + const ber = writer.buffer + + t.ok(ber) + + t.equal(ber.length, 3, 'Wrong length for an int') + t.equal(ber[0], 0x02, 'ASN.1 tag wrong') + t.equal(ber[1], 0x01, 'length wrong') + t.equal(ber[2], 0x80, 'value wrong (byte 1)') + + t.end() +}) + +test('write 2 byte negative int', function (t) { + const writer = new BerWriter() + + writer.writeInt(-22400) + const ber = writer.buffer + + t.ok(ber) + + t.equal(ber.length, 4, 'Wrong length for an int') + t.equal(ber[0], 0x02, 'ASN.1 tag wrong') + t.equal(ber[1], 0x02, 'length wrong') + t.equal(ber[2], 0xa8, 'value wrong (byte 1)') + t.equal(ber[3], 0x80, 'value wrong (byte 2)') + + t.end() +}) + +test('write 3 byte negative int', function (t) { + const writer = new BerWriter() + + writer.writeInt(-481653) + const ber = writer.buffer + + t.ok(ber) + + t.equal(ber.length, 5, 'Wrong length for an int') + t.equal(ber[0], 0x02, 'ASN.1 tag wrong') + t.equal(ber[1], 0x03, 'length wrong') + t.equal(ber[2], 0xf8, 'value wrong (byte 1)') + t.equal(ber[3], 0xa6, 'value wrong (byte 2)') + t.equal(ber[4], 0x8b, 'value wrong (byte 3)') + + t.end() +}) + +test('write 4 byte negative int', function (t) { + const writer = new BerWriter() + + writer.writeInt(-1522904131) + const ber = writer.buffer + + t.ok(ber) + + t.equal(ber.length, 6, 'Wrong length for an int') + t.equal(ber[0], 0x02, 'ASN.1 tag wrong') + t.equal(ber[1], 0x04, 'length wrong') + t.equal(ber[2], 0xa5, 'value wrong (byte 1)') + t.equal(ber[3], 0x3a, 'value wrong (byte 2)') + t.equal(ber[4], 0x53, 'value wrong (byte 3)') + t.equal(ber[5], 0xbd, 'value wrong (byte 4)') + + t.end() +}) + +test('write boolean', function (t) { + const writer = new BerWriter() + + writer.writeBoolean(true) + writer.writeBoolean(false) + const ber = writer.buffer + + t.ok(ber) + t.equal(ber.length, 6, 'Wrong length') + t.equal(ber[0], 0x01, 'tag wrong') + t.equal(ber[1], 0x01, 'length wrong') + t.equal(ber[2], 0xff, 'value wrong') + t.equal(ber[3], 0x01, 'tag wrong') + t.equal(ber[4], 0x01, 'length wrong') + t.equal(ber[5], 0x00, 'value wrong') + + t.end() +}) + +test('write string', function (t) { + const writer = new BerWriter() + writer.writeString('hello world') + const ber = writer.buffer + + t.ok(ber) + t.equal(ber.length, 13, 'wrong length') + t.equal(ber[0], 0x04, 'wrong tag') + t.equal(ber[1], 11, 'wrong length') + t.equal(ber.slice(2).toString('utf8'), 'hello world', 'wrong value') + + t.end() +}) + +test('write buffer', function (t) { + const writer = new BerWriter() + // write some stuff to start with + writer.writeString('hello world') + let ber = writer.buffer + const buf = Buffer.from([0x04, 0x0b, 0x30, 0x09, 0x02, 0x01, 0x0f, 0x01, 0x01, + 0xff, 0x01, 0x01, 0xff]) + writer.writeBuffer(buf.slice(2, buf.length), 0x04) + ber = writer.buffer + + t.ok(ber) + t.equal(ber.length, 26, 'wrong length') + t.equal(ber[0], 0x04, 'wrong tag') + t.equal(ber[1], 11, 'wrong length') + t.equal(ber.slice(2, 13).toString('utf8'), 'hello world', 'wrong value') + t.equal(ber[13], buf[0], 'wrong tag') + t.equal(ber[14], buf[1], 'wrong length') + for (let i = 13, j = 0; i < ber.length && j < buf.length; i++, j++) { + t.equal(ber[i], buf[j], 'buffer contents not identical') + } + t.end() +}) + +test('write string array', function (t) { + const writer = new BerWriter() + writer.writeStringArray(['hello world', 'fubar!']) + const ber = writer.buffer + + t.ok(ber) + + t.equal(ber.length, 21, 'wrong length') + t.equal(ber[0], 0x04, 'wrong tag') + t.equal(ber[1], 11, 'wrong length') + t.equal(ber.slice(2, 13).toString('utf8'), 'hello world', 'wrong value') + + t.equal(ber[13], 0x04, 'wrong tag') + t.equal(ber[14], 6, 'wrong length') + t.equal(ber.slice(15).toString('utf8'), 'fubar!', 'wrong value') + + t.end() +}) + +test('resize internal buffer', function (t) { + const writer = new BerWriter({ size: 2 }) + writer.writeString('hello world') + const ber = writer.buffer + + t.ok(ber) + t.equal(ber.length, 13, 'wrong length') + t.equal(ber[0], 0x04, 'wrong tag') + t.equal(ber[1], 11, 'wrong length') + t.equal(ber.slice(2).toString('utf8'), 'hello world', 'wrong value') + + t.end() +}) + +test('sequence', function (t) { + const writer = new BerWriter({ size: 25 }) + writer.startSequence() + writer.writeString('hello world') + writer.endSequence() + const ber = writer.buffer + + t.ok(ber) + t.equal(ber.length, 15, 'wrong length') + t.equal(ber[0], 0x30, 'wrong tag') + t.equal(ber[1], 13, 'wrong length') + t.equal(ber[2], 0x04, 'wrong tag') + t.equal(ber[3], 11, 'wrong length') + t.equal(ber.slice(4).toString('utf8'), 'hello world', 'wrong value') + + t.end() +}) + +test('nested sequence', function (t) { + const writer = new BerWriter({ size: 25 }) + writer.startSequence() + writer.writeString('hello world') + writer.startSequence() + writer.writeString('hello world') + writer.endSequence() + writer.endSequence() + const ber = writer.buffer + + t.ok(ber) + t.equal(ber.length, 30, 'wrong length') + t.equal(ber[0], 0x30, 'wrong tag') + t.equal(ber[1], 28, 'wrong length') + t.equal(ber[2], 0x04, 'wrong tag') + t.equal(ber[3], 11, 'wrong length') + t.equal(ber.slice(4, 15).toString('utf8'), 'hello world', 'wrong value') + t.equal(ber[15], 0x30, 'wrong tag') + t.equal(ber[16], 13, 'wrong length') + t.equal(ber[17], 0x04, 'wrong tag') + t.equal(ber[18], 11, 'wrong length') + t.equal(ber.slice(19, 30).toString('utf8'), 'hello world', 'wrong value') + + t.end() +}) + +test('LDAP bind message', function (t) { + const dn = 'cn=foo,ou=unit,o=test' + const writer = new BerWriter() + writer.startSequence() + writer.writeInt(3) // msgid = 3 + writer.startSequence(0x60) // ldap bind + writer.writeInt(3) // ldap v3 + writer.writeString(dn) + writer.writeByte(0x80) + writer.writeByte(0x00) + writer.endSequence() + writer.endSequence() + const ber = writer.buffer + + t.ok(ber) + t.equal(ber.length, 35, 'wrong length (buffer)') + t.equal(ber[0], 0x30, 'wrong tag') + t.equal(ber[1], 33, 'wrong length') + t.equal(ber[2], 0x02, 'wrong tag') + t.equal(ber[3], 1, 'wrong length') + t.equal(ber[4], 0x03, 'wrong value') + t.equal(ber[5], 0x60, 'wrong tag') + t.equal(ber[6], 28, 'wrong length') + t.equal(ber[7], 0x02, 'wrong tag') + t.equal(ber[8], 1, 'wrong length') + t.equal(ber[9], 0x03, 'wrong value') + t.equal(ber[10], 0x04, 'wrong tag') + t.equal(ber[11], dn.length, 'wrong length') + t.equal(ber.slice(12, 33).toString('utf8'), dn, 'wrong value') + t.equal(ber[33], 0x80, 'wrong tag') + t.equal(ber[34], 0x00, 'wrong len') + + t.end() +}) + +test('Write OID', function (t) { + const oid = '1.2.840.113549.1.1.1' + const writer = new BerWriter() + writer.writeOID(oid) + + const expected = Buffer.from([0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x01]) + const ber = writer.buffer + t.equal(ber.compare(expected), 0) + + t.end() +}) diff --git a/lib/index.js b/lib/index.js index ede3ab2..17f1761 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,9 +3,7 @@ // If you have no idea what ASN.1 or BER is, see this: // ftp://ftp.rsa.com/pub/pkcs/ascii/layman.asc -var Ber = require('./ber/index'); - - +const Ber = require('./ber/index') // --- Exported API @@ -17,4 +15,4 @@ module.exports = { BerWriter: Ber.Writer -}; +} diff --git a/package.json b/package.json index 0273d98..4760653 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,34 @@ { - "author": "Joyent (joyent.com)", + "originalAuthor": "Joyent (joyent.com)", "contributors": [ "Mark Cavage ", "David Gwynne ", "Yunong Xiao ", "Alex Wilson " ], - "name": "asn1", + "name": "@ldapjs/asn1", "description": "Contains parsers and serializers for ASN.1 (currently BER only)", - "version": "0.2.4", + "version": "1.0.0", "repository": { "type": "git", - "url": "git://github.com/joyent/node-asn1.git" + "url": "git://github.com/ldapjs/asn1.git" }, "main": "lib/index.js", - "dependencies": { - "safer-buffer": "~2.1.2" - }, "devDependencies": { - "tape": "^5.0.1", - "eslint": "^7.1.0", - "eslint-plugin-joyent": "^2.1.0", - "nyc": "^15.1.0", - "tap-nyc": "^1.0.3" + "@fastify/pre-commit": "^2.0.2", + "standard": "^16.0.4", + "tap": "^16.0.1" }, "scripts": { - "test": "tape ./test/ber/*.test.js", - "cover": "nyc tape test/run.js | tap-nyc", - "lint": "eslint ." + "test": "tap --no-coverage-report -R terse", + "test:cov": "tap -R terse", + "test:cov:html": "tap -R terse --coverage-report=html", + "test:watch": "tap -w --no-coverage-report -R terse", + "lint": "standard" }, - "license": "MIT" + "license": "MIT", + "pre-commit": [ + "lint", + "test" + ] } diff --git a/test/ber/reader.test.js b/test/ber/reader.test.js deleted file mode 100644 index 467bcbe..0000000 --- a/test/ber/reader.test.js +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright 2011 Mark Cavage All rights reserved. - -var test = require('tape'); -var Buffer = require('safer-buffer').Buffer; - - - -// --- Globals - -var BerReader; - - - -// --- Tests - -test('load library', function (t) { - BerReader = require('../../lib/index').BerReader; - t.ok(BerReader); - try { - var reader = new BerReader(); - t.equal(reader, null, 'reader'); - t.fail('Should have thrown'); - } catch (e) { - t.ok(e instanceof TypeError, 'Should have been a type error'); - } - t.end(); -}); - - -test('read byte', function (t) { - var reader = new BerReader(Buffer.from([0xde])); - t.ok(reader); - t.equal(reader.readByte(), 0xde, 'wrong value'); - t.end(); -}); - - -test('read 1 byte int', function (t) { - var reader = new BerReader(Buffer.from([0x02, 0x01, 0x03])); - t.ok(reader); - t.equal(reader.readInt(), 0x03, 'wrong value'); - t.equal(reader.length, 0x01, 'wrong length'); - t.end(); -}); - - -test('read 2 byte int', function (t) { - var reader = new BerReader(Buffer.from([0x02, 0x02, 0x7e, 0xde])); - t.ok(reader); - t.equal(reader.readInt(), 0x7ede, 'wrong value'); - t.equal(reader.length, 0x02, 'wrong length'); - t.end(); -}); - - -test('read 3 byte int', function (t) { - var reader = new BerReader(Buffer.from([0x02, 0x03, 0x7e, 0xde, 0x03])); - t.ok(reader); - t.equal(reader.readInt(), 0x7ede03, 'wrong value'); - t.equal(reader.length, 0x03, 'wrong length'); - t.end(); -}); - - -test('read 4 byte int', function (t) { - var reader = new BerReader(Buffer.from([0x02, 0x04, 0x7e, 0xde, 0x03, 0x01])); - t.ok(reader); - t.equal(reader.readInt(), 0x7ede0301, 'wrong value'); - t.equal(reader.length, 0x04, 'wrong length'); - t.end(); -}); - - -test('read 1 byte negative int', function (t) { - var reader = new BerReader(Buffer.from([0x02, 0x01, 0xdc])); - t.ok(reader); - t.equal(reader.readInt(), -36, 'wrong value'); - t.equal(reader.length, 0x01, 'wrong length'); - t.end(); -}); - - -test('read 2 byte negative int', function (t) { - var reader = new BerReader(Buffer.from([0x02, 0x02, 0xc0, 0x4e])); - t.ok(reader); - t.equal(reader.readInt(), -16306, 'wrong value'); - t.equal(reader.length, 0x02, 'wrong length'); - t.end(); -}); - - -test('read 3 byte negative int', function (t) { - var reader = new BerReader(Buffer.from([0x02, 0x03, 0xff, 0x00, 0x19])); - t.ok(reader); - t.equal(reader.readInt(), -65511, 'wrong value'); - t.equal(reader.length, 0x03, 'wrong length'); - t.end(); -}); - - -test('read 4 byte negative int', function (t) { - var reader = new BerReader(Buffer.from([0x02, 0x04, 0x91, 0x7c, 0x22, 0x1f])); - t.ok(reader); - t.equal(reader.readInt(), -1854135777, 'wrong value'); - t.equal(reader.length, 0x04, 'wrong length'); - t.end(); -}); - - -test('read boolean true', function (t) { - var reader = new BerReader(Buffer.from([0x01, 0x01, 0xff])); - t.ok(reader); - t.equal(reader.readBoolean(), true, 'wrong value'); - t.equal(reader.length, 0x01, 'wrong length'); - t.end(); -}); - - -test('read boolean false', function (t) { - var reader = new BerReader(Buffer.from([0x01, 0x01, 0x00])); - t.ok(reader); - t.equal(reader.readBoolean(), false, 'wrong value'); - t.equal(reader.length, 0x01, 'wrong length'); - t.end(); -}); - - -test('read enumeration', function (t) { - var reader = new BerReader(Buffer.from([0x0a, 0x01, 0x20])); - t.ok(reader); - t.equal(reader.readEnumeration(), 0x20, 'wrong value'); - t.equal(reader.length, 0x01, 'wrong length'); - t.end(); -}); - - -test('read string', function (t) { - var dn = 'cn=foo,ou=unit,o=test'; - var buf = Buffer.alloc(dn.length + 2); - buf[0] = 0x04; - buf[1] = Buffer.byteLength(dn); - buf.write(dn, 2); - var reader = new BerReader(buf); - t.ok(reader); - t.equal(reader.readString(), dn, 'wrong value'); - t.equal(reader.length, dn.length, 'wrong length'); - t.end(); -}); - - -test('read sequence', function (t) { - var reader = new BerReader(Buffer.from([0x30, 0x03, 0x01, 0x01, 0xff])); - t.ok(reader); - t.equal(reader.readSequence(), 0x30, 'wrong value'); - t.equal(reader.length, 0x03, 'wrong length'); - t.equal(reader.readBoolean(), true, 'wrong value'); - t.equal(reader.length, 0x01, 'wrong length'); - t.end(); -}); - - -test('anonymous LDAPv3 bind', function (t) { - var BIND = Buffer.alloc(14); - BIND[0] = 0x30; // Sequence - BIND[1] = 12; // len - BIND[2] = 0x02; // ASN.1 Integer - BIND[3] = 1; // len - BIND[4] = 0x04; // msgid (make up 4) - BIND[5] = 0x60; // Bind Request - BIND[6] = 7; // len - BIND[7] = 0x02; // ASN.1 Integer - BIND[8] = 1; // len - BIND[9] = 0x03; // v3 - BIND[10] = 0x04; // String (bind dn) - BIND[11] = 0; // len - BIND[12] = 0x80; // ContextSpecific (choice) - BIND[13] = 0; // simple bind - - // Start testing ^^ - var ber = new BerReader(BIND); - t.equal(ber.readSequence(), 48, 'Not an ASN.1 Sequence'); - t.equal(ber.length, 12, 'Message length should be 12'); - t.equal(ber.readInt(), 4, 'Message id should have been 4'); - t.equal(ber.readSequence(), 96, 'Bind Request should have been 96'); - t.equal(ber.length, 7, 'Bind length should have been 7'); - t.equal(ber.readInt(), 3, 'LDAP version should have been 3'); - t.equal(ber.readString(), '', 'Bind DN should have been empty'); - t.equal(ber.length, 0, 'string length should have been 0'); - t.equal(ber.readByte(), 0x80, 'Should have been ContextSpecific (choice)'); - t.equal(ber.readByte(), 0, 'Should have been simple bind'); - t.equal(null, ber.readByte(), 'Should be out of data'); - t.end(); -}); - - -test('long string', function (t) { - var buf = Buffer.alloc(256); - var s = - '2;649;CN=Red Hat CS 71GA Demo,O=Red Hat CS 71GA Demo,C=US;' + - 'CN=RHCS Agent - admin01,UID=admin01,O=redhat,C=US [1] This is ' + - 'Teena Vradmin\'s description.'; - buf[0] = 0x04; - buf[1] = 0x81; - buf[2] = 0x94; - buf.write(s, 3); - var ber = new BerReader(buf.slice(0, 3 + s.length)); - t.equal(ber.readString(), s); - t.end(); -}); diff --git a/test/ber/writer.test.js b/test/ber/writer.test.js deleted file mode 100644 index a9d483e..0000000 --- a/test/ber/writer.test.js +++ /dev/null @@ -1,369 +0,0 @@ -// Copyright 2011 Mark Cavage All rights reserved. - -var test = require('tape'); -var Buffer = require('safer-buffer').Buffer; - -// --- Globals - -var BerWriter; - - - -// --- Tests - -test('load library', function (t) { - BerWriter = require('../../lib/index').BerWriter; - t.ok(BerWriter); - t.ok(new BerWriter()); - t.end(); -}); - - -test('write byte', function (t) { - var writer = new BerWriter(); - - writer.writeByte(0xC2); - var ber = writer.buffer; - - t.ok(ber); - t.equal(ber.length, 1, 'Wrong length'); - t.equal(ber[0], 0xC2, 'value wrong'); - - t.end(); -}); - - -test('write 1 byte int', function (t) { - var writer = new BerWriter(); - - writer.writeInt(0x7f); - var ber = writer.buffer; - - t.ok(ber); - t.equal(ber.length, 3, 'Wrong length for an int: ' + ber.length); - t.equal(ber[0], 0x02, 'ASN.1 tag wrong (2) -> ' + ber[0]); - t.equal(ber[1], 0x01, 'length wrong(1) -> ' + ber[1]); - t.equal(ber[2], 0x7f, 'value wrong(3) -> ' + ber[2]); - - t.end(); -}); - - -test('write 2 byte int', function (t) { - var writer = new BerWriter(); - - writer.writeInt(0x7ffe); - var ber = writer.buffer; - - t.ok(ber); - t.equal(ber.length, 4, 'Wrong length for an int'); - t.equal(ber[0], 0x02, 'ASN.1 tag wrong'); - t.equal(ber[1], 0x02, 'length wrong'); - t.equal(ber[2], 0x7f, 'value wrong (byte 1)'); - t.equal(ber[3], 0xfe, 'value wrong (byte 2)'); - - t.end(); -}); - - -test('write 3 byte int', function (t) { - var writer = new BerWriter(); - - writer.writeInt(0x7ffffe); - var ber = writer.buffer; - - t.ok(ber); - t.equal(ber.length, 5, 'Wrong length for an int'); - t.equal(ber[0], 0x02, 'ASN.1 tag wrong'); - t.equal(ber[1], 0x03, 'length wrong'); - t.equal(ber[2], 0x7f, 'value wrong (byte 1)'); - t.equal(ber[3], 0xff, 'value wrong (byte 2)'); - t.equal(ber[4], 0xfe, 'value wrong (byte 3)'); - - t.end(); -}); - - -test('write 4 byte int', function (t) { - var writer = new BerWriter(); - - writer.writeInt(0x7ffffffe); - var ber = writer.buffer; - - t.ok(ber); - - t.equal(ber.length, 6, 'Wrong length for an int'); - t.equal(ber[0], 0x02, 'ASN.1 tag wrong'); - t.equal(ber[1], 0x04, 'length wrong'); - t.equal(ber[2], 0x7f, 'value wrong (byte 1)'); - t.equal(ber[3], 0xff, 'value wrong (byte 2)'); - t.equal(ber[4], 0xff, 'value wrong (byte 3)'); - t.equal(ber[5], 0xfe, 'value wrong (byte 4)'); - - t.end(); -}); - - -test('write 1 byte negative int', function (t) { - var writer = new BerWriter(); - - writer.writeInt(-128); - var ber = writer.buffer; - - t.ok(ber); - - t.equal(ber.length, 3, 'Wrong length for an int'); - t.equal(ber[0], 0x02, 'ASN.1 tag wrong'); - t.equal(ber[1], 0x01, 'length wrong'); - t.equal(ber[2], 0x80, 'value wrong (byte 1)'); - - t.end(); -}); - - -test('write 2 byte negative int', function (t) { - var writer = new BerWriter(); - - writer.writeInt(-22400); - var ber = writer.buffer; - - t.ok(ber); - - t.equal(ber.length, 4, 'Wrong length for an int'); - t.equal(ber[0], 0x02, 'ASN.1 tag wrong'); - t.equal(ber[1], 0x02, 'length wrong'); - t.equal(ber[2], 0xa8, 'value wrong (byte 1)'); - t.equal(ber[3], 0x80, 'value wrong (byte 2)'); - - t.end(); -}); - - -test('write 3 byte negative int', function (t) { - var writer = new BerWriter(); - - writer.writeInt(-481653); - var ber = writer.buffer; - - t.ok(ber); - - t.equal(ber.length, 5, 'Wrong length for an int'); - t.equal(ber[0], 0x02, 'ASN.1 tag wrong'); - t.equal(ber[1], 0x03, 'length wrong'); - t.equal(ber[2], 0xf8, 'value wrong (byte 1)'); - t.equal(ber[3], 0xa6, 'value wrong (byte 2)'); - t.equal(ber[4], 0x8b, 'value wrong (byte 3)'); - - t.end(); -}); - - -test('write 4 byte negative int', function (t) { - var writer = new BerWriter(); - - writer.writeInt(-1522904131); - var ber = writer.buffer; - - t.ok(ber); - - t.equal(ber.length, 6, 'Wrong length for an int'); - t.equal(ber[0], 0x02, 'ASN.1 tag wrong'); - t.equal(ber[1], 0x04, 'length wrong'); - t.equal(ber[2], 0xa5, 'value wrong (byte 1)'); - t.equal(ber[3], 0x3a, 'value wrong (byte 2)'); - t.equal(ber[4], 0x53, 'value wrong (byte 3)'); - t.equal(ber[5], 0xbd, 'value wrong (byte 4)'); - - t.end(); -}); - - -test('write boolean', function (t) { - var writer = new BerWriter(); - - writer.writeBoolean(true); - writer.writeBoolean(false); - var ber = writer.buffer; - - t.ok(ber); - t.equal(ber.length, 6, 'Wrong length'); - t.equal(ber[0], 0x01, 'tag wrong'); - t.equal(ber[1], 0x01, 'length wrong'); - t.equal(ber[2], 0xff, 'value wrong'); - t.equal(ber[3], 0x01, 'tag wrong'); - t.equal(ber[4], 0x01, 'length wrong'); - t.equal(ber[5], 0x00, 'value wrong'); - - t.end(); -}); - - -test('write string', function (t) { - var writer = new BerWriter(); - writer.writeString('hello world'); - var ber = writer.buffer; - - t.ok(ber); - t.equal(ber.length, 13, 'wrong length'); - t.equal(ber[0], 0x04, 'wrong tag'); - t.equal(ber[1], 11, 'wrong length'); - t.equal(ber.slice(2).toString('utf8'), 'hello world', 'wrong value'); - - t.end(); -}); - -test('write buffer', function (t) { - var writer = new BerWriter(); - // write some stuff to start with - writer.writeString('hello world'); - var ber = writer.buffer; - var buf = Buffer.from([0x04, 0x0b, 0x30, 0x09, 0x02, 0x01, 0x0f, 0x01, 0x01, - 0xff, 0x01, 0x01, 0xff]); - writer.writeBuffer(buf.slice(2, buf.length), 0x04); - ber = writer.buffer; - - t.ok(ber); - t.equal(ber.length, 26, 'wrong length'); - t.equal(ber[0], 0x04, 'wrong tag'); - t.equal(ber[1], 11, 'wrong length'); - t.equal(ber.slice(2, 13).toString('utf8'), 'hello world', 'wrong value'); - t.equal(ber[13], buf[0], 'wrong tag'); - t.equal(ber[14], buf[1], 'wrong length'); - for (var i = 13, j = 0; i < ber.length && j < buf.length; i++, j++) { - t.equal(ber[i], buf[j], 'buffer contents not identical'); - } - t.end(); -}); - -test('write string array', function (t) { - var writer = new BerWriter(); - writer.writeStringArray(['hello world', 'fubar!']); - var ber = writer.buffer; - - t.ok(ber); - - t.equal(ber.length, 21, 'wrong length'); - t.equal(ber[0], 0x04, 'wrong tag'); - t.equal(ber[1], 11, 'wrong length'); - t.equal(ber.slice(2, 13).toString('utf8'), 'hello world', 'wrong value'); - - t.equal(ber[13], 0x04, 'wrong tag'); - t.equal(ber[14], 6, 'wrong length'); - t.equal(ber.slice(15).toString('utf8'), 'fubar!', 'wrong value'); - - t.end(); -}); - - -test('resize internal buffer', function (t) { - var writer = new BerWriter({size: 2}); - writer.writeString('hello world'); - var ber = writer.buffer; - - t.ok(ber); - t.equal(ber.length, 13, 'wrong length'); - t.equal(ber[0], 0x04, 'wrong tag'); - t.equal(ber[1], 11, 'wrong length'); - t.equal(ber.slice(2).toString('utf8'), 'hello world', 'wrong value'); - - t.end(); -}); - - -test('sequence', function (t) { - var writer = new BerWriter({size: 25}); - writer.startSequence(); - writer.writeString('hello world'); - writer.endSequence(); - var ber = writer.buffer; - - t.ok(ber); - console.log(ber); - t.equal(ber.length, 15, 'wrong length'); - t.equal(ber[0], 0x30, 'wrong tag'); - t.equal(ber[1], 13, 'wrong length'); - t.equal(ber[2], 0x04, 'wrong tag'); - t.equal(ber[3], 11, 'wrong length'); - t.equal(ber.slice(4).toString('utf8'), 'hello world', 'wrong value'); - - t.end(); -}); - - -test('nested sequence', function (t) { - var writer = new BerWriter({size: 25}); - writer.startSequence(); - writer.writeString('hello world'); - writer.startSequence(); - writer.writeString('hello world'); - writer.endSequence(); - writer.endSequence(); - var ber = writer.buffer; - - t.ok(ber); - t.equal(ber.length, 30, 'wrong length'); - t.equal(ber[0], 0x30, 'wrong tag'); - t.equal(ber[1], 28, 'wrong length'); - t.equal(ber[2], 0x04, 'wrong tag'); - t.equal(ber[3], 11, 'wrong length'); - t.equal(ber.slice(4, 15).toString('utf8'), 'hello world', 'wrong value'); - t.equal(ber[15], 0x30, 'wrong tag'); - t.equal(ber[16], 13, 'wrong length'); - t.equal(ber[17], 0x04, 'wrong tag'); - t.equal(ber[18], 11, 'wrong length'); - t.equal(ber.slice(19, 30).toString('utf8'), 'hello world', 'wrong value'); - - t.end(); -}); - - -test('LDAP bind message', function (t) { - var dn = 'cn=foo,ou=unit,o=test'; - var writer = new BerWriter(); - writer.startSequence(); - writer.writeInt(3); // msgid = 3 - writer.startSequence(0x60); // ldap bind - writer.writeInt(3); // ldap v3 - writer.writeString(dn); - writer.writeByte(0x80); - writer.writeByte(0x00); - writer.endSequence(); - writer.endSequence(); - var ber = writer.buffer; - - t.ok(ber); - t.equal(ber.length, 35, 'wrong length (buffer)'); - t.equal(ber[0], 0x30, 'wrong tag'); - t.equal(ber[1], 33, 'wrong length'); - t.equal(ber[2], 0x02, 'wrong tag'); - t.equal(ber[3], 1, 'wrong length'); - t.equal(ber[4], 0x03, 'wrong value'); - t.equal(ber[5], 0x60, 'wrong tag'); - t.equal(ber[6], 28, 'wrong length'); - t.equal(ber[7], 0x02, 'wrong tag'); - t.equal(ber[8], 1, 'wrong length'); - t.equal(ber[9], 0x03, 'wrong value'); - t.equal(ber[10], 0x04, 'wrong tag'); - t.equal(ber[11], dn.length, 'wrong length'); - t.equal(ber.slice(12, 33).toString('utf8'), dn, 'wrong value'); - t.equal(ber[33], 0x80, 'wrong tag'); - t.equal(ber[34], 0x00, 'wrong len'); - - t.end(); -}); - - -test('Write OID', function (t) { - var oid = '1.2.840.113549.1.1.1'; - var writer = new BerWriter(); - writer.writeOID(oid); - - var ber = writer.buffer; - t.ok(ber); - console.log(require('util').inspect(ber)); - console.log(require('util').inspect(Buffer.from([0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x01, 0x01]))); - - t.end(); -}); diff --git a/test/run.js b/test/run.js deleted file mode 100644 index 5e7e80e..0000000 --- a/test/run.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -/* - * Copyright (c) 2018, Joyent, Inc. - */ - -'use strict'; - -// --- Run All Tests - -require('./ber/reader.test.js'); -require('./ber/writer.test.js');