use der_parser::oid::Oid; use nom::HexDisplay; use std::cmp::min; use std::env; use std::io; use x509_parser::prelude::*; #[cfg(feature = "validate")] use x509_parser::validate::Validate; const PARSE_ERRORS_FATAL: bool = false; #[cfg(feature = "validate")] const VALIDATE_ERRORS_FATAL: bool = false; fn print_hex_dump(bytes: &[u8], max_len: usize) { let m = min(bytes.len(), max_len); print!("{}", &bytes[..m].to_hex(16)); if bytes.len() > max_len { println!("... "); } } fn format_oid(oid: &Oid) -> String { match oid2sn(oid, oid_registry()) { Ok(s) => s.to_owned(), _ => format!("{}", oid), } } fn generalname_to_string(gn: &GeneralName) -> String { match gn { GeneralName::DNSName(name) => format!("DNSName:{}", name), GeneralName::DirectoryName(n) => format!("DirName:{}", n), GeneralName::EDIPartyName(obj) => format!("EDIPartyName:{:?}", obj), GeneralName::IPAddress(n) => format!("IPAddress:{:?}", n), GeneralName::OtherName(oid, n) => format!("OtherName:{}, {:?}", oid, n), GeneralName::RFC822Name(n) => format!("RFC822Name:{}", n), GeneralName::RegisteredID(oid) => format!("RegisteredID:{}", oid), GeneralName::URI(n) => format!("URI:{}", n), GeneralName::X400Address(obj) => format!("X400Address:{:?}", obj), } } fn print_x509_extension(oid: &Oid, ext: &X509Extension) { print!(" {}: ", format_oid(oid)); print!(" Critical={}", ext.critical); print!(" len={}", ext.value.len()); println!(); match ext.parsed_extension() { ParsedExtension::BasicConstraints(bc) => { println!(" X509v3 CA: {}", bc.ca); } ParsedExtension::CRLDistributionPoints(points) => { println!(" X509v3 CRL Distribution Points:"); for point in points { if let Some(name) = &point.distribution_point { println!(" Full Name: {:?}", name); } if let Some(reasons) = &point.reasons { println!(" Reasons: {}", reasons); } if let Some(crl_issuer) = &point.crl_issuer { print!(" CRL Issuer: "); for gn in crl_issuer { print!("{} ", generalname_to_string(gn)); } println!(); } println!(); } } ParsedExtension::KeyUsage(ku) => { println!(" X509v3 Key Usage: {}", ku); } ParsedExtension::NSCertType(ty) => { println!(" Netscape Cert Type: {}", ty); } ParsedExtension::SubjectAlternativeName(san) => { for name in &san.general_names { println!(" X509v3 SAN: {:?}", name); } } ParsedExtension::SubjectKeyIdentifier(id) => { let mut s = id.0.iter() .fold(String::with_capacity(3 * id.0.len()), |a, b| { a + &format!("{:02x}:", b) }); s.pop(); println!(" X509v3 Subject Key Identifier: {}", &s); } x => println!(" {:?}", x), } } fn print_x509_digest_algorithm(alg: &AlgorithmIdentifier, level: usize) { println!( "{:indent$}Oid: {}", "", format_oid(&alg.algorithm), indent = level ); if let Some(parameter) = &alg.parameters { println!( "{:indent$}Parameter: {:?}", "", parameter.header.tag, indent = level ); if let Ok(bytes) = parameter.as_slice() { print_hex_dump(bytes, 32); } } else { println!("{:indent$}Parameter: ", "", indent = level); } } fn print_x509_info(x509: &X509Certificate) -> io::Result<()> { println!(" Subject: {}", x509.subject()); println!(" Signature Algorithm:"); print_x509_digest_algorithm(&x509.signature_algorithm, 4); println!(" Issuer: {}", x509.issuer()); println!(" Serial: {}", x509.tbs_certificate.raw_serial_as_string()); println!(" Validity:"); println!(" NotBefore: {}", x509.validity().not_before.to_rfc2822()); println!(" NotAfter: {}", x509.validity().not_after.to_rfc2822()); println!(" is_valid: {}", x509.validity().is_valid()); println!(" Extensions:"); for ext in x509.extensions() { print_x509_extension(&ext.oid, ext); } println!(); #[cfg(feature = "validate")] { // structure validation status let (ok, warnings, errors) = x509.validate_to_vec(); print!("Structure validation status: "); if ok { println!("Ok"); } else { println!("FAIL"); } for warning in &warnings { println!(" [W] {}", warning); } for error in &errors { println!(" [E] {}", error); } println!(); if VALIDATE_ERRORS_FATAL && !errors.is_empty() { return Err(io::Error::new(io::ErrorKind::Other, "validation failed")); } } Ok(()) } fn handle_certificate(file_name: &str, data: &[u8]) -> io::Result<()> { match parse_x509_certificate(data) { Ok((_, x509)) => { print_x509_info(&x509)?; Ok(()) } Err(e) => { let s = format!("Error while parsing {}: {}", file_name, e); if PARSE_ERRORS_FATAL { Err(io::Error::new(io::ErrorKind::Other, s)) } else { eprintln!("{}", s); Ok(()) } } } } pub fn main() -> io::Result<()> { for file_name in env::args().skip(1) { println!("File: {}", file_name); let data = std::fs::read(file_name.clone()).expect("Unable to read file"); if matches!((data[0], data[1]), (0x30, 0x81..=0x83)) { // probably DER handle_certificate(&file_name, &data)?; } else { // try as PEM for (n, pem) in Pem::iter_from_buffer(&data).enumerate() { let pem = pem.expect("Could not decode the PEM file"); let data = &pem.contents; println!("Certificate [{}]", n); handle_certificate(&file_name, data)?; } } } Ok(()) }