Preliminary x.509 support. This is some of the ugliest code I've ever written, but it works. Ish.
This commit is contained in:
118
src/x509/validity.rs
Normal file
118
src/x509/validity.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use chrono::{DateTime,Utc};
|
||||
use simple_asn1::{ASN1Block,ASN1Class,ASN1EncodeErr,FromASN1,ToASN1};
|
||||
use x509::error::X509ParseError;
|
||||
|
||||
#[derive(Clone,Debug,PartialEq)]
|
||||
pub struct Validity {
|
||||
not_before: DateTime<Utc>,
|
||||
not_after: DateTime<Utc>
|
||||
}
|
||||
|
||||
fn decode_validity_data(bs: &ASN1Block) -> Result<Validity,X509ParseError> {
|
||||
// Validity ::= SEQUENCE {
|
||||
// notBefore Time,
|
||||
// notAfter Time }
|
||||
match bs {
|
||||
&ASN1Block::Sequence(_, _, ref valxs) => {
|
||||
if valxs.len() != 2 {
|
||||
return Err(X509ParseError::IllFormedValidity);
|
||||
}
|
||||
let nb = get_time(&valxs[0])?;
|
||||
let na = get_time(&valxs[1])?;
|
||||
Ok(Validity{ not_before: nb, not_after: na })
|
||||
}
|
||||
_ =>
|
||||
Err(X509ParseError::IllFormedValidity)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromASN1 for Validity {
|
||||
type Error = X509ParseError;
|
||||
|
||||
fn from_asn1(v: &[ASN1Block])
|
||||
-> Result<(Validity,&[ASN1Block]),X509ParseError>
|
||||
{
|
||||
match v.split_first() {
|
||||
None =>
|
||||
Err(X509ParseError::NotEnoughData),
|
||||
Some((x, rest)) => {
|
||||
let v = decode_validity_data(&x)?;
|
||||
Ok((v, rest))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_validity_data(c: ASN1Class, v: &Validity) -> ASN1Block {
|
||||
let mut vs = Vec::with_capacity(2);
|
||||
vs.push(ASN1Block::GeneralizedTime(c, 0, v.not_before));
|
||||
vs.push(ASN1Block::GeneralizedTime(c, 0, v.not_after));
|
||||
ASN1Block::Sequence(c, 0, vs)
|
||||
}
|
||||
|
||||
impl ToASN1 for Validity {
|
||||
type Error = ASN1EncodeErr;
|
||||
|
||||
fn to_asn1_class(&self, c: ASN1Class)
|
||||
-> Result<Vec<ASN1Block>,ASN1EncodeErr>
|
||||
{
|
||||
let block = encode_validity_data(c, self);
|
||||
Ok(vec![block])
|
||||
}
|
||||
}
|
||||
|
||||
fn get_time(b: &ASN1Block) -> Result<DateTime<Utc>, X509ParseError> {
|
||||
match b {
|
||||
&ASN1Block::UTCTime(_, _, v) => Ok(v.clone()),
|
||||
&ASN1Block::GeneralizedTime(_, _, v) => Ok(v.clone()),
|
||||
_ =>
|
||||
Err(X509ParseError::IllFormedValidity)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use chrono::TimeZone;
|
||||
use chrono::offset::LocalResult;
|
||||
use quickcheck::{Arbitrary,Gen};
|
||||
use rand::Rng;
|
||||
use super::*;
|
||||
|
||||
fn arbitrary_date<G: Gen>(g: &mut G) -> DateTime<Utc> {
|
||||
loop {
|
||||
let y = g.gen_range(1900,3000);
|
||||
let mo = g.gen_range(0,12);
|
||||
let d = g.gen_range(0,31);
|
||||
let h = g.gen_range(0,24);
|
||||
let mi = g.gen_range(0,60);
|
||||
let s = g.gen_range(0,60);
|
||||
match Utc.ymd_opt(y,mo,d).and_hms_opt(h,mi,s) {
|
||||
LocalResult::None =>
|
||||
continue,
|
||||
LocalResult::Single(x) =>
|
||||
return x,
|
||||
LocalResult::Ambiguous(x,_) =>
|
||||
return x
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Arbitrary for Validity {
|
||||
fn arbitrary<G: Gen>(g: &mut G) -> Validity {
|
||||
Validity {
|
||||
not_before: arbitrary_date(g),
|
||||
not_after: arbitrary_date(g)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quickcheck! {
|
||||
fn validity_roundtrips(v: Validity) -> bool {
|
||||
let bstr = encode_validity_data(ASN1Class::Universal, &v);
|
||||
match decode_validity_data(&bstr) {
|
||||
Err(_) => false,
|
||||
Ok(v2) => v == v2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user