diff --git a/Cargo.toml b/Cargo.toml index af65514..84b0fa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,5 @@ log = "*" rand = "*" num = "*" enum_primitive = "*" +rustc-serialize = "*" +chrono = "*" diff --git a/src/der/bit_string.rs b/src/der/bit_string.rs new file mode 100644 index 0000000..e621d68 --- /dev/null +++ b/src/der/bit_string.rs @@ -0,0 +1,63 @@ +use der::{Tag, FromValue, DerResult}; +use super::DerErrorKind::InvalidVal; + +// there are *two* BIT STRING types. +// the first one is, well, bit vector. +// all bits except for unused ones are meaningful: +// for example, `0000_0000` != `0000` since the first one has 8 bits +// but the second one has 4 bits. +// +// the second one is bitfield structure. this works in a different way! +// for example, in the following structure: +// ``` +// example ::= BIT STRING { +// first (0), +// second (1), +// third (2) +// } +// ``` +// +// `{ first = false, second = true, third = false }` is encoded to `01`, +// not `010`. that is, all trailing zeros are removed. +// it is specified in x.680 (22.7) and x.690 (11.2.2) in a horrible way. +// +// this module only defines the first bit vector type. for the second one, +// use `bit_string_fields` macro. + +#[derive(Clone, PartialEq, Debug)] +pub struct BitString { + pub unused_bits: u8, + pub data: Vec, +} + +from_value!(BitString: Tag::BitString); + +/// Parse DER value and return (unused bits, bitstring value). +pub fn from_der<'a>(value: &'a [u8]) -> DerResult<(u8, &'a [u8])> { + let len = value.len(); + if len == 0 { + return der_err!(InvalidVal, "found zero-length BitString"); + } + + let unused_bits = value[0]; + if unused_bits >= 8 { + return der_err!(InvalidVal, "unused bits >= 8 found"); + } + if unused_bits > 0 && len == 1 { + return der_err!(InvalidVal, "unused bits != 0 but no bits found"); + } + + Ok((unused_bits, &value[1..])) +} + +impl FromValue for BitString { + fn from_value(value: &[u8]) -> DerResult { + let (unused_bits, data) = try!(from_der(value)); + let data = data.to_vec(); + + Ok(BitString { + unused_bits: unused_bits, + data: data, + }) + } +} diff --git a/src/der/macros.rs b/src/der/macros.rs new file mode 100644 index 0000000..801ab82 --- /dev/null +++ b/src/der/macros.rs @@ -0,0 +1,405 @@ +// TODO remove debug line +macro_rules! der_err { + ($kind:expr, $($args:tt)*) => ({ + let e = format!($($args)*); + let e = format!("{} {} / {}", file!(), line!(), e); + ::der::DerError::new($kind, e) + }) +} + +macro_rules! from_sequence { + ($seq_name:ident) => ( + impl $crate::der::FromTlv for $seq_name { + fn from_tlv(tag: $crate::der::Tag, value: &[u8]) -> $crate::der::DerResult<$seq_name> { + match tag { + ::der::Tag::Sequence => { + let seq_parser = $crate::der::reader::DerReader::new(value); + let result: $seq_name = try!($seq_name::from_seq(seq_parser)); + Ok(result) + } + _ => return der_err!($crate::der::DerErrorKind::InvalidTag, + "unexpected tag: {:?}", + tag), + } + } + } + + ) +} + +macro_rules! from_value { + ($ty_name:ty: $base_tag:pat) => ( + impl ::der::FromTlv for $ty_name { + fn from_tlv(tag: ::der::Tag, value: &[u8]) -> ::der::DerResult<$ty_name> { + match tag { + $base_tag => ::der::FromValue::from_value(value), + _ => return der_err!($crate::der::DerErrorKind::InvalidTag, + "unexpected tag: {:?}", + tag), + } + } + } + ) +} + +macro_rules! ctx_sp { + (P, $e:expr) => ( + ::der::Tag::Primitive($e, $crate::der::TagClass::ContextSpecific) + ); + (C, $e:expr) => ( + ::der::Tag::Constructed($e, $crate::der::TagClass::ContextSpecific) + ); +} + +macro_rules! sequence { + ( + $(#[$a:meta])* + struct $seq_name:ident { + $( + $item_name:ident: $item_ty:ty, + )+ + } + ) => ( + sequence_opts!( + $(#[$a])* + struct $seq_name { + $( + $item_name(): $item_ty, + )+ + } + ); + ) +} + +macro_rules! sequence_opts { + ( + $(#[$a:meta])* + struct $seq_name:ident { + $( + $item_name:ident($($opts:tt)*): $item_ty:ty, + )+ + } + ) => ( + $(#[$a])* + #[derive(Debug)] + pub struct $seq_name { + $( + pub $item_name: $item_ty, + )+ + } + + from_sequence!($seq_name); + + impl $seq_name { + fn from_seq(mut parser: $crate::der::reader::DerReader) + -> $crate::der::DerResult<$seq_name> { + $( + let $item_name: $item_ty = { + let ret = sequence_item!($item_ty, parser, $($opts)*); + ret + }; + )+ + + if let Some((tag, value)) = try!(parser.peek_tlv()) { + return der_err!($crate::der::DerErrorKind::InvalidTag, + "should be no element left, found {:?} in {}", + (tag, value), + stringify!($seq_name)); + } + + Ok($seq_name { + $( + $item_name: $item_name, + )+ + }) + } + } + ) +} + +macro_rules! sequence_item { + ($t:ty, $parser:expr,) => ({ + let (tag, value) = try!($parser.next_tlv()); + let result: $t = try!($crate::der::FromTlv::from_tlv(tag, value)); + result + }); + ($t:ty, $parser:expr, OPTIONAL, $($tag:path),+) => ({ + match try!($parser.peek_tlv()) { + None => None, + Some((tag, value)) => { + if $(tag == $tag)||+ { + $parser.bump(); + // `$t` is `Option` + let result: $t = Some(try!($crate::der::FromTlv::from_tlv(tag, value))); + result + } else { + None + } + } + } + }); + ($t:ty, $parser:expr, DEFAULT, $default:expr, $($tag:path),+) => ({ + match try!($parser.peek_tlv()) { + None => $default, + Some((tag, value)) => { + if $(tag == $tag)||+ { + $parser.bump(); + let result: $t = try!($crate::der::FromTlv::from_tlv(tag, value)); + // NOTE: DER requires `result != $default`, + // but invalid cases are found in practice: + // https://bugzilla.mozilla.org/show_bug.cgi?id=1031093 + // we may just have to disable it later. + if result == $default { + return der_err!($crate::der::DerErrorKind::InvalidVal, + "value identical to default: {}", + result); + } + result + } else { + $default + } + } + } + }); + ($t:ty, $parser:expr, IMPLICIT_OPTIONAL[$cls:ident:$id:expr], $orig_tag:path) => ({ + match try!($parser.peek_tlv()) { + None => None, + Some((tag, value)) => { + if tag == ctx_sp!($cls, $id) { + $parser.bump(); + let result: $t = Some(try!($crate::der::FromTlv::from_tlv($orig_tag, value))); + result + } else { + None + } + } + } + }); + ($t:ty, $parser:expr, EXPLICIT_OPTIONAL[$cls:ident:$id:expr]) => ({ + try!($parser.explicit(ctx_sp!($cls, $id), + |tag, value: &[u8]| { + let result = try!($crate::der::FromTlv::from_tlv(tag, value)); + Ok(Some(result)) + }, + || Ok(None) + )) + }); + ($t:ty, $parser:expr, EXPLICIT_DEFAULT[$cls:ident:$id:expr], $def:expr) => ({ + try!($parser.explicit(ctx_sp!($cls, $id), + |tag, value: &[u8]| { + let result = try!($crate::der::FromTlv::from_tlv(tag, value)); + Ok(result) + }, + || Ok($def) + )) + }); +} + +macro_rules! sequence_of { + ( + struct $seq_name:ident = $item_ty:ident($len_min:expr) + ) => ( + #[derive(Debug)] + pub struct $seq_name { + pub seq: Vec<$item_ty>, + } + + impl ::der::FromTlv for $seq_name { + fn from_tlv(tag: ::der::Tag, value: &[u8]) -> ::der::DerResult<$seq_name> { + match tag { + ::der::Tag::Sequence => { + let seq_parser = $crate::der::reader::DerReader::new(value); + let value: $seq_name = try!($seq_name::from_seq(seq_parser)); + Ok(value) + } + _ => return der_err!($crate::der::DerErrorKind::InvalidTag, + "expected Seq, unexpected tag: {:?}", + tag), + } + } + + } + + impl $seq_name { + fn from_seq(mut parser: ::der::reader::DerReader) -> ::der::DerResult<$seq_name> { + let mut seq: Vec<$item_ty> = Vec::new(); + + while !parser.is_eof() { + let (tag, value) = try!(parser.next_tlv()); + let item: $item_ty = try!($crate::der::FromTlv::from_tlv(tag, value)); + seq.push(item); + } + + let len_min: usize = $len_min; + + if seq.len() < len_min { + return der_err!($crate::der::DerErrorKind::InvalidVal, + "sequence shorter than {}", + len_min); + } + + Ok($seq_name { + seq: seq, + }) + } + } + ) +} + +macro_rules! bit_string_fields { + ( + struct $name:ident { + $( + $bit_name:ident($i:expr), + )+ + } + ) => ( + #[derive(Debug)] + pub struct $name { + $( + pub $bit_name: bool, + )+ + } + + from_value!($name: Tag::BitString); + + impl FromValue for $name { + fn from_value(value: &[u8]) -> DerResult<$name> { + let (unused_bits, value) = try!(::der::bit_string::from_der(value)); + let total_bits = value.len() * 8 - unused_bits as usize; + + // TODO `11100000` <- trailing bits should be disallowed + $( + let byte_offset = $i / 8; + let $bit_name: bool = if $i < total_bits { + (value[byte_offset] >> (7 - ($i % 8))) & 1 == 1 + } else { + false + }; + )+ + + Ok($name { + $( + $bit_name: $bit_name, + )+ + }) + } + } + ) +} + +// parses the following form: +// SEQUENCE { +// id OBJECT IDENTIFIER, +// value ANY DEFINED BY id +// } +macro_rules! enum_obj_id { + ( + enum $enum_name:ident { + $( + $name:ident($t:ty) = $val:pat, + )+ + } + ) => ( + #[derive(Debug)] + pub enum $enum_name { + $( + $name($t), + )+ + + Unknown, + } + + from_sequence!($enum_name); + impl $enum_name { + fn from_seq(mut reader: DerReader) -> DerResult<$enum_name> { + // FIXME: this exists only because `id` freezes reader. + #[derive(Debug)] + enum ObjId { + $( + $name, + )+ + + Unknown, + } + + let ext = { + let (tag, id) = try!(reader.next_tlv()); + if tag != Tag::ObjectIdentifier { + return der_err!(::der::DerErrorKind::InvalidTag, + "expected ObjectIdentifier, found {:?}", + tag); + } + + let ext = match id { + $( + $val => ObjId::$name, + )+ + _ => ObjId::Unknown, + }; + debug!("id: {:?} -> {:?}", id, ext); + ext + }; + + match ext { + $( + ObjId::$name => { + let (tag, value) = try!(reader.next_tlv()); + debug!("ext tag {:?} value {:?}", tag, value); + let result: $t = try!(FromTlv::from_tlv(tag, value)); + if !reader.is_eof() { + return der_err!(::der::DerErrorKind::InvalidTag, + "too many TLV elements"); + } + debug!("result: {}, {:?}", stringify!($name), result); + return Ok($enum_name::$name(result)); + } + )+ + ObjId::Unknown => { + return Ok($enum_name::Unknown); + } + } + } + } + ) +} + +// macro for enum based on one-byte integer +// e.g. `INTEGER { two-prime(0), multi(1) }` +macro_rules! enum_integer { + ( + enum $enum_name:ident { + $( + $name:ident = $val:pat, + )+ + } + ) => ( + #[derive(Debug)] + #[derive(PartialEq)] + pub enum $enum_name { + $( + $name, + )+ + } + + from_value!($enum_name: $crate::der::Tag::Integer); + + impl $crate::der::FromValue for $enum_name { + fn from_value(value: &[u8]) -> $crate::der::DerResult<$enum_name> { + let len = value.len(); + if len != 1 { + return der_err!($crate::der::DerErrorKind::InvalidVal, + "expected length 1, found {}", len); + } + let value = match value[0] { + $( + $val => $enum_name::$name, + )+ + other => return der_err!($crate::der::DerErrorKind::InvalidVal, + "unknown value: {}", other), + }; + Ok(value) + } + } + ) +} diff --git a/src/der/mod.rs b/src/der/mod.rs new file mode 100644 index 0000000..58ef5d7 --- /dev/null +++ b/src/der/mod.rs @@ -0,0 +1,160 @@ +// DER reader + +use std::fmt; + +pub use self::reader::DerReader; +pub use self::bit_string::BitString; +pub use self::obj_id::ObjId; +pub use self::time::Time; + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum DerErrorKind { + /// length field is too big or invalid + InvalidLen, + /// unknown or unsupported tag value found + InvalidTag, + /// unexpected eof while reading tlv + Eof, + /// value field has invalid data + InvalidVal, +} + +#[derive(Debug)] +pub struct DerError { + pub kind: DerErrorKind, + pub desc: String, +} + +impl fmt::Display for DerError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.desc.fmt(f) + } +} + +impl DerError { + pub fn new(kind: DerErrorKind, desc: String) -> DerResult { + Err(DerError { + kind: kind, + desc: desc, + }) + } +} + +pub type DerResult = Result; + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum TagClass { + Universal = 0b00, + Application = 0b01, + ContextSpecific = 0b10, + Private = 0b11, +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum Tag { + // primitives + Boolean, // 0x01 + Integer, // 0x02 + BitString, // 0x03 + OctetString, // 0x04 + Null, // 0x05 + ObjectIdentifier, // 0x06 + ObjectDescriptor, // 0x07 + External, // 0x08 + Real, // 0x09 + Enumerated, // 0x0a + EmbeddedPdv, // 0x0b + + // wow much string + Utf8String, // 0x0c + NumericString, // 0x12 + PrintableString, // 0x13 + TeletexString, // 0x14 + VideotexString, // 0x15 + Ia5String, // 0x16 + GraphicString, // 0x19 + VisibleString, // 0x1a + GeneralString, // 0x1b + UniversalString, // 0x1c + BmpString, // 0x1e + + UtcTime, // 0x17 + GeneralizedTime, // 0x18 + + // constructed + Sequence, // 0x10 + Set, // 0x11 + + Primitive(u8, TagClass), // tag, tag_class + Constructed(u8, TagClass), //tag, tag_class +} + +pub trait FromTlv { + fn from_tlv(tag: Tag, value: &[u8]) -> DerResult; +} + +pub trait FromValue: FromTlv { + fn from_value(value: &[u8]) -> DerResult; +} + +#[macro_use] pub mod macros; +pub mod reader; + +// basic primitive types + +pub mod bit_string; +pub mod obj_id; +pub mod string; +pub mod time; + +from_value!((): Tag::Null); +impl FromValue for () { + fn from_value(value: &[u8]) -> DerResult<()> { + if value.len() != 0 { + return der_err!(DerErrorKind::InvalidVal, "Null with non-zero length"); + } + Ok(()) + } +} + +from_value!(bool: Tag::Boolean); +impl FromValue for bool { + fn from_value(value: &[u8]) -> DerResult { + if value.len() != 1 { + return der_err!(DerErrorKind::InvalidVal, "boolean with wrong length"); + } + match value[0] { + 0 => Ok(false), + 255 => Ok(true), + _val => { + return der_err!(DerErrorKind::InvalidVal, "boolean with wrong value: {}", _val); + } + } + } +} + +from_value!(Vec: Tag::OctetString); +impl FromValue for Vec { + fn from_value(value: &[u8]) -> DerResult> { + Ok(value.to_vec()) + } +} + +#[derive(Debug)] +pub struct Any(pub Tag, pub Vec); +impl FromTlv for Any { + fn from_tlv(tag: Tag, value: &[u8]) -> DerResult { + Ok(Any(tag, value.to_vec())) + } +} + +#[derive(Debug)] +pub struct Integer(pub Vec); +from_value!(Integer: Tag::Integer); +impl FromValue for Integer { + fn from_value(value: &[u8]) -> DerResult { + Ok(Integer(value.to_vec())) + } +} + +#[cfg(test)] mod test; diff --git a/src/der/obj_id.rs b/src/der/obj_id.rs new file mode 100644 index 0000000..f19f62b --- /dev/null +++ b/src/der/obj_id.rs @@ -0,0 +1,24 @@ +use super::{Tag, FromValue, DerResult}; +use super::DerErrorKind::InvalidVal; + +#[derive(PartialEq, Debug)] +pub struct ObjId { + // DER encdoing of object identifier. + pub value: Vec, +} + +from_value!(ObjId: Tag::ObjectIdentifier); + +impl FromValue for ObjId { + fn from_value(value: &[u8]) -> DerResult { + let len = value.len(); + + if len == 0 { + return der_err!(InvalidVal, "ObjectIdentifier with zero length"); + } + + Ok(ObjId { + value: value.to_vec() + }) + } +} diff --git a/src/der/reader.rs b/src/der/reader.rs new file mode 100644 index 0000000..71675ab --- /dev/null +++ b/src/der/reader.rs @@ -0,0 +1,263 @@ +// # Notes +// +// - DER only! +// - Tag must be `< 31` (long tag is not supported) and length must be `< 65536`. + +// TODO max_len + +use std::cell::Cell; +use super::{Tag, TagClass}; +use super::DerResult; +use super::DerErrorKind::{InvalidLen, InvalidTag, Eof}; + +pub struct DerReader<'a> { + buf: &'a [u8], + pos: Cell, + cur: Option<(Tag, &'a [u8])>, +} + +impl<'a> DerReader<'a> { + pub fn new(buf: &'a [u8]) -> DerReader<'a> { + DerReader { + buf: buf, + pos: Cell::new(0), + cur: None, + } + } +} + +// internal utilities +impl<'a> DerReader<'a> { + fn read_u8(&mut self) -> DerResult { + match self.buf.get(self.pos.get()) { + Some(&val) => { + self.pos.set(self.pos.get() + 1); + Ok(val) + } + None => return der_err!(Eof, "out-of-range"), + } + } + + fn read_tag(&mut self) -> DerResult { + let (tag_class, is_constructed, tag) = { + let b0 = try!(self.read_u8()); + let class = match b0 >> 6 { + 0b00 => TagClass::Universal, + 0b01 => TagClass::Application, + 0b10 => TagClass::ContextSpecific, + 0b11 => TagClass::Private, + _ => unreachable!(), + }; + let is_constructed = (b0 >> 5) & 0b1 == 0b1; + + let tag = if b0 & 0b1_1111 == 0b1_1111 { + // tag can be > 31, but we just don't support it. + return der_err!(InvalidTag, "unsupported tag value > 31"); + } else { + b0 & 0b1_1111 + }; + + (class, is_constructed, tag) + }; + + let tag = if !is_constructed { + // primitives + if tag_class != TagClass::Universal { + Tag::Primitive(tag, tag_class) + } else { + match tag { + 0x00 => return der_err!(InvalidTag, "EndOfContents found in DER"), + + 0x01 => Tag::Boolean, + 0x02 => Tag::Integer, + 0x03 => Tag::BitString, + 0x04 => Tag::OctetString, + 0x05 => Tag::Null, + 0x06 => Tag::ObjectIdentifier, + 0x07 => Tag::ObjectDescriptor, + 0x09 => Tag::Real, + 0x0a => Tag::Enumerated, + 0x0c => Tag::Utf8String, + 0x12 => Tag::NumericString, + 0x13 => Tag::PrintableString, + 0x14 => Tag::TeletexString, + 0x15 => Tag::VideotexString, + 0x16 => Tag::Ia5String, + 0x17 => Tag::UtcTime, + 0x18 => Tag::GeneralizedTime, + 0x19 => Tag::GraphicString, + 0x1a => Tag::VisibleString, + 0x1b => Tag::GeneralString, + 0x1c => Tag::UniversalString, + 0x1e => Tag::BmpString, + + 0x08 | 0x0b | 0x10 | 0x11 => { + return der_err!(InvalidTag, "Constructed tag found in Primitive: {}", tag); + } + _ => return der_err!(InvalidTag, "Unexpected tag in Primitive: {}", tag), + } + } + } else { + if tag_class != TagClass::Universal { + Tag::Constructed(tag, tag_class) + } else { + match tag { + 0x08 => Tag::External, + 0x0b => Tag::EmbeddedPdv, + 0x10 => Tag::Sequence, + 0x11 => Tag::Set, + + 0x00 ... 0x0c | 0x12 ... 0x1e => { + return der_err!(InvalidTag, "Primitive tag found in Constructed: {}", tag); + } + + _ => return der_err!(InvalidTag, "Unexpected tag in Constructed: {}", tag), + } + } + }; + + Ok(tag) + } + + // length is actually u16 + fn read_len(&mut self) -> DerResult { + let len = { + let b0 = try!(self.read_u8()); + if b0 == 0b1000_0000 { + return der_err!(InvalidLen, "indefinite length found in DER"); + } else if b0 >> 7 == 1 { + // long form. + // FIXME: check "overlong" bytes e.g. 0x82 0x00 0x01 + // I guess DER prohibits such one.. + let lenlen = b0 & 0b111_1111; + if lenlen == 0b111_1111 { + return der_err!(InvalidLen, "illegal length"); + } + // we just don't expect >= 2^16 bytes. + if lenlen > 2 { + return der_err!(InvalidLen, "unsupported length: {}", lenlen); + } + let mut len: u32 = 0; + for _i in 0..lenlen { + let next = try!(self.read_u8()); + len = (len << 8) | (next as u32); + } + len as usize + } else { + b0 as usize + } + }; + + Ok(len) + } + + fn read_value(&mut self, len: usize) -> DerResult<&'a [u8]> { + let new_pos = self.pos.get() + len; + if new_pos > self.buf.len() { + return der_err!(Eof, "length too large: {}", len); + } + + let slice = &self.buf[self.pos.get() ..new_pos]; + self.pos.set(self.pos.get() + len); + Ok(slice) + } +} + +// basic methods +impl<'a> DerReader<'a> { + pub fn is_eof(&self) -> bool { + self.cur == None && (self.buf.len() == self.pos.get()) + } + + // may return Ok(None) if eof + // call `.bump()` if returned tlv has been consumed + pub fn peek_tlv(&mut self) -> DerResult> { + if self.is_eof() { + return Ok(None); + } + let (tag, len) = match self.cur { + None => { + let tag = try!(self.read_tag()); + let len = try!(self.read_len()); + let val = try!(self.read_value(len)); + self.cur = Some((tag, val)); + debug!("peek_tlv: tag {:?} val {:?}", tag, val); + (tag, val) + } + Some(tag_len) => tag_len + }; + Ok(Some((tag, len))) + } + + pub fn bump(&mut self) { + self.cur = None; + } + + pub fn next_tlv(&mut self) -> DerResult<(Tag, &'a [u8])> { + match try!(self.peek_tlv()) { + None => der_err!(Eof, "end of stream when reading TLV"), + Some(next) => { + self.bump(); + Ok(next) + } + } + } +} + +// helpers +impl<'a> DerReader<'a> { + pub fn explicit(&mut self, + expected_tag: Tag, + mut matched: F1, + mut unmatched: F2) -> DerResult where + F1: FnMut(Tag, &[u8]) -> DerResult, + F2: FnMut() -> DerResult, + { + match try!(self.peek_tlv()) { + None => unmatched(), + Some((tag, value)) => { + if tag == expected_tag { + self.bump(); + + // actual data is wrapped in Constructed tag. + let mut exp_parser = DerReader::new(value); + + let (tag, value) = try!(exp_parser.next_tlv()); + let result = try!(matched(tag, value)); + + // only one should be packed? + if let Some(some) = try!(exp_parser.peek_tlv()) { + return der_err!(InvalidTag, + "unexpected tag {:?} inside {:?}", + some, + expected_tag); + } + + Ok(result) + } else { + unmatched() + } + } + } + } + + pub fn default(&mut self, + expected_tag: Tag, + mut matched: F1, + mut unmatched: F2) -> DerResult where + F1: FnMut(&[u8]) -> DerResult, + F2: FnMut() -> DerResult, + { + match try!(self.peek_tlv()) { + None => unmatched(), + Some((tag, value)) => { + if expected_tag == tag { + self.bump(); + matched(value) + } else { + unmatched() + } + } + } + } +} diff --git a/src/der/string.rs b/src/der/string.rs new file mode 100644 index 0000000..c9c9bc3 --- /dev/null +++ b/src/der/string.rs @@ -0,0 +1,72 @@ +use std::str; +use std::borrow::ToOwned; + +use der::{Tag, FromTlv, DerResult}; +use der::DerErrorKind::{InvalidTag, InvalidVal}; + +// ASN.1 strings are jokes. there are so many string types with their own subtle rules +// and some of the rules are ignored in wild. +// +// PrintableString doesn't allow '*' but some certs contain such values. +// https://code.google.com/p/go/issues/detail?id=850 +// (mozilla pkix also accepts '*'.) +// +// PrintableString also doesn't allow '@', so you cannot print e-mail address. +// Due to this, some legacy certs used IA5String where it cannot occur. +// +// there are *three* unicode strings. UniversalString and Utf8String are +// basically same in ASN.1 but differently encoded in DER. +// BMPString is.. BMP subset. encoded as ucs2. +// (did you know ucs2 is big endian in spec?) +// +// also keep in mind that Rust doesn't accept invalid surrogates. +// we will just treat such string as "invalid". + +// FIXME: I'm going to live with the std String for now. +// I'm not saying this is unimportant; just saying that I'm tired right now and +// I'll revise this in other day. + +impl FromTlv for String { + fn from_tlv(tag: Tag, value: &[u8]) -> DerResult { + match tag { + Tag::Utf8String | Tag::PrintableString | Tag::Ia5String | + Tag::TeletexString | Tag::VisibleString => { + match str::from_utf8(value) { + Ok(value) => Ok(value.to_owned()), + Err(err) => return der_err!(InvalidVal, "invalid utf-8: {}, \"{:?}\"", err, value), + } + } + // TODO: UniversalString, BmpString + _ => return der_err!(InvalidTag, "unexpected tag \"{:?}\" for String", tag), + } + } +} + +// TODO + +// pub struct PrintableString<'a>(&'a [u8]); + +// impl<'a> PrintableString<'a> { +// pub fn from_bytes(bytes: &'a [u8]) -> Option> { +// for b in bytes.iter() { +// match b { +// b'A'...b'Z' | b'a'...b'z' | b'0'...b'9' | b' ' | +// b'\'' | b'(' | b')' | b'+' | b',' | b'-' | b'.' | b'/' | +// b':' | b'=' | b'?' => {} +// _ => return None, +// } +// } +// Some(PrintableString(bytes)) +// } +// } + +// // TODO +// #[derive(Debug)] +// pub struct Ia5String(pub Vec); +// from_value!(Ia5String: Tag::Ia5String); + +// impl FromValue for Ia5String { +// fn from_value(value: &[u8]) -> DerResult { +// Ok(Ia5String(value.to_vec())) +// } +// } diff --git a/src/der/test.rs b/src/der/test.rs new file mode 100644 index 0000000..ef91b32 --- /dev/null +++ b/src/der/test.rs @@ -0,0 +1,93 @@ +use der::{Tag, FromTlv, FromValue, DerResult, DerErrorKind}; +use der::reader::DerReader; + +macro_rules! assert_err { + ($e:expr, $expected:expr) => ( + match $e { + Err(ref e) if e.kind == $expected => {} + actual => { + panic!("expected {:?}, found {:?}", $expected, actual); + } + } + ) +} + +#[derive(Debug, PartialEq)] +pub struct OctetString(Vec); +from_value!(OctetString: Tag::OctetString); +impl FromValue for OctetString { + fn from_value(value: &[u8]) -> DerResult { + Ok(OctetString(value.to_vec())) + } +} + +sequence_opts!(#[derive(PartialEq)] struct DefaultOptional { + default(DEFAULT, false, Tag::Boolean): bool, + optional(OPTIONAL, Tag::OctetString): Option, + null(): (), +}); + +#[test] +fn test_default_optional() { + let ders: Vec<(Vec, DefaultOptional)> = vec![ + (vec![0x30, 0x02, 0x05, 0x00], DefaultOptional { + default: false, + optional: None, + null: (), + }), + (vec![0x30, 0x05, 0x01, 0x01, 0xFF, 0x05, 0x00], DefaultOptional { + default: true, + optional: None, + null: (), + }), + (vec![0x30, 0x05, 0x04, 0x01, 0x12, 0x05, 0x00], DefaultOptional { + default: false, + optional: Some(OctetString(vec!(0x12))), + null: (), + }), + (vec![0x30, 0x08, 0x01, 0x01, 0xFF, 0x04, 0x01, 0x12, 0x05, 0x00], DefaultOptional { + default: true, + optional: Some(OctetString(vec!(0x12))), + null: (), + }), + ]; + + for &(ref der, ref expected) in ders.iter() { + let mut reader = DerReader::new(&der); + let (tag, value) = reader.next_tlv().unwrap(); + let actual: DefaultOptional = FromTlv::from_tlv(tag, value).unwrap(); + assert_eq!(expected, &actual); + assert!(reader.is_eof()); + } +} + +// http://www.intelsecurity.com/advanced-threat-research/berserk.html +// http://www.intelsecurity.com/resources/wp-berserk-analysis-part-2.pdf +#[test] +fn test_length_overflow() { + let mut der = vec![0x05]; + // 0x80 | 0x59, length is represented as 0x59-bytes. + let len = 0xd9; + der.push(len); + for _ in 0..(len - 4) { + der.push(0xff); + } + der.push_all(&[0x00, 0x00, 0x00, 0x02]); + // null + der.push_all(&[0x05, 0x00]); + + let mut reader = DerReader::new(&der); + let next = reader.next_tlv(); + assert_err!(next, DerErrorKind::InvalidLen); +} + +// null should be null. +#[test] +fn test_null() { + let der = [0x05, 0x01, 0x01]; + + let mut reader = DerReader::new(&der); + let (tag, value) = reader.next_tlv().unwrap(); + let null_or_err: DerResult<()> = FromTlv::from_tlv(tag, value); + assert_err!(null_or_err, DerErrorKind::InvalidVal); +} diff --git a/src/der/time.rs b/src/der/time.rs new file mode 100644 index 0000000..d9a309c --- /dev/null +++ b/src/der/time.rs @@ -0,0 +1,95 @@ +use chrono::{DateTime, UTC, TimeZone}; + +use super::{Tag, DerResult, FromTlv}; +use super::DerErrorKind::{InvalidTag, InvalidVal}; + +#[derive(Debug)] +pub struct Time { + pub time: DateTime, +} + +impl FromTlv for Time { + fn from_tlv(tag: Tag, value: &[u8]) -> DerResult