use zeroize::Zeroizing;
use crate::error::{Error, Result};
pub(crate) const MAX_INPUT_LEN: usize = 64 * 1024;
pub(crate) const INTEGER: u8 = 0x02;
pub(crate) const BIT_STRING: u8 = 0x03;
pub(crate) const OCTET_STRING: u8 = 0x04;
pub(crate) const OID: u8 = 0x06;
pub(crate) const SEQUENCE: u8 = 0x30;
pub(crate) const SET: u8 = 0x31;
pub(crate) const CONTEXT_0: u8 = 0xA0;
pub(crate) const CONTEXT_1: u8 = 0xA1;
pub(crate) const CONTEXT_1_PRIMITIVE: u8 = 0x81;
const CONSTRUCTED: u8 = 0x20;
#[derive(Clone, Copy)]
pub(crate) struct Element<'a> {
pub(crate) tag: u8,
pub(crate) encoding: &'a [u8],
pub(crate) contents: &'a [u8],
}
#[derive(Clone, Copy)]
pub(crate) struct Reader<'a> {
rest: &'a [u8],
}
impl<'a> Reader<'a> {
pub(crate) fn new(input: &'a [u8]) -> Result<Self> {
if input.len() > MAX_INPUT_LEN {
return Err(Error::InvalidKey("DER input too large"));
}
Ok(Reader { rest: input })
}
pub(crate) fn is_empty(&self) -> bool {
self.rest.is_empty()
}
pub(crate) fn peek_tag(&self) -> Option<u8> {
self.rest.first().copied()
}
pub(crate) fn element(&mut self) -> Result<Element<'a>> {
let input = self.rest;
let (&tag, after_tag) = input
.split_first()
.ok_or(Error::InvalidKey("DER truncated"))?;
if tag & 0x1F == 0x1F {
return Err(Error::InvalidKey("DER high-tag-number form"));
}
let (&first, mut after_len) = after_tag
.split_first()
.ok_or(Error::InvalidKey("DER truncated"))?;
let len = match first {
0x00..=0x7F => usize::from(first),
0x80 => return Err(Error::InvalidKey("DER indefinite length")),
0x81..=0x84 => {
let count = usize::from(first & 0x7F);
let (octets, rest) = after_len
.split_at_checked(count)
.ok_or(Error::InvalidKey("DER truncated"))?;
if octets.first() == Some(&0) {
return Err(Error::InvalidKey("DER length not minimal"));
}
let len = octets
.iter()
.fold(0usize, |acc, &octet| (acc << 8) | usize::from(octet));
if len < 0x80 {
return Err(Error::InvalidKey("DER length not minimal"));
}
after_len = rest;
len
}
_ => return Err(Error::InvalidKey("DER length too long")),
};
let header = input.len() - after_len.len();
let (contents, rest) = after_len
.split_at_checked(len)
.ok_or(Error::InvalidKey("DER length exceeds input"))?;
self.rest = rest;
Ok(Element {
tag,
encoding: &input[..header + len],
contents,
})
}
pub(crate) fn expect(&mut self, tag: u8) -> Result<Element<'a>> {
let element = self.element()?;
if element.tag == tag {
Ok(element)
} else {
Err(Error::InvalidKey("DER unexpected tag"))
}
}
pub(crate) fn optional(&mut self, tag: u8) -> Result<Option<Element<'a>>> {
if self.peek_tag() == Some(tag) {
self.element().map(Some)
} else {
Ok(None)
}
}
pub(crate) fn sequence(&mut self) -> Result<Reader<'a>> {
self.explicit(SEQUENCE)
}
pub(crate) fn explicit(&mut self, tag: u8) -> Result<Reader<'a>> {
let element = self.expect(tag)?;
Ok(Reader {
rest: element.contents,
})
}
pub(crate) fn integer(&mut self) -> Result<&'a [u8]> {
let contents = self.expect(INTEGER)?.contents;
check_integer(contents)?;
Ok(contents)
}
pub(crate) fn unsigned_integer(&mut self) -> Result<&'a [u8]> {
let contents = self.integer()?;
match contents {
[first, ..] if first & 0x80 != 0 => Err(Error::InvalidKey("DER INTEGER negative")),
[0x00, magnitude @ ..] if !magnitude.is_empty() => Ok(magnitude),
_ => Ok(contents),
}
}
pub(crate) fn small_unsigned(&mut self) -> Result<u8> {
match self.unsigned_integer()? {
[value] => Ok(*value),
_ => Err(Error::InvalidKey("DER INTEGER out of range")),
}
}
pub(crate) fn octet_string(&mut self) -> Result<&'a [u8]> {
Ok(self.string(OCTET_STRING)?.contents)
}
pub(crate) fn bit_string(&mut self) -> Result<&'a [u8]> {
self.implicit_bit_string(BIT_STRING)
}
pub(crate) fn implicit_bit_string(&mut self, tag: u8) -> Result<&'a [u8]> {
match self.string(tag)?.contents {
[0x00, octets @ ..] => Ok(octets),
[] => Err(Error::InvalidKey("DER BIT STRING empty")),
[_, ..] => Err(Error::InvalidKey("DER BIT STRING has unused bits")),
}
}
#[cfg(test)]
pub(crate) fn null(&mut self) -> Result<()> {
const NULL: u8 = 0x05;
if self.expect(NULL)?.contents.is_empty() {
Ok(())
} else {
Err(Error::InvalidKey("DER NULL not empty"))
}
}
pub(crate) fn oid(&mut self) -> Result<&'a [u8]> {
let element = self.expect(OID)?;
check_oid(element.contents)?;
Ok(element.encoding)
}
fn string(&mut self, tag: u8) -> Result<Element<'a>> {
let element = self.element()?;
if element.tag == tag {
Ok(element)
} else if element.tag == tag | CONSTRUCTED {
Err(Error::InvalidKey("DER constructed string"))
} else {
Err(Error::InvalidKey("DER unexpected tag"))
}
}
pub(crate) fn finish(self) -> Result<()> {
if self.rest.is_empty() {
Ok(())
} else {
Err(Error::InvalidKey("DER trailing data"))
}
}
}
fn check_integer(contents: &[u8]) -> Result<()> {
match contents {
[] => Err(Error::InvalidKey("DER INTEGER empty")),
[0x00, next, ..] if next & 0x80 == 0 => Err(Error::InvalidKey("DER INTEGER not minimal")),
[0xFF, next, ..] if next & 0x80 != 0 => Err(Error::InvalidKey("DER INTEGER not minimal")),
_ => Ok(()),
}
}
fn check_oid(contents: &[u8]) -> Result<()> {
let malformed = Err(Error::InvalidKey("DER OBJECT IDENTIFIER malformed"));
if contents.last().is_none_or(|last| last & 0x80 != 0) {
return malformed;
}
let mut at_start = true;
for &octet in contents {
if at_start && octet == 0x80 {
return malformed;
}
at_start = octet & 0x80 == 0;
}
Ok(())
}
pub(crate) const MAX_DEPTH: usize = 16;
pub(crate) fn check_well_formed(contents: &[u8], set_of: bool) -> Result<()> {
#[derive(Clone, Copy)]
struct Level<'a> {
reader: Reader<'a>,
sorted: bool,
previous: Option<&'a [u8]>,
}
let empty = Level {
reader: Reader { rest: &[] },
sorted: false,
previous: None,
};
let mut stack = [empty; MAX_DEPTH + 1];
stack[0] = Level {
reader: Reader::new(contents)?,
sorted: set_of,
previous: None,
};
let mut depth: usize = 1;
while let Some(level) = depth.checked_sub(1).and_then(|top| stack.get_mut(top)) {
if level.reader.is_empty() {
depth -= 1;
continue;
}
let element = level.reader.element()?;
if level.sorted {
if level
.previous
.is_some_and(|previous| !set_of_ordered(previous, element.encoding))
{
return Err(Error::InvalidKey("DER SET OF not sorted"));
}
level.previous = Some(element.encoding);
}
check_universal(element)?;
if element.tag & CONSTRUCTED != 0 {
let next = stack
.get_mut(depth)
.ok_or(Error::InvalidKey("DER nesting too deep"))?;
*next = Level {
reader: Reader {
rest: element.contents,
},
sorted: element.tag == SET,
previous: None,
};
depth += 1;
}
}
Ok(())
}
fn set_of_ordered(first: &[u8], second: &[u8]) -> bool {
let len = first.len().max(second.len());
for i in 0..len {
let a = first.get(i).copied().unwrap_or(0);
let b = second.get(i).copied().unwrap_or(0);
if a != b {
return a < b;
}
}
true
}
fn check_universal(element: Element<'_>) -> Result<()> {
if element.tag & 0xC0 != 0 {
return Ok(());
}
let number = element.tag & 0x1F;
let constructed = element.tag & CONSTRUCTED != 0;
let always_constructed = matches!(number, 8 | 11 | 16 | 17 | 29);
if constructed != always_constructed {
let string = matches!(number, 3 | 4 | 7 | 12 | 18..=30) && number != 29;
return if constructed && string {
Err(Error::InvalidKey("DER constructed string"))
} else {
Err(Error::InvalidKey("DER unexpected tag"))
};
}
let contents = element.contents;
match number {
0 => Err(Error::InvalidKey("DER end-of-contents")),
1 => match contents {
[0x00] | [0xFF] => Ok(()),
_ => Err(Error::InvalidKey("DER BOOLEAN not canonical")),
},
2 | 10 => check_integer(contents),
3 => match contents {
[] => Err(Error::InvalidKey("DER BIT STRING empty")),
[0x00, ..] => Ok(()),
[unused, .., last] if *unused <= 7 && last & ((1u8 << unused) - 1) == 0 => Ok(()),
_ => Err(Error::InvalidKey("DER BIT STRING malformed")),
},
5 if contents.is_empty() => Ok(()),
5 => Err(Error::InvalidKey("DER NULL not empty")),
6 | 13 => check_oid(contents),
_ => Ok(()),
}
}
fn header_len(content_len: usize) -> usize {
if content_len < 0x80 {
2
} else {
2 + length_octets(content_len)
}
}
fn length_octets(len: usize) -> usize {
let bits = usize::BITS - len.leading_zeros();
bits.div_ceil(8) as usize
}
pub(crate) fn encoded_len(content_len: usize) -> usize {
header_len(content_len) + content_len
}
pub(crate) fn bit_string_len(octets_len: usize) -> usize {
encoded_len(1 + octets_len)
}
fn minimal_unsigned(magnitude: &[u8]) -> (&[u8], bool) {
let start = magnitude
.iter()
.position(|&octet| octet != 0)
.unwrap_or(magnitude.len());
match magnitude.get(start..) {
Some(digits @ [first, ..]) => (digits, first & 0x80 != 0),
_ => (&[0x00], false),
}
}
pub(crate) fn unsigned_integer_len(magnitude: &[u8]) -> usize {
let (digits, pad) = minimal_unsigned(magnitude);
encoded_len(digits.len() + usize::from(pad))
}
pub(crate) struct Writer {
buf: Zeroizing<Vec<u8>>,
planned: usize,
}
impl Writer {
pub(crate) fn new(planned: usize) -> Result<Self> {
if planned > MAX_INPUT_LEN {
return Err(Error::InvalidKey("DER output too large"));
}
Ok(Writer {
buf: Zeroizing::new(Vec::with_capacity(planned)),
planned,
})
}
pub(crate) fn raw(&mut self, bytes: &[u8]) -> Result<()> {
if bytes.len() > self.planned - self.buf.len() {
return Err(Error::InvalidKey("DER output length mismatch"));
}
self.buf.extend_from_slice(bytes);
Ok(())
}
pub(crate) fn header(&mut self, tag: u8, content_len: usize) -> Result<()> {
let mut header = [0u8; 2 + core::mem::size_of::<usize>()];
header[0] = tag;
let used = if content_len < 0x80 {
header[1] = content_len as u8;
2
} else {
let count = length_octets(content_len);
header[1] = 0x80 | count as u8;
let be = content_len.to_be_bytes();
header[2..2 + count].copy_from_slice(&be[be.len() - count..]);
2 + count
};
self.raw(&header[..used])
}
pub(crate) fn element(&mut self, tag: u8, contents: &[u8]) -> Result<()> {
self.header(tag, contents.len())?;
self.raw(contents)
}
pub(crate) fn bit_string(&mut self, tag: u8, octets: &[u8]) -> Result<()> {
self.header(tag, 1 + octets.len())?;
self.raw(&[0x00])?;
self.raw(octets)
}
pub(crate) fn unsigned_integer(&mut self, magnitude: &[u8]) -> Result<()> {
let (digits, pad) = minimal_unsigned(magnitude);
self.header(INTEGER, digits.len() + usize::from(pad))?;
if pad {
self.raw(&[0x00])?;
}
self.raw(digits)
}
pub(crate) fn finish(self) -> Result<Zeroizing<Vec<u8>>> {
if self.buf.len() == self.planned {
Ok(self.buf)
} else {
Err(Error::InvalidKey("DER output length mismatch"))
}
}
}
#[cfg(test)]
pub(super) mod fixtures {
use std::path::{Path, PathBuf};
use crate::json::{self, Value};
fn dir(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/data/keys")
.join(name)
}
pub(crate) fn load(dir_name: &str, file: &str) -> Vec<u8> {
let path = dir(dir_name).join(file);
let bytes = std::fs::read(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
if file.ends_with(".pem") {
pem_rfc7468::decode_vec(&bytes)
.unwrap_or_else(|e| panic!("{file}: {e}"))
.1
} else {
bytes
}
}
pub(crate) fn files(dir_name: &str, keep: impl Fn(&str) -> bool) -> Vec<String> {
let mut names: Vec<String> = std::fs::read_dir(dir(dir_name))
.unwrap()
.map(|entry| entry.unwrap().file_name().into_string().unwrap())
.filter(|name| name.ends_with(".der") || name.ends_with(".pem"))
.filter(|name| keep(name))
.collect();
names.sort();
names
}
pub(crate) struct Negative {
pub(crate) id: String,
pub(crate) file: String,
pub(crate) expect: String,
}
pub(crate) fn negatives() -> Vec<Negative> {
let bytes = std::fs::read(dir("negative").join("manifest.json")).unwrap();
let manifest = json::parse(&bytes).unwrap();
let field =
|entry: &Value, name: &str| entry.get(name).and_then(Value::as_str).unwrap().to_owned();
manifest
.as_array()
.unwrap()
.iter()
.map(|entry| Negative {
id: field(entry, "id"),
file: field(entry, "file"),
expect: field(entry, "expect"),
})
.collect()
}
pub(crate) fn negative(id: &str) -> Vec<u8> {
let entry = negatives().into_iter().find(|n| n.id == id).unwrap();
load("negative", &entry.file)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn invalid(result: Result<impl Sized>) -> &'static str {
match result {
Err(Error::InvalidKey(detail)) => detail,
Err(other) => panic!("expected InvalidKey, got {other:?}"),
Ok(_) => panic!("expected InvalidKey, got Ok"),
}
}
fn one(input: &[u8]) -> Result<Element<'_>> {
let mut reader = Reader::new(input)?;
let element = reader.element()?;
reader.finish()?;
Ok(element)
}
#[test]
fn reads_a_short_form_element() {
let input = [0x04, 0x02, 0xAB, 0xCD];
let element = one(&input).unwrap();
assert_eq!(element.tag, 0x04);
assert_eq!(element.contents, &[0xAB, 0xCD]);
assert_eq!(element.encoding, &input);
}
#[test]
fn reads_long_form_lengths_of_one_to_four_octets() {
for (header, len) in [
(vec![0x04, 0x81, 0x80], 0x80usize),
(vec![0x04, 0x81, 0xFF], 0xFF),
(vec![0x04, 0x82, 0x01, 0x00], 0x100),
(vec![0x04, 0x82, 0xFF, 0xF0], 0xFFF0),
] {
let mut input = header.clone();
input.resize(header.len() + len, 0x5A);
let element = one(&input).unwrap();
assert_eq!(element.contents.len(), len, "{header:02x?}");
}
}
#[test]
fn long_form_for_a_short_length_is_rejected() {
assert_eq!(
invalid(one(&[0x04, 0x81, 0x01, 0x00])),
"DER length not minimal"
);
assert_eq!(invalid(one(&[0x04, 0x81, 0x7F])), "DER length not minimal");
}
#[test]
fn leading_zero_length_octets_are_rejected() {
let mut input = vec![0x04, 0x82, 0x00, 0x80];
input.resize(4 + 0x80, 0);
assert_eq!(invalid(one(&input)), "DER length not minimal");
}
#[test]
fn indefinite_length_is_rejected() {
assert_eq!(
invalid(one(&[0x30, 0x80, 0x05, 0x00, 0x00, 0x00])),
"DER indefinite length"
);
}
#[test]
fn lengths_of_five_or_more_octets_are_rejected() {
assert_eq!(
invalid(one(&[0x04, 0x85, 0x01, 0x00, 0x00, 0x00, 0x00])),
"DER length too long"
);
assert_eq!(invalid(one(&[0x04, 0xFF])), "DER length too long");
}
#[test]
fn a_length_beyond_the_input_is_rejected() {
assert_eq!(
invalid(one(&[0x04, 0x03, 0x01, 0x02])),
"DER length exceeds input"
);
assert_eq!(
invalid(one(&[0x04, 0x82, 0x01, 0x00, 0x00])),
"DER length exceeds input"
);
}
#[test]
fn truncated_headers_are_rejected() {
assert_eq!(invalid(one(&[])), "DER truncated");
assert_eq!(invalid(one(&[0x04])), "DER truncated");
assert_eq!(invalid(one(&[0x04, 0x82, 0x01])), "DER truncated");
}
#[test]
fn high_tag_number_form_is_rejected() {
assert_eq!(
invalid(one(&[0x3F, 0x10, 0x00])),
"DER high-tag-number form"
);
assert_eq!(
invalid(one(&[0x9F, 0x01, 0x00])),
"DER high-tag-number form"
);
}
#[test]
fn finish_rejects_leftover_bytes() {
assert_eq!(invalid(one(&[0x05, 0x00, 0x00])), "DER trailing data");
let mut reader = Reader::new(&[0x05, 0x00, 0x05, 0x00]).unwrap();
reader.element().unwrap();
assert_eq!(invalid(reader.finish()), "DER trailing data");
}
#[test]
fn the_input_is_capped_at_64_kib() {
let mut input = vec![0x04, 0x82, 0xFF, 0xFC];
input.resize(4 + 0xFFFC, 0);
assert_eq!(input.len(), MAX_INPUT_LEN);
assert!(one(&input).is_ok());
input.push(0);
assert_eq!(invalid(Reader::new(&input)), "DER input too large");
}
#[test]
fn elements_are_sub_slices_of_the_input() {
let input = [0x30, 0x04, 0x04, 0x02, 0xA5, 0xA5];
let element = one(&input).unwrap();
let range = input.as_ptr_range();
assert!(range.contains(&element.contents.as_ptr()));
assert!(range.contains(&element.encoding.as_ptr()));
}
fn read_all<'a, T>(
input: &'a [u8],
read: impl FnOnce(&mut Reader<'a>) -> Result<T>,
) -> Result<T> {
let mut reader = Reader::new(input)?;
let value = read(&mut reader)?;
reader.finish()?;
Ok(value)
}
#[test]
fn expect_checks_the_tag() {
let element = read_all(&[0x05, 0x00], |r| r.expect(0x05)).unwrap();
assert_eq!(element.contents, &[] as &[u8]);
assert_eq!(
invalid(read_all(&[0x05, 0x00], |r| r.expect(0x04))),
"DER unexpected tag"
);
assert_eq!(invalid(read_all(&[], |r| r.expect(0x04))), "DER truncated");
}
#[test]
fn sequence_and_explicit_tags_read_their_contents() {
let input = [0x30, 0x06, 0xA0, 0x02, 0x05, 0x00, 0x05, 0x00];
let (first, second) = read_all(&input, |r| {
let mut seq = r.sequence()?;
let mut tagged = seq.explicit(0xA0)?;
tagged.null()?;
tagged.finish()?;
seq.null()?;
seq.finish()?;
Ok((input[2], input[6]))
})
.unwrap();
assert_eq!((first, second), (0xA0, 0x05));
assert_eq!(
invalid(read_all(&[0x31, 0x00], |r| r.sequence().map(|_| ()))),
"DER unexpected tag"
);
}
#[test]
fn optional_reads_only_a_matching_tag() {
let input = [0x81, 0x01, 0x00, 0x05, 0x00];
read_all(&input, |r| {
assert!(r.optional(0xA0)?.is_none());
assert_eq!(r.optional(0x81)?.map(|e| e.contents), Some(&[0u8][..]));
assert!(r.optional(0x81)?.is_none());
r.null()
})
.unwrap();
}
#[test]
fn integers_must_be_minimal() {
for ok in [
&[0x02, 0x01, 0x00][..],
&[0x02, 0x01, 0x7F],
&[0x02, 0x02, 0x00, 0x80],
&[0x02, 0x01, 0x80],
&[0x02, 0x02, 0xFF, 0x7F],
] {
let contents = read_all(ok, Reader::integer).unwrap();
assert_eq!(contents, &ok[2..]);
}
for (bad, why) in [
(&[0x02, 0x00][..], "DER INTEGER empty"),
(&[0x02, 0x02, 0x00, 0x00], "DER INTEGER not minimal"),
(&[0x02, 0x02, 0x00, 0x7F], "DER INTEGER not minimal"),
(&[0x02, 0x02, 0xFF, 0x80], "DER INTEGER not minimal"),
(&[0x02, 0x02, 0xFF, 0xFF], "DER INTEGER not minimal"),
] {
assert_eq!(invalid(read_all(bad, Reader::integer)), why, "{bad:02x?}");
}
}
#[test]
fn unsigned_integers_reject_negatives_and_drop_the_sign_octet() {
assert_eq!(
read_all(&[0x02, 0x02, 0x00, 0x80], Reader::unsigned_integer).unwrap(),
&[0x80]
);
assert_eq!(
read_all(&[0x02, 0x01, 0x00], Reader::unsigned_integer).unwrap(),
&[0x00]
);
assert_eq!(
read_all(&[0x02, 0x02, 0x01, 0x00], Reader::unsigned_integer).unwrap(),
&[0x01, 0x00]
);
assert_eq!(
invalid(read_all(&[0x02, 0x01, 0x80], Reader::unsigned_integer)),
"DER INTEGER negative"
);
assert_eq!(
invalid(read_all(
&[0x02, 0x02, 0xFF, 0x00],
Reader::unsigned_integer
)),
"DER INTEGER negative"
);
}
#[test]
fn small_unsigned_reads_versions() {
assert_eq!(
read_all(&[0x02, 0x01, 0x00], Reader::small_unsigned).unwrap(),
0
);
assert_eq!(
read_all(&[0x02, 0x01, 0x02], Reader::small_unsigned).unwrap(),
2
);
assert_eq!(
read_all(&[0x02, 0x02, 0x00, 0xFF], Reader::small_unsigned).unwrap(),
255
);
assert_eq!(
invalid(read_all(&[0x02, 0x02, 0x01, 0x00], Reader::small_unsigned)),
"DER INTEGER out of range"
);
assert_eq!(
invalid(read_all(&[0x02, 0x01, 0xFF], Reader::small_unsigned)),
"DER INTEGER negative"
);
}
#[test]
fn strings_must_be_primitive() {
assert_eq!(
read_all(&[0x04, 0x02, 0x01, 0x02], Reader::octet_string).unwrap(),
&[0x01, 0x02]
);
assert_eq!(
invalid(read_all(
&[0x24, 0x03, 0x04, 0x01, 0x00],
Reader::octet_string
)),
"DER constructed string"
);
assert_eq!(
invalid(read_all(
&[0x23, 0x03, 0x03, 0x01, 0x00],
Reader::bit_string
)),
"DER constructed string"
);
assert_eq!(
invalid(read_all(&[0x05, 0x00], Reader::octet_string)),
"DER unexpected tag"
);
}
#[test]
fn bit_strings_must_have_no_unused_bits() {
assert_eq!(
read_all(&[0x03, 0x03, 0x00, 0xAB, 0xCD], Reader::bit_string).unwrap(),
&[0xAB, 0xCD]
);
assert_eq!(
read_all(&[0x03, 0x01, 0x00], Reader::bit_string).unwrap(),
&[] as &[u8]
);
assert_eq!(
invalid(read_all(&[0x03, 0x02, 0x01, 0xAA], Reader::bit_string)),
"DER BIT STRING has unused bits"
);
assert_eq!(
invalid(read_all(&[0x03, 0x00], Reader::bit_string)),
"DER BIT STRING empty"
);
assert_eq!(
read_all(&[0x81, 0x02, 0x00, 0x11], |r| r.implicit_bit_string(0x81)).unwrap(),
&[0x11]
);
assert_eq!(
invalid(read_all(&[0x81, 0x02, 0x07, 0x80], |r| r.implicit_bit_string(0x81))),
"DER BIT STRING has unused bits"
);
}
#[test]
fn null_must_be_empty() {
read_all(&[0x05, 0x00], Reader::null).unwrap();
assert_eq!(
invalid(read_all(&[0x05, 0x01, 0x00], Reader::null)),
"DER NULL not empty"
);
}
#[test]
fn object_identifiers_must_have_minimal_subidentifiers() {
let ed25519 = [0x06, 0x03, 0x2B, 0x65, 0x70];
assert_eq!(read_all(&ed25519, Reader::oid).unwrap(), &ed25519);
let p256 = [0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07];
assert_eq!(read_all(&p256, Reader::oid).unwrap(), &p256);
for bad in [
&[0x06, 0x00][..],
&[0x06, 0x04, 0x2B, 0x80, 0x65, 0x70],
&[0x06, 0x02, 0x80, 0x01],
&[0x06, 0x02, 0x2B, 0x86],
] {
assert_eq!(
invalid(read_all(bad, Reader::oid)),
"DER OBJECT IDENTIFIER malformed",
"{bad:02x?}"
);
}
}
#[test]
fn peek_and_is_empty_do_not_consume() {
let mut reader = Reader::new(&[0x05, 0x00]).unwrap();
assert_eq!(reader.peek_tag(), Some(0x05));
assert!(!reader.is_empty());
reader.null().unwrap();
assert_eq!(reader.peek_tag(), None);
assert!(reader.is_empty());
}
fn wrap(tag: u8, contents: &[u8]) -> Vec<u8> {
let mut out = vec![tag];
match contents.len() {
len @ 0..=0x7F => out.push(len as u8),
len @ 0x80..=0xFF => out.extend_from_slice(&[0x81, len as u8]),
len => out.extend_from_slice(&[0x82, (len >> 8) as u8, len as u8]),
}
out.extend_from_slice(contents);
out
}
const RFC8410_ATTRIBUTES: [u8; 31] = [
0x30, 0x1D, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x09, 0x14, 0x31,
0x0F, 0x0C, 0x0D, 0x43, 0x75, 0x72, 0x64, 0x6C, 0x65, 0x20, 0x43, 0x68, 0x61, 0x69, 0x72,
0x73,
];
#[test]
fn well_formed_der_is_accepted() {
check_well_formed(&[], true).unwrap();
check_well_formed(&RFC8410_ATTRIBUTES, true).unwrap();
let nested = wrap(
0x30,
&[
&wrap(0x30, &wrap(0x30, &[0x02, 0x01, 0x05]))[..],
&[0x01, 0x01, 0xFF],
&[0x03, 0x02, 0x04, 0xF0],
&[0x61, 0x00],
&[0x80, 0x01, 0xAA],
&wrap(0x31, &[0x02, 0x01, 0x01, 0x02, 0x01, 0x02]),
]
.concat(),
);
check_well_formed(&nested, false).unwrap();
}
#[test]
fn nested_framing_errors_are_rejected() {
for (bad, why) in [
(
wrap(0x30, &[0x04, 0x81, 0x01, 0x00]),
"DER length not minimal",
),
(wrap(0x30, &[0x04, 0x05, 0x00]), "DER length exceeds input"),
(wrap(0x30, &[0x3F, 0x10, 0x00]), "DER high-tag-number form"),
(
wrap(0x30, &[0x30, 0x80, 0x00, 0x00]),
"DER indefinite length",
),
] {
assert_eq!(invalid(check_well_formed(&bad, false)), why, "{bad:02x?}");
}
}
#[test]
fn primitive_type_rules_apply_at_any_depth() {
for (bad, why) in [
(wrap(0x30, &[0x00, 0x00]), "DER end-of-contents"),
(
wrap(0x30, &wrap(0x24, &[0x04, 0x00])),
"DER constructed string",
),
(
wrap(0x30, &wrap(0x2C, &[0x0C, 0x00])),
"DER constructed string",
),
(
wrap(0x30, &[0x02, 0x02, 0x00, 0x01]),
"DER INTEGER not minimal",
),
(
wrap(0x30, &[0x0A, 0x02, 0xFF, 0x80]),
"DER INTEGER not minimal",
),
(wrap(0x30, &[0x01, 0x01, 0x01]), "DER BOOLEAN not canonical"),
(wrap(0x30, &[0x01, 0x00]), "DER BOOLEAN not canonical"),
(wrap(0x30, &[0x05, 0x01, 0x00]), "DER NULL not empty"),
(
wrap(0x30, &[0x06, 0x02, 0x80, 0x01]),
"DER OBJECT IDENTIFIER malformed",
),
(
wrap(0x30, &[0x03, 0x02, 0x08, 0x00]),
"DER BIT STRING malformed",
),
(wrap(0x30, &[0x03, 0x01, 0x01]), "DER BIT STRING malformed"),
(wrap(0x30, &[0x03, 0x00]), "DER BIT STRING empty"),
(
wrap(0x30, &[0x03, 0x02, 0x04, 0xF8]),
"DER BIT STRING malformed",
),
(wrap(0x30, &[0x10, 0x00]), "DER unexpected tag"),
(
wrap(0x30, &wrap(0x22, &[0x02, 0x01, 0x00])),
"DER unexpected tag",
),
] {
assert_eq!(invalid(check_well_formed(&bad, false)), why, "{bad:02x?}");
}
}
#[test]
fn set_of_components_must_be_in_ascending_order() {
let ascending = [0x02, 0x01, 0x01, 0x02, 0x01, 0x02];
let descending = [0x02, 0x01, 0x02, 0x02, 0x01, 0x01];
check_well_formed(&ascending, true).unwrap();
assert_eq!(
invalid(check_well_formed(&descending, true)),
"DER SET OF not sorted"
);
check_well_formed(&descending, false).unwrap();
assert_eq!(
invalid(check_well_formed(&wrap(0x31, &descending), false)),
"DER SET OF not sorted"
);
check_well_formed(&[0x05, 0x00, 0x05, 0x00], true).unwrap();
check_well_formed(&[0x04, 0x00, 0x04, 0x01, 0x00], true).unwrap();
assert_eq!(
invalid(check_well_formed(&[0x04, 0x01, 0x00, 0x04, 0x00], true)),
"DER SET OF not sorted"
);
}
#[test]
fn pkcs8_attributes_are_ordered_by_whole_encoding_not_by_tag() {
let short = wrap(
0x30,
&[
&[0x06, 0x03, 0x55, 0x04, 0x0A][..],
&wrap(0x31, &[0x0C, 0x01, 0x61]),
]
.concat(),
);
let long = wrap(
0x30,
&[
&[0x06, 0x03, 0x55, 0x04, 0x03][..],
&wrap(0x31, &[0x0C, 0x02, 0x61, 0x62]),
]
.concat(),
);
check_well_formed(&[&short[..], &long].concat(), true).unwrap();
assert_eq!(
invalid(check_well_formed(&[&long[..], &short].concat(), true)),
"DER SET OF not sorted"
);
}
#[test]
fn nested_sets_are_checked_as_set_of_even_where_tag_order_disagrees() {
let attribute = |values: &[u8]| {
wrap(
0x30,
&[&[0x06, 0x03, 0x55, 0x04, 0x03][..], &wrap(0x31, values)].concat(),
)
};
let octet_order = [0x81, 0x01, 0x00, 0xA0, 0x00];
let tag_order = [0xA0, 0x00, 0x81, 0x01, 0x00];
check_well_formed(&attribute(&octet_order), true).unwrap();
assert_eq!(
invalid(check_well_formed(&attribute(&tag_order), true)),
"DER SET OF not sorted"
);
}
#[test]
fn deep_nesting_is_rejected_without_recursion() {
let mut value = vec![0x05, 0x00];
for _ in 0..2_000 {
value = wrap(0x30, &value);
}
assert!(value.len() <= MAX_INPUT_LEN);
assert_eq!(
invalid(check_well_formed(&value, false)),
"DER nesting too deep"
);
let mut shallow = vec![0x05, 0x00];
for _ in 0..MAX_DEPTH {
shallow = wrap(0x30, &shallow);
}
check_well_formed(&shallow, false).unwrap();
assert_eq!(
invalid(check_well_formed(&wrap(0x30, &shallow), false)),
"DER nesting too deep"
);
}
#[test]
fn writer_lengths_are_minimal() {
for (len, header) in [
(0usize, &[0x04, 0x00][..]),
(0x7F, &[0x04, 0x7F]),
(0x80, &[0x04, 0x81, 0x80]),
(0xFF, &[0x04, 0x81, 0xFF]),
(0x100, &[0x04, 0x82, 0x01, 0x00]),
(0xFFFB, &[0x04, 0x82, 0xFF, 0xFB]),
] {
let contents = vec![0xA5; len];
assert_eq!(encoded_len(len), header.len() + len);
let mut writer = Writer::new(encoded_len(len)).unwrap();
writer.element(OCTET_STRING, &contents).unwrap();
let out = writer.finish().unwrap();
assert_eq!(&out[..header.len()], header, "{len}");
assert_eq!(read_all(&out, Reader::octet_string).unwrap(), &contents[..]);
}
}
#[test]
fn writer_unsigned_integers_are_minimal_and_positive() {
for (magnitude, encoding) in [
(&[][..], &[0x02, 0x01, 0x00][..]),
(&[0x00], &[0x02, 0x01, 0x00]),
(&[0x00, 0x00, 0x05], &[0x02, 0x01, 0x05]),
(&[0x7F], &[0x02, 0x01, 0x7F]),
(&[0x80], &[0x02, 0x02, 0x00, 0x80]),
(&[0x00, 0xFF, 0x01], &[0x02, 0x03, 0x00, 0xFF, 0x01]),
(&[0x01, 0x00, 0x01], &[0x02, 0x03, 0x01, 0x00, 0x01]),
] {
assert_eq!(unsigned_integer_len(magnitude), encoding.len());
let mut writer = Writer::new(encoding.len()).unwrap();
writer.unsigned_integer(magnitude).unwrap();
let out = writer.finish().unwrap();
assert_eq!(&out[..], encoding, "{magnitude:02x?}");
let read = read_all(&out, Reader::unsigned_integer).unwrap();
let expected: &[u8] = match magnitude.iter().position(|&b| b != 0) {
Some(i) => &magnitude[i..],
None => &[0x00],
};
assert_eq!(read, expected);
}
}
#[test]
fn writer_bit_strings_have_no_unused_bits() {
let mut writer = Writer::new(bit_string_len(2)).unwrap();
writer
.bit_string(CONTEXT_1_PRIMITIVE, &[0xAB, 0xCD])
.unwrap();
let out = writer.finish().unwrap();
assert_eq!(&out[..], &[0x81, 0x03, 0x00, 0xAB, 0xCD]);
}
#[test]
fn writer_builds_nested_structures_from_planned_lengths() {
let inner = encoded_len(1);
let body = unsigned_integer_len(&[0]) + 2 + encoded_len(inner);
let mut writer = Writer::new(encoded_len(body)).unwrap();
writer.header(SEQUENCE, body).unwrap();
writer.unsigned_integer(&[0]).unwrap();
writer.raw(&[0x05, 0x00]).unwrap();
writer.header(CONTEXT_0, inner).unwrap();
writer.element(OCTET_STRING, &[0x01]).unwrap();
let out = writer.finish().unwrap();
assert_eq!(
&out[..],
&[
0x30, 0x0A, 0x02, 0x01, 0x00, 0x05, 0x00, 0xA0, 0x03, 0x04, 0x01, 0x01
]
);
}
#[test]
fn writer_rejects_writes_past_or_short_of_the_plan() {
let mut writer = Writer::new(2).unwrap();
assert_eq!(
invalid(writer.element(OCTET_STRING, &[0x00])),
"DER output length mismatch"
);
let mut writer = Writer::new(3).unwrap();
writer.raw(&[0x05, 0x00]).unwrap();
assert_eq!(invalid(writer.finish()), "DER output length mismatch");
assert_eq!(
invalid(Writer::new(MAX_INPUT_LEN + 1)),
"DER output too large"
);
}
#[test]
fn writer_output_is_zeroizing() {
fn zeroizing(_: &zeroize::Zeroizing<Vec<u8>>) {}
let mut writer = Writer::new(2).unwrap();
writer.raw(&[0x05, 0x00]).unwrap();
let out = writer.finish().unwrap();
zeroizing(&out);
assert!(out.capacity() >= 2);
}
#[test]
fn an_explicit_tag_wraps_a_complete_inner_encoding() {
let input = [CONTEXT_1, 0x04, BIT_STRING, 0x02, 0x00, 0x04];
let point = read_all(&input, |r| {
let mut tagged = r.explicit(CONTEXT_1)?;
let point = tagged.bit_string()?;
tagged.finish()?;
Ok(point)
})
.unwrap();
assert_eq!(point, &[0x04]);
assert_eq!(
invalid(read_all(&[0x81, 0x02, 0x00, 0x04], |r| r
.explicit(CONTEXT_1)
.map(|_| ()))),
"DER unexpected tag"
);
}
mod properties {
use proptest::prelude::*;
use super::super::{Reader, check_well_formed};
proptest! {
#![proptest_config(ProptestConfig::with_cases(1_000))]
#[test]
fn arbitrary_bytes_never_panic(bytes in proptest::collection::vec(any::<u8>(), 0..512), set_of in any::<bool>()) {
let _ = check_well_formed(&bytes, set_of);
let mut reader = Reader::new(&bytes).unwrap();
while let Ok(element) = reader.element() {
prop_assert!(element.encoding.len() >= 2 + element.contents.len());
let mut inner = Reader::new(element.contents).unwrap();
let _ = (inner.integer(), inner.bit_string(), inner.oid(), inner.null());
}
}
}
}
}