diff --git a/Cargo.toml b/Cargo.toml index 2f8f47a..edda0bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,10 @@ name = "simple_asn1" version = "0.1.0" authors = ["Adam Wick "] +categories = ["encoding"] +keywords = ["ASN.1","data encoding","DER"] +license-file = "LICENSE" +repository = "https://github.com/acw/simple_asn1" [dependencies] chrono = "^0.4.0" diff --git a/src/lib.rs b/src/lib.rs index ef4e1e0..d8bc41c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,31 @@ +//! 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 chrono; extern crate num; #[cfg(test)] @@ -9,6 +37,25 @@ use num::{BigInt,BigUint,FromPrimitive,One,ToPrimitive,Zero}; use std::iter::FromIterator; use std::mem::size_of; +/// 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 class of the block and 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. #[derive(Clone,Debug)] pub enum ASN1Block { Boolean(ASN1Class, usize, bool), @@ -31,6 +78,8 @@ pub enum ASN1Block { } 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(c,_,_) => c, @@ -53,6 +102,8 @@ impl ASN1Block { } } + /// 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, @@ -136,10 +187,13 @@ impl PartialEq for ASN1Block { } } +/// 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) } @@ -164,6 +218,10 @@ impl<'a> PartialEq for &'a OID { } } +/// 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 ),* ) => {{ @@ -179,9 +237,7 @@ macro_rules! oid { const PRINTABLE_CHARS: &'static str = "ABCDEFGHIJKLMOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'()+,-./:=? "; -#[derive(Clone,Copy,Debug,PartialEq)] -pub enum ASN1Class { Universal, Application, ContextSpecific, Private } - +/// An error that can arise decoding ASN.1 primitive blocks. #[derive(Clone,Debug,PartialEq)] pub enum ASN1DecodeErr { EmptyBuffer, @@ -192,6 +248,7 @@ pub enum ASN1DecodeErr { InvalidDateValue(String) } +/// An error that can arise encoding ASN.1 primitive blocks. #[derive(Clone,Debug,PartialEq)] pub enum ASN1EncodeErr { ObjectIdentHasTooFewFields, @@ -199,6 +256,8 @@ pub enum ASN1EncodeErr { ObjectIdentVal2TooLarge } +/// 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) } @@ -466,6 +525,8 @@ fn decode_length(i: &[u8], index: &mut usize) -> Result { } } +/// 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 @@ -768,6 +829,9 @@ fn encode_len(x: usize) -> Vec { // ---------------------------------------------------------------------------- +/// 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; @@ -775,6 +839,9 @@ pub trait FromASN1WithBody : Sized { -> 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; @@ -792,12 +859,16 @@ impl FromASN1WithBody for T { } } +/// 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. pub trait ToASN1 { type Error : From; @@ -808,6 +879,8 @@ pub trait ToASN1 { -> 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)?;