use std::fmt;
use num_bigint::BigUint;
use crate::error::X509Error;
use crate::extensions::*;
use crate::objects::*;
use crate::time::ASN1Time;
use data_encoding::HEXUPPER;
use der_parser::ber::{BerObjectContent, BitStringObject};
use der_parser::der::DerObject;
use der_parser::oid::Oid;
use std::collections::HashMap;
#[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],
pub(crate) parsed_extension: ParsedExtension<'a>,
}
impl<'a> X509Extension<'a> {
pub fn new(
oid: Oid<'a>,
critical: bool,
value: &'a [u8],
parsed_extension: ParsedExtension<'a>,
) -> X509Extension<'a> {
X509Extension {
oid,
critical,
value,
parsed_extension,
}
}
pub fn parsed_extension(&self) -> &ParsedExtension<'a> {
&self.parsed_extension
}
}
#[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>>,
pub(crate) raw: &'a [u8],
}
impl<'a> X509Name<'a> {
pub fn as_raw(&self) -> &'a [u8] {
self.raw
}
}
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: HashMap<Oid<'a>, 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: ASN1Time,
pub not_after: ASN1Time,
}
impl Validity {
pub fn time_to_expiration(&self) -> Option<std::time::Duration> {
let now = ASN1Time::now();
if !self.is_valid_at(now) {
return None;
}
self.not_after - now
}
#[inline]
pub fn is_valid_at(&self, time: ASN1Time) -> bool {
time >= self.not_before && time < self.not_after
}
#[inline]
pub fn is_valid(&self) -> bool {
self.is_valid_at(ASN1Time::now())
}
}
#[test]
fn check_validity_expiration() {
let mut v = Validity {
not_before: ASN1Time::now(),
not_after: ASN1Time::now(),
};
assert_eq!(v.time_to_expiration(), None);
v.not_after = (v.not_after + std::time::Duration::new(60, 0)).unwrap();
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 extensions(&self) -> &HashMap<Oid, X509Extension> {
&self.extensions
}
pub fn basic_constraints(&self) -> Option<(bool, &BasicConstraints)> {
let ext = self.extensions.get(&OID_EXT_BC)?;
match ext.parsed_extension {
ParsedExtension::BasicConstraints(ref bc) => Some((ext.critical, bc)),
_ => None,
}
}
pub fn key_usage(&self) -> Option<(bool, &KeyUsage)> {
let ext = self.extensions.get(&OID_EXT_KU)?;
match ext.parsed_extension {
ParsedExtension::KeyUsage(ref ku) => Some((ext.critical, ku)),
_ => None,
}
}
pub fn extended_key_usage(&self) -> Option<(bool, &ExtendedKeyUsage)> {
let ext = self.extensions.get(&OID_EXT_EKU)?;
match ext.parsed_extension {
ParsedExtension::ExtendedKeyUsage(ref eku) => Some((ext.critical, eku)),
_ => None,
}
}
pub fn policy_constraints(&self) -> Option<(bool, &PolicyConstraints)> {
let ext = self.extensions.get(&OID_EXT_POLICYCONSTRAINTS)?;
match ext.parsed_extension {
ParsedExtension::PolicyConstraints(ref pc) => Some((ext.critical, pc)),
_ => None,
}
}
pub fn inhibit_anypolicy(&self) -> Option<(bool, &InhibitAnyPolicy)> {
let ext = self.extensions.get(&OID_EXT_INHIBITANYPOLICY)?;
match ext.parsed_extension {
ParsedExtension::InhibitAnyPolicy(ref iap) => Some((ext.critical, iap)),
_ => None,
}
}
pub fn policy_mappings(&self) -> Option<(bool, &PolicyMappings)> {
let ext = self.extensions.get(&OID_EXT_POLICYMAPPINGS)?;
match ext.parsed_extension {
ParsedExtension::PolicyMappings(ref pm) => Some((ext.critical, pm)),
_ => None,
}
}
pub fn subject_alternative_name(&self) -> Option<(bool, &SubjectAlternativeName)> {
let ext = self.extensions.get(&OID_EXT_SAN)?;
match ext.parsed_extension {
ParsedExtension::SubjectAlternativeName(ref san) => Some((ext.critical, san)),
_ => None,
}
}
pub fn name_constraints(&self) -> Option<(bool, &NameConstraints)> {
let ext = self.extensions.get(&OID_EXT_NAMECONSTRAINTS)?;
match ext.parsed_extension {
ParsedExtension::NameConstraints(ref nc) => Some((ext.critical, nc)),
_ => None,
}
}
pub fn is_ca(&self) -> bool {
self.basic_constraints()
.map(|(_, bc)| bc.ca)
.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: ASN1Time,
pub next_update: Option<ASN1Time>,
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: ASN1Time,
pub extensions: Vec<X509Extension<'a>>,
}
fn attribute_value_to_string(attr: &DerObject, _attr_type: &Oid) -> Result<String, X509Error> {
match attr.content {
BerObjectContent::NumericString(s)
| BerObjectContent::PrintableString(s)
| BerObjectContent::UTF8String(s)
| BerObjectContent::IA5String(s) => Ok(s.to_owned()),
_ => {
attr.as_slice()
.map(|s| HEXUPPER.encode(s))
.or(Err(X509Error::InvalidX509Name))
}
}
}
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| {
let val_str = attribute_value_to_string(&attr.attr_value, &attr.attr_type)?;
let sn_str = match oid2sn(&attr.attr_type) {
Ok(s) => String::from(s),
_ => format!("{:?}", attr.attr_type),
};
let rdn = format!("{}={}", sn_str, val_str);
match _vec2.len() {
0 => Ok(rdn),
_ => Ok(_vec2 + " + " + &rdn),
}
})
})
.map(|v| match _vec.len() {
0 => v,
_ => _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),
}
}
#[inline]
pub fn subject(&self) -> &X509Name {
&self.tbs_certificate.subject
}
#[inline]
pub fn issuer(&self) -> &X509Name {
&self.tbs_certificate.issuer
}
#[inline]
pub fn validity(&self) -> &Validity {
&self.tbs_certificate.validity
}
#[inline]
pub fn extensions(&self) -> &HashMap<Oid, X509Extension> {
self.tbs_certificate.extensions()
}
}
#[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;
#[test]
fn test_x509_name() {
let name = X509Name {
rdn_seq: vec![
RelativeDistinguishedName {
set: vec![AttributeTypeAndValue {
attr_type: oid!(2.5.4.6),
attr_value: DerObject::from_obj(BerObjectContent::PrintableString("FR")),
}],
},
RelativeDistinguishedName {
set: vec![AttributeTypeAndValue {
attr_type: oid!(2.5.4.8),
attr_value: DerObject::from_obj(BerObjectContent::PrintableString(
"Some-State",
)),
}],
},
RelativeDistinguishedName {
set: vec![AttributeTypeAndValue {
attr_type: oid!(2.5.4.10),
attr_value: DerObject::from_obj(BerObjectContent::PrintableString(
"Internet Widgits Pty Ltd",
)),
}],
},
RelativeDistinguishedName {
set: vec![
AttributeTypeAndValue {
attr_type: oid!(2.5.4.3),
attr_value: DerObject::from_obj(BerObjectContent::PrintableString(
"Test1",
)),
},
AttributeTypeAndValue {
attr_type: oid!(2.5.4.3),
attr_value: DerObject::from_obj(BerObjectContent::PrintableString(
"Test2",
)),
},
],
},
],
raw: &[],
};
assert_eq!(
name.to_string(),
"C=FR, ST=Some-State, O=Internet Widgits Pty Ltd, CN=Test1 + CN=Test2"
);
}
}