class ASN1::BER

Overview

A single ASN.1 Basic Encoding Rules (BER) TLV element: an identifier (tag), a length and a payload. Used to build and parse SNMP, LDAP, X.509 and similar protocols.

require "bindata/asn1"

ber = ASN1::BER.new
ber.set_integer(42)
io.write_bytes(ber)

ber = io.read_bytes(ASN1::BER)
ber.get_integer # => 42

Typed payload accessors live in data_types.cr (#get_integer/#set_integer, #get_object_id/#set_object_id, #get_string, #get_boolean, ...). A constructed element can be split into / built from sub-elements with #children / #children=.

Defined in:

bindata/asn1.cr
bindata/asn1/data_types.cr
bindata/asn1/identifier.cr
bindata/asn1/length.cr

Constant Summary

AFTER_DESERIALIZE = [] of Nil
BEFORE_SERIALIZE = [] of Nil
DIRECT_STRING_TAGS = {UniversalTags::UTF8String, UniversalTags::CharacterString, UniversalTags::PrintableString, UniversalTags::IA5String, UniversalTags::OctetString, UniversalTags::NumericString, UniversalTags::VisibleString, UniversalTags::GeneralString, UniversalTags::GraphicString}

String types whose repertoire is ASCII-compatible, so the UTF-8 String.new decodes them directly. (T61String / VideotexString use the T.61 / videotex character sets, which are neither ASCII nor UTF-8, so they are not decoded here rather than mis-decoded.)

ENDIAN = ["big"]
GENERALIZEDTIME_FORMAT = /\A(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d+)?(Z|[+-]\d{4})\z/

YYYYMMDDHHMMSS(.fff)?(Z|±HHMM).

KLASS_NAME = [ASN1::BER]
MAX_OID_ARC_BYTES = 32

Largest sub-identifier we decode, in continuation octets. A base-128 arc of this many bytes holds ~224 bits — far beyond any real OID arc (a UUID-based 2.25 arc is 128-bit, ~19 bytes). Bounds the BigInt build against the super-linear CPU cost of a hostile continuation run.

PARTS = [{type: "basic", name: identifier, cls: ASN1::BER::Identifier, onlyif: nil, verify: nil, value: nil}, {type: "basic", name: length, cls: ASN1::BER::Length, onlyif: nil, verify: nil, value: nil}] of Nil
REMAINING = [] of Nil
UTCTIME_FORMAT = /\A(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})?(Z|[+-]\d{4})\z/

YYMMDDHHMM[SS](Z|±HHMM) — the seconds are optional.

Class Method Summary

Instance Method Summary

Instance methods inherited from class BinData

__format__ : IO::ByteFormat __format__, max_content_length : Int32 max_content_length, max_content_length=(max_content_length : Int32) max_content_length=, read(io : IO) : IO read, to_io(io : IO, format : IO::ByteFormat = IO::ByteFormat::SystemEndian) to_io, to_s(io) to_s, to_slice to_slice, write(io : IO) write

Class methods inherited from class BinData

bit_fields bit_fields, from_io(io : IO, format : IO::ByteFormat = IO::ByteFormat::SystemEndian) from_io, from_slice(bytes : Slice, format : IO::ByteFormat = IO::ByteFormat::SystemEndian) from_slice

Macros inherited from class BinData

