///|
/// Decodes the content octets of a BOOLEAN.
///
/// DER requires `0xFF` for true and `0x00` for false; any other value is
/// rejected.
pub fn decode_boolean(content : BytesView) -> Bool raise DerError {
if content.length() != 1 {
raise InvalidValue("BOOLEAN must have exactly one content octet")
}
match content.unsafe_get(0).to_int() {
0x00 => false
0xFF => true
_ => raise InvalidValue("BOOLEAN must be 0x00 or 0xFF in DER")
}
}
///|
/// Decodes the content octets of a NULL, which must be empty.
pub fn decode_null(content : BytesView) -> Unit raise DerError {
if content.length() != 0 {
raise InvalidValue("NULL must have empty content")
}
}
///|
/// 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.
pub fn decode_integer(content : BytesView) -> @bigint.BigInt raise DerError {
if content.length() == 0 {
raise InvalidValue("INTEGER must have at least one content octet")
}
if content.length() > 1 {
let first = content.unsafe_get(0).to_int()
let second = content.unsafe_get(1).to_int()
if first == 0x00 && (second & 0x80) == 0 {
raise InvalidValue("INTEGER is not minimally encoded")
}
if first == 0xFF && (second & 0x80) != 0 {
raise InvalidValue("INTEGER is not minimally encoded")
}
}
let raw = @bigint.BigInt::from_octets(content)
if (content.unsafe_get(0).to_int() & 0x80) == 0 {
return raw
}
let modulus = @bigint.BigInt::from_int(1) << (8 * content.length())
raw - modulus
}
///|
/// 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.
pub fn decode_bit_string(content : BytesView) -> (Int, Bytes) raise DerError {
if content.length() == 0 {
raise InvalidValue("BIT STRING must have at least one content octet")
}
let unused = content.unsafe_get(0).to_int()
if unused > 7 {
raise InvalidValue("BIT STRING unused bit count must be 0-7")
}
let value = @buffer.from_bytes(content[1:]).to_bytes()
if value.length() == 0 && unused != 0 {
raise InvalidValue("BIT STRING unused bit count must be 0 when empty")
}
if value.length() > 0 {
let last = value.unsafe_get(value.length() - 1).to_int()
if unused > 0 && (last & ((1 << unused) - 1)) != 0 {
raise InvalidValue("BIT STRING has non-zero unused bits")
}
}
(unused, value)
}
///|
/// Decodes a UTF8String.
pub fn decode_utf8_string(content : BytesView) -> String raise DerError {
@utf8.decode(content) catch {
@utf8.Malformed(_) => raise InvalidValue("invalid UTF-8 in UTF8String")
}
}
///|
/// Decodes a PrintableString, whose alphabet is ASCII (X.680, 41).
pub fn decode_printable_string(content : BytesView) -> String raise DerError {
decode_ascii(content, "PrintableString")
}
///|
/// Decodes an IA5String, whose alphabet is the 128 ASCII characters (X.680, 43).
pub fn decode_ia5_string(content : BytesView) -> String raise DerError {
decode_ascii(content, "IA5String")
}
///|
/// Decodes a NumericString, whose alphabet is the digits and the space.
pub fn decode_numeric_string(content : BytesView) -> String raise DerError {
for i in 0.. 0x39) {
raise InvalidValue(
"NumericString contains a character outside its alphabet",
)
}
}
decode_ascii(content, "NumericString")
}
///|
/// Decodes a VisibleString, whose alphabet is the printable ASCII range.
pub fn decode_visible_string(content : BytesView) -> String raise DerError {
for i in 0.. 0x7E {
raise InvalidValue(
"VisibleString contains a character outside its alphabet",
)
}
}
decode_ascii(content, "VisibleString")
}
///|
/// 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.
pub fn decode_bmp_string(content : BytesView) -> String raise DerError {
let decoded = @utf16.decode(content, endianness=@utf16.Big) catch {
@utf16.Malformed(_) => raise InvalidValue("invalid UTF-16 in BMPString")
}
for ch in decoded {
if Char::to_int(ch) > 0xFFFF {
raise InvalidValue("BMPString contains a character outside the BMP")
}
}
decoded
}
///|
/// Decodes a UniversalString, which is UTF-32BE.
pub fn decode_universal_string(content : BytesView) -> String raise DerError {
if content.length() % 4 != 0 {
raise InvalidValue("UniversalString length must be a multiple of four")
}
let builder = StringBuilder()
let mut index = 0
while index < content.length() {
let code = (content.unsafe_get(index).to_int() << 24) |
(content.unsafe_get(index + 1).to_int() << 16) |
(content.unsafe_get(index + 2).to_int() << 8) |
content.unsafe_get(index + 3).to_int()
if code < 0 {
raise InvalidValue("UniversalString code point is out of range")
}
match code.to_char() {
Some(ch) => builder.write_char(ch)
None =>
raise InvalidValue("UniversalString code point is not a scalar value")
}
index = index + 4
}
builder.to_string()
}
///|
/// Decodes an ASCII-only string type, raising `InvalidValue` for any byte above
/// `0x7F`.
fn decode_ascii(content : BytesView, kind : String) -> String raise DerError {
let buffer = @buffer.Buffer(size_hint=content.length())
for i in 0.. 0x7F {
raise InvalidValue(kind + " contains a non-ASCII byte")
}
buffer.write_byte(byte.to_byte())
}
@utf8.decode_lossy(buffer.to_bytes()[:])
}