asn1-ber
This project is a clone (not a fork) of the asn1 project, and as such is a drop in replacement. The 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 module), so I have committed to maintaining this clone and for it to be a drop in replacement.
This module provides the ability to generate and parse ASN1.BER objects.
This module is installed using node package manager (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.
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.jsBuffer
instance to render ASN1.BER types to a binary string, when creating theBuffer
instance its size must be specified, thesize
attribute specifies how bit this buffer should initially be, when theBuffer
instance has not space a new instance will be created which will be larger than the originalsize
specified, this growth is controlled by thegrowthFactor
variable, defaults to1024
-
growthFactor
- When the Node.jsBuffer
instance used to render ASN1.BER types to a binary string becomes full theBuffer
instance will be made larger, the size of the new instance is calculated using its current size multiplied by thegrowthFactor
attribute, defaults to8
, for example, with asize
of1024
and agrowthFactor
of8
when theBuffer
instance becomes full the new size would be calculated as8192
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 theWriter.writeBuffer()
method inlib/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 frompackage.json
- Create
.npmignore
file - Correct names of error classes imported and used in
lib/ber/writer.js
which result innot defined
error messages - Remove
require(sys)
statement fromtst/ber/writer.test.js
because it is no longer supported or required - Boolean logic error using
instanceof
in thewriteStringArray()
method inlib/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.