use der_parser::der::Tag; use der_parser::oid::Oid; use nom::HexDisplay; use std::cmp::min; use std::convert::TryFrom; use std::env; use std::io; use std::net::{Ipv4Addr, Ipv6Addr}; use x509_parser::prelude::*; use x509_parser::public_key::PublicKey; use x509_parser::signature_algorithm::SignatureAlgorithm; 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:?}"), GeneralName::Invalid(tag, b) => format!("Invalid:tag={tag},data={b:?}"), } } fn print_x509_extension(oid: &Oid, ext: &X509Extension) { println!( " [crit:{} l:{}] {}: ", ext.critical, ext.value.len(), format_oid(oid) ); match ext.parsed_extension() { ParsedExtension::AuthorityKeyIdentifier(aki) => { println!(" X509v3 Authority Key Identifier"); if let Some(key_id) = &aki.key_identifier { println!(" Key Identifier: {key_id:x}"); } if let Some(issuer) = &aki.authority_cert_issuer { for name in issuer { println!(" Cert Issuer: {name}"); } } if let Some(serial) = aki.authority_cert_serial { println!(" Cert Serial: {}", format_serial(serial)); } } ParsedExtension::BasicConstraints(bc) => { println!(" X509v3 CA: {}", bc.ca); } ParsedExtension::CRLDistributionPoints(points) => { println!(" X509v3 CRL Distribution Points:"); for point in points.iter() { 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 { let s = match name { GeneralName::DNSName(s) => { format!("DNS:{s}") } GeneralName::IPAddress(b) => { let ip = match b.len() { 4 => { let b = <[u8; 4]>::try_from(*b).unwrap(); let ip = Ipv4Addr::from(b); format!("{ip}") } 16 => { let b = <[u8; 16]>::try_from(*b).unwrap(); let ip = Ipv6Addr::from(b); format!("{ip}") } l => format!("invalid (len={l})"), }; format!("IP Address:{ip}") } _ => { format!("{name:?}") } }; println!(" X509v3 SAN: {s}"); } } ParsedExtension::SubjectKeyIdentifier(id) => { println!(" X509v3 Subject Key Identifier: {id:x}"); } 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 { let s = match parameter.tag() { Tag::Oid => { let oid = parameter.as_oid().unwrap(); format_oid(&oid) } _ => format!("{}", parameter.tag()), }; println!("{:indent$}Parameter: {}", "", s, indent = level); let bytes = parameter.as_bytes(); print_hex_dump(bytes, 32); } else { println!("{:indent$}Parameter: ", "", indent = level); } } fn print_x509_info(x509: &X509Certificate) -> io::Result<()> { let version = x509.version(); if version.0 < 3 { println!(" Version: {version}"); } else { println!(" Version: INVALID({})", version.0); } println!(" Serial: {}", x509.tbs_certificate.raw_serial_as_string()); println!(" Subject: {}", x509.subject()); println!(" Issuer: {}", x509.issuer()); println!(" Validity:"); println!(" NotBefore: {}", x509.validity().not_before); println!(" NotAfter: {}", x509.validity().not_after); println!(" is_valid: {}", x509.validity().is_valid()); println!(" Subject Public Key Info:"); print_x509_ski(x509.public_key()); print_x509_signature_algorithm(&x509.signature_algorithm, 4); println!(" Signature Value:"); for l in format_number_to_hex_with_colon(&x509.signature_value.data, 16) { println!(" {l}"); } println!(" Extensions:"); for ext in x509.extensions() { print_x509_extension(&ext.oid, ext); } println!(); print!("Structure validation status: "); #[cfg(feature = "validate")] { let mut logger = VecLogger::default(); // structure validation status let ok = X509StructureValidator .chain(X509CertificateValidator) .validate(x509, &mut logger); if ok { println!("Ok"); } else { println!("FAIL"); } for warning in logger.warnings() { println!(" [W] {warning}"); } for error in logger.errors() { println!(" [E] {error}"); } println!(); if VALIDATE_ERRORS_FATAL && !logger.errors().is_empty() { return Err(io::Error::new(io::ErrorKind::Other, "validation failed")); } } #[cfg(not(feature = "validate"))] { println!("Unknown (feature 'validate' not enabled)"); } #[cfg(any(feature = "verify", feature = "verify-aws"))] { print!("Signature verification: "); if x509.subject() == x509.issuer() { if x509.verify_signature(None).is_ok() { println!("OK"); println!(" [I] certificate is self-signed"); } else if x509.subject() == x509.issuer() { println!("FAIL"); println!(" [W] certificate looks self-signed, but signature verification failed"); } } else { // if subject is different from issuer, we cannot verify certificate without the public key of the issuer println!("N/A"); } } Ok(()) } fn print_x509_signature_algorithm(signature_algorithm: &AlgorithmIdentifier, indent: usize) { match SignatureAlgorithm::try_from(signature_algorithm) { Ok(sig_alg) => { print!(" Signature Algorithm: "); match sig_alg { SignatureAlgorithm::DSA => println!("DSA"), SignatureAlgorithm::ECDSA => println!("ECDSA"), SignatureAlgorithm::ED25519 => println!("ED25519"), SignatureAlgorithm::RSA => println!("RSA"), SignatureAlgorithm::RSASSA_PSS(params) => { println!("RSASSA-PSS"); let indent_s = format!("{:indent$}", "", indent = indent + 2); println!( "{}Hash Algorithm: {}", indent_s, format_oid(params.hash_algorithm_oid()), ); print!("{indent_s}Mask Generation Function: "); if let Ok(mask_gen) = params.mask_gen_algorithm() { println!( "{}/{}", format_oid(&mask_gen.mgf), format_oid(&mask_gen.hash), ); } else { println!("INVALID"); } println!("{}Salt Length: {}", indent_s, params.salt_length()); } SignatureAlgorithm::RSAAES_OAEP(params) => { println!("RSAAES-OAEP"); let indent_s = format!("{:indent$}", "", indent = indent + 2); println!( "{}Hash Algorithm: {}", indent_s, format_oid(params.hash_algorithm_oid()), ); print!("{indent_s}Mask Generation Function: "); if let Ok(mask_gen) = params.mask_gen_algorithm() { println!( "{}/{}", format_oid(&mask_gen.mgf), format_oid(&mask_gen.hash), ); } else { println!("INVALID"); } println!( "{}pSourceFunc: {}", indent_s, format_oid(¶ms.p_source_alg().algorithm), ); } } } Err(e) => { eprintln!("Could not parse signature algorithm: {e}"); println!(" Signature Algorithm:"); print_x509_digest_algorithm(signature_algorithm, indent); } } } fn print_x509_ski(public_key: &SubjectPublicKeyInfo) { println!(" Public Key Algorithm:"); print_x509_digest_algorithm(&public_key.algorithm, 6); match public_key.parsed() { Ok(PublicKey::RSA(rsa)) => { println!(" RSA Public Key: ({} bit)", rsa.key_size()); // print_hex_dump(rsa.modulus, 1024); for l in format_number_to_hex_with_colon(rsa.modulus, 16) { println!(" {l}"); } if let Ok(e) = rsa.try_exponent() { println!(" exponent: 0x{e:x} ({e})"); } else { println!(" exponent: :"); print_hex_dump(rsa.exponent, 32); } } Ok(PublicKey::EC(ec)) => { println!(" EC Public Key: ({} bit)", ec.key_size()); for l in format_number_to_hex_with_colon(ec.data(), 16) { println!(" {l}"); } // // identify curve // if let Some(params) = &public_key.algorithm.parameters { // let curve_oid = params.as_oid(); // let curve = curve_oid // .map(|oid| { // oid_registry() // .get(oid) // .map(|entry| entry.sn()) // .unwrap_or("") // }) // .unwrap_or(""); // println!(" Curve: {}", curve); // } } Ok(PublicKey::DSA(y)) => { println!(" DSA Public Key: ({} bit)", 8 * y.len()); for l in format_number_to_hex_with_colon(y, 16) { println!(" {l}"); } } Ok(PublicKey::GostR3410(y)) => { println!(" GOST R 34.10-94 Public Key: ({} bit)", 8 * y.len()); for l in format_number_to_hex_with_colon(y, 16) { println!(" {l}"); } } Ok(PublicKey::GostR3410_2012(y)) => { println!(" GOST R 34.10-2012 Public Key: ({} bit)", 8 * y.len()); for l in format_number_to_hex_with_colon(y, 16) { println!(" {l}"); } } Ok(PublicKey::Unknown(b)) => { println!(" Unknown key type"); print_hex_dump(b, 256); if let Ok((rem, res)) = der_parser::parse_der(b) { eprintln!("rem: {} bytes", rem.len()); eprintln!("{res:?}"); } else { eprintln!(" "); } } Err(_) => { println!(" INVALID PUBLIC KEY"); } } // dbg!(&public_key); // todo!(); } fn format_number_to_hex_with_colon(b: &[u8], row_size: usize) -> Vec { let mut v = Vec::with_capacity(1 + b.len() / row_size); for r in b.chunks(row_size) { let s = r.iter().fold(String::with_capacity(3 * r.len()), |a, b| { a + &format!("{b:02x}:") }); v.push(s) } v } 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() { match pem { Ok(pem) => { let data = &pem.contents; println!("Certificate [{n}]"); handle_certificate(&file_name, data)?; } Err(e) => { eprintln!("Error while decoding PEM entry {n}: {e}"); } } } } } Ok(()) }