# See the file "COPYING" in the main distribution directory for copyright. module ASN1; ############################################################################### # ASN.1 structure decoding # # A Layman's Guide to a Subset of ASN.1, BER, and DER # http://luca.ntop.org/Teaching/Appunti/asn1.html # # ASN.1 Tutorial from Computer Networks and Open Systems: # An Application Development Perspective # https://www.obj-sys.com/asn1tutorial/asn1only.html # # The ASN1JS tool (http://lapo.it/asn1js and https://github.com/lapo-luchini/asn1js) # is invaluable in debugging ASN.1 ############################################################################### import spicy; #- ASN.1 data types ---------------------------------------------------------- # https://www.obj-sys.com/asn1tutorial/node124.html # https://www.obj-sys.com/asn1tutorial/node10.html public type ASN1Type = enum { Boolean = 1, Integer = 2, BitString = 3, OctetString = 4, NullVal = 5, ObjectIdentifier = 6, ObjectDescriptor = 7, InstanceOf = 8, Real = 9, Enumerated = 10, EmbeddedPDV = 11, UTF8String = 12, RelativeOID = 13, Sequence = 16, Set = 17, NumericString = 18, PrintableString = 19, TeletextString = 20, VideotextString = 21, IA5String = 22, UTCTime = 23, GeneralizedTime = 24, GraphicString = 25, VisibleString = 26, GeneralString = 27, UniversalString = 28, CharacterString = 29, BMPString = 30 }; #- ASN.1 data classes -------------------------------------------------------- public type ASN1Class = enum { Universal = 0, Application = 1, ContextSpecific = 2, Private = 3 }; #- ASN.1 tag definition (including length) ------------------------------------ type LengthType = unit { var len: uint64; var tag_len: uint8; data : bitfield(8) { num: 0..6; islong: 7; }; switch ( self.data.islong ) { 0 -> : void { self.len = self.data.num; self.tag_len = 1; } 1 -> : bytes &size=self.data.num &convert=$$.to_uint(spicy::ByteOrder::Network) { self.len = $$; self.tag_len = self.data.num + 1; } }; }; type ASN1Tag = unit { : bitfield(8) { type_: 0..4 &convert=ASN1Type($$); constructed: 5 &convert=cast($$); class: 6..7 &convert=ASN1Class($$); }; }; #- ASN.1 bit string ----------------------------------------------------------- # https://www.obj-sys.com/asn1tutorial/node10.html type ASN1BitString = unit(len: uint64, constructed: bool) { : uint8; # unused bits value_bits: bytes &size=(len - 1); # TODO - constructed form # https://github.com/zeek/spicy/issues/921 # `bytes` needs << and >> support before we can implement complex bitstrings # }; #- ASN.1 octet string --------------------------------------------------------- # https://www.obj-sys.com/asn1tutorial/node10.html type ASN1OctetString = unit(len: uint64, constructed: bool) { value: bytes &size = len; # TODO - constructed form }; #- ASN.1 various string types ------------------------------------------------- # https://www.obj-sys.com/asn1tutorial/node124.html type ASN1String = unit(tag: ASN1Tag, len: uint64) { var encoding: spicy::Charset; on %init { switch ( tag.type_ ) { # see "Restricted Character String Types" in # "Generic String Encoding Rules (GSER) for ASN.1 Types" # (https://datatracker.ietf.org/doc/html/rfc3641#section-3.2) case ASN1Type::PrintableString, ASN1Type::GeneralizedTime, ASN1Type::UTCTime: { self.encoding = spicy::Charset::ASCII; } case ASN1Type::UTF8String, ASN1Type::GeneralString, ASN1Type::CharacterString, ASN1Type::GraphicString, ASN1Type::IA5String, ASN1Type::NumericString, ASN1Type::TeletextString, ASN1Type::VideotextString, ASN1Type::VisibleString, # TODO: RFC3641 mentions special UTF-8 mapping rules for # BMPString and UniversalString. This *may* not be correct. ASN1Type::BMPString, ASN1Type::UniversalString: { self.encoding = spicy::Charset::UTF8; } } } value: ASN1OctetString(len, tag.constructed) &convert=$$.value.decode(self.encoding); } &convert=self.value; #- ASN.1 OID ------------------------------------------------------------------ # https://www.obj-sys.com/asn1tutorial/node124.html type ASN1ObjectIdentifierNibble = unit { data : bitfield(8) { num: 0..6; more: 7; }; } &convert=self.data; type ASN1ObjectIdentifier = unit(len: uint64) { var oidbytes: bytes; var temp: uint64; var oidstring: string; : uint8 if ( len >= 1 ) { self.temp = $$ / 40; self.oidbytes += ("%d" % (self.temp)).encode(); self.temp = $$ % 40; self.oidbytes += (".%d" % (self.temp)).encode(); self.temp = 0; } sublist: ASN1ObjectIdentifierNibble[len - 1] foreach { self.temp = ( self.temp<<7 ) | $$.num; if ( $$.more != 1 ) { self.oidbytes += (".%d" % (self.temp)).encode(); self.temp = 0; } } on %done { self.oidstring = self.oidbytes.decode(); } }; #- ASN.1 message header (tag + length information) ---------------------------- public type ASN1Header = unit { tag: ASN1Tag; len: LengthType; }; #- ASN.1 message body --------------------------------------------------------- public type ASN1Body = unit(head: ASN1Header, recursive: bool) { switch ( head.tag.type_ ) { ASN1Type::Boolean -> bool_value: uint8 &convert=cast($$) &requires=head.len.len==1; ASN1Type::Integer, ASN1Type::Enumerated -> num_value: bytes &size=head.len.len &convert=$$.to_int(spicy::ByteOrder::Big); ASN1Type::NullVal -> null_value: bytes &size=0 &requires=head.len.len==0; ASN1Type::BitString -> bitstr_value: ASN1BitString(head.len.len, head.tag.constructed); ASN1Type::OctetString -> str_value: ASN1OctetString(head.len.len, head.tag.constructed) &convert=$$.value.decode(spicy::Charset::ASCII); ASN1Type::ObjectIdentifier -> str_value: ASN1ObjectIdentifier(head.len.len) &convert=$$.oidstring; ASN1Type::BMPString, ASN1Type::CharacterString, ASN1Type::GeneralizedTime, ASN1Type::GeneralString, ASN1Type::GraphicString, ASN1Type::IA5String, ASN1Type::NumericString, ASN1Type::PrintableString, ASN1Type::TeletextString, ASN1Type::UTCTime, ASN1Type::UTF8String, ASN1Type::VideotextString, ASN1Type::VisibleString, ASN1Type::UniversalString -> str_value: ASN1String(head.tag, head.len.len); ASN1Type::Sequence, ASN1Type::Set -> seq: ASN1SubMessages(head.len.len) if (recursive); # TODO: ASN1Type values not handled yet ASN1Type::ObjectDescriptor, ASN1Type::InstanceOf, ASN1Type::Real, ASN1Type::EmbeddedPDV, ASN1Type::RelativeOID -> unimplemented_value: bytes &size=head.len.len; # unknown (to me) ASN.1 enumeration, skip over silently * -> unimplemented_value: bytes &size=head.len.len; }; }; #- ASN.1 array of ASN.1 sequence/set sub-messages (up to msgLen bytes) -------- public type ASN1SubMessages = unit(msgLen: uint64) { submessages: ASN1Message(True)[] &eod; } &size=msgLen; #- ASN.1 message with header and body ----------------------------------------- # Universal or Application/ContextSpecific/Private # - if Universal, body:ASN1Body is parsed # - else, application_data:bytes stores data array public type ASN1Message = unit(recursive: bool) { var application_id: int32; head: ASN1Header; switch ( self.head.tag.class ) { ASN1Class::Universal -> body: ASN1Body(self.head, recursive); ASN1Class::Application, ASN1Class::ContextSpecific, ASN1Class::Private -> application_data: bytes &size=self.head.len.len { self.application_id = cast(self.head.tag.type_); } }; };