///|
/// Builds a `Bytes` from an array of octets.
fn bytes_of(items : Array[Byte]) -> Bytes {
Bytes::from_array(items[:])
}
///|
/// Concatenates several byte slices into one buffer.
fn concat(parts : Array[BytesView]) -> Bytes {
let mut size = 0
for part in parts {
size = size + part.length()
}
let buffer = @buffer.Buffer(size_hint=size)
for part in parts {
buffer.write_bytes(part)
}
buffer.to_bytes()
}
///|
/// Encodes the identifier octets of `tag`, using the high-tag-number form for
/// tag numbers of 31 or more (X.690, 8.1.2).
pub fn encode_tag(tag : Tag) -> Bytes raise DerError {
let class_bits = match tag.cls {
Universal => 0x00
Application => 0x40
ContextSpecific => 0x80
Private => 0xC0
}
let constructed_bit = if tag.constructed { 0x20 } else { 0x00 }
if tag.number < 0 {
raise InvalidValue("tag number must not be negative")
}
if tag.number < 0x1F {
bytes_of([((class_bits | constructed_bit | tag.number) & 0xFF).to_byte()])
} else {
let buffer = @buffer.Buffer(size_hint=4)
buffer.write_byte(((class_bits | constructed_bit | 0x1F) & 0xFF).to_byte())
let groups : Array[Int] = []
let mut remaining = tag.number
while remaining > 0 {
groups.push(remaining & 0x7F)
remaining = remaining >> 7
}
for i in 0.. Bytes raise DerError {
if length < 0 {
raise InvalidValue("length must not be negative")
}
if length < 0x80 {
return bytes_of([length.to_byte()])
}
let buffer = @buffer.Buffer(size_hint=5)
let octets : Array[Int] = []
let mut remaining = length
while remaining > 0 {
octets.push(remaining & 0xFF)
remaining = remaining >> 8
}
buffer.write_byte((0x80 | octets.length()).to_byte())
for i in 0.. Bytes raise DerError {
concat([encode_tag(tag)[:], encode_length(content.length())[:], content])
}
///|
/// Encodes a primitive value with the given universal tag number.
fn encode_universal(number : Int, content : BytesView) -> Bytes raise DerError {
encode_tlv(Tag::new(Universal, number), content)
}
///|
/// Encodes a BOOLEAN. DER requires `0xFF` for true and `0x00` for false.
pub fn encode_boolean(value : Bool) -> Bytes raise DerError {
encode_universal(TAG_BOOLEAN, if value { b"\xff"[:] } else { b"\x00"[:] })
}
///|
/// Encodes a NULL.
pub fn encode_null() -> Bytes raise DerError {
encode_universal(TAG_NULL, b""[:])
}
///|
/// 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).
pub fn encode_integer_content(value : @bigint.BigInt) -> Bytes {
if value.is_zero() {
return b"\x00"
}
if value.compare_int(0) > 0 {
let magnitude = value.to_octets()
if (magnitude.unsafe_get(0).to_int() & 0x80) != 0 {
concat([b"\x00"[:], magnitude[:]])
} else {
magnitude
}
} else {
let magnitude = value.neg().to_octets()
// Smallest octet count n with -2^(8n-1) <= value, that is
// magnitude <= 2^(8n-1). Start from a tight lower bound and adjust.
let bits = value.neg().bit_length()
let mut octet_count = if bits <= 1 { 1 } else { (bits - 1) / 8 }
if octet_count < 1 {
octet_count = 1
}
let one = @bigint.BigInt::from_int(1)
while magnitude_view(magnitude) > one << (8 * octet_count - 1) {
octet_count = octet_count + 1
}
let modulus = @bigint.BigInt::from_int(1) << (8 * octet_count)
// Two's complement of a negative value is 2^(8n) + value.
(modulus + value).to_octets(length=octet_count)
}
}
///|
/// Interprets an unsigned big-endian octet string as a non-negative integer.
fn magnitude_view(octets : Bytes) -> @bigint.BigInt {
@bigint.BigInt::from_octets(octets[:])
}
///|
/// Encodes an INTEGER from an arbitrary-precision value.
pub fn encode_integer(value : @bigint.BigInt) -> Bytes raise DerError {
encode_universal(TAG_INTEGER, encode_integer_content(value)[:])
}
///|
/// Encodes an INTEGER from a machine integer.
pub fn encode_int(value : Int) -> Bytes raise DerError {
encode_integer(@bigint.BigInt::from_int(value))
}
///|
/// Encodes an INTEGER from a 64-bit integer.
pub fn encode_int64(value : Int64) -> Bytes raise DerError {
encode_integer(@bigint.BigInt::from_int64(value))
}
///|
/// Encodes an ENUMERATED from a machine integer.
pub fn encode_enumerated(value : Int) -> Bytes raise DerError {
encode_universal(
TAG_ENUMERATED,
encode_integer_content(@bigint.BigInt::from_int(value))[:],
)
}
///|
/// Encodes the content octets of an OBJECT IDENTIFIER, including the combined
/// first two arcs (X.690, 8.19).
pub fn encode_object_identifier_content(
oid : ObjectIdentifier,
) -> Bytes raise DerError {
let arcs = oid.arcs()
if arcs.length() < 2 {
raise InvalidValue("OBJECT IDENTIFIER needs at least two arcs")
}
let first = arcs[0]
let second = arcs[1]
if first < 0L || first > 2L {
raise InvalidValue("first OBJECT IDENTIFIER arc must be 0, 1, or 2")
}
if first < 2L && (second < 0L || second > 39L) {
raise InvalidValue(
"second arc must be at most 39 when the first arc is 0 or 1",
)
}
if second < 0L {
raise InvalidValue("OBJECT IDENTIFIER arcs must not be negative")
}
let buffer = @buffer.Buffer(size_hint=arcs.length() + 2)
write_subidentifier(buffer, first * 40L + second)
for i in 2.. Unit {
let groups : Array[Int] = []
let mut remaining = value
while remaining > 0L {
groups.push((remaining & 0x7FL).to_int())
remaining = remaining >> 7
}
if groups.is_empty() {
buffer.write_byte(0)
return
}
for i in 0.. Bytes raise DerError {
encode_universal(
TAG_OBJECT_IDENTIFIER,
encode_object_identifier_content(oid)[:],
)
}
///|
/// Encodes an OCTET STRING.
pub fn encode_octet_string(value : BytesView) -> Bytes raise DerError {
encode_universal(TAG_OCTET_STRING, value)
}
///|
/// Encodes the content octets of a BIT STRING: the unused-bit count followed by
/// the value octets (X.690, 8.6 and 11.2).
pub fn encode_bit_string_content(
value : BytesView,
unused_bits? : Int = 0,
) -> Bytes raise DerError {
if unused_bits < 0 || unused_bits > 7 {
raise InvalidValue("BIT STRING unused bit count must be 0-7")
}
if value.length() == 0 && unused_bits != 0 {
raise InvalidValue("BIT STRING unused bit count must be 0 when empty")
}
if value.length() > 0 && unused_bits > 0 {
let last = value.unsafe_get(value.length() - 1).to_int()
if (last & ((1 << unused_bits) - 1)) != 0 {
raise InvalidValue("BIT STRING has non-zero unused bits")
}
}
concat([bytes_of([unused_bits.to_byte()])[:], value])
}
///|
/// Encodes a BIT STRING.
pub fn encode_bit_string(
value : BytesView,
unused_bits? : Int = 0,
) -> Bytes raise DerError {
encode_universal(
TAG_BIT_STRING,
encode_bit_string_content(value, unused_bits~)[:],
)
}
///|
/// Encodes a UTF8String.
pub fn encode_utf8_string(value : StringView) -> Bytes raise DerError {
encode_universal(TAG_UTF8_STRING, @utf8.encode(value)[:])
}
///|
/// Encodes an ASCII-only string type, rejecting any character above `0x7F`.
fn encode_ascii(
number : Int,
value : StringView,
allowed : (Char) -> Bool,
kind : String,
) -> Bytes raise DerError {
let buffer = @buffer.Buffer(size_hint=value.length())
for ch in value {
if !allowed(ch) {
raise InvalidValue(kind + " contains a character outside its alphabet")
}
buffer.write_byte(Char::to_int(ch).to_byte())
}
encode_universal(number, buffer.to_bytes()[:])
}
///|
/// Encodes a PrintableString, whose alphabet is defined by X.680 41.
pub fn encode_printable_string(value : StringView) -> Bytes raise DerError {
encode_ascii(
TAG_PRINTABLE_STRING,
value,
fn(ch) {
match ch {
'A'..='Z' | 'a'..='z' | '0'..='9' => true
' '
| '\''
| '('
| ')'
| '+'
| ','
| '-'
| '.'
| '/'
| ':'
| '='
| '?' => true
_ => false
}
},
"PrintableString",
)
}
///|
/// Encodes an IA5String, whose alphabet is the 128 ASCII characters.
pub fn encode_ia5_string(value : StringView) -> Bytes raise DerError {
encode_ascii(
TAG_IA5_STRING,
value,
fn(ch) { Char::to_int(ch) <= 0x7F },
"IA5String",
)
}
///|
/// Encodes a NumericString, whose alphabet is the digits and the space.
pub fn encode_numeric_string(value : StringView) -> Bytes raise DerError {
encode_ascii(
TAG_NUMERIC_STRING,
value,
fn(ch) {
match ch {
'0'..='9' | ' ' => true
_ => false
}
},
"NumericString",
)
}
///|
/// Encodes a VisibleString, whose alphabet is the printable ASCII range.
pub fn encode_visible_string(value : StringView) -> Bytes raise DerError {
encode_ascii(
TAG_VISIBLE_STRING,
value,
fn(ch) {
let code = Char::to_int(ch)
code >= 0x20 && code <= 0x7E
},
"VisibleString",
)
}
///|
/// Encodes a BMPString as UTF-16BE without a byte order mark (X.680, 8.4).
pub fn encode_bmp_string(value : StringView) -> Bytes raise DerError {
for ch in value {
if Char::to_int(ch) > 0xFFFF {
raise InvalidValue("BMPString contains a character outside the BMP")
}
}
encode_universal(
TAG_BMP_STRING,
@utf16.encode(value, endianness=@utf16.Big)[:],
)
}
///|
/// Encodes a UniversalString as UTF-32BE (X.680, 8.7).
pub fn encode_universal_string(value : StringView) -> Bytes raise DerError {
let buffer = @buffer.Buffer(size_hint=value.length() * 4)
for ch in value {
let code = Char::to_int(ch)
buffer.write_byte(((code >> 24) & 0xFF).to_byte())
buffer.write_byte(((code >> 16) & 0xFF).to_byte())
buffer.write_byte(((code >> 8) & 0xFF).to_byte())
buffer.write_byte((code & 0xFF).to_byte())
}
encode_universal(TAG_UNIVERSAL_STRING, buffer.to_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.
pub struct DerWriter {
items : Array[Bytes]
}
///|
/// Creates an empty writer.
pub fn DerWriter::new() -> DerWriter {
{ items: [], }
}
///|
/// Appends one complete tag-length-value element.
pub fn DerWriter::add(self : DerWriter, element : BytesView) -> Unit {
self.items.push(@buffer.from_bytes(element).to_bytes())
}
///|
/// Number of elements added so far.
pub fn DerWriter::length(self : DerWriter) -> Int {
self.items.length()
}
///|
/// Whether the writer is empty.
pub fn DerWriter::is_empty(self : DerWriter) -> Bool {
self.items.is_empty()
}
///|
/// Concatenates everything added so far, without adding a wrapper.
pub fn DerWriter::to_content(self : DerWriter) -> Bytes {
concat(self.items.map(fn(item) { item[:] }))
}
///|
/// Wraps everything added so far in a SEQUENCE.
pub fn DerWriter::to_sequence(self : DerWriter) -> Bytes raise DerError {
encode_tlv(
Tag::new(Universal, TAG_SEQUENCE, constructed=true),
self.to_content()[:],
)
}
///|
/// Wraps everything added so far in a SET OF, sorting the elements by their
/// encoding as required for DER (X.690, 11.6).
pub fn DerWriter::to_set_of(self : DerWriter) -> Bytes raise DerError {
let sorted = self.items.copy()
sorted.sort_by(fn(a, b) { compare_octets(a[:], b[:]) })
encode_tlv(
Tag::new(Universal, TAG_SET, constructed=true),
concat(sorted.map(fn(item) { item[:] }))[:],
)
}
///|
/// Wraps everything added so far in an explicitly tagged constructed value
/// (X.690, 8.14.3).
pub fn DerWriter::to_explicit(
self : DerWriter,
number : Int,
) -> Bytes raise DerError {
encode_tlv(
Tag::new(ContextSpecific, number, constructed=true),
self.to_content()[:],
)
}
///|
/// Wraps a single encoded value in an explicitly tagged constructed value.
pub fn encode_explicit(number : Int, inner : BytesView) -> Bytes raise DerError {
let writer = DerWriter::new()
writer.add(inner)
writer.to_explicit(number)
}
///|
/// Encodes a primitive implicitly tagged value, for example a context-specific
/// string inside a certificate.
pub fn encode_context_primitive(
number : Int,
content : BytesView,
) -> Bytes raise DerError {
encode_tlv(Tag::new(ContextSpecific, number), content)
}
///|
/// Encodes a constructed value from already-encoded elements.
pub fn encode_sequence(elements : Array[BytesView]) -> Bytes raise DerError {
encode_tlv(
Tag::new(Universal, TAG_SEQUENCE, constructed=true),
concat(elements)[:],
)
}
///|
/// Encodes a DER SET OF, sorting the elements by their encoding.
pub fn encode_set_of(elements : Array[BytesView]) -> Bytes raise DerError {
let copies : Array[Bytes] = elements.map(fn(element) {
@buffer.from_bytes(element).to_bytes()
})
copies.sort_by(fn(a, b) { compare_octets(a[:], b[:]) })
encode_tlv(
Tag::new(Universal, TAG_SET, constructed=true),
concat(copies.map(fn(item) { item[:] }))[:],
)
}
///|
/// 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.
pub fn compare_octets(a : BytesView, b : BytesView) -> Int {
let shared = if a.length() < b.length() { a.length() } else { b.length() }
for i in 0.. Bytes raise DerError {
match self {
Primitive(tag, content) => encode_tlv(tag, content)
Constructed(tag, children) => {
let encoded = children.map(child => child.encode())
if tag.cls == Universal && tag.number == TAG_SET {
encoded.sort_by(fn(a, b) { compare_octets(a[:], b[:]) })
}
encode_tlv(tag, concat(encoded.map(fn(item) { item[:] }))[:])
}
}
}