//! A small ASN.1 parsing library for Rust. In particular, this library is used //! to translate the binary DER encoding of an ASN.1-formatted document into the //! core primitives of ASN.1. It is assumed that you can do what you need to //! from there. //! //! The critical items for this document are the traits `ToASN1` and `FromASN1`. //! The first takes your data type and encodes it into a `Vec` of simple ASN.1 //! structures (`ASN1Block`s). The latter inverts the process. //! //! Items that implement `ToASN1` can be used with the function `der_encode` //! to provide single-step encoding of a data type to binary DER encoding. //! Similarly, items that are `FromASN` can be single-step decoded using //! the helper function `der_decode`. //! //! You can implement one or both traits, depending on your needs. If you do //! implement both, the obvious encode/decode quickcheck property is strongly //! advised. //! //! For decoding schemes that require the actual bytes associated with the //! binary representation, we also provide `FromASN1WithBody`. This can be //! used with the offset information in the primitive `ASN1Block`s to, for //! example, validate signatures in X509 documents. //! //! Finally, this library supports ASN.1 class information. I'm still not sure //! why it's useful, but there it is. //! //! Please send any bug reports, patches, and curses to the GitHub repository //! at https://github.com/acw/simple_asn1. extern crate alloc; mod ber; mod bitstring; use chrono::{DateTime, TimeZone, Utc}; pub use num_bigint::{BigInt, BigUint}; use num_traits::{FromPrimitive, One, ToPrimitive, Zero}; #[cfg(test)] use quickcheck::quickcheck; use std::fmt; use std::iter::FromIterator; use std::mem::size_of; use std::str::Utf8Error; /// An ASN.1 block class. /// /// I'm not sure if/when these are used, but here they are in case you want /// to do something with them. #[derive(Clone, Copy, Debug, PartialEq)] pub enum ASN1Class { Universal, Application, ContextSpecific, Private, } /// A primitive block from ASN.1. /// /// Primitive blocks all contain the offset from the beginning of the parsed /// document, followed by whatever data is associated with the block. The latter /// should be fairly self-explanatory, so let's discuss the offset. /// /// The offset is only valid during the reading process. It is ignored for /// the purposes of encoding blocks into their binary form. It is also /// ignored for the purpose of comparisons via `==`. It is included entirely /// to support the parsing of things like X509 certificates, in which it is /// necessary to know when particular blocks end. /// /// The [`ASN1Class`] of explicitly tagged blocks is either `Application`, /// `ContextSpecific` or `Private`. `Unknown` can have any class. /// The class of all other variants is `Universal`. /// /// [`ASN1Class`]: enum.ASN1Class.html #[derive(Clone, Debug)] pub enum ASN1Block { Boolean(usize, bool), Integer(usize, BigInt), BitString(usize, usize, Vec), OctetString(usize, Vec), Null(usize), ObjectIdentifier(usize, OID), UTF8String(usize, String), PrintableString(usize, String), TeletexString(usize, String), IA5String(usize, String), UTCTime(usize, DateTime), GeneralizedTime(usize, DateTime), UniversalString(usize, String), BMPString(usize, String), Sequence(usize, Vec), Set(usize, Vec), /// An explicitly tagged block. /// /// The class can be either `Application`, `ContextSpecific` or `Private`. /// The other parameters are `offset`, `tag` and `content`. /// /// This block is always `constructed`. Explicit(ASN1Class, usize, BigUint, Box), /// An unkown block. /// /// The parameters are `class`, `constructed`, `offset`, `tag` and /// `content`. Unknown(ASN1Class, bool, usize, BigUint, Vec), } impl ASN1Block { /// Get the class associated with the given ASN1Block, regardless of what /// kind of block it is. pub fn class(&self) -> ASN1Class { match self { &ASN1Block::Boolean(_, _) => ASN1Class::Universal, &ASN1Block::Integer(_, _) => ASN1Class::Universal, &ASN1Block::BitString(_, _, _) => ASN1Class::Universal, &ASN1Block::OctetString(_, _) => ASN1Class::Universal, &ASN1Block::Null(_) => ASN1Class::Universal, &ASN1Block::ObjectIdentifier(_, _) => ASN1Class::Universal, &ASN1Block::UTF8String(_, _) => ASN1Class::Universal, &ASN1Block::PrintableString(_, _) => ASN1Class::Universal, &ASN1Block::TeletexString(_, _) => ASN1Class::Universal, &ASN1Block::IA5String(_, _) => ASN1Class::Universal, &ASN1Block::UTCTime(_, _) => ASN1Class::Universal, &ASN1Block::GeneralizedTime(_, _) => ASN1Class::Universal, &ASN1Block::UniversalString(_, _) => ASN1Class::Universal, &ASN1Block::BMPString(_, _) => ASN1Class::Universal, &ASN1Block::Sequence(_, _) => ASN1Class::Universal, &ASN1Block::Set(_, _) => ASN1Class::Universal, &ASN1Block::Explicit(c, _, _, _) => c, &ASN1Block::Unknown(c, _, _, _, _) => c, } } /// Get the starting offset associated with the given ASN1Block, regardless /// of what kind of block it is. pub fn offset(&self) -> usize { match self { &ASN1Block::Boolean(o, _) => o, &ASN1Block::Integer(o, _) => o, &ASN1Block::BitString(o, _, _) => o, &ASN1Block::OctetString(o, _) => o, &ASN1Block::Null(o) => o, &ASN1Block::ObjectIdentifier(o, _) => o, &ASN1Block::UTF8String(o, _) => o, &ASN1Block::PrintableString(o, _) => o, &ASN1Block::TeletexString(o, _) => o, &ASN1Block::IA5String(o, _) => o, &ASN1Block::UTCTime(o, _) => o, &ASN1Block::GeneralizedTime(o, _) => o, &ASN1Block::UniversalString(o, _) => o, &ASN1Block::BMPString(o, _) => o, &ASN1Block::Sequence(o, _) => o, &ASN1Block::Set(o, _) => o, &ASN1Block::Explicit(_, o, _, _) => o, &ASN1Block::Unknown(_, _, o, _, _) => o, } } } impl PartialEq for ASN1Block { fn eq(&self, other: &ASN1Block) -> bool { match (self, other) { (&ASN1Block::Boolean(_, a1), &ASN1Block::Boolean(_, a2)) => (a1 == a2), (&ASN1Block::Integer(_, ref a1), &ASN1Block::Integer(_, ref a2)) => (a1 == a2), (&ASN1Block::BitString(_, a1, ref b1), &ASN1Block::BitString(_, a2, ref b2)) => { (a1 == a2) && (b1 == b2) } (&ASN1Block::OctetString(_, ref a1), &ASN1Block::OctetString(_, ref a2)) => (a1 == a2), (&ASN1Block::Null(_), &ASN1Block::Null(_)) => true, (&ASN1Block::ObjectIdentifier(_, ref a1), &ASN1Block::ObjectIdentifier(_, ref a2)) => { a1 == a2 } (&ASN1Block::UTF8String(_, ref a1), &ASN1Block::UTF8String(_, ref a2)) => (a1 == a2), (&ASN1Block::PrintableString(_, ref a1), &ASN1Block::PrintableString(_, ref a2)) => { a1 == a2 } (&ASN1Block::TeletexString(_, ref a1), &ASN1Block::TeletexString(_, ref a2)) => { a1 == a2 } (&ASN1Block::IA5String(_, ref a1), &ASN1Block::IA5String(_, ref a2)) => (a1 == a2), (&ASN1Block::UTCTime(_, ref a1), &ASN1Block::UTCTime(_, ref a2)) => (a1 == a2), (&ASN1Block::GeneralizedTime(_, ref a1), &ASN1Block::GeneralizedTime(_, ref a2)) => { a1 == a2 } (&ASN1Block::UniversalString(_, ref a1), &ASN1Block::UniversalString(_, ref a2)) => { a1 == a2 } (&ASN1Block::BMPString(_, ref a1), &ASN1Block::BMPString(_, ref a2)) => (a1 == a2), (&ASN1Block::Sequence(_, ref a1), &ASN1Block::Sequence(_, ref a2)) => (a1 == a2), (&ASN1Block::Set(_, ref a1), &ASN1Block::Set(_, ref a2)) => (a1 == a2), ( &ASN1Block::Explicit(a1, _, ref b1, ref c1), &ASN1Block::Explicit(a2, _, ref b2, ref c2), ) => (a1 == a2) && (b1 == b2) && (c1 == c2), ( &ASN1Block::Unknown(a1, b1, _, ref c1, ref d1), &ASN1Block::Unknown(a2, b2, _, ref c2, ref d2), ) => (a1 == a2) && (b1 == b2) && (c1 == c2) && (d1 == d2), _ => false, } } } /// An ASN.1 OID. #[derive(Clone, Debug, PartialEq)] pub struct OID(Vec); impl OID { /// Generate an ASN.1. The vector should be in the obvious format, /// with each component going left-to-right. pub fn new(x: Vec) -> OID { OID(x) } /// converts the pub fn as_raw(&self) -> Result, ASN1EncodeErr> { match (self.0.get(0), self.0.get(1)) { (Some(v1), Some(v2)) => { let two = BigUint::from_u8(2).unwrap(); // first, validate that the first two items meet spec if v1 > &two { return Err(ASN1EncodeErr::ObjectIdentVal1TooLarge); } let u175 = BigUint::from_u8(175).unwrap(); let u39 = BigUint::from_u8(39).unwrap(); let bound = if v1 == &two { u175 } else { u39 }; if v2 > &bound { return Err(ASN1EncodeErr::ObjectIdentVal2TooLarge); } // the following unwraps must be safe, based on the // validation above. let value1 = v1.to_u8().unwrap(); let value2 = v2.to_u8().unwrap(); let byte1 = (value1 * 40) + value2; // now we can build all the rest of the body let mut body = vec![byte1]; for num in self.0.iter().skip(2) { let mut local = encode_base127(&num); body.append(&mut local); } Ok(body) } _ => Err(ASN1EncodeErr::ObjectIdentHasTooFewFields), } } } impl<'a> PartialEq for &'a OID { fn eq(&self, v2: &OID) -> bool { let &&OID(ref vec1) = self; let &OID(ref vec2) = v2; if vec1.len() != vec2.len() { return false; } for i in 0..vec1.len() { if vec1[i] != vec2[i] { return false; } } true } } /// A handy macro for generating OIDs from a sequence of `u64`s. /// /// Usage: oid!(1,2,840,113549,1,1,1) creates an OID that matches /// 1.2.840.113549.1.1.1. (Coincidentally, this is RSA.) #[macro_export] macro_rules! oid { ( $( $e: expr ),* ) => {{ let mut res = Vec::new(); $( res.push(BigUint::from($e as u64)); )* OID::new(res) }}; } const PRINTABLE_CHARS: &'static str = "ABCDEFGHIJKLMOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'()+,-./:=? "; /// An error that can arise decoding ASN.1 primitive blocks. #[derive(Clone, Debug, PartialEq)] pub enum ASN1DecodeErr { EmptyBuffer, BadBooleanLength(usize), LengthTooLarge(usize), UTF8DecodeFailure(Utf8Error), PrintableStringDecodeFailure, InvalidDateValue(String), InvalidBitStringLength(isize), /// Not a valid ASN.1 class InvalidClass(u8), /// Expected more input /// /// Invalid ASN.1 input can lead to this error. Incomplete, } impl fmt::Display for ASN1DecodeErr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ASN1DecodeErr::EmptyBuffer => { write!(f, "Encountered an empty buffer decoding ASN1 block.") } ASN1DecodeErr::BadBooleanLength(x) => { write!(f, "Bad length field in boolean block: {}", x) } ASN1DecodeErr::LengthTooLarge(x) => { write!(f, "Length field too large for object type: {}", x) } ASN1DecodeErr::UTF8DecodeFailure(x) => { write!(f, "UTF8 string failed to properly decode: {}", x) } ASN1DecodeErr::PrintableStringDecodeFailure => { write!(f, "Printable string failed to properly decode.") } ASN1DecodeErr::InvalidDateValue(x) => write!(f, "Invalid date value: {}", x), ASN1DecodeErr::InvalidBitStringLength(i) => { write!(f, "Invalid length of bit string: {}", i) } ASN1DecodeErr::InvalidClass(i) => write!(f, "Invalid class value: {}", i), ASN1DecodeErr::Incomplete => write!(f, "Incomplete data or invalid ASN1"), } } } /// An error that can arise encoding ASN.1 primitive blocks. #[derive(Clone, Debug, PartialEq)] pub enum ASN1EncodeErr { ObjectIdentHasTooFewFields, ObjectIdentVal1TooLarge, ObjectIdentVal2TooLarge, } impl fmt::Display for ASN1EncodeErr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ASN1EncodeErr::ObjectIdentHasTooFewFields => { write!(f, "ASN1 object identifier has too few fields.") } ASN1EncodeErr::ObjectIdentVal1TooLarge => { write!(f, "First value in ASN1 OID is too big.") } ASN1EncodeErr::ObjectIdentVal2TooLarge => { write!(f, "Second value in ASN1 OID is too big.") } } } } /// Translate a binary blob into a series of `ASN1Block`s, or provide an /// error if it didn't work. pub fn from_der(i: &[u8]) -> Result, ASN1DecodeErr> { from_der_(i, 0) } fn from_der_(i: &[u8], start_offset: usize) -> Result, ASN1DecodeErr> { let mut result: Vec = Vec::new(); let mut index: usize = 0; let len = i.len(); while index < len { let soff = start_offset + index; let (tag, constructed, class) = decode_tag(i, &mut index)?; let len = decode_length(i, &mut index)?; let checklen = index .checked_add(len) .ok_or(ASN1DecodeErr::LengthTooLarge(len))?; if checklen > i.len() { return Err(ASN1DecodeErr::Incomplete); } let body = &i[index..(index + len)]; if class != ASN1Class::Universal { if constructed { // Try to read as explicitly tagged match from_der_(body, start_offset + index) { Ok(mut items) => { if items.len() == 1 { result.push(ASN1Block::Explicit( class, soff, tag, Box::new(items.remove(0)), )); index += len; continue; } } Err(_) => {} } } result.push(ASN1Block::Unknown( class, constructed, soff, tag, body.to_vec(), )); index += len; continue; } // Universal class match tag.to_u8() { // BOOLEAN Some(0x01) => { if len != 1 { return Err(ASN1DecodeErr::BadBooleanLength(len)); } result.push(ASN1Block::Boolean(soff, body[0] != 0)); } // INTEGER Some(0x02) => { let res = BigInt::from_signed_bytes_be(&body); result.push(ASN1Block::Integer(soff, res)); } // BIT STRING Some(0x03) if body.len() == 0 => result.push(ASN1Block::BitString(soff, 0, Vec::new())), Some(0x03) => { let bits = (&body[1..]).to_vec(); let bitcount = bits.len() * 8; let rest = body[0] as usize; if bitcount < rest { return Err(ASN1DecodeErr::InvalidBitStringLength( bitcount as isize - rest as isize, )); } let nbits = bitcount - (body[0] as usize); result.push(ASN1Block::BitString(soff, nbits, bits)) } // OCTET STRING Some(0x04) => result.push(ASN1Block::OctetString(soff, body.to_vec())), // NULL Some(0x05) => { result.push(ASN1Block::Null(soff)); } // OBJECT IDENTIFIER Some(0x06) => { let mut value1 = BigUint::zero(); if body.len() == 0 { return Err(ASN1DecodeErr::Incomplete); } let mut value2 = BigUint::from_u8(body[0]).unwrap(); let mut oidres = Vec::new(); let mut bindex = 1; if body[0] >= 40 { if body[0] < 80 { value1 = BigUint::one(); value2 = value2 - BigUint::from_u8(40).unwrap(); } else { value1 = BigUint::from_u8(2).unwrap(); value2 = value2 - BigUint::from_u8(80).unwrap(); } } oidres.push(value1); oidres.push(value2); while bindex < body.len() { oidres.push(decode_base127(body, &mut bindex)?); } let res = OID(oidres); result.push(ASN1Block::ObjectIdentifier(soff, res)) } // UTF8STRING Some(0x0C) => match String::from_utf8(body.to_vec()) { Ok(v) => result.push(ASN1Block::UTF8String(soff, v)), Err(e) => return Err(ASN1DecodeErr::UTF8DecodeFailure(e.utf8_error())), }, // SEQUENCE Some(0x10) => match from_der_(body, start_offset + index) { Ok(items) => result.push(ASN1Block::Sequence(soff, items)), Err(e) => return Err(e), }, // SET Some(0x11) => match from_der_(body, start_offset + index) { Ok(items) => result.push(ASN1Block::Set(soff, items)), Err(e) => return Err(e), }, // PRINTABLE STRING Some(0x13) => { let mut res = String::new(); let val = body.iter().map(|x| *x as char); for c in val { if PRINTABLE_CHARS.contains(c) { res.push(c); } else { return Err(ASN1DecodeErr::PrintableStringDecodeFailure); } } result.push(ASN1Block::PrintableString(soff, res)); } // TELETEX STRINGS Some(0x14) => match String::from_utf8(body.to_vec()) { Ok(v) => result.push(ASN1Block::TeletexString(soff, v)), Err(e) => return Err(ASN1DecodeErr::UTF8DecodeFailure(e.utf8_error())), }, // IA5 (ASCII) STRING Some(0x16) => { let val = body.iter().map(|x| *x as char); let res = String::from_iter(val); result.push(ASN1Block::IA5String(soff, res)) } // UTCTime Some(0x17) => { if body.len() != 13 { return Err(ASN1DecodeErr::InvalidDateValue(format!("{}", body.len()))); } let v = String::from_iter(body.iter().map(|x| *x as char)); match Utc.datetime_from_str(&v, "%y%m%d%H%M%SZ") { Err(_) => return Err(ASN1DecodeErr::InvalidDateValue(v)), Ok(t) => result.push(ASN1Block::UTCTime(soff, t)), } } // GeneralizedTime Some(0x18) => { if body.len() < 15 { return Err(ASN1DecodeErr::InvalidDateValue(format!("{}", body.len()))); } let mut v: String = String::from_utf8(body.to_vec()) .map_err(|e| ASN1DecodeErr::UTF8DecodeFailure(e.utf8_error()))?; // Make sure the string is ascii, otherwise we cannot insert // chars at specific bytes. if !v.is_ascii() { return Err(ASN1DecodeErr::InvalidDateValue(v)); } // We need to add padding back to the string if it's not there. if !v.contains('.') { v.insert(14, '.') } while v.len() < 25 { let idx = v.len() - 1; v.insert(idx, '0'); } match Utc.datetime_from_str(&v, "%Y%m%d%H%M%S.%fZ") { Err(_) => return Err(ASN1DecodeErr::InvalidDateValue(v)), Ok(t) => result.push(ASN1Block::GeneralizedTime(soff, t)), } } // UNIVERSAL STRINGS Some(0x1C) => match String::from_utf8(body.to_vec()) { Ok(v) => result.push(ASN1Block::UniversalString(soff, v)), Err(e) => return Err(ASN1DecodeErr::UTF8DecodeFailure(e.utf8_error())), }, // UNIVERSAL STRINGS Some(0x1E) => match String::from_utf8(body.to_vec()) { Ok(v) => result.push(ASN1Block::BMPString(soff, v)), Err(e) => return Err(ASN1DecodeErr::UTF8DecodeFailure(e.utf8_error())), }, // Dunno. _ => { result.push(ASN1Block::Unknown( class, constructed, soff, tag, body.to_vec(), )); } } index += len; } if result.is_empty() { Err(ASN1DecodeErr::EmptyBuffer) } else { Ok(result) } } /// Returns the tag, if the type is constructed and the class. fn decode_tag(i: &[u8], index: &mut usize) -> Result<(BigUint, bool, ASN1Class), ASN1DecodeErr> { if *index >= i.len() { return Err(ASN1DecodeErr::Incomplete); } let tagbyte = i[*index]; let constructed = (tagbyte & 0b0010_0000) != 0; let class = decode_class(tagbyte)?; let basetag = tagbyte & 0b1_1111; *index += 1; if basetag == 0b1_1111 { let res = decode_base127(i, index)?; Ok((res, constructed, class)) } else { Ok((BigUint::from(basetag), constructed, class)) } } fn decode_base127(i: &[u8], index: &mut usize) -> Result { let mut res = BigUint::zero(); loop { if *index >= i.len() { return Err(ASN1DecodeErr::Incomplete); } let nextbyte = i[*index]; *index += 1; res = (res << 7) + BigUint::from(nextbyte & 0x7f); if (nextbyte & 0x80) == 0 { return Ok(res); } } } fn decode_class(i: u8) -> Result { match i >> 6 { 0b00 => Ok(ASN1Class::Universal), 0b01 => Ok(ASN1Class::Application), 0b10 => Ok(ASN1Class::ContextSpecific), 0b11 => Ok(ASN1Class::Private), _ => Err(ASN1DecodeErr::InvalidClass(i)), } } fn decode_length(i: &[u8], index: &mut usize) -> Result { if *index >= i.len() { return Err(ASN1DecodeErr::Incomplete); } let startbyte = i[*index]; // NOTE: Technically, this size can be much larger than a usize. // However, our whole universe starts to break down if we get // things that big. So we're boring, and only accept lengths // that fit within a usize. *index += 1; if startbyte >= 0x80 { let mut lenlen = (startbyte & 0x7f) as usize; let mut res = 0; if lenlen > size_of::() { return Err(ASN1DecodeErr::LengthTooLarge(lenlen)); } while lenlen > 0 { if *index >= i.len() { return Err(ASN1DecodeErr::Incomplete); } res = (res << 8) + (i[*index] as usize); *index += 1; lenlen -= 1; } Ok(res) } else { Ok(startbyte as usize) } } /// Given an `ASN1Block`, covert it to its DER encoding, or return an error /// if something broke along the way. pub fn to_der(i: &ASN1Block) -> Result, ASN1EncodeErr> { match i { // BOOLEAN &ASN1Block::Boolean(_, val) => { let inttag = BigUint::one(); let mut tagbytes = encode_tag(ASN1Class::Universal, false, &inttag); tagbytes.push(1); tagbytes.push(if val { 0xFF } else { 0x00 }); Ok(tagbytes) } // INTEGER &ASN1Block::Integer(_, ref int) => { let mut base = int.to_signed_bytes_be(); let mut lenbytes = encode_len(base.len()); let inttag = BigUint::from_u8(0x02).unwrap(); let mut tagbytes = encode_tag(ASN1Class::Universal, false, &inttag); let mut result = Vec::new(); result.append(&mut tagbytes); result.append(&mut lenbytes); result.append(&mut base); Ok(result) } // BIT STRING &ASN1Block::BitString(_, bits, ref vs) => { let inttag = BigUint::from_u8(0x03).unwrap(); let mut tagbytes = encode_tag(ASN1Class::Universal, false, &inttag); if bits == 0 { tagbytes.push(0); Ok(tagbytes) } else { let mut lenbytes = encode_len(vs.len() + 1); let nbits = (vs.len() * 8) - bits; let mut result = Vec::new(); result.append(&mut tagbytes); result.append(&mut lenbytes); result.push(nbits as u8); result.extend_from_slice(vs); Ok(result) } } // OCTET STRING &ASN1Block::OctetString(_, ref bytes) => { let inttag = BigUint::from_u8(0x04).unwrap(); let mut tagbytes = encode_tag(ASN1Class::Universal, false, &inttag); let mut lenbytes = encode_len(bytes.len()); let mut result = Vec::new(); result.append(&mut tagbytes); result.append(&mut lenbytes); result.extend_from_slice(bytes); Ok(result) } // NULL &ASN1Block::Null(_) => { let inttag = BigUint::from_u8(0x05).unwrap(); let mut result = encode_tag(ASN1Class::Universal, false, &inttag); result.push(0); Ok(result) } // OBJECT IDENTIFIER &ASN1Block::ObjectIdentifier(_, OID(ref nums)) => { match (nums.get(0), nums.get(1)) { (Some(v1), Some(v2)) => { let two = BigUint::from_u8(2).unwrap(); // first, validate that the first two items meet spec if v1 > &two { return Err(ASN1EncodeErr::ObjectIdentVal1TooLarge); } let u175 = BigUint::from_u8(175).unwrap(); let u39 = BigUint::from_u8(39).unwrap(); let bound = if v1 == &two { u175 } else { u39 }; if v2 > &bound { return Err(ASN1EncodeErr::ObjectIdentVal2TooLarge); } // the following unwraps must be safe, based on the // validation above. let value1 = v1.to_u8().unwrap(); let value2 = v2.to_u8().unwrap(); let byte1 = (value1 * 40) + value2; // now we can build all the rest of the body let mut body = vec![byte1]; for num in nums.iter().skip(2) { let mut local = encode_base127(&num); body.append(&mut local); } // now that we have the body, we can build the header let inttag = BigUint::from_u8(0x06).unwrap(); let mut result = encode_tag(ASN1Class::Universal, false, &inttag); let mut lenbytes = encode_len(body.len()); result.append(&mut lenbytes); result.append(&mut body); Ok(result) } _ => Err(ASN1EncodeErr::ObjectIdentHasTooFewFields), } } // SEQUENCE &ASN1Block::Sequence(_, ref items) => { let mut body = Vec::new(); // put all the subsequences into a block for x in items.iter() { let mut bytes = to_der(x)?; body.append(&mut bytes); } let inttag = BigUint::from_u8(0x10).unwrap(); let mut lenbytes = encode_len(body.len()); // SEQUENCE and SET mut have the constructed encoding form (bit 5) set // See: https://docs.microsoft.com/en-us/windows/desktop/seccertenroll/about-encoded-tag-bytes let mut tagbytes = encode_tag(ASN1Class::Universal, true, &inttag); let mut res = Vec::new(); res.append(&mut tagbytes); res.append(&mut lenbytes); res.append(&mut body); Ok(res) } // SET &ASN1Block::Set(_, ref items) => { let mut body = Vec::new(); // put all the subsequences into a block for x in items.iter() { let mut bytes = to_der(x)?; body.append(&mut bytes); } let inttag = BigUint::from_u8(0x11).unwrap(); let mut lenbytes = encode_len(body.len()); // SEQUENCE and SET mut have the constructed encoding form (bit 5) set // See: https://docs.microsoft.com/en-us/windows/desktop/seccertenroll/about-encoded-tag-bytes let mut tagbytes = encode_tag(ASN1Class::Universal, true, &inttag); let mut res = Vec::new(); res.append(&mut tagbytes); res.append(&mut lenbytes); res.append(&mut body); Ok(res) } &ASN1Block::UTCTime(_, ref time) => { let mut body = time.format("%y%m%d%H%M%SZ").to_string().into_bytes(); let inttag = BigUint::from_u8(0x17).unwrap(); let mut lenbytes = encode_len(body.len()); let mut tagbytes = encode_tag(ASN1Class::Universal, false, &inttag); let mut res = Vec::new(); res.append(&mut tagbytes); res.append(&mut lenbytes); res.append(&mut body); Ok(res) } &ASN1Block::GeneralizedTime(_, ref time) => { let base = time.format("%Y%m%d%H%M%S.%f").to_string(); let zclear = base.trim_end_matches('0'); let dclear = zclear.trim_end_matches('.'); let mut body = format!("{}Z", dclear).into_bytes(); let inttag = BigUint::from_u8(0x18).unwrap(); let mut lenbytes = encode_len(body.len()); let mut tagbytes = encode_tag(ASN1Class::Universal, false, &inttag); let mut res = Vec::new(); res.append(&mut tagbytes); res.append(&mut lenbytes); res.append(&mut body); Ok(res) } &ASN1Block::UTF8String(_, ref str) => { encode_asn1_string(0x0c, false, ASN1Class::Universal, str) } &ASN1Block::PrintableString(_, ref str) => { encode_asn1_string(0x13, true, ASN1Class::Universal, str) } &ASN1Block::TeletexString(_, ref str) => { encode_asn1_string(0x14, false, ASN1Class::Universal, str) } &ASN1Block::UniversalString(_, ref str) => { encode_asn1_string(0x1c, false, ASN1Class::Universal, str) } &ASN1Block::IA5String(_, ref str) => { encode_asn1_string(0x16, true, ASN1Class::Universal, str) } &ASN1Block::BMPString(_, ref str) => { encode_asn1_string(0x1e, false, ASN1Class::Universal, str) } &ASN1Block::Explicit(class, _, ref tag, ref item) => { let mut tagbytes = encode_tag(class, true, tag); let mut bytes = to_der(item)?; let mut lenbytes = encode_len(bytes.len()); let mut res = Vec::new(); res.append(&mut tagbytes); res.append(&mut lenbytes); res.append(&mut bytes); Ok(res) } // Unknown blocks &ASN1Block::Unknown(class, c, _, ref tag, ref bytes) => { let mut tagbytes = encode_tag(class, c, tag); let mut lenbytes = encode_len(bytes.len()); let mut res = Vec::new(); res.append(&mut tagbytes); res.append(&mut lenbytes); res.extend_from_slice(bytes); Ok(res) } } } fn encode_asn1_string( tag: u8, force_chars: bool, c: ASN1Class, s: &String, ) -> Result, ASN1EncodeErr> { let mut body = { if force_chars { let mut out = Vec::new(); for c in s.chars() { out.push(c as u8); } out } else { s.clone().into_bytes() } }; let inttag = BigUint::from_u8(tag).unwrap(); let mut lenbytes = encode_len(body.len()); let mut tagbytes = encode_tag(c, false, &inttag); let mut res = Vec::new(); res.append(&mut tagbytes); res.append(&mut lenbytes); res.append(&mut body); Ok(res) } fn encode_tag(c: ASN1Class, constructed: bool, t: &BigUint) -> Vec { let cbyte = encode_class(c); match t.to_u8() { Some(mut x) if x < 31 => { if constructed { x |= 0b0010_0000; } vec![cbyte | x] } _ => { let mut res = encode_base127(t); let mut x = cbyte | 0b0001_1111; if constructed { x |= 0b0010_0000; } res.insert(0, x); res } } } fn encode_base127(v: &BigUint) -> Vec { let mut acc = v.clone(); let mut res = Vec::new(); let u128 = BigUint::from_u8(128).unwrap(); let zero = BigUint::zero(); if acc == zero { res.push(0); return res; } while acc > zero { // we build this vector backwards let digit = &acc % &u128; acc = acc >> 7; match digit.to_u8() { None => panic!("7 bits don't fit into 8, cause ..."), Some(x) if res.is_empty() => res.push(x), Some(x) => res.push(x | 0x80), } } res.reverse(); res } fn encode_class(c: ASN1Class) -> u8 { match c { ASN1Class::Universal => 0b0000_0000, ASN1Class::Application => 0b0100_0000, ASN1Class::ContextSpecific => 0b1000_0000, ASN1Class::Private => 0b1100_0000, } } fn encode_len(x: usize) -> Vec { if x < 128 { vec![x as u8] } else { let mut bstr = Vec::new(); let mut work = x; // convert this into bytes, backwards while work > 0 { bstr.push(work as u8); work = work >> 8; } // encode the front of the length let len = bstr.len() as u8; bstr.push(len | 0x80); // and then reverse it into the right order bstr.reverse(); bstr } } // ---------------------------------------------------------------------------- /// A trait defining types that can be decoded from an `ASN1Block` stream, /// assuming they also have access to the underlying bytes making up the /// stream. pub trait FromASN1WithBody: Sized { type Error: From; fn from_asn1_with_body<'a>( v: &'a [ASN1Block], _b: &[u8], ) -> Result<(Self, &'a [ASN1Block]), Self::Error>; } /// A trait defining types that can be decoded from an `ASN1Block` stream. /// Any member of this trait is also automatically a member of /// `FromASN1WithBody`, as it can obviously just ignore the body. pub trait FromASN1: Sized { type Error: From; fn from_asn1(v: &[ASN1Block]) -> Result<(Self, &[ASN1Block]), Self::Error>; } impl FromASN1WithBody for T { type Error = T::Error; fn from_asn1_with_body<'a>( v: &'a [ASN1Block], _b: &[u8], ) -> Result<(T, &'a [ASN1Block]), T::Error> { T::from_asn1(v) } } /// Automatically decode a type via DER encoding, assuming that the type /// is a member of `FromASN1` or `FromASN1WithBody`. pub fn der_decode(v: &[u8]) -> Result { let vs = from_der(v)?; T::from_asn1_with_body(&vs, v).and_then(|(a, _)| Ok(a)) } /// The set of types that can automatically converted into a sequence /// of `ASN1Block`s. You should probably use to_asn1() but implement /// to_asn1_class(). The former has a default implementation that passes /// `ASN1Class::Universal` as the tag to use, which should be good for /// most people. pub trait ToASN1 { type Error: From; fn to_asn1(&self) -> Result, Self::Error> { self.to_asn1_class(ASN1Class::Universal) } fn to_asn1_class(&self, c: ASN1Class) -> Result, Self::Error>; } /// Automatically encode a type into binary via DER encoding, assuming /// that the type is a member of `ToASN1`. pub fn der_encode(v: &T) -> Result, T::Error> { let blocks = T::to_asn1(&v)?; let mut res = Vec::new(); for block in blocks { let mut x = to_der(&block)?; res.append(&mut x); } Ok(res) } // ---------------------------------------------------------------------------- #[cfg(test)] mod tests { use super::*; use chrono::offset::LocalResult; use quickcheck::{Arbitrary, Gen}; use rand::{distributions::Standard, Rng}; use std::fs::File; use std::io::Read; impl Arbitrary for ASN1Class { fn arbitrary(g: &mut G) -> ASN1Class { match g.gen::() % 4 { 0 => ASN1Class::Private, 1 => ASN1Class::ContextSpecific, 2 => ASN1Class::Universal, 3 => ASN1Class::Application, _ => panic!("I weep for a broken life."), } } } quickcheck! { fn class_encdec_roundtrips(c: ASN1Class) -> bool { c == decode_class(encode_class(c.clone())).unwrap() } fn class_decenc_roundtrips(v: u8) -> bool { (v & 0b11000000) == encode_class(decode_class(v).unwrap()) } } #[derive(Clone, Debug)] struct RandomUint { x: BigUint, } impl Arbitrary for RandomUint { fn arbitrary(g: &mut G) -> RandomUint { let v = BigUint::from_u32(g.gen::()).unwrap(); RandomUint { x: v } } } quickcheck! { fn tags_encdec_roundtrips(c: ASN1Class, con: bool, t: RandomUint) -> bool { let bytes = encode_tag(c, con, &t.x); let mut zero = 0; let (t2, con2, c2) = decode_tag(&bytes[..], &mut zero).unwrap(); (c == c2) && (con == con2) && (t.x == t2) } fn len_encdec_roundtrips(l: usize) -> bool { let bytes = encode_len(l); let mut zero = 0; match decode_length(&bytes[..], &mut zero) { Err(_) => false, Ok(l2) => l == l2 } } } #[derive(Clone, Debug)] struct RandomInt { x: BigInt, } impl Arbitrary for RandomInt { fn arbitrary(g: &mut G) -> RandomInt { let v = BigInt::from_i64(g.gen::()).unwrap(); RandomInt { x: v } } } #[allow(type_alias_bounds)] type ASN1BlockGen = fn(&mut G, usize) -> ASN1Block; fn arb_boolean(g: &mut G, _d: usize) -> ASN1Block { let v = g.gen::(); ASN1Block::Boolean(0, v) } fn arb_integer(g: &mut G, _d: usize) -> ASN1Block { let d = RandomInt::arbitrary(g); ASN1Block::Integer(0, d.x) } fn arb_bitstr(g: &mut G, _d: usize) -> ASN1Block { let size = g.gen::() as usize % 16; let maxbits = (size as usize) * 8; let modbits = g.gen::() as usize % 8; let nbits = if modbits > maxbits { maxbits } else { maxbits - modbits }; let bytes = g.sample_iter::(&Standard).take(size).collect(); ASN1Block::BitString(0, nbits, bytes) } fn arb_octstr(g: &mut G, _d: usize) -> ASN1Block { let size = g.gen::() as usize % 16; let bytes = g.sample_iter::(&Standard).take(size).collect(); ASN1Block::OctetString(0, bytes) } fn arb_null(_g: &mut G, _d: usize) -> ASN1Block { ASN1Block::Null(0) } impl Arbitrary for OID { fn arbitrary(g: &mut G) -> OID { let count = g.gen_range::(0, 40); let val1 = g.gen::() % 3; let v2mod = if val1 == 2 { 176 } else { 40 }; let val2 = g.gen::() % v2mod; let v1 = BigUint::from_u8(val1).unwrap(); let v2 = BigUint::from_u8(val2).unwrap(); let mut nums = vec![v1, v2]; for _ in 0..count { let num = RandomUint::arbitrary(g); nums.push(num.x); } OID(nums) } } fn arb_objid(g: &mut G, _d: usize) -> ASN1Block { let oid = OID::arbitrary(g); ASN1Block::ObjectIdentifier(0, oid) } fn arb_seq(g: &mut G, d: usize) -> ASN1Block { let count = g.gen_range::(1, 64); let mut items = Vec::new(); for _ in 0..count { items.push(limited_arbitrary(g, d - 1)); } ASN1Block::Sequence(0, items) } fn arb_set(g: &mut G, d: usize) -> ASN1Block { let count = g.gen_range::(1, 64); let mut items = Vec::new(); for _ in 0..count { items.push(limited_arbitrary(g, d - 1)); } ASN1Block::Set(0, items) } fn arb_print(g: &mut G, _d: usize) -> ASN1Block { let count = g.gen_range::(0, 384); let mut items = Vec::new(); for _ in 0..count { let v = g.choose(PRINTABLE_CHARS.as_bytes()).unwrap(); items.push(*v as char); } ASN1Block::PrintableString(0, String::from_iter(items.iter())) } fn arb_ia5(g: &mut G, _d: usize) -> ASN1Block { let count = g.gen_range::(0, 384); let mut items = Vec::new(); for _ in 0..count { items.push(g.gen::() as char); } ASN1Block::IA5String(0, String::from_iter(items.iter())) } fn arb_utf8(g: &mut G, _d: usize) -> ASN1Block { let val = String::arbitrary(g); ASN1Block::UTF8String(0, val) } fn arb_tele(g: &mut G, _d: usize) -> ASN1Block { let val = String::arbitrary(g); ASN1Block::TeletexString(0, val) } fn arb_uni(g: &mut G, _d: usize) -> ASN1Block { let val = String::arbitrary(g); ASN1Block::UniversalString(0, val) } fn arb_bmp(g: &mut G, _d: usize) -> ASN1Block { let val = String::arbitrary(g); ASN1Block::BMPString(0, val) } fn arb_utc(g: &mut G, _d: usize) -> ASN1Block { loop { let y = g.gen_range::(1970, 2069); let m = g.gen_range::(1, 13); let d = g.gen_range::(1, 32); match Utc.ymd_opt(y, m, d) { LocalResult::None => {} LocalResult::Single(d) => { let h = g.gen_range::(0, 24); let m = g.gen_range::(0, 60); let s = g.gen_range::(0, 60); let t = d.and_hms(h, m, s); return ASN1Block::UTCTime(0, t); } LocalResult::Ambiguous(_, _) => {} } } } fn arb_time(g: &mut G, _d: usize) -> ASN1Block { loop { let y = g.gen_range::(0, 10000); let m = g.gen_range::(1, 13); let d = g.gen_range::(1, 32); match Utc.ymd_opt(y, m, d) { LocalResult::None => {} LocalResult::Single(d) => { let h = g.gen_range::(0, 24); let m = g.gen_range::(0, 60); let s = g.gen_range::(0, 60); let n = g.gen_range::(0, 1000000000); let t = d.and_hms_nano(h, m, s, n); return ASN1Block::GeneralizedTime(0, t); } LocalResult::Ambiguous(_, _) => {} } } } fn arb_explicit(g: &mut G, d: usize) -> ASN1Block { let mut class = ASN1Class::arbitrary(g); if class == ASN1Class::Universal { // Universal is invalid for an explicitly tagged block class = ASN1Class::ContextSpecific; } let tag = RandomUint::arbitrary(g); let item = limited_arbitrary(g, d - 1); ASN1Block::Explicit(class, 0, tag.x, Box::new(item)) } fn arb_unknown(g: &mut G, _d: usize) -> ASN1Block { let class = ASN1Class::arbitrary(g); let tag = RandomUint::arbitrary(g); let size = g.gen_range::(0, 128); let items = g.sample_iter::(&Standard).take(size).collect(); ASN1Block::Unknown(class, false, 0, tag.x, items) } fn limited_arbitrary(g: &mut G, d: usize) -> ASN1Block { let mut possibles: Vec> = vec![ arb_boolean, arb_integer, arb_bitstr, arb_octstr, arb_null, arb_objid, arb_utf8, arb_print, arb_tele, arb_uni, arb_ia5, arb_utc, arb_time, arb_bmp, arb_unknown, ]; if d > 0 { possibles.push(arb_seq); possibles.push(arb_set); possibles.push(arb_explicit); } match g.choose(&possibles[..]) { Some(f) => f(g, d), None => panic!("Couldn't generate arbitrary value."), } } impl Arbitrary for ASN1Block { fn arbitrary(g: &mut G) -> ASN1Block { limited_arbitrary(g, 2) } } quickcheck! { fn encode_decode_roundtrips(v: ASN1Block) -> bool { match to_der(&v) { Err(e) => { println!("Serialization error: {:?}", e); false } Ok(bytes) => match from_der(&bytes[..]) { Err(e) => { println!("Parse error: {:?}", e); false } Ok(ref rvec) if rvec.len() == 1 => { let v2 = rvec.get(0).unwrap(); if &v != v2 { println!("Original: {:?}", v); println!("Constructed: {:?}", v2); } &v == v2 } Ok(_) => { println!("Too many results returned."); false } } } } } fn result_int(v: i16) -> Result, ASN1DecodeErr> { let val = BigInt::from(v); Ok(vec![ASN1Block::Integer(0, val)]) } #[test] fn generalized_time_tests() { check_spec( &Utc.ymd(1992, 5, 21).and_hms(0, 0, 0), "19920521000000Z".to_string(), ); check_spec( &Utc.ymd(1992, 6, 22).and_hms(12, 34, 21), "19920622123421Z".to_string(), ); check_spec( &Utc.ymd(1992, 7, 22).and_hms_milli(13, 21, 00, 300), "19920722132100.3Z".to_string(), ); } fn check_spec(d: &DateTime, s: String) { let b = ASN1Block::GeneralizedTime(0, d.clone()); match to_der(&b) { Err(_) => assert_eq!(format!("Broken: {}", d), s), Ok(ref vec) => { let mut resvec = vec.clone(); resvec.remove(0); resvec.remove(0); assert_eq!(String::from_utf8(resvec).unwrap(), s); match from_der_(vec, 0) { Err(_) => assert_eq!(format!("Broken [reparse]: {}", d), s), Ok(mut vec) => { assert_eq!(vec.len(), 1); match vec.pop() { None => assert!(false, "The world's gone mad again."), Some(ASN1Block::GeneralizedTime(_, d2)) => assert_eq!(&d2, d), Some(_) => assert!(false, "Bad reparse of GeneralizedTime."), } } } } } } #[test] fn base_integer_tests() { assert_eq!(from_der(&vec![0x02, 0x01, 0x00]), result_int(0)); assert_eq!(from_der(&vec![0x02, 0x01, 0x7F]), result_int(127)); assert_eq!(from_der(&vec![0x02, 0x02, 0x00, 0x80]), result_int(128)); assert_eq!(from_der(&vec![0x02, 0x02, 0x01, 0x00]), result_int(256)); assert_eq!(from_der(&vec![0x02, 0x01, 0x80]), result_int(-128)); assert_eq!(from_der(&vec![0x02, 0x02, 0xFF, 0x7F]), result_int(-129)); } fn can_parse(f: &str) -> Result, ASN1DecodeErr> { let mut fd = File::open(f).unwrap(); let mut buffer = Vec::new(); let _amt = fd.read_to_end(&mut buffer); from_der(&buffer[..]) } #[test] fn x509_tests() { can_parse("test/server.bin").unwrap(); can_parse("test/key.bin").unwrap(); } #[test] fn encode_base127_zero() { let zero = BigUint::from(0 as u64); let encoded = encode_base127(&zero); let expected: Vec = vec![0x0]; assert_eq!(expected, encoded); } #[test] fn raw_oid_eq() { // data taken from https://tools.ietf.org/html/rfc4880 // ( OID as vector of unsigned integers , asn1 encoded block) // comparision is not done against the full length, but only for // the actually encoded OID part (see the expect statement further down) let md5 = ( oid!(1, 2, 840, 113549, 2, 5), vec![ 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10, ], ); let ripmed160 = ( oid!(1, 3, 36, 3, 2, 1), vec![ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x24, 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14, ], ); let sha1 = ( oid!(1, 3, 14, 3, 2, 26), vec![ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14, ], ); let sha224 = ( oid!(2, 16, 840, 1, 101, 3, 4, 2, 4), vec![ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1C, ], ); let sha256 = ( oid!(2, 16, 840, 1, 101, 3, 4, 2, 1), vec![ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20, ], ); let sha384 = ( oid!(2, 16, 840, 1, 101, 3, 4, 2, 2), vec![ 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30, ], ); let sha512 = ( oid!(2, 16, 840, 1, 101, 3, 4, 2, 3), vec![ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40, ], ); let tests: Vec<(OID, Vec)> = vec![md5, ripmed160, sha1, sha224, sha256, sha384, sha512]; for test in tests { let expected = test.1; let raw_oid = test.0.as_raw().expect("Failed to convert OID to raw"); assert_eq!(raw_oid, &expected[6..(expected.len() - 4)]); } } }