// ASN.1 Distinguished Encoding Rules (X.690): the type-length-value encoding X.509 certificates
// are built from. Every value is a tag byte, a definite length (short form under 128, long form
// above), and the contents; the constructed types (SEQUENCE, SET) concatenate their members,
// and the primitives (INTEGER, OID, BIT STRING, …) carry their canonical minimal encoding. This
// is the byte layer mooncat's real X.509 server certificate is assembled on, the shape curl
// parses.
///|
/// Encode a definite length (X.690 §8.1.3): the short form is the single byte for lengths under
/// 128; the long form is `0x80 | n` followed by the length in `n` big-endian bytes.
pub fn der_length(n : Int) -> Bytes {
if n < 128 {
let b = Buffer()
b.write_byte(n.to_byte())
b.to_bytes()
} else {
let digits = []
let mut m = n
while m > 0 {
digits.push(m % 256)
m = m / 256
}
let be = digits.rev()
let buf = Buffer()
buf.write_byte((0x80 | be.length()).to_byte())
for d in be {
buf.write_byte(d.to_byte())
}
buf.to_bytes()
}
}
///|
/// A tag-length-value: the `tag` byte, the definite length of `value`, then `value`.
pub fn der_tlv(tag : Int, value : Bytes) -> Bytes {
let buf = Buffer()
buf.write_byte(tag.to_byte())
buf.write_bytes(der_length(value.length())[:])
buf.write_bytes(value[:])
buf.to_bytes()
}
///|
/// A DER INTEGER (tag 0x02) from a big-endian magnitude: strip redundant leading zero bytes,
/// then prepend one 0x00 if the top bit is set, so the value stays non-negative (X.690 §8.3).
pub fn der_integer(magnitude : Bytes) -> Bytes {
if magnitude.length() == 0 {
return der_tlv(0x02, b"\x00")
}
let mut start = 0
while start < magnitude.length() - 1 && magnitude[start] == b'\x00' {
start = start + 1
}
let stripped = magnitude[start:magnitude.length()].to_owned()
let value = if (stripped[0].to_int() & 0x80) != 0 {
let buf = Buffer()
buf.write_byte(b'\x00')
buf.write_bytes(stripped[:])
buf.to_bytes()
} else {
stripped
}
der_tlv(0x02, value)
}
///|
/// A DER OBJECT IDENTIFIER (tag 0x06) from its arcs (X.690 §8.19): the first byte encodes
/// `40·arc1 + arc2`, and each later arc is base-128 big-endian with the high bit set on every
/// byte but the last.
pub fn der_oid(arcs : Array[Int]) -> Bytes {
let body = Buffer()
body.write_byte((40 * arcs[0] + arcs[1]).to_byte())
for i = 2; i < arcs.length(); i = i + 1 {
let v = arcs[i]
let digits = []
let mut n = v
if n == 0 {
digits.push(0)
}
while n > 0 {
digits.push(n % 128)
n = n / 128
}
let be = digits.rev()
for j = 0; j < be.length(); j = j + 1 {
let last = j == be.length() - 1
body.write_byte((if last { be[j] } else { be[j] | 0x80 }).to_byte())
}
}
der_tlv(0x06, body.to_bytes())
}
///|
/// A DER BIT STRING (tag 0x03) with no unused trailing bits: a leading 0x00 count then `content`.
pub fn der_bit_string(content : Bytes) -> Bytes {
let buf = Buffer()
buf.write_byte(b'\x00')
buf.write_bytes(content[:])
der_tlv(0x03, buf.to_bytes())
}
///|
/// A DER OCTET STRING (tag 0x04).
pub fn der_octet_string(content : Bytes) -> Bytes {
der_tlv(0x04, content)
}
///|
/// A DER SEQUENCE (tag 0x30): the concatenated `elements`.
pub fn der_sequence(elements : Array[Bytes]) -> Bytes {
let buf = Buffer()
for e in elements {
buf.write_bytes(e[:])
}
der_tlv(0x30, buf.to_bytes())
}
///|
/// A DER SET (tag 0x31): the concatenated `elements`.
pub fn der_set(elements : Array[Bytes]) -> Bytes {
let buf = Buffer()
for e in elements {
buf.write_bytes(e[:])
}
der_tlv(0x31, buf.to_bytes())
}
///|
/// A DER NULL (tag 0x05, empty).
pub fn der_null() -> Bytes {
der_tlv(0x05, b"")
}
///|
/// A DER UTF8String (tag 0x0c).
pub fn der_utf8_string(s : String) -> Bytes {
der_tlv(0x0c, @utf8.encode(s))
}
///|
/// A DER UTCTime (tag 0x17), the `YYMMDDHHMMSSZ` form X.509 validity uses before 2050.
pub fn der_utc_time(s : String) -> Bytes {
der_tlv(0x17, @utf8.encode(s))
}
///|
/// A context-specific constructed value `[tag_num]` wrapping `content` (tag `0xA0 | tag_num`) —
/// the EXPLICIT tagging X.509 uses for the certificate version and extensions.
pub fn der_explicit(tag_num : Int, content : Bytes) -> Bytes {
der_tlv(0xa0 | tag_num, content)
}