From 762b9b2fa49374199706e016ef805116ed1a3503 Mon Sep 17 00:00:00 2001 From: Adam Wick Date: Tue, 19 Dec 2017 10:21:36 -0800 Subject: [PATCH] Add time value support. --- Cargo.toml | 3 +- src/lib.rs | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 171 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 205e2e6..2f8f47a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,8 @@ version = "0.1.0" authors = ["Adam Wick "] [dependencies] -num = "^0.1.40" +chrono = "^0.4.0" +num = "^0.1.40" [dev-dependencies] quickcheck = "^0.4.1" diff --git a/src/lib.rs b/src/lib.rs index cc66309..f18a0ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,11 @@ +extern crate chrono; extern crate num; #[cfg(test)] #[macro_use] extern crate quickcheck; +use chrono::{DateTime,TimeZone,Utc}; +use chrono::offset::LocalResult; use num::{BigInt,BigUint,FromPrimitive,One,ToPrimitive,Zero}; use std::iter::FromIterator; use std::mem::size_of; @@ -15,11 +18,13 @@ pub enum ASN1Block { OctetString(ASN1Class, Vec), Null(ASN1Class), ObjectIdentifier(ASN1Class, OID), - TeletexString(ASN1Class, String), - PrintableString(ASN1Class, String), - UniversalString(ASN1Class, String), - IA5String(ASN1Class, String), UTF8String(ASN1Class, String), + PrintableString(ASN1Class, String), + TeletexString(ASN1Class, String), + IA5String(ASN1Class, String), + UTCTime(ASN1Class, DateTime), + GeneralizedTime(ASN1Class, DateTime), + UniversalString(ASN1Class, String), BMPString(ASN1Class, String), Sequence(ASN1Class, Vec), Set(ASN1Class, Vec), @@ -78,7 +83,8 @@ pub enum ASN1DecodeErr { BadBooleanLength, LengthTooLarge, UTF8DecodeFailure, - PrintableStringDecodeFailure + PrintableStringDecodeFailure, + InvalidDateValue(String) } #[derive(Clone,Debug,PartialEq)] @@ -208,6 +214,45 @@ pub fn from_der(i: &[u8]) -> Result,ASN1DecodeErr> { let val = body.iter().map(|x| *x as char); result.push(ASN1Block::IA5String(class, String::from_iter(val))) } + // 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(class, t)) + } + } + } + // GeneralizedTime + Some(0x18) => { + if body.len() < 15 { + return Err(ASN1DecodeErr::InvalidDateValue(format!("{}",body.len()))); + } + + let mut v = String::from_iter(body.iter().map(|x| *x as char)); + // We need to add padding back to the string if it's not there. + if v.find('.').is_none() { + v.insert(15, '.') + } + while v.len() < 25 { + let idx = v.len() - 1; + v.insert(idx, '0'); + } + // FIXME (?): Zero padding + 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(class, t)) + } + } + } // UNIVERSAL STRINGS Some(0x1C) => { match String::from_utf8(body.to_vec()) { @@ -457,6 +502,34 @@ pub fn to_der(i: &ASN1Block) -> Result,ASN1EncodeErr> { res.append(&mut body); Ok(res) } + &ASN1Block::UTCTime(cl, 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(cl, &inttag); + + let mut res = Vec::new(); + res.append(&mut tagbytes); + res.append(&mut lenbytes); + res.append(&mut body); + Ok(res) + } + &ASN1Block::GeneralizedTime(cl, ref time) => { + let base = time.format("%Y%m%d%H%M%S.%f").to_string(); + let zclear = base.trim_right_matches('0'); + let dclear = zclear.trim_right_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(cl, &inttag); + + let mut res = Vec::new(); + res.append(&mut tagbytes); + res.append(&mut lenbytes); + res.append(&mut body); + Ok(res) + } &ASN1Block::UTF8String(cl, ref str) => encode_asn1_string(0x0c, false, cl, str), &ASN1Block::PrintableString(cl, ref str) => @@ -808,6 +881,67 @@ mod tests { ASN1Block::UTF8String(class, val) } + fn arb_tele(g: &mut G, _d: usize) -> ASN1Block { + let class = ASN1Class::arbitrary(g); + let val = String::arbitrary(g); + ASN1Block::TeletexString(class, val) + } + + fn arb_uni(g: &mut G, _d: usize) -> ASN1Block { + let class = ASN1Class::arbitrary(g); + let val = String::arbitrary(g); + ASN1Block::UniversalString(class, val) + } + + fn arb_bmp(g: &mut G, _d: usize) -> ASN1Block { + let class = ASN1Class::arbitrary(g); + let val = String::arbitrary(g); + ASN1Block::BMPString(class, val) + } + + fn arb_utc(g: &mut G, _d: usize) -> ASN1Block { + let class = ASN1Class::arbitrary(g); + + 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(class, t); + } + LocalResult::Ambiguous(_,_) => {} + } + } + } + + fn arb_time(g: &mut G, _d: usize) -> ASN1Block { + let class = ASN1Class::arbitrary(g); + + 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(class, t); + } + LocalResult::Ambiguous(_,_) => {} + } + } + } + fn arb_unknown(g: &mut G, _d: usize) -> ASN1Block { let class = ASN1Class::arbitrary(g); let tag = RandomUint::arbitrary(g); @@ -825,9 +959,14 @@ mod tests { arb_octstr, arb_null, arb_objid, - arb_print, - arb_ia5, arb_utf8, + arb_print, + arb_tele, + arb_uni, + arb_ia5, + arb_utc, + arb_time, + arb_bmp, arb_unknown]; if d > 0 { @@ -882,6 +1021,29 @@ mod tests { Ok(vec![ASN1Block::Integer(ASN1Class::Universal, 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(ASN1Class::Universal, 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); + } + } + } + #[test] fn base_integer_tests() { assert_eq!(from_der(&vec![0x02,0x01,0x00]), result_int(0));