use std::fmt;
use num_bigint::BigUint;
use time::Tm;
use crate::error::X509Error;
use crate::objects::{nid2sn, oid2nid};
use crate::parse_ext_basicconstraints;
use der_parser::ber::BitStringObject;
use der_parser::der::DerObject;
use der_parser::oid::Oid;
#[derive(Debug, PartialEq)]
pub enum X509Version {
V1,
V2,
V3,
Invalid(u32),
}
#[derive(Debug, PartialEq)]
pub struct X509Extension<'a> {
pub oid: Oid<'a>,
pub critical: bool,
pub value: &'a [u8],
}
#[derive(Debug, PartialEq)]
pub struct AttributeTypeAndValue<'a> {
pub attr_type: Oid<'a>,
pub attr_value: DerObject<'a>,
}
#[derive(Debug, PartialEq)]
pub struct RelativeDistinguishedName<'a> {
pub set: Vec<AttributeTypeAndValue<'a>>,
}
#[derive(Debug, PartialEq)]
pub struct SubjectPublicKeyInfo<'a> {
pub algorithm: AlgorithmIdentifier<'a>,
pub subject_public_key: BitStringObject<'a>,
}
#[derive(Debug, PartialEq)]
pub struct AlgorithmIdentifier<'a> {
pub algorithm: Oid<'a>,
pub parameters: DerObject<'a>,
}
#[derive(Debug, PartialEq)]
pub struct X509Name<'a> {
pub rdn_seq: Vec<RelativeDistinguishedName<'a>>,
}
impl<'a> fmt::Display for X509Name<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match x509name_to_string(&self.rdn_seq) {
Ok(o) => write!(f, "{}", o),
Err(_) => write!(f, "<X509Error: Invalid X.509 name>"),
}
}
}
#[derive(Debug, PartialEq)]
pub struct TbsCertificate<'a> {
pub version: u32,
pub serial: BigUint,
pub signature: AlgorithmIdentifier<'a>,
pub issuer: X509Name<'a>,
pub validity: Validity,
pub subject: X509Name<'a>,
pub subject_pki: SubjectPublicKeyInfo<'a>,
pub issuer_uid: Option<UniqueIdentifier<'a>>,
pub subject_uid: Option<UniqueIdentifier<'a>>,
pub extensions: Vec<X509Extension<'a>>,
pub(crate) raw: &'a [u8],
pub(crate) raw_serial: &'a [u8],
}
impl<'a> AsRef<[u8]> for TbsCertificate<'a> {
fn as_ref(&self) -> &[u8] {
&self.raw
}
}
#[derive(Debug, PartialEq)]
pub struct Validity {
pub not_before: Tm,
pub not_after: Tm,
}
impl Validity {
pub fn time_to_expiration(&self) -> Option<std::time::Duration> {
let now = time::now().to_timespec();
let nb = self.not_before.to_timespec();
let na = self.not_after.to_timespec();
if now < nb {
return None;
}
if now.sec >= na.sec {
return None;
}
Some(std::time::Duration::from_secs((na.sec - now.sec) as u64))
}
}
#[test]
fn check_validity_expiration() {
let mut v = Validity {
not_before: time::now(),
not_after: time::now(),
};
assert_eq!(v.time_to_expiration(), None);
v.not_after = v.not_after + time::Duration::minutes(1);
assert!(v.time_to_expiration().is_some());
assert!(v.time_to_expiration().unwrap() <= std::time::Duration::from_secs(60));
assert!(v.time_to_expiration().unwrap() > std::time::Duration::from_secs(50));
}
#[derive(Debug, PartialEq)]
pub struct UniqueIdentifier<'a>(pub BitStringObject<'a>);
impl<'a> TbsCertificate<'a> {
pub fn is_ca(&self) -> bool {
self.extensions
.iter()
.find(|ext| ext.oid == Oid::from(&[2, 5, 29, 19]).unwrap())
.and_then(|ext| {
if let Ok((_, bc)) = parse_ext_basicconstraints(ext.value) {
Some(bc.ca)
} else {
None
}
})
.unwrap_or(false)
}
pub fn raw_serial(&self) -> &[u8] {
self.raw_serial
}
pub fn raw_serial_as_string(&self) -> String {
let mut s = self
.raw_serial
.iter()
.fold(String::with_capacity(3 * self.raw_serial.len()), |a, b| {
a + &format!("{:02x}:", b)
});
s.pop();
s
}
}
#[derive(Debug, PartialEq)]
pub struct TbsCertList<'a> {
pub version: Option<u32>,
pub signature: AlgorithmIdentifier<'a>,
pub issuer: X509Name<'a>,
pub this_update: Tm,
pub next_update: Option<Tm>,
pub revoked_certificates: Vec<RevokedCertificate<'a>>,
pub extensions: Vec<X509Extension<'a>>,
pub(crate) raw: &'a [u8],
}
impl<'a> AsRef<[u8]> for TbsCertList<'a> {
fn as_ref(&self) -> &[u8] {
&self.raw
}
}
#[derive(Debug, PartialEq)]
pub struct RevokedCertificate<'a> {
pub user_certificate: BigUint,
pub revocation_date: Tm,
pub extensions: Vec<X509Extension<'a>>,
}
fn x509name_to_string(rdn_seq: &[RelativeDistinguishedName]) -> Result<String, X509Error> {
rdn_seq.iter().fold(Ok(String::new()), |acc, rdn| {
acc.and_then(|mut _vec| {
rdn.set
.iter()
.fold(Ok(String::new()), |acc2, attr| {
acc2.and_then(|mut _vec2| {
match attr.attr_value.as_slice() {
Ok(s) => {
let sn_res = oid2nid(&attr.attr_type).and_then(nid2sn);
let sn_str = match sn_res {
Ok(s) => String::from(s),
_ => format!("{:?}", attr.attr_type),
};
let val_str = String::from_utf8_lossy(s);
let rdn = format!("{}={}", sn_str, val_str);
match _vec2.len() {
0 => Ok(rdn),
_ => Ok(_vec2 + " + " + &rdn),
}
}
_ => Err(X509Error::InvalidX509Name),
}
})
})
.and_then(|v| match _vec.len() {
0 => Ok(v),
_ => Ok(_vec + ", " + &v),
})
})
})
}
#[derive(Debug, PartialEq)]
pub struct X509Certificate<'a> {
pub tbs_certificate: TbsCertificate<'a>,
pub signature_algorithm: AlgorithmIdentifier<'a>,
pub signature_value: BitStringObject<'a>,
}
impl<'a> X509Certificate<'a> {
pub fn version(&self) -> X509Version {
match self.tbs_certificate.version {
0 => X509Version::V1,
1 => X509Version::V2,
2 => X509Version::V3,
n => X509Version::Invalid(n),
}
}
}
#[derive(Debug)]
pub struct CertificateRevocationList<'a> {
pub tbs_cert_list: TbsCertList<'a>,
pub signature_algorithm: AlgorithmIdentifier<'a>,
pub signature_value: BitStringObject<'a>,
}
#[cfg(test)]
mod tests {
use super::*;
use der_parser::ber::BerObjectContent;
use der_parser::oid::Oid;
#[test]
fn test_x509_name() {
let name = X509Name {
rdn_seq: vec![
RelativeDistinguishedName {
set: vec![AttributeTypeAndValue {
attr_type: Oid::from(&[2, 5, 4, 6]).unwrap(),
attr_value: DerObject::from_obj(BerObjectContent::PrintableString("FR")),
}],
},
RelativeDistinguishedName {
set: vec![AttributeTypeAndValue {
attr_type: Oid::from(&[2, 5, 4, 8]).unwrap(),
attr_value: DerObject::from_obj(BerObjectContent::PrintableString(
"Some-State",
)),
}],
},
RelativeDistinguishedName {
set: vec![AttributeTypeAndValue {
attr_type: Oid::from(&[2, 5, 4, 10]).unwrap(),
attr_value: DerObject::from_obj(BerObjectContent::PrintableString(
"Internet Widgits Pty Ltd",
)),
}],
},
RelativeDistinguishedName {
set: vec![
AttributeTypeAndValue {
attr_type: Oid::from(&[2, 5, 4, 3]).unwrap(),
attr_value: DerObject::from_obj(BerObjectContent::PrintableString(
"Test1",
)),
},
AttributeTypeAndValue {
attr_type: Oid::from(&[2, 5, 4, 3]).unwrap(),
attr_value: DerObject::from_obj(BerObjectContent::PrintableString(
"Test2",
)),
},
],
},
],
};
assert_eq!(
name.to_string(),
"C=FR, ST=Some-State, O=Internet Widgits Pty Ltd, CN=Test1 + CN=Test2"
);
}
}