diff options
| author | Franck <franck@fcuny.net> | 2022-11-13 13:27:23 -0800 |
|---|---|---|
| committer | Franck <franck@fcuny.net> | 2022-11-13 13:27:23 -0800 |
| commit | b075a84043726de7d419faf765ce52f5dcd3cbe7 (patch) | |
| tree | a959e5422544274e13aab44fb747b981f89040db | |
| parent | Merge pull request 'fcuny/minor-changes' (#2) from fcuny/reduce-build-time in... (diff) | |
| parent | ref: print succinct or detailed information about a cert (diff) | |
| download | x-b075a84043726de7d419faf765ce52f5dcd3cbe7.tar.gz | |
Merge pull request 'ref: print succinct or detailed information about a cert' (#3) from fcuny/rendering into main
Reviewed-on: https://git.fcuny.net/fcuny/x509-info/pulls/3
| -rw-r--r-- | src/x509-info/.drone.yml | 1 | ||||
| -rw-r--r-- | src/x509-info/src/main.rs | 76 | ||||
| -rw-r--r-- | src/x509-info/src/output.rs | 100 |
3 files changed, 106 insertions, 71 deletions
diff --git a/src/x509-info/.drone.yml b/src/x509-info/.drone.yml index f3ec80f..7c469a4 100644 --- a/src/x509-info/.drone.yml +++ b/src/x509-info/.drone.yml @@ -33,6 +33,7 @@ steps: - nix develop --command cargo test - nix build . - nix run . -- fcuny.net + - nix run . -- --format long fcuny.net --- kind: pipeline type: docker diff --git a/src/x509-info/src/main.rs b/src/x509-info/src/main.rs index 99d2c65..d9eed13 100644 --- a/src/x509-info/src/main.rs +++ b/src/x509-info/src/main.rs @@ -1,8 +1,8 @@ extern crate webpki_roots; mod client; +mod output; -use chrono::TimeZone as _; use clap::Parser; use x509_parser::prelude::*; @@ -15,6 +15,9 @@ struct Args { /// Port to check #[clap(short, long, default_value_t = 443)] port: u16, + + #[clap(short, long,value_enum, default_value_t = output::OutputFormat::Short)] + format: output::OutputFormat, } fn main() { @@ -28,7 +31,7 @@ fn main() { Ok(certs) => { let (_, cert) = x509_parser::certificate::X509Certificate::from_der(certs[0].as_ref()).unwrap(); - pretty_print(cert); + output::OutputFormat::print(args.format, cert); } Err(e) => { println!("error: {}", e); @@ -36,72 +39,3 @@ fn main() { } }; } - -fn pretty_print(cert: X509Certificate) { - println!( - "\tSubject: CN={} O={} L={}", - cert.subject() - .iter_common_name() - .next() - .and_then(|cn| cn.as_str().ok()) - .unwrap(), - cert.subject() - .iter_organization() - .next() - .and_then(|o| o.as_str().ok()) - .unwrap_or_default(), - cert.subject() - .iter_locality() - .next() - .and_then(|l| l.as_str().ok()) - .unwrap_or_default(), - ); - println!( - "\tIssuer: CN={} O={} L={}", - cert.issuer() - .iter_common_name() - .next() - .and_then(|cn| cn.as_str().ok()) - .unwrap(), - cert.issuer() - .iter_organization() - .next() - .and_then(|o| o.as_str().ok()) - .unwrap_or_default(), - cert.issuer() - .iter_locality() - .next() - .and_then(|l| l.as_str().ok()) - .unwrap_or_default(), - ); - - let not_before = chrono::Local - .timestamp(cert.validity().not_before.timestamp(), 0) - .to_rfc3339(); - - let not_after = chrono::Local - .timestamp(cert.validity().not_after.timestamp(), 0) - .to_rfc3339(); - - if let Some(subnames) = subject_alternative_name(cert) { - let dns_names = subnames.join(", "); - println!("\tDNS Names: {}", dns_names); - } - - println!("\tValidity Period"); - println!("\t\tNot before: {}", not_before); - println!("\t\tNot After: {}", not_after); -} - -fn subject_alternative_name(cert: X509Certificate) -> Option<Vec<String>> { - let mut subnames = Vec::new(); - if let Ok(Some(san)) = cert.subject_alternative_name() { - let san = san.value; - for name in &san.general_names { - if let GeneralName::DNSName(name) = name { - subnames.push(name.to_string()); - } - } - } - Some(subnames) -} diff --git a/src/x509-info/src/output.rs b/src/x509-info/src/output.rs new file mode 100644 index 0000000..7cfa13e --- /dev/null +++ b/src/x509-info/src/output.rs @@ -0,0 +1,100 @@ +use chrono::prelude::*; +use std::net::{Ipv4Addr, Ipv6Addr}; +use x509_parser::prelude::*; + +/// How to format the output data. +#[derive(PartialEq, Eq, Debug, Copy, Clone, clap::ValueEnum)] +pub enum OutputFormat { + /// Format the output as one line of plain text. + Short, + + /// Format the output as plain text. + Long, +} + +impl OutputFormat { + pub fn print(self, cert: X509Certificate) { + match self { + Self::Short => { + self.short(cert); + } + Self::Long => { + self.to_text(cert); + } + } + } + + fn short(self, cert: X509Certificate) { + let not_after = chrono::Local.timestamp(cert.validity().not_after.timestamp(), 0); + let now: DateTime<Local> = Local::now(); + let remaining = not_after - now; + println!( + "{} is valid until {} ({} days left)", + cert.subject() + .iter_common_name() + .next() + .and_then(|cn| cn.as_str().ok()) + .unwrap(), + not_after.to_rfc2822(), + remaining.num_days(), + ); + } + + fn to_text(self, cert: X509Certificate) { + let not_before = chrono::Local.timestamp(cert.validity().not_before.timestamp(), 0); + let not_after = chrono::Local.timestamp(cert.validity().not_after.timestamp(), 0); + let now: DateTime<Local> = Local::now(); + let remaining = not_after - now; + let validity_duration = not_after - not_before; + + println!("certificate"); + println!(" version: {}", cert.version); + println!(" serial: {}", cert.tbs_certificate.raw_serial_as_string()); + println!(" subject: {}", cert.subject()); + println!(" issuer: {}", cert.issuer()); + + println!(" validity"); + println!(" not before : {}", not_before.to_rfc2822()); + println!(" not after : {}", not_after.to_rfc2822()); + println!(" validity days : {}", validity_duration.num_days()); + println!(" remaining days: {}", remaining.num_days()); + + println!(" SANs:"); + if let Some(subnames) = subject_alternative_name(cert) { + for name in subnames { + println!(" {}", name); + } + } + } +} + +fn subject_alternative_name(cert: X509Certificate) -> Option<Vec<String>> { + let mut subnames = Vec::new(); + if let Ok(Some(san)) = cert.subject_alternative_name() { + let san = san.value; + 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), + }; + subnames.push(s); + } + } + Some(subnames) +} |