__add_enum_field(name, cls, onlyif, verify, value, encoding, enum_type) __add_enum_field, __build_methods__ __build_methods__, after_deserialize(&block) after_deserialize, array(name, length, onlyif = nil, verify = nil, value = nil) array, before_serialize(&block) before_serialize, bit_field(onlyif = nil, verify = nil, endian = nil, &block) bit_field, bits(size, name, value = nil, default = nil) bits, bool(name, default = false) bool, bytes(name, length, onlyif = nil, verify = nil, value = nil, default = nil) bytes, custom(name, onlyif = nil, verify = nil, value = nil) custom, endian(format) endian, enum_bits(size, name) enum_bits, enum_field(size, name, onlyif = nil, verify = nil, value = nil) enum_field, field(type_declaration, onlyif = nil, verify = nil, value = nil, length = nil, read_next = nil, encoding = nil, endian = nil) field, float32(name, onlyif = nil, verify = nil, value = nil, default = nil) float32, float32be(name, onlyif = nil, verify = nil, value = nil, default = nil) float32be, float32le(name, onlyif = nil, verify = nil, value = nil, default = nil) float32le, float64(name, onlyif = nil, verify = nil, value = nil, default = nil) float64, float64be(name, onlyif = nil, verify = nil, value = nil, default = nil) float64be, float64le(name, onlyif = nil, verify = nil, value = nil, default = nil) float64le, group(name, onlyif = nil, verify = nil, value = nil, &block) group, int128(name, onlyif = nil, verify = nil, value = nil, default = nil) int128, int128be(name, onlyif = nil, verify = nil, value = nil, default = nil) int128be, int128le(name, onlyif = nil, verify = nil, value = nil, default = nil) int128le, int16(name, onlyif = nil, verify = nil, value = nil, default = nil) int16, int16be(name, onlyif = nil, verify = nil, value = nil, default = nil) int16be, int16le(name, onlyif = nil, verify = nil, value = nil, default = nil) int16le, int32(name, onlyif = nil, verify = nil, value = nil, default = nil) int32, int32be(name, onlyif = nil, verify = nil, value = nil, default = nil) int32be, int32le(name, onlyif = nil, verify = nil, value = nil, default = nil) int32le, int64(name, onlyif = nil, verify = nil, value = nil, default = nil) int64, int64be(name, onlyif = nil, verify = nil, value = nil, default = nil) int64be, int64le(name, onlyif = nil, verify = nil, value = nil, default = nil) int64le, int8(name, onlyif = nil, verify = nil, value = nil, default = nil) int8, int8be(name, onlyif = nil, verify = nil, value = nil, default = nil) int8be, int8le(name, onlyif = nil, verify = nil, value = nil, default = nil) int8le, remaining_bytes(name, onlyif = nil, verify = nil, default = nil) remaining_bytes, skip(length, onlyif = nil, verify = nil) skip, string(name, onlyif = nil, verify = nil, length = nil, value = nil, encoding = nil, default = nil) string, uint128(name, onlyif = nil, verify = nil, value = nil, default = nil) uint128, uint128be(name, onlyif = nil, verify = nil, value = nil, default = nil) uint128be, uint128le(name, onlyif = nil, verify = nil, value = nil, default = nil) uint128le, uint16(name, onlyif = nil, verify = nil, value = nil, default = nil) uint16, uint16be(name, onlyif = nil, verify = nil, value = nil, default = nil) uint16be, uint16le(name, onlyif = nil, verify = nil, value = nil, default = nil) uint16le, uint32(name, onlyif = nil, verify = nil, value = nil, default = nil) uint32, uint32be(name, onlyif = nil, verify = nil, value = nil, default = nil) uint32be, uint32le(name, onlyif = nil, verify = nil, value = nil, default = nil) uint32le, uint64(name, onlyif = nil, verify = nil, value = nil, default = nil) uint64, uint64be(name, onlyif = nil, verify = nil, value = nil, default = nil) uint64be, uint64le(name, onlyif = nil, verify = nil, value = nil, default = nil) uint64le, uint8(name, onlyif = nil, verify = nil, value = nil, default = nil) uint8, uint8be(name, onlyif = nil, verify = nil, value = nil, default = nil) uint8be, uint8le(name, onlyif = nil, verify = nil, value = nil, default = nil) uint8le, variable_array(name, read_next, onlyif = nil, verify = nil, value = nil) variable_array

Class Method Detail

def self.bit_fields #

[View source]

Instance Method Detail

def __format__ : IO::ByteFormat #

A group or bit_field captures the endianness at its declaration point, so declaring endian after one would silently leave it system-endian. Fail loudly.


[View source]
def bitstring_unused_bits : UInt8 #

The number of unused (padding) bits in the final data byte (0..7).


[View source]
def children #

Parses the payload as a sequence of nested BER elements. The max_content_length and #max_depth caps propagate to each child.

Only valid for a constructed element; on a primitive the payload is raw content, not a TLV list, so parsing it would yield garbage. Raises ASN1::Error in that case.


[View source]
def children=(parts) #

Encodes parts into the payload and marks this element constructed. The tag class/number are left untouched — set them yourself (e.g. to a universal Sequence/Set, or a constructed context tag) so #sequence? reflects the intended type; this accessor only guarantees the #constructed flag.


