std/asn1
import std/asn1 to use it. Source: src/std/asn1.iyi at v0.18.0.
import std/asn1::{Der, DerValue, DerClass, Asn1Error} cert = Der.parse(bytes).or_panic # exactly one value, nothing after
tbs = cert.children.or_panic[0] # a SEQUENCE's elements
tbs.raw # its own bytes, header included
serial = tbs.children.or_panic[1].unsigned_integer.or_panic
algorithm = seq[0].oid.or_panic # "1.2.840.113549.1.1.11"
not_after = validity[1].time.or_panic # a Time, from UTCTime or GeneralizedTimeReading only. A TLS client reads certificates and verifies signatures; it writes no DER, so there is no writer here to be wrong.
A value is a view into the bytes it was read from: content and raw copy, nothing else does, and children reads one level at a time. A caller walks as deep as its schema asks and no deeper, so nothing here recurses on what the input says; Der.walk is the one walk over a whole tree, and it stops at MAX_DEPTH.
DER is one encoding for each value, and a reader that takes a second one lets two different byte strings carry one certificate. Each of these is an Asn1Error naming the offset, never a value made up or a byte skipped:
* an indefinite length (0x80), which is BER's and not DER's;
* a length written longer than it has to be: the long form for a
length under 128, or a leading zero byte in it;
* a tag number written in the high form when it fits the low one, or
with a leading 0x80 byte;
* a length past the bytes that are there, or bytes after the value;
* a type DER makes primitive written constructed (an INTEGER, a BIT
STRING, a string, a time), or a SEQUENCE or SET written primitive;
* an INTEGER with a redundant leading 0x00 or 0xFF byte, or none at all;
* a BOOLEAN that is not 0x00 or 0xFF, a NULL with content;
* an OBJECT IDENTIFIER subidentifier with a leading 0x80 byte, or one
left unterminated;
* a BIT STRING whose unused bits are more than 7, or not zero;
* a time that is not the one form DER allows (below), or not a date.DER also sorts the elements of a SET OF by their encodings. That is not checked: a certificate's names are SETs of one, and OpenSSL and Go take an unsorted SET too.
Times. A UTCTime is exactly YYMMDDHHMMSSZ, a two-digit year 50 to 99 being 1950 to 1999 and 00 to 49 being 2000 to 2049, as RFC 5280 section 4.1.2.5.1 reads it. A GeneralizedTime is YYYYMMDDHHMMSSZ with, as X.690 section 11.7 allows, a fraction of a second after a . that does not end in a zero. Both end in Z: DER writes no other zone. RFC 5280 forbids the fraction in a certificate, which is std/x509's to refuse.
Strings. UTF8String must be well-formed UTF-8. PrintableString holds only its alphabet - letters, digits, space and '()+,-./:=? - and IA5String only ASCII, NumericString digits and space, VisibleString the printable ASCII. BMPString is UTF-16 big-endian without surrogates (UCS-2) and UniversalString UTF-32 big-endian; both are answered as UTF-8. TeletexString (T.61) is read as ISO-8859-1, the way Go and OpenSSL read the certificates that still carry one.
UTF-8 is checked here rather than through std/unicode, whose tables every program reading a certificate would otherwise compile.
Pure computation over the bytes given: no platform, no clock, no randomness. std/time is imported for its Time alone.
Asn1Errorstruct
What Der.parse, children and the typed readers answer for bytes that are not the DER they were asked for: a sentence, and the offset in the bytes Der.parse was given where the trouble is.
pub struct Asn1Error
def initialize(message : String, offset : Int32)
def offset : Int32
def to_s : String
endDerstruct
pub struct Der
def initialize
# The one value *bytes* hold, and nothing after it.
def self.parse(bytes : Array(UInt8)) : DerValue | Asn1Error
# Every value under *value*, *value* first, in the order they are
# written, each with its depth below it: the whole tree read and every
# length and tag checked. Primitive contents are not decoded; that is
# the typed readers' job, asked of the values a schema names.
def self.walk(value : DerValue, & : (DerValue, Int32 -> Nil)) : Nil | Asn1Error
endDerClassenum
The two bits at the top of an identifier byte.
pub enum DerClass : Int32
Universal = 0
Application = 1
ContextSpecific = 2
Private = 3
endDerTagstruct
The universal tag numbers this module reads (X.680 section 8.6).
pub struct DerTag
def initialize
endDerValuestruct
One value: its identifier, and where its header and content are in the bytes it was read from.
pub struct DerValue
# A BIT STRING whose bits fill whole bytes: a key, a signature.
def bit_string : Array(UInt8) | Asn1Error
# A BIT STRING's bytes and how many bits at the end of the last are not
# part of it, which DER sets to zero: a flags field such as keyUsage.
def bit_string_bits : ::Tuple(Array(UInt8), Int32) | Asn1Error
def boolean : Bool | Asn1Error
# The values a constructed value holds, read one level down.
def children : Array(DerValue) | Asn1Error
def constructed? : Bool
# The content bytes, copied.
def content : Array(UInt8)
# The offset of the first content byte, and of the byte after the value.
def content_offset : Int32
def content_size : Int32
def context?(tag : Int32) : Bool
# The identifier, said the way an error names it: `INTEGER`,
# `[0] constructed`, `APPLICATION 3`.
def describe : String
def end_offset : Int32
# An ENUMERATED that fits an Int64: a CRL entry's reason code, an
# OCSP response's status.
def enumerated : Int64 | Asn1Error
# An ENUMERATED's two's-complement bytes, held to INTEGER's rules.
def enumerated_bytes : Array(UInt8) | Asn1Error
# The one value inside an explicit context tag `[n]`, as X.509 writes a
# certificate's version and extensions.
def explicit(tag : Int32) : DerValue | Asn1Error
def header_size : Int32
def initialize(data : Array(UInt8), tag_class : DerClass, constructed : Bool, tag : Int32, offset : Int32, header_size : Int32, content_size : Int32)
# An INTEGER that fits an Int64.
def integer : Int64 | Asn1Error
# An INTEGER's two's-complement bytes, as written: checked to be the
# fewest that hold it. Only an INTEGER: an ENUMERATED is written the same
# way under another tag, and reading one here took an Ecdsa-Sig-Value
# whose r or s was tagged ENUMERATED - a signature no DER reader accepts.
def integer_bytes : Array(UInt8) | Asn1Error
def null : Nil | Asn1Error
def octet_string : Array(UInt8) | Asn1Error
def offset : Int32
# An OBJECT IDENTIFIER in dotted form, `"2.5.4.3"`. A subidentifier past
# an Int64 is refused: no algorithm or attribute is one.
def oid : String | Asn1Error
# The whole value as it was written, header included: what a signature
# over a certificate's `tbsCertificate` is computed on.
def raw : Array(UInt8)
# One value's header at *at*, its content within *stop*.
def self.read(data : Array(UInt8), at : Int32, stop : Int32) : DerValue | Asn1Error
# A SEQUENCE's elements: `children`, after checking it is one.
def sequence : Array(DerValue) | Asn1Error
# A SET's elements.
def set : Array(DerValue) | Asn1Error
# Any of the string types X.509 names are written in, as UTF-8.
def string : String | Asn1Error
def tag : Int32
def tag_class : DerClass
# A UTCTime or a GeneralizedTime, in the one form DER writes each.
def time : Time | Asn1Error
def universal?(tag : Int32) : Bool
def self.universal_name(tag : Int32) : String
# A non-negative INTEGER's magnitude, big-endian, without the 0x00 a
# high bit makes DER write: an RSA modulus, a serial number. A negative
# one is refused.
def unsigned_integer : Array(UInt8) | Asn1Error
endError for Asn1Errorimpl
impl Error for Asn1Error
def message : String
end
ASN.1 values read from DER (ITU-T X.690, the Distinguished Encoding Rules), written in iyi: what a certificate, a key or a signature is written in.