Low-Level ASN.1 API
This page is the detailed reference for awesn1 low-level APIs.
Use this page when you work directly with ASN.1 elements, tags, DER bytes, and primitive payload encoding/decoding.
For the data-class-first flow, see the Serialization Tutorial.
ASN.1 Model: Primitives vs Structures
awesn1 distinguishes two layers of abstraction:
- Generic ASN.1 elements:
Raw TLV nodes (
Asn1Element,Asn1Primitive,Asn1Structure) used when you parse bytes, inspect tags, assert structure, or build custom workflows. - Rich semantic types:
Domain-focused wrappers such as
Asn1Integer,Asn1Real,Asn1String,Asn1Time, andObjectIdentifierthat add type-specific behaviour on top of primitive bytes.
When doing low-level ASN.1 work, you usually parse/build with generic elements first, then convert to semantic types where needed. See ASN.1-Specific Rich Types.
More often than not, awesn1's first-class kotlinx-serialization integration will serve you better, because almost everything can be modelled using plain kotlin types.
Base Model
Asn1Element: Base type for all ASN.1 nodes (hastag,contentLength,derEncoded, pretty-print support).Asn1Element.Tag: Encodes tag number, tag class, andCONSTRUCTEDbit.Asn1Primitive: A leaf element with rawcontent: ByteArray.Asn1Structure: A constructed element withchildren: List<Asn1Element>.
Structure Implementations
Asn1Sequence: ordered structure (SEQUENCE).Asn1Set: set semantics, DER sorting rules.Asn1SetOf: set semantics + same-tag constraint across children.Asn1ExplicitlyTagged: context-specific constructed wrapper.Asn1CustomStructure: custom-tagged structure (constructed or primitive semantics).Asn1EncapsulatingOctetString:OCTET STRINGthat contains ASN.1 children.
Octet Strings
Asn1OctetStringis the abstraction.Asn1PrimitiveOctetStringholds raw bytes.Asn1EncapsulatingOctetStringholds child ASN.1 elements while still tagged asOCTET STRING.
ASN.1-Specific Rich Types
The core module includes rich semantic types beyond raw TLV primitives:
Asn1Integer: arbitrary-precision ASN.1 INTEGER handling.Asn1Real: ASN.1 REAL with arbitrary precision model and IEEE-754 bridges.Asn1Stringhierarchy: UTF8, Printable, IA5, BMP, Numeric, etc.Asn1Time: bridgesInstantto UTC/Generalized Time.Asn1BitString: bit-level representation with padding-bit tracking.BitSet: pure-Kotlin bitset implementation.ObjectIdentifier: OID support (string/components/bytes/UUID-based constructors). See Object Identifiers (OID) Deep Dive.
Object Identifiers (OID) Deep Dive
What an OID is
An ASN.1 OBJECT IDENTIFIER (OID) is a globally unique identifier in a hierarchical number tree, commonly written in dotted decimal notation:
1.2.840.1135492.5.4.31.3.6.1.5.5.7
Each number is called an arc (or node). The full path identifies exactly one object in the global OID namespace.
Why OIDs are Instrumental
OIDs are the backbone of many ASN.1-based ecosystems because they provide stable, interoperable identifiers for semantics, not just bytes. They are used to identify:
- algorithms (for example signature/hash algorithms)
- attributes (for example X.509 distinguished name fields)
- extensions (for example certificate extensions)
- protocol message/object types
- vendor/private namespaces
Without OIDs, two systems might parse the same ASN.1 structure but disagree on meaning.
In awesn1 specifically, OIDs are also central to open polymorphism by identifier; see the serialization tutorial section Open Polymorphism by OID.
Encoding Model (DER/BER content bytes)
ASN.1 OID content encoding is special:
- The first two arcs are folded into one value:
40 * arc0 + arc1. - Remaining arcs are encoded as base-128 varints.
- The ASN.1 tag is universal
OBJECT IDENTIFIER(0x06).
awesn1 handles this through ObjectIdentifier and its content-byte helpers.
For background on the historical encoding shape, see Microsoft's summary: About Object Identifier.
awesn1 Validation and Behaviour
Current ObjectIdentifier validation enforces:
- empty OIDs are rejected
- at least two arcs are required for node-based construction
- first arc must be
0,1, or2 - if first arc is
0or1, second arc must be< 40 - for current implementation constraints, first-byte-form parsing also limits accepted first-subidentifier values to the current supported range
For invalid inputs, constructors/decoders throw Asn1Exception/Asn1StructuralException.
Creating and Converting OIDs
ObjectIdentifier supports multiple entry points:
ObjectIdentifier(vararg nodes: UInt)for numeric arcsObjectIdentifier("1.2.840.113549")(also space-separated strings)ObjectIdentifier(uuid)for deterministic2.25.<uuid-as-integer>OIDs
Useful properties and conversions:
oid.bytes: ASN.1 OID content bytes (not full TLV)oid.nodes: lazily decoded arc listoid.toString(): dotted decimal form
Encoding/decoding APIs:
ObjectIdentifier.encodeToTlv(): wraps bytes inAsn1Primitive(Tag.OID, ...)ObjectIdentifier.decodeFromAsn1ContentBytes(bytes)Asn1Primitive.readOid()
Human-Readable Naming (KnownOIDs)
KnownOIDs is a mutable mapping from ObjectIdentifier to description strings. It is intended for diagnostics and developer-facing output, not wire semantics.
- Add custom descriptions with
KnownOIDs[oid] = "..." - If the
oidsmodule is on your classpath, callKnownOIDs.describeAll()to preload common names - Pretty-printing can use these names for easier debugging
Serialization Behaviour
ObjectIdentifier has dual behaviour:
- with DER codecs, it encodes/decodes as ASN.1
OBJECT IDENTIFIER - with non-DER serializers, it falls back to string form via
ObjectIdentifierStringSerializer
Raw ASN.1 Decoding
Typical Pipeline
- Parse DER bytes into
Asn1Element. - Assert expected tags/structure.
- Decode primitive content bytes into Kotlin/rich types.
Parse Entry Points
Asn1Element.parse(source: ByteArray): Asn1ElementAsn1Element.parseAll(source: ByteArray): List<Asn1Element>Asn1Element.parseFirst(source: ByteArray): Pair<Asn1Element, ByteArray>Asn1Element.parseFromDerHexString(derEncoded: String): Asn1Element
Tag assertion helpers:
Asn1Element.assertTag(tag: Asn1Element.Tag)Asn1Element.assertTag(tagNumber: ULong)
Example
High-level Decoding Contract (Asn1Decodable)
- Implement
doDecode(src)for type-specific decoding. - Optionally use/override
verifyTag(src, assertTag). - Use
decodeFromTlv(src, assertTag)for throwing decode. - Use
decodeFromTlvOrNull(src, assertTag)for non-throwing decode. - Use
decodeFromDer(src, assertTag)/decodeFromDerOrNull(src, assertTag)for direct DER decoding.
Generic Primitive Decoder
Asn1Primitive.decode(assertTag: ULong, transform: (ByteArray) -> T): TAsn1Primitive.decode(assertTag: Asn1Element.Tag, transform: (ByteArray) -> T): TAsn1Primitive.decodeOrNull(tag: ULong, transform: (ByteArray) -> T): T?
Asn1Primitive Typed Decode Helpers
| Category | Functions |
|---|---|
| Boolean | decodeToBoolean, decodeToBooleanOrNull |
| Integer family | decodeToInt, decodeToIntOrNull, decodeToLong, decodeToLongOrNull, decodeToUInt, decodeToUIntOrNull, decodeToULong, decodeToULongOrNull, decodeToAsn1Integer, decodeToAsn1IntegerOrNull |
| REAL family | decodeToAsn1Real, decodeToAsn1RealOrNull, decodeToDouble, decodeToDoubleOrNull, decodeToFloat, decodeToFloatOrNull |
| Enum family | decodeToEnumOrdinal, decodeToEnumOrdinalOrNull, decodeToEnum, decodeToEnumOrNull |
| String family | asAsn1String, decodeToUtf8String, decodeToUniversalString, decodeToIa5String, decodeToBmpString, decodeToTeletextString, decodeToPrintableString, decodeToNumericString, decodeToVisibleString, decodeToGeneralString, decodeToGraphicString, decodeToUnrestrictedString, decodeToVideotexString, decodeToString, decodeToStringOrNull |
| Time | decodeToInstant, decodeToInstantOrNull |
| Bit string | asAsn1BitString |
| Null handling | readNull, readNullOrNull |
Content-Byte Decode Helpers
These APIs decode only ASN.1 primitive payload bytes (no tag/length):
Int.decodeFromAsn1ContentBytesLong.decodeFromAsn1ContentBytesUInt.decodeFromAsn1ContentBytesULong.decodeFromAsn1ContentBytesBoolean.decodeFromAsn1ContentBytesString.decodeFromAsn1ContentBytesAsn1Integer.decodeFromAsn1ContentBytesAsn1Real.decodeFromAsn1ContentBytesObjectIdentifier.decodeFromAsn1ContentBytesInstant.decodeUtcTimeFromAsn1ContentBytesInstant.decodeGeneralizedTimeFromAsn1ContentBytes
Example
Raw ASN.1 Encoding
Typical Pipeline
- Start from a rich type or Kotlin primitive.
- Produce
Asn1Element(encodeToTlvor low-level primitive builders). - Materialize DER bytes (
derEncodedorencodeToDer).
High-level Encoding Contract (Asn1Encodable)
- Implement
encodeToTlv(). - Use
encodeToTlvOrNull()for non-throwing behaviour. - Use
encodeToDer()/encodeToDerOrNull()for DER output. - Use
withImplicitTag(...)overloads on encodable types.
Asn1Element.derEncoded provides lazy DER encoding for all ASN.1 nodes.
Primitive-Level Encoding Helpers
encodeToAsn1Primitive() is provided for:
Boolean,Int,Long,UInt,ULongEnum<*>Asn1Integer,Asn1RealDouble,Float(OrNullvariants available)String(UTF-8 ASN.1 string)
Content-Byte Encoding Helpers
encodeToAsn1ContentBytes() is provided for:
Boolean,Int,Long,UInt,ULongEnum<*>Asn1IntegerAsn1Real(member function)
Specialized Encoding Helpers
ByteArray.encodeToAsn1OctetStringPrimitive()ByteArray.encodeToAsn1BitStringPrimitive()ByteArray.encodeToAsn1BitStringContentBytes()Instant.encodeToAsn1UtcTimePrimitive()Instant.encodeToAsn1GeneralizedTimePrimitive()
Tagging: EXPLICIT and IMPLICIT
- EXPLICIT tagging wraps one or more children in a constructed context-specific container.
- IMPLICIT tagging replaces an element tag (with explicit control over class/constructed template).
Useful helpers:
Asn1.ExplicitlyTagged(tagNumber) { ... }Asn1.ExplicitTag(tagNumber)Asn1.ImplicitTag(tagNumber, tagClass)element withImplicitTag (...)tagNumber withClass TagClass.*tagNumber without CONSTRUCTED
ASN.1 Builder DSL Reference
Main DSL constructors under Asn1:
- Structures:
Sequence,SequenceOrNull,SequenceSafe,Set,SetOrNull,SetSafe,SetOf,SetOfOrNull,SetOfSafe,ExplicitlyTagged,ExplicitlyTaggedOrNull,OctetStringEncapsulating - Primitive builders:
Bool,Int(all integer overloads),Real(float/double),Enumerated,OctetString,BitString,Utf8String,PrintableString,Null,UtcTime,GeneralizedTime
Basic DSL
val frame = Asn1.Sequence {
+Asn1.Int(7)
+Asn1.Bool(true)
+Asn1.UtcTime(kotlin.time.Instant.parse("2026-01-01T12:30:45Z"))
}
val derHex = frame.derEncoded.toHexString()
derHex shouldBe /* (1)! */"30150201070101ff170d3236303130313132333034355a"
- Explore on asn1js.eu
Expanded Tagging
val tagged = Asn1.Sequence {
+Asn1.ExplicitlyTagged(1u) {
+Asn1.Bool(false)
}
+(Asn1.Utf8String("Foo") withImplicitTag (0xCAFEuL withClass TagClass.PRIVATE))
+(Asn1.Sequence { +Asn1.Int(42) } withImplicitTag (0x5EuL without CONSTRUCTED))
}
tagged.derEncoded.toHexString() shouldBe /* (1)! */"3013a103010100df83957e03466f6f9f5e0302012a"
- Explore on asn1js.eu
Debugging and Inspection
Asn1Element.prettyPrint()andAsn1Encodable.prettyPrintAsn1()provide verbose human-readable trees.Asn1Element.toDerHexString()renders DER as hex.- OID pretty printing can include names from
KnownOIDsmappings.
See Also
- Serialization Tutorial: DER format via
kotlinx.serialization.