[View source]
def constructed #

[View source]
def constructed=(custom : Bool) #

[View source]
def eoc? #

Whether this is an end-of-contents marker (00 00) — a primitive universal EndOfContent element with an empty payload, used to terminate indefinite content.


[View source]
def extended #

[View source]
def extended=(parts : Array(ExtendedIdentifier)) #

[View source]
def extended? #

[View source]
def get_bit_array : BitArray #

The BIT STRING's significant bits as a BitArray, numbered MSB-first from the first data byte (ASN.1 bit 0 is the high bit of the first byte).


[View source]
def get_bitstring : Bytes #

Reads the BIT STRING data bytes (dropping the leading unused-bit count). The final byte's low #bitstring_unused_bits bits are padding. Any unused count 0..7 is accepted; use #get_bit_array for the exact significant bits.


[View source]
def get_boolean #

Reads the payload as a BOOLEAN.


[View source]
def get_bytes : Slice(UInt8) #

Returns the raw bytes


[View source]
def get_hexstring(universal = true, tag = UniversalTags::OctetString) #

Gets a hex representation of the bytes


[View source]
def get_integer(check_tags = {UniversalTags::Integer, UniversalTags::Enumerated}, check_class = TagClass::Universal) : Int64 #

Reads the payload as a two's-complement signed INTEGER (or ENUMERATED).

With the default universal check_class the tag is validated against check_tags. With a non-universal check_class (e.g. an SNMP context-tagged Counter/Gauge) only the class is checked — the universal-tag check is skipped, since check_tags are universal tags and don't apply to a context tag.


[View source]
def get_integer_bytes : Bytes #

[View source]
def get_object_id #

Returns the object ID in string format.

Sub-identifiers are decoded as big-endian base-128 numbers per X.690 §8.19 (every octet but the last has its high bit set), so arcs of any size are supported. BigInt is used because OID arcs are unbounded (e.g. UUID-based OIDs under 2.25, ITU-T X.667).


[View source]
def get_string #

Decodes the payload to a String. BMPString is transcoded from UTF-16BE and UniversalString from UTF-32BE (leniently — surrogate pairs are accepted, not strict UCS-2/UCS-4); the ASCII-repertoire types are read directly. An incomplete/malformed byte sequence raises ASN1::InvalidPayload; individual invalid code points may be substituted by the platform transcoder.


[View source]
def get_time : Time #

Reads the payload as a UTCTime or GeneralizedTime, normalised to UTC.

A time zone is required (Z or a numeric ±HHMM offset); a bare local time is rejected. UTCTime's two-digit year uses the RFC 5280 pivot (>= 50 => 19xx, < 50 => 20xx).


[View source]
def identifier : Identifier #

Components of a BER object


def identifier=(identifier : Identifier) #

Components of a BER object


def inspect(io : IO) : Nil #
Description copied from class Reference

Appends a String representation of this object which includes its class name, its object address and the values of all instance variables.

class Person
  def initialize(@name : String, @age : Int32)
  end
end

Person.new("John", 32).inspect # => #<Person:0x10fd31f20 @name="John", @age=32>

[View source]
def length : Length #

def length=(length : Length) #

def max_depth : Int32 #

Maximum nesting depth that #children and the indefinite-length reader will descend before raising MaxDepthExceeded. The default (100) guards both a recursive consumer #children walk and the eager recursion of #read over nested indefinite-length elements against stack overflow (a few KB of 30 80 … / 24 80 … encodes thousands of levels); 0 disables the limit (and with it the indefinite-read recursion bound). Propagated to each child alongside max_content_length.


[View source]
def max_depth=(max_depth : Int32) #

Maximum nesting depth that #children and the indefinite-length reader will descend before raising MaxDepthExceeded. The default (100) guards both a recursive consumer #children walk and the eager recursion of #read over nested indefinite-length elements against stack overflow (a few KB of 30 80 … / 24 80 … encodes thousands of levels); 0 disables the limit (and with it the indefinite-read recursion bound). Propagated to each child alongside max_content_length.


[View source]
def null? #

Whether this is a well-formed universal NULL element: primitive, tag 05, empty payload (X.690 §8.8).


[View source]
def payload : Bytes #

