#oilleelssq-wq/MoonBit-PKI

    A pure-MoonBit ASN.1 DER codec and X.509 certificate toolkit.

    #Reading DER

    DerReader walks tag-length-value elements. read_single decodes exactly one element and rejects trailing bytes:

    ///|
    test "read a single element" {
    let value = try! @asn1.DerReader::from_bytes(b"\x02\x01\x2a").read_single()
    inspect(value.tag().number, content="2")
    inspect(value.content().length(), content="1")
    }

    #Decoding primitives

    ///|
    test "decode an integer" {
    let content = try! @asn1.DerReader::from_bytes(b"\x02\x01\x2a").read_expected(
    @asn1.TAG_INTEGER,
    )
    inspect(try! @asn1.decode_integer(content).to_string(), content="42")
    }

    #Object identifiers

    ///|
    test "decode an object identifier" {
    let content = try! @asn1.DerReader::from_bytes(
    b"\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01",
    ).read_expected(@asn1.TAG_OBJECT_IDENTIFIER)
    inspect(
    try! @asn1.decode_object_identifier(content).to_string(),
    content="1.2.840.113549.1.1.1",
    )
    }

    #Encoding

    Every encoder returns a complete tag-length-value element, and integers are written in the minimal two's complement form that DER requires:

    ///|
    test "encode an integer" {
    inspect(@hex.encode(try! @asn1.encode_int(128)), content="02020080")
    inspect(@hex.encode(try! @asn1.encode_int(-129)), content="0202ff7f")
    }

    DerWriter assembles nested structures from encoded elements:

    ///|
    test "build a sequence" {
    let writer = @asn1.DerWriter::new()
    writer.add(try! @asn1.encode_int(1)[:])
    writer.add(try! @asn1.encode_boolean(true)[:])
    inspect(@hex.encode(try! writer.to_sequence()), content="30060201010101ff")
    }

    A decoded value can be encoded again, and the result is canonical:

    ///|
    test "decode and re-encode" {
    let bytes : Bytes = b"\x30\x08\x02\x01\x2a\x30\x03\x01\x01\xff"
    let value = try! @asn1.DerReader::from_bytes(bytes).read_single()
    inspect(value.child(1).child(0).as_boolean(), content="true")
    inspect(
    @hex.encode(try! value.encode()) == @hex.encode(bytes),
    content="true",
    )
    }

    #Times

    UTCTime and GeneralizedTime decode into Asn1Time. Encoding always uses the UTC designator that DER requires:

    ///|
    test "encode a time" {
    let time = @asn1.Asn1Time::new(2024, 5, 6, 7, 8, 9)
    inspect(time.to_string(), content="2024-05-06T07:08:09Z")
    inspect(
    @hex.encode(try! @asn1.encode_utc_time(time)),
    content="170d3234303530363037303830395a",
    )
    }

    DerError

    pub(all) suberror DerError {
    UnexpectedEof
    InvalidTag(Int)
    IndefiniteLength
    Truncated(Int)
    LengthTooLong(Int)
    NonMinimalLength(Int)
    ExpectedTag(String, String)
    InvalidValue(String)
    TrailingData(Int)
    } derive(
    Debug
    )

    Errors raised while decoding ASN.1 DER/BER input.

    The decoder only accepts the canonical forms allowed by the Distinguished Encoding Rules (X.690): definite, minimal lengths and minimal high-tag-number and subidentifier encodings. Anything else is reported through one of these variants.

    DerError::to_repr

    DerError::to_string

    fn DerError::to_string(self : DerError) -> String

    Renders the error as a human readable message.

    Asn1Time

    pub struct Asn1Time {
    year : Int
    month : Int
    day : Int
    hour : Int
    minute : Int
    second : Int
    utc_offset_minutes : Int
    }

    A calendar date and time as carried by UTCTime and GeneralizedTime.

    utc_offset_minutes is the offset from UTC. DER requires the UTC designator (Z), so every encoding produced by this package uses an offset of 0; the decoder still accepts the numeric offsets that BER permits.

    Asn1Time::is_valid

    fn Asn1Time::is_valid(self : Asn1Time) -> Bool

    Whether every field is inside its valid range.

    Asn1Time::new

    fn Asn1Time::new(year : Int, month : Int, day : Int, hour : Int, minute : Int, second : Int, utc_offset_minutes? : Int) -> Asn1Time

    Creates a time value. Use Asn1Time::validate to check the field ranges.

    Asn1Time::to_string

    fn Asn1Time::to_string(self : Asn1Time) -> String

    Renders the value as 2024-05-06T07:08:09Z, or with a numeric offset when one is present.

    Der

    pub(all) enum Der {
    Primitive(Tag, BytesView)
    Constructed(Tag, Array[Der])
    }

    A decoded ASN.1 value.

    Der::as_bit_string

    fn Der::as_bit_string(self : Der) -> (Int, Bytes) raise DerError

    Decodes this value as a BIT STRING.

    Der::as_boolean

    fn Der::as_boolean(self : Der) -> Bool raise DerError

    Decodes this value as a BOOLEAN.

    Der::as_explicit

    fn Der::as_explicit(self : Der, number : Int) -> Der raise DerError

    Returns the single child of an explicitly tagged value (X.690, 8.14.3).

    Der::as_integer

    fn Der::as_integer(self : Der) ->
    BigInt
    raise DerError

    Decodes this value as an INTEGER.

    Der::as_null

    fn Der::as_null(self : Der) -> Unit raise DerError

    Checks that this value is a NULL.

    Der::as_object_identifier

    fn Der::as_object_identifier(self : Der) -> ObjectIdentifier raise DerError

    Decodes this value as an OBJECT IDENTIFIER.

    Der::as_octet_string

    fn Der::as_octet_string(self : Der) -> Bytes raise DerError

    Copies this value's OCTET STRING content into a fresh buffer.

    Der::as_octet_string_view

    fn Der::as_octet_string_view(self : Der) -> BytesView raise DerError

    Returns a view of this value's OCTET STRING content.

    Der::as_sequence

    fn Der::as_sequence(self : Der) -> Array[Der] raise DerError

    Returns the children of this value, accepting SEQUENCE and SET.

    Der::as_string

    fn Der::as_string(self : Der) -> String raise DerError

    Decodes this value as a string, dispatching on the universal tag number.

    TeletexString and T61String are not decoded; their content is usually Latin-1, so callers should read it as bytes and convert explicitly.

    Der::as_time

    fn Der::as_time(self : Der) -> Asn1Time raise DerError

    Decodes this value as a time: UTCTime or GeneralizedTime.

    Der::as_utf8_string

    fn Der::as_utf8_string(self : Der) -> String raise DerError

    Decodes this value as a UTF8String.

    Der::child

    fn Der::child(self : Der, index : Int) -> Der raise DerError

    Returns the index-th child of a constructed value.

    Der::children

    fn Der::children(self : Der) -> Array[Der] raise DerError

    Returns the children of a constructed value.

    Der::content

    fn Der::content(self : Der) -> BytesView raise DerError

    Returns the raw content octets of a primitive value.

    Der::encode

    fn Der::encode(self : Der) -> Bytes raise DerError

    Re-encodes a decoded value in DER.

    Children of a SET are reordered by their encoding, so the result is canonical even if the input was not.

    Der::is_constructed

    fn Der::is_constructed(self : Der) -> Bool

    Whether this value is constructed.

    Der::tag

    fn Der::tag(self : Der) -> Tag

    The tag of this value.

    Der::to_string

    fn Der::to_string(self : Der) -> String

    Renders the value as an indented tree, handy for debugging and for the command line tools built on top of this package.

    DerReader

    pub struct DerReader {
    input : BytesView
    pos : Int
    }

    A cursor that decodes tag-length-value elements from a BytesView.

    The reader never copies: content views point into the original buffer.

    DerReader::from_bytes

    fn DerReader::from_bytes(input : Bytes) -> DerReader

    Creates a reader over a byte buffer.

    DerReader::is_empty

    fn DerReader::is_empty(self : DerReader) -> Bool

    Whether the cursor has reached the end of the input.

    DerReader::new

    fn DerReader::new(input : BytesView) -> DerReader

    Creates a reader over input.

    DerReader::offset

    fn DerReader::offset(self : DerReader) -> Int

    Absolute offset of the cursor inside the underlying byte buffer.

    DerReader::peek_tag

    fn DerReader::peek_tag(self : DerReader) -> Tag raise DerError

    Returns the tag of the next element without consuming it.

    DerReader::read_all

    fn DerReader::read_all(self : DerReader) -> Array[Der] raise DerError

    Decodes every remaining element in the input.

    DerReader::read_element

    fn DerReader::read_element(self : DerReader) -> Der raise DerError

    Decodes one element, recursing into constructed values.

    DerReader::read_expected

    fn DerReader::read_expected(self : DerReader, number : Int) -> BytesView raise DerError

    Reads one element, asserting that it is a primitive universal value with the given tag number, and returns its content octets.

    DerReader::read_single

    fn DerReader::read_single(self : DerReader) -> Der raise DerError

    Decodes exactly one element, raising TrailingData if anything follows it.

    DerReader::read_tlv

    fn DerReader::read_tlv(self : DerReader) -> (Tag, BytesView) raise DerError

    Reads one element and returns its tag together with a view of its content octets. The cursor advances past the element.

    DerReader::remaining

    fn DerReader::remaining(self : DerReader) -> Int

    Number of bytes left to read.

    DerWriter

    pub struct DerWriter {
    items : Array[Bytes]
    }

    Builds a constructed DER value from already-encoded elements.

    This is the ergonomic way to assemble nested structures such as sequences, sets and explicitly tagged fields.

    DerWriter::add

    fn DerWriter::add(self : DerWriter, element : BytesView) -> Unit

    Appends one complete tag-length-value element.

    DerWriter::is_empty

    fn DerWriter::is_empty(self : DerWriter) -> Bool

    Whether the writer is empty.

    DerWriter::length

    fn DerWriter::length(self : DerWriter) -> Int

    Number of elements added so far.

    DerWriter::new

    fn DerWriter::new() -> DerWriter

    Creates an empty writer.

    DerWriter::to_content

    fn DerWriter::to_content(self : DerWriter) -> Bytes

    Concatenates everything added so far, without adding a wrapper.

    DerWriter::to_explicit

    fn DerWriter::to_explicit(self : DerWriter, number : Int) -> Bytes raise DerError

    Wraps everything added so far in an explicitly tagged constructed value (X.690, 8.14.3).

    DerWriter::to_sequence

    fn DerWriter::to_sequence(self : DerWriter) -> Bytes raise DerError

    Wraps everything added so far in a SEQUENCE.

    DerWriter::to_set_of

    fn DerWriter::to_set_of(self : DerWriter) -> Bytes raise DerError

    Wraps everything added so far in a SET OF, sorting the elements by their encoding as required for DER (X.690, 11.6).

    ObjectIdentifier

    pub struct ObjectIdentifier {
    arcs : Array[Int64]
    }

    A decoded OBJECT IDENTIFIER, stored as its arc values.

    The first arc is always 0, 1, or 2 and the second arc is at most 39 when the first arc is 0 or 1 (X.690, 8.19).

    ObjectIdentifier::arcs

    fn ObjectIdentifier::arcs(self : ObjectIdentifier) -> Array[Int64]

    The arcs of the identifier.

    ObjectIdentifier::from_arcs

    fn ObjectIdentifier::from_arcs(arcs : Array[Int64]) -> ObjectIdentifier raise DerError

    Creates an object identifier from its arcs, validating the two leading arcs.

    ObjectIdentifier::from_string

    fn ObjectIdentifier::from_string(text : StringView) -> ObjectIdentifier raise DerError

    Parses a dotted-decimal object identifier such as 2.5.29.17.

    ObjectIdentifier::to_string

    fn ObjectIdentifier::to_string(self : ObjectIdentifier) -> String

    Renders the identifier in dotted decimal notation, for example 1.2.840.113549.1.1.1.

    Tag

    pub struct Tag {
    cls : TagClass
    constructed : Bool
    number : Int
    } derive(Eq,
    Debug
    )

    A decoded ASN.1 tag: its class, whether the value is constructed, and the tag number.

    Tag::equal

    fn Tag::equal(Tag, Tag) -> Bool

    Tag::new

    fn Tag::new(cls : TagClass, number : Int, constructed? : Bool) -> Tag

    Creates a tag. constructed defaults to false.

    Tag::not_equal

    fn Tag::not_equal(x : Tag, y : Tag) -> Bool

    Tag::to_repr

    Tag::to_string

    fn Tag::to_string(self : Tag) -> String

    Renders the tag in a compact, human readable form such as [CONTEXT 0 constructed].

    TagClass

    pub(all) enum TagClass {
    Universal
    Application
    ContextSpecific
    Private
    } derive(Eq,
    Debug
    )

    ASN.1 tag class, carried in the two most significant bits of an identifier octet (X.690, 8.1.2.2).

    TagClass::equal

    fn TagClass::equal(TagClass, TagClass) -> Bool

    TagClass::not_equal

    fn TagClass::not_equal(x : TagClass, y : TagClass) -> Bool

    TagClass::to_repr

    TAG_BIT_STRING

    let TAG_BIT_STRING : Int

    TAG_BMP_STRING

    let TAG_BMP_STRING : Int

    TAG_BOOLEAN

    let TAG_BOOLEAN : Int

    Universal tag numbers defined by X.680 that are used by the encoder and decoder.

    TAG_ENUMERATED

    let TAG_ENUMERATED : Int

    TAG_GENERALIZED_TIME

    let TAG_GENERALIZED_TIME : Int

    TAG_IA5_STRING

    let TAG_IA5_STRING : Int

    TAG_INTEGER

    let TAG_INTEGER : Int

    TAG_NULL

    let TAG_NULL : Int

    TAG_NUMERIC_STRING

    let TAG_NUMERIC_STRING : Int

    TAG_OBJECT_IDENTIFIER

    let TAG_OBJECT_IDENTIFIER : Int

    TAG_OCTET_STRING

    let TAG_OCTET_STRING : Int

    TAG_PRINTABLE_STRING

    let TAG_PRINTABLE_STRING : Int

    TAG_SEQUENCE

    let TAG_SEQUENCE : Int

    TAG_SET

    let TAG_SET : Int

    TAG_T61_STRING

    let TAG_T61_STRING : Int

    TAG_UNIVERSAL_STRING

    let TAG_UNIVERSAL_STRING : Int

    TAG_UTC_TIME

    let TAG_UTC_TIME : Int

    TAG_UTF8_STRING

    let TAG_UTF8_STRING : Int

    TAG_VISIBLE_STRING

    let TAG_VISIBLE_STRING : Int

    compare_octets

    fn compare_octets(a : BytesView, b : BytesView) -> Int

    Compares two octet strings the way DER orders the components of a SET OF: byte by byte as unsigned values, with a prefix ordering before its extension.

    days_in_month

    fn days_in_month(year : Int, month : Int) -> Int

    Number of days in a month, or 0 when the month is out of range.

    decode_bit_string

    fn decode_bit_string(content : BytesView) -> (Int, Bytes) raise DerError

    Decodes the value bits of a BIT STRING.

    The first content octet is the number of unused trailing bits of the last byte (0-7); the remaining octets hold the value with those bits cleared, as required by X.690, 11.2.

    decode_bmp_string

    fn decode_bmp_string(content : BytesView) -> String raise DerError

    Decodes a BMPString, which is UTF-16BE without a byte order mark.

    BMPString is UCS-2, so characters outside the basic multilingual plane (which UTF-16 encodes as surrogate pairs) are rejected.

    decode_boolean

    fn decode_boolean(content : BytesView) -> Bool raise DerError

    Decodes the content octets of a BOOLEAN.

    DER requires 0xFF for true and 0x00 for false; any other value is rejected.

    decode_generalized_time

    fn decode_generalized_time(content : BytesView) -> Asn1Time raise DerError

    Decodes a GeneralizedTime with seconds present and no fractional seconds, which is the only form DER and RFC 5280 allow.

    decode_ia5_string

    fn decode_ia5_string(content : BytesView) -> String raise DerError

    Decodes an IA5String, whose alphabet is the 128 ASCII characters (X.680, 43).

    decode_integer

    fn decode_integer(content : BytesView) ->
    BigInt
    raise DerError

    Decodes the content octets of an INTEGER into an arbitrary-precision integer.

    DER INTEGERs use two's complement, big-endian encoding with at least one content octet.

    decode_null

    fn decode_null(content : BytesView) -> Unit raise DerError

    Decodes the content octets of a NULL, which must be empty.

    decode_numeric_string

    fn decode_numeric_string(content : BytesView) -> String raise DerError

    Decodes a NumericString, whose alphabet is the digits and the space.

    decode_object_identifier

    fn decode_object_identifier(content : BytesView) -> ObjectIdentifier raise DerError

    Decodes the content octets of an OBJECT IDENTIFIER.

    decode_printable_string

    fn decode_printable_string(content : BytesView) -> String raise DerError

    Decodes a PrintableString, whose alphabet is ASCII (X.680, 41).

    decode_universal_string

    fn decode_universal_string(content : BytesView) -> String raise DerError

    Decodes a UniversalString, which is UTF-32BE.

    decode_utc_time

    fn decode_utc_time(content : BytesView) -> Asn1Time raise DerError

    Decodes a UTCTime. Two-digit years are mapped to 1950-2049, as required by RFC 5280.

    decode_utf8_string

    fn decode_utf8_string(content : BytesView) -> String raise DerError

    Decodes a UTF8String.

    decode_visible_string

    fn decode_visible_string(content : BytesView) -> String raise DerError

    Decodes a VisibleString, whose alphabet is the printable ASCII range.

    encode_bit_string

    fn encode_bit_string(value : BytesView, unused_bits? : Int) -> Bytes raise DerError

    Encodes a BIT STRING.

    encode_bit_string_content

    fn encode_bit_string_content(value : BytesView, unused_bits? : Int) -> Bytes raise DerError

    Encodes the content octets of a BIT STRING: the unused-bit count followed by the value octets (X.690, 8.6 and 11.2).

    encode_bmp_string

    fn encode_bmp_string(value : StringView) -> Bytes raise DerError

    Encodes a BMPString as UTF-16BE without a byte order mark (X.680, 8.4).

    encode_boolean

    fn encode_boolean(value : Bool) -> Bytes raise DerError

    Encodes a BOOLEAN. DER requires 0xFF for true and 0x00 for false.

    encode_context_primitive

    fn encode_context_primitive(number : Int, content : BytesView) -> Bytes raise DerError

    Encodes a primitive implicitly tagged value, for example a context-specific string inside a certificate.

    encode_enumerated

    fn encode_enumerated(value : Int) -> Bytes raise DerError

    Encodes an ENUMERATED from a machine integer.

    encode_explicit

    fn encode_explicit(number : Int, inner : BytesView) -> Bytes raise DerError

    Wraps a single encoded value in an explicitly tagged constructed value.

    encode_generalized_time

    fn encode_generalized_time(value : Asn1Time) -> Bytes raise DerError

    Encodes a GeneralizedTime. DER requires the Z designator.

    encode_ia5_string

    fn encode_ia5_string(value : StringView) -> Bytes raise DerError

    Encodes an IA5String, whose alphabet is the 128 ASCII characters.

    encode_int

    fn encode_int(value : Int) -> Bytes raise DerError

    Encodes an INTEGER from a machine integer.

    encode_int64

    fn encode_int64(value : Int64) -> Bytes raise DerError

    Encodes an INTEGER from a 64-bit integer.

    encode_integer

    fn encode_integer(value :
    BigInt
    ) -> Bytes raise DerError

    Encodes an INTEGER from an arbitrary-precision value.

    encode_integer_content

    fn encode_integer_content(value :
    BigInt
    ) -> Bytes

    Encodes the content octets of an INTEGER: minimal two's complement, big-endian, always at least one octet (X.690, 8.3 and 11.3).

    encode_length

    fn encode_length(length : Int) -> Bytes raise DerError

    Encodes a definite DER length using the minimal number of octets (X.690, 8.1.3 and 10.1).

    encode_null

    fn encode_null() -> Bytes raise DerError

    Encodes a NULL.

    encode_numeric_string

    fn encode_numeric_string(value : StringView) -> Bytes raise DerError

    Encodes a NumericString, whose alphabet is the digits and the space.

    encode_object_identifier

    fn encode_object_identifier(oid : ObjectIdentifier) -> Bytes raise DerError

    Encodes an OBJECT IDENTIFIER.

    encode_object_identifier_content

    fn encode_object_identifier_content(oid : ObjectIdentifier) -> Bytes raise DerError

    Encodes the content octets of an OBJECT IDENTIFIER, including the combined first two arcs (X.690, 8.19).

    encode_octet_string

    fn encode_octet_string(value : BytesView) -> Bytes raise DerError

    Encodes an OCTET STRING.

    encode_printable_string

    fn encode_printable_string(value : StringView) -> Bytes raise DerError

    Encodes a PrintableString, whose alphabet is defined by X.680 41.

    encode_sequence

    fn encode_sequence(elements : Array[BytesView]) -> Bytes raise DerError

    Encodes a constructed value from already-encoded elements.

    encode_set_of

    fn encode_set_of(elements : Array[BytesView]) -> Bytes raise DerError

    Encodes a DER SET OF, sorting the elements by their encoding.

    encode_tag

    fn encode_tag(tag : Tag) -> Bytes raise DerError

    Encodes the identifier octets of tag, using the high-tag-number form for tag numbers of 31 or more (X.690, 8.1.2).

    encode_time

    fn encode_time(value : Asn1Time) -> Bytes raise DerError

    Encodes a time as UTCTime when the year is in 1950-2049 and as GeneralizedTime otherwise, which is what X.509 profile requires.

    encode_tlv

    fn encode_tlv(tag : Tag, content : BytesView) -> Bytes raise DerError

    Encodes a complete tag-length-value element.

    encode_universal_string

    fn encode_universal_string(value : StringView) -> Bytes raise DerError

    Encodes a UniversalString as UTF-32BE (X.680, 8.7).

    encode_utc_time

    fn encode_utc_time(value : Asn1Time) -> Bytes raise DerError

    Encodes a UTCTime. DER requires years in 1950-2049 and the Z designator.

    encode_utf8_string

    fn encode_utf8_string(value : StringView) -> Bytes raise DerError

    Encodes a UTF8String.

    encode_visible_string

    fn encode_visible_string(value : StringView) -> Bytes raise DerError

    Encodes a VisibleString, whose alphabet is the printable ASCII range.

    is_leap_year

    fn is_leap_year(year : Int) -> Bool

    Whether the year is a leap year in the proleptic Gregorian calendar.