193 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Rust
		
	
	
	
			
		
		
	
	
			193 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Rust
		
	
	
	
| 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!("... <continued>");
 | |
|     }
 | |
| }
 | |
| 
 | |
| 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: <PRESENT> {:?}",
 | |
|             "",
 | |
|             parameter.header.tag,
 | |
|             indent = level
 | |
|         );
 | |
|         if let Ok(bytes) = parameter.as_slice() {
 | |
|             print_hex_dump(bytes, 32);
 | |
|         }
 | |
|     } else {
 | |
|         println!("{:indent$}Parameter: <ABSENT>", "", 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(())
 | |
| }
 |