[View source]
def payload=(payload : Bytes) #

[View source]
def read(io : IO) : IO #
Description copied from class BinData

Reads the fields of this instance from io, in declaration order, and returns io. Raises BinData::ParseError (or BinData::VerificationException) on malformed input.


[View source]
def sequence? #

Whether this is a constructed universal Sequence or Set, i.e. an element whose payload is itself a list of BER elements (see #children).


[View source]
def set_bit_array(bits : BitArray) #

Sets a BIT STRING from a BitArray (numbered MSB-first, as ASN.1 expects).


[View source]
def set_bitstring(bytes : Bytes, unused_bits : Int = 0) #

Sets a BIT STRING from bytes, with unused_bits (0..7) of padding in the final byte.


[View source]
def set_boolean(value) #

Sets a BOOLEAN payload.


[View source]
def set_bytes(data, tag = UniversalTags::OctetString, tag_class = TagClass::Universal) #

Sets the raw payload bytes and the given tag.


[View source]
def set_hexstring(string, tag = UniversalTags::OctetString, tag_class = TagClass::Universal) #

Sets bytes from a hexstring


[View source]
def set_integer(value, tag = UniversalTags::Integer, tag_class = TagClass::Universal) #

Encodes value as a minimal two's-complement INTEGER payload. ameba:disable Metrics/CyclomaticComplexity


[View source]
def set_null #

Sets an empty, primitive NULL payload (encodes as 05 00).


[View source]
def set_object_id(oid) #

Sets a string representing an object ID.

Each arc is encoded as a big-endian base-128 number per X.690 §8.19. Arcs are parsed as BigInt, so arbitrarily large values are supported.


[View source]
def set_string(string, tag = UniversalTags::UTF8String, tag_class = TagClass::Universal) #

Sets a string. BMPString is encoded to UTF-16BE and UniversalString to UTF-32BE; every other type stores the string's UTF-8 bytes. tag may be a UniversalTags or its integer value.


[View source]
def set_time(time : Time, tag = UniversalTags::GeneralizedTime) #

Encodes time (converted to UTC) as a GeneralizedTime (default) or UTCTime, in the canonical …Z form. UTCTime can only represent years 1950..2049. Sub-second precision is dropped (DER forbids fractional seconds), so a Time with a fractional part does not round-trip exactly through #get_time.


[View source]
def size #

The current payload length in bytes. Reads from the payload itself (not the decoded Length, which is only refreshed on #write), so it is correct for in-memory-built objects and for indefinite-length elements too.


[View source]
def strict=(strict : Bool) #

When set, reject non-canonical (DER) encodings: non-minimal / indefinite length, non-{00,FF} / multi-byte BOOLEAN, empty / non-minimal INTEGER, non-minimal OID, embedded-NUL strings, and an out-of-order universal SET OF (tag 17). Default false keeps the BER-permissive behaviour. Set it before reading (the length checks fire during #read), and it propagates to #children.

Not (yet) covered: an implicitly context-tagged SET OF (indistinguishable from a SEQUENCE without a schema), GeneralizedTime/UTCTime canonical form, BIT STRING padding bits, and the primitive-vs-constructed rule for strings.


[View source]
def strict? : Bool #

When set, reject non-canonical (DER) encodings: non-minimal / indefinite length, non-{00,FF} / multi-byte BOOLEAN, empty / non-minimal INTEGER, non-minimal OID, embedded-NUL strings, and an out-of-order universal SET OF (tag 17). Default false keeps the BER-permissive behaviour. Set it before reading (the length checks fire during #read), and it propagates to #children.

Not (yet) covered: an implicitly context-tagged SET OF (indistinguishable from a SEQUENCE without a schema), GeneralizedTime/UTCTime canonical form, BIT STRING padding bits, and the primitive-vs-constructed rule for strings.


[View source]
def tag #

The universal tag as a UniversalTags enum. Raises unless this is a universal-class element.


[View source]
def tag_class #

[View source]
def tag_class=(tag : TagClass) #

[View source]
def tag_number #

[View source]
def tag_number=(tag_type : Int | UniversalTags) #

[View source]
def write(io : IO) #
Description copied from class BinData

Writes the fields of this instance to io in declaration order. Raises BinData::WriteError (or BinData::VerificationException) on failure.


[View source]