Skip to content

markabrahams/node-asn1-ber

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

50 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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.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.

About

Generate and parse ASN.1 BER objects

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •