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(())
|
|
}
|