aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFranck <franck@fcuny.net>2022-11-13 13:27:23 -0800
committerFranck <franck@fcuny.net>2022-11-13 13:27:23 -0800
commitb075a84043726de7d419faf765ce52f5dcd3cbe7 (patch)
treea959e5422544274e13aab44fb747b981f89040db
parentMerge pull request 'fcuny/minor-changes' (#2) from fcuny/reduce-build-time in... (diff)
parentref: print succinct or detailed information about a cert (diff)
downloadx-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.yml1
-rw-r--r--src/x509-info/src/main.rs76
-rw-r--r--src/x509-info/src/output.rs100
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)
+}