610 lines
20 KiB
Markdown
610 lines
20 KiB
Markdown
|
|
# asn1-ber
|
|
|
|
**This project is a clone (not a fork) of the [asn1][asn1] project, and
|
|
as such is a drop in replacement. The [asn1][asn1] project has received little
|
|
attention over the past few years and is used in a number of heavily dependant
|
|
modules (one being my own [net-snmp][net-snmp] module), so I have committed to
|
|
maintaining this clone and for it to be a drop in replacement.**
|
|
|
|
[asn1]: https://github.com/mcavage/node-asn1
|
|
[net-snmp]: https://github.com/stephenwvickers/node-net-snmp
|
|
|
|
This module provides the ability to generate and parse ASN1.BER objects.
|
|
|
|
This module is installed using [node package manager (npm)][npm]:
|
|
|
|
npm install asn1-ber
|
|
|
|
It is loaded using the `require()` function:
|
|
|
|
var asn1 = require("asn1-ber")
|
|
|
|
A reader or writer can then be created to read or write objects:
|
|
|
|
// Let's create an ASN1.BER object using the writing interface:
|
|
var writer = new asn1.BerWriter()
|
|
|
|
writer.startSequence()
|
|
writer.writeBoolean(true)
|
|
writer.writeBoolean(false)
|
|
writer.endSequence()
|
|
|
|
var buffer = writer.buffer
|
|
|
|
// Now let's read the data back from the buffer:
|
|
var reader = new asn1.BerReader(buffer)
|
|
|
|
reader.readSequence()
|
|
reader.readBoolean() // first boolean is true
|
|
reader.readBoolean() // second boolean is false
|
|
|
|
It is assumed that users are somewhat familiar with ASN1 and BER encoding.
|
|
|
|
[nodejs]: http://nodejs.org "Node.js"
|
|
[npm]: https://npmjs.org/ "npm"
|
|
|
|
# Constants
|
|
|
|
The following sections describe constants exported and used by this module.
|
|
|
|
## asn1.Ber
|
|
|
|
This object contains constants which can be used wherever a `tag` parameter
|
|
can be provided. For example, the `asn1.BerWriter.writeBoolean()` method
|
|
accepts an optional `tag` parameter. In this method any of the following
|
|
constants could be used (or a number if the required type is not defined) to
|
|
specify the tag which be used when encoding the value, and in this particular
|
|
case would default to `asn1.Ber.Boolean`, e.g.:
|
|
|
|
writer.writeBoolean(true, asn1.Ber.Boolean)
|
|
|
|
The following constants are defined in this object:
|
|
|
|
* `EOC`
|
|
* `Boolean`
|
|
* `Integer`
|
|
* `BitString`
|
|
* `OctetString`
|
|
* `Null`
|
|
* `OID`
|
|
* `ObjectDescriptor`
|
|
* `External`
|
|
* `Real`
|
|
* `Enumeration`
|
|
* `PDV`
|
|
* `Utf8String`
|
|
* `RelativeOID`
|
|
* `Sequence`
|
|
* `Set`
|
|
* `NumericString`
|
|
* `PrintableString`
|
|
* `T61String`
|
|
* `VideotexString`
|
|
* `IA5String`
|
|
* `UTCTime`
|
|
* `GeneralizedTime`
|
|
* `GraphicString`
|
|
* `VisibleString`
|
|
* `GeneralString`
|
|
* `UniversalString`
|
|
* `CharacterString`
|
|
* `BMPString`
|
|
* `Constructor`
|
|
* `Context`
|
|
|
|
# Using This Module
|
|
|
|
This module exposes two interfaces, one for reading ASN1.BER objects from
|
|
Node.js `Buffer` object instances, and another for writing ASN1.BER objects to
|
|
Node.js `Buffer` instances.
|
|
|
|
These two interfaces, and all their functions and methods, are documented in
|
|
seperate sections below.
|
|
|
|
## Writing Objects
|
|
|
|
### Overview
|
|
|
|
ASN1.BER objects can be generated programatically using various methods. An
|
|
instance of the `BerWriter` class is instantiated and its methods used to do
|
|
this. Once an object is complete the associated Node.js `Buffer` object
|
|
instance can be obtained by accessing the `buffer` attribute of the `BerWriter`
|
|
object instance.
|
|
|
|
In the following example a simple sequence of two boolean objects is written,
|
|
then the `Buffer` instance obtained:
|
|
|
|
var writer = new asn1.BerWriter()
|
|
|
|
writer.startSequence()
|
|
writer.writeBoolean(true)
|
|
writer.writeBoolean(false)
|
|
writer.endSequence()
|
|
|
|
var buffer = writer.buffer
|
|
|
|
The resulting buffer will contain the following:
|
|
|
|
var buffer = Buffer.alloc([
|
|
asn1.Ber.Sequence | asn1.Ber.Constructor,
|
|
6, // length of the data contained within the sequence
|
|
asn1.Ber.Boolean,
|
|
1,
|
|
255, // true
|
|
asn1.Ber.Boolean,
|
|
1,
|
|
0, // false
|
|
)
|
|
|
|
### new asn1.BerWriter([options])
|
|
|
|
Instantiates and returns an instance of the `BerWriter` class:
|
|
|
|
var options = {
|
|
size: 1024,
|
|
growthFactor: 8
|
|
}
|
|
|
|
var writer = new asn1.BerWriter(options)
|
|
|
|
The optional `options` parameter is an object, and can contain the following
|
|
items:
|
|
|
|
* `size` - The writer uses a Node.js `Buffer` instance to render ASN1.BER types
|
|
to a binary string, when creating the `Buffer` instance its size must be
|
|
specified, the `size` attribute specifies how bit this buffer should
|
|
initially be, when the `Buffer` instance has not space a new instance will
|
|
be created which will be larger than the original `size` specified, this
|
|
growth is controlled by the `growthFactor` variable, defaults to `1024`
|
|
* `growthFactor` - When the Node.js `Buffer` instance used to render ASN1.BER
|
|
types to a binary string becomes full the `Buffer` instance will be made
|
|
larger, the size of the new instance is calculated using its current size
|
|
multiplied by the `growthFactor` attribute, defaults to `8`, for example,
|
|
with a `size` of `1024` and a `growthFactor` of `8` when the `Buffer`
|
|
instance becomes full the new size would be calculated as `8192`
|
|
|
|
### writer.buffer
|
|
|
|
Once an object is complete the Node.js `Buffer` instance can be obtained via the
|
|
writes `buffer` attribute, e.g.:
|
|
|
|
var buffer = writer.buffer
|
|
|
|
The `Buffer` instance returned will be a copy of the internal instance used by
|
|
the writer and can be safely modified once obtained.
|
|
|
|
### writer.startSequence([tag]) & writer.endSequence()
|
|
|
|
The `startSequence()` method starts a new sequence. This method can be called
|
|
multiple times, and a matching call to the `endSequence()` method must be made
|
|
for each time `startSequence()` is called.
|
|
|
|
The optional `tag` parameter is one of the constants defined in the `asn1.Ber`
|
|
object, or a number if the required type is not pre-defined, and defaults to
|
|
`asn1.Ber.Sequence | asn1.Ber.Constructor`.
|
|
|
|
The following example writes two sequences and a boolean, each nested in the
|
|
previous:
|
|
|
|
writer.startSequence()
|
|
writer.startSequence()
|
|
writer.writeBoolean()
|
|
writer.endSequence()
|
|
writer.endSequence()
|
|
|
|
### writer.writeBoolean(boolean, [tag])
|
|
|
|
The `writeBoolean()` method writes an object of type boolean.
|
|
|
|
The `boolean` parameter can be the values `true` or `false`. The optional `tag`
|
|
parameter is one of the constants defined in the `asn1.Ber` object, or a number
|
|
if the required type is not pre-defined, and defaults to `asn1.Ber.Boolean`.
|
|
|
|
The following example writes two different boolean values, and in one case the
|
|
`tag` is specified as `asn1.Ber.Integer`:
|
|
|
|
writer.writeBoolean(false)
|
|
writer.writeBoolean(true, asn1.Ber.Integer)
|
|
|
|
### writer.writeBuffer(buffer, [tag])
|
|
|
|
The `writerBuffer()` method writes a Node.js `Buffer` instance as a sequence of
|
|
bytes, i.e. it will interpret it in any way.
|
|
|
|
The `buffer` parameter is an instance of the Node.js `Buffer` class. The
|
|
optional `tag` parameter is one of the constants defined in the `asn1.Ber`
|
|
object, or a number if the required type is not pre-defined. If no tag is
|
|
specified then `buffer` is assumed to already contain the tag and length for
|
|
the object to be written, i.e. it is assumed to contain a pre-formatted
|
|
ASN1.BER object such as a sequence, and will be inserted as is with no tag or
|
|
length.
|
|
|
|
The following two examples write a single byte in different ways. One provides
|
|
a tag, in which case `writeBuffer()` will write the tag and length, and in the
|
|
other no tag is provided, so `writeBuffer()` will NOT write a tag or length:
|
|
|
|
var b1 = Buffer.alloc([0x01])
|
|
writer.writeBuffer(b1, asn1.Ber.Integer)
|
|
|
|
var b2 = Buffer.alloc([asn1.Ber.Integer, 0x01, 0x01])
|
|
writer.writeBuffer(b2)
|
|
|
|
### writer.writeByte(byte)
|
|
|
|
The `writeByte()` method writes a single byte, i.e. not tag or length are
|
|
written.
|
|
|
|
The `byte` parameter is an integer in the range `0` to `255`.
|
|
|
|
The `writeByte()` method can be used to insert ad-hoc data into the data stream.
|
|
The following example writes the integer `2` using only the `writeByte()` method
|
|
instead of using the `writeInt()` method:
|
|
|
|
writer.writeByte(asn1.Ber.Integer) // tag
|
|
writer.writeByte(1) // length == 1
|
|
writer.writeByte(2) // integer == 2
|
|
|
|
### writer.writeEnumeration(integer, [tag])
|
|
|
|
The `writeEnumeration()` method writes the value of an enumerated type.
|
|
|
|
The `integer` parameter is the integer value of the enumerated type. The
|
|
optional `tag` parameter is one of the constants defined in the `asn1.Ber`
|
|
object, or a number if the required type is not pre-defined, and defaults to
|
|
`asn1.Ber.Enumeration`.
|
|
|
|
The following example writes the value `2` which identifies a value for an
|
|
enumerated type:
|
|
|
|
writer.writeEnumeration(2, asn1.Ber.Enumeration)
|
|
|
|
### writer.writeInt(integer, [tag])
|
|
|
|
The `writeInt()` method writes a signed or unsigned integer.
|
|
|
|
The `integer` parameter is a signed or unsigned integer of 4 bytes in size. The
|
|
optional `tag` parameter is one of the constants defined in the `asn1.Ber`
|
|
object, or a number if the required type is not pre-defined, and defaults to
|
|
`asn1.Ber.Integer`.
|
|
|
|
The following example writes the value `-123`, and since no tag is provided the
|
|
type `asn1.Ber.Integer` will be used:
|
|
|
|
writer.writeInt(-123)
|
|
|
|
### writer.writeNull()
|
|
|
|
The `writeNull()` method writes 2 bytes, the first is the type `asn1.Ber.Null`
|
|
and the second is the 1 byte integer `0`.
|
|
|
|
The following example writes a key and value pair, with the value being
|
|
specified as undefined using `writeNull()`:
|
|
|
|
writer.writeString("description") // key is a string
|
|
writer.writeNull() // value is undefined
|
|
|
|
### writer.writeOID(oid, [tag])
|
|
|
|
The `writeOID()` method writes an object identifier.
|
|
|
|
The `oid` parameter is an object identifier in the formar of `1.3.6.1`, i.e.
|
|
dotted decimal. The optional `tag` parameter is one of the constants defined
|
|
in the `asn1.Ber` object, or a number if the required type is not pre-defined,
|
|
and defaults to `asn1.Ber.OID`.
|
|
|
|
The following example writes the object identifier `1.3.6.1`, and since no tag
|
|
is provided the type `asn1.Ber.OID` will be used:
|
|
|
|
writer.writeOID("1.3.6.1")
|
|
|
|
### writer.writeString(string, [tag])
|
|
|
|
The `writeString()` method writes a string.
|
|
|
|
The `string` parameter is a string, i.e. not a Node.js `Buffer` instance. The
|
|
optional `tag` parameter is one of the constants defined in the `asn1.Ber`
|
|
object, or a number if the required type is not pre-defined, and defaults to
|
|
`asn1.Ber.OctetString`.
|
|
|
|
The following example writes the string `description`, and since no tag is
|
|
provided the type `asn1.Ber.OctetString` will be used:
|
|
|
|
writer.writeString("description")
|
|
|
|
### writer.writeStringArray(strings, [tag])
|
|
|
|
The `writeStringArray()` method writes multiple strings by called the
|
|
`writeString()` method.
|
|
|
|
The `strings` parameter is an array of strings to pass when making repeated
|
|
calls to the `writeString()` method. The optional `tag` parameter is one of
|
|
the constants defined in the `asn1.Ber` object, or a number if the required
|
|
type is not pre-defined, and defaults to `asn1.Ber.OctetString`.
|
|
|
|
The following two examples are equivilant, and will both write two strings, and
|
|
since no tag is provided the type `asn1.Ber.OctetString` will be used:
|
|
|
|
writer.writeString("one")
|
|
writer.writeString("two")
|
|
|
|
writer.writeStringArray(["one", "two"])
|
|
|
|
## Reading Objects
|
|
|
|
### Overview
|
|
|
|
The reader interface reads from a Node.js `Buffer` instance containing an
|
|
ASN1.BER object. A number of methods are used to read specific types of data,
|
|
also ensuring the required tags also exist for each. An instance of the
|
|
`BerReader` class is instantiated, providing the constructor with a Node.js
|
|
`Buffer` instance, and methods used to do this.
|
|
|
|
As data is read from the `Buffer` instance an offset to the next location from
|
|
which to read data, i.e. following the last data read, is maintained and
|
|
incremented based on the amount of data read per method call.
|
|
|
|
In the following example the appropriate methods are used to read a buffer
|
|
containing an ASN1.BER object:
|
|
|
|
var buffer = Buffer.alloc([
|
|
asn1.Ber.Sequence | asn1.Ber.Constructor,
|
|
6, // length of the data contained within the sequence
|
|
asn1.Ber.Boolean,
|
|
1,
|
|
255, // true
|
|
asn1.Ber.Boolean,
|
|
1,
|
|
0, // false
|
|
)
|
|
|
|
var reader = new asn1.BerReader(buffer)
|
|
|
|
reader.readSequence(asn1.Ber.Sequence | asn1.Ber.Constructor)
|
|
reader.readBoolean() // 1st boolean is true
|
|
reader.readBoolean() // 2nd boolean is false
|
|
|
|
### new asn1.BerReader(buffer)
|
|
|
|
Instantiates and returns an instance of the `BerReader` class:
|
|
|
|
var reader = new asn1.BerReader(buffer)
|
|
|
|
The `buffer` parameter is an instance of the Node.js `Buffer` class, this is
|
|
typically referred to as the "input buffer" throughout this documentation.
|
|
|
|
### reader.peek()
|
|
|
|
The `peek()` method is sugar for the following method call:
|
|
|
|
var byte = reader.readByte(true)
|
|
|
|
### reader.readBoolean([tag])
|
|
|
|
The `readBoolean()` method reads a boolean value from the input buffer and
|
|
returns either `true` or `false`.
|
|
|
|
The optional `tag` parameter is one of the constants defined in the `asn1.Ber`
|
|
object, or a number if the required type is not pre-defined, and defaults to
|
|
`asn1.Ber.Boolean`.
|
|
|
|
The following example reads a boolean value, since no tag is specified the
|
|
type `asn1.Ber.Boolean` is used to validate the type being read:
|
|
|
|
var bool = reader.readBoolean()
|
|
|
|
### reader.readByte([peek])
|
|
|
|
The `readByte()` method reads and returns the next byte from the input buffer,
|
|
and advances the read offset by 1.
|
|
|
|
The optional `peek` parameter, if passed as `true`, will cause the read offset
|
|
NOT to be incremented. This provides a way to look at the next byte in the
|
|
input stream without consuming it.
|
|
|
|
The following example reads a a boolean value if the next object is of the type
|
|
`asn1.Ber.Boolean`:
|
|
|
|
if (reader.readByte(true) == asn1.Ber.Boolean) {
|
|
reader.readByte() // consume the type
|
|
reader.readByte() // consume length, we assume 1, we /should/ really check
|
|
|
|
var value = reader.readByte() ? true : false
|
|
}
|
|
|
|
### reader.readEnumeration([tag])
|
|
|
|
The `readEnumeration()` method reads an integer value of the enumerated type
|
|
and returns it.
|
|
|
|
The optional `tag` parameter is one of the constants defined in the `asn1.Ber`
|
|
object, or a number if the required type is not pre-defined, and defaults to
|
|
`asn1.Ber.Enumeration`.
|
|
|
|
The following example reads an enumerated value, since no tag is specified the
|
|
type `asn1.Ber.Enumeration` is used to validate the type being read:
|
|
|
|
var integer = reader.readEnumeration()
|
|
|
|
### reader.readInt([tag])
|
|
|
|
The `readEnumeration()` method reads a signed or unsigned integer and returns
|
|
it.
|
|
|
|
The optional `tag` parameter is one of the constants defined in the `asn1.Ber`
|
|
object, or a number if the required type is not pre-defined, and defaults to
|
|
`asn1.Ber.Integer`.
|
|
|
|
The following example reads an integer value, since no tag is specified the
|
|
type `asn1.Ber.Integer` is used to validate the type being read:
|
|
|
|
var integer = reader.readInt()
|
|
|
|
### reader.readOID([tag])
|
|
|
|
The `readOID()` method reads an object identifier and returns it in the format
|
|
`1.3.6.1`, i.e. in dotted decimal.
|
|
|
|
The optional `tag` parameter is one of the constants defined in the `asn1.Ber`
|
|
object, or a number if the required type is not pre-defined, and defaults to
|
|
`asn1.Ber.OID`.
|
|
|
|
The following example reads a object identifier, since no tag is specified the
|
|
type `asn1.Ber.OID` is used to validate the type being read:
|
|
|
|
var oid = reader.readOID()
|
|
|
|
### reader.readSequence([tag])
|
|
|
|
The `readSequence()` method attempts to read the next sequence and its length
|
|
from the input buffer.
|
|
|
|
The optional `tag` parameter is one of the constants defined in the `asn1.Ber`
|
|
object, or a number if the required type is not pre-defined. If no `tag` is
|
|
defined the type of the sequence is not verified and simply accepted.
|
|
|
|
If there was not enough data left in the input buffer to read the sequence then
|
|
`null` will be returned, otherwise the sequences type will be returned, i.e.
|
|
if `tag` was specified then the sequence type will be equal to `tag`.
|
|
|
|
The following example reads all sequences, each containing a key and value,
|
|
until there are no more sequences left:
|
|
|
|
var kvs = []
|
|
|
|
while (true) {
|
|
var tag = reader.readSequence() // We don't care about the sequences type
|
|
if (! tag)
|
|
break
|
|
|
|
var key = reader.readString()
|
|
var value = reader.readString()
|
|
|
|
kvs.push({key: key, value: value})
|
|
}
|
|
|
|
### reader.readString([tag], [retbuf])
|
|
|
|
The `readString()` method reads a value from the input buffer, and if `retbuf`
|
|
is specified as `true` will return a Node.js `Buffer` instance containing the
|
|
bytes read, otherwise an attempt to parse the data as a `utf8` string is made
|
|
and the resulting string will be returned, i.e. not a `Buffer` instance.
|
|
|
|
The optional `tag` parameter is one of the constants defined in the `asn1.Ber`
|
|
object, or a number if the required type is not pre-defined, and defaults to
|
|
`asn1.Ber.OctetString`.
|
|
|
|
The following example reads a string and requests it be returned as a `Buffer`
|
|
instance:
|
|
|
|
var buffer = reader.readString(asn1.Ber.OctetString, true)
|
|
|
|
# Changes
|
|
|
|
## Version 1.0.0 - 22/07/2017
|
|
|
|
* Negative numbers are read when unsigned integers should be used in some
|
|
places
|
|
* The `tag` parameter to the `Writer.writeBuffer()` method in
|
|
`lib/ber/writer.js` should be optional so that pre-formatted buffers can be
|
|
written that already include a tag and length
|
|
* The `license` attribute is missing from `package.json`
|
|
* Create `.npmignore` file
|
|
* Correct names of error classes imported and used in `lib/ber/writer.js`
|
|
which result in `not defined` error messages
|
|
* Remove `require(sys)` statement from `tst/ber/writer.test.js` because it is
|
|
no longer supported or required
|
|
* Boolean logic error using `instanceof` in the `writeStringArray()` method in
|
|
`lib/ber/writer.js`
|
|
* Improve documentation
|
|
* Migrate tests to the mocha framework
|
|
* The `tag` parameter should be optional for methods which imply a type
|
|
* Sort out indentation and use tabs
|
|
|
|
## Version 1.0.1 - 22/07/2017
|
|
|
|
* Minor changes to the README.md file
|
|
|
|
## Version 1.0.2 - 01/02/2018
|
|
|
|
* The lib/ber/reader.js/Reader.prototype.readInt() method always uses the tag
|
|
type ASN1.Integer
|
|
* The lib/ber/reader.js/Reader.prototype.readEnumeration() method always uses
|
|
the tag type ASN1.Enumeration
|
|
|
|
## Version 1.0.3 - 06/06/2018
|
|
|
|
* Added NoSpaceships Ltd as a maintainer
|
|
|
|
## Version 1.0.6 - 06/06/2018
|
|
|
|
* Transfer to NoSpaceships github account
|
|
|
|
## Version 1.0.7 - 06/06/2018
|
|
|
|
* Update author to NoSpaceships
|
|
|
|
## Version 1.0.9 - 07/06/2018
|
|
|
|
* Remove redundant sections from README.md
|
|
|
|
## Version 1.1.0 - 08/06/2018
|
|
|
|
* Change author and add write support for short OIDs
|
|
|
|
## Version 1.1.1 - 20/06/2021
|
|
|
|
* Update buffer allocation to supported Buffer.alloc calls
|
|
|
|
## Version 1.1.2 - 08/12/2021
|
|
|
|
* Fix zero-length octet string buffer writing
|
|
|
|
## Version 1.1.3 - 07/06/2022
|
|
|
|
* Fix 5-byte integer encoding and decoding
|
|
|
|
## Version 1.1.4 - 07/06/2022
|
|
|
|
* Remove modulo 2^32 on reading integers
|
|
|
|
## Version 1.2.0 - 07/06/2022
|
|
|
|
* Allow no tag check on reading integers
|
|
|
|
## Version 1.2.1 - 07/06/2022
|
|
|
|
* Add bit string reading
|
|
|
|
## Version 1.2.2 - 07/06/2022
|
|
|
|
* Improve writeInt() function
|
|
|
|
# License
|
|
|
|
Copyright (c) 2020 Mark Abrahams <mark@abrahams.co.nz>
|
|
|
|
Copyright (c) 2018 NoSpaceships Ltd <hello@nospaceships.com>
|
|
|
|
Copyright (c) 2017 Stephen Vickers <stephen.vickers.sv@gmail.com>
|
|
|
|
Copyright (c) 2011 Mark Cavage <mcavage@gmail.com>
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|