From 51640af5aa964b8eafa28e06e49528588b937a36 Mon Sep 17 00:00:00 2001 From: Franck Cuny Date: Tue, 28 Mar 2023 18:48:21 -0700 Subject: add a flag to check expired certificate Add the `--insecure` flag so we can check certificates that are expired. When using the short format for the output (the default), if the certificate has expired, it will report how many days ago. For certificates that have not expired, the remaining number of days will be printed. --- src/x509-info/Cargo.toml | 2 +- src/x509-info/README.md | 29 ++++++++++++++-------- src/x509-info/src/client.rs | 59 ++++++++++++++++++++++++++++++++++----------- src/x509-info/src/main.rs | 6 ++++- src/x509-info/src/output.rs | 34 ++++++++++++++++++-------- 5 files changed, 94 insertions(+), 36 deletions(-) diff --git a/src/x509-info/Cargo.toml b/src/x509-info/Cargo.toml index 4120200..f6b4e3e 100644 --- a/src/x509-info/Cargo.toml +++ b/src/x509-info/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] chrono = {version = "0.4.20", features = ["clock"], default-features = false } clap = {version = "4.0.18", features = ["derive", "cargo"]} -rustls = "0.20.7" +rustls = {version = "0.20.7", features = ["dangerous_configuration"]} rustls-native-certs = "0.6.2" webpki-roots = "0.22.5" x509-parser = "0.14" diff --git a/src/x509-info/README.md b/src/x509-info/README.md index 61d450e..f7e9121 100644 --- a/src/x509-info/README.md +++ b/src/x509-info/README.md @@ -7,8 +7,7 @@ At this point it's pretty clear that I'll never remember the syntax for `openssl ## Usage ``` shell -> x509-info --help -Usage: x509-info [OPTIONS] +$ Usage: x509-info [OPTIONS] Arguments: @@ -20,6 +19,9 @@ Options: [default: 443] + -i, --insecure + Accept invalid certificate + -f, --format [default: short] @@ -37,29 +39,36 @@ Options: The default format will print a short message: ``` shell -> x509-info twitter.com -twitter.com is valid until Mon, 12 Dec 2022 15:59:59 -0800 (29 days left) +$ x509-info twitter.com +twitter.com: Mon, 11 Dec 2023 15:59:59 -0800 (257 days left) ``` It's possible to get more details: ``` shell -> x509-info --format long twitter.com +$ x509-info --format=long twitter.com certificate version: V3 - serial: 0d:e1:52:69:6b:2f:96:70:d6:c7:db:18:ce:1c:71:a0 + serial: 0a:2c:01:b8:2b:5d:47:73:9a:5a:01:1a:6f:dc:1a:20 subject: C=US, ST=California, L=San Francisco, O=Twitter, Inc., CN=twitter.com issuer: C=US, O=DigiCert Inc, CN=DigiCert TLS RSA SHA256 2020 CA1 validity - not before : Sun, 12 Dec 2021 16:00:00 -0800 - not after : Mon, 12 Dec 2022 15:59:59 -0800 - validity days : 364 - remaining days: 29 + not before : Sat, 10 Dec 2022 16:00:00 -0800 + not after : Mon, 11 Dec 2023 15:59:59 -0800 + validity days : 365 + remaining days: 257 SANs: DNS:twitter.com DNS:www.twitter.com ``` +You can also check expired certificates: + +``` shell +$ x509-info --insecure expired.badssl.com +: Sun, 12 Apr 2015 16:59:59 -0700 (it expired -2907 days ago) +``` + ## Notes Could the same be achieved with a wrapper around `openssl` ? yes. diff --git a/src/x509-info/src/client.rs b/src/x509-info/src/client.rs index 11c48db..344e300 100644 --- a/src/x509-info/src/client.rs +++ b/src/x509-info/src/client.rs @@ -6,22 +6,53 @@ use std::error::Error; use std::io::Write; use std::sync::Arc; -pub fn get_certificates(domain: String, port: u16) -> Result, Box> { +struct SkipServerVerification; + +impl SkipServerVerification { + fn new() -> Arc { + Arc::new(Self) + } +} +impl rustls::client::ServerCertVerifier for SkipServerVerification { + fn verify_server_cert( + &self, + _end_entity: &rustls::Certificate, + _intermediates: &[rustls::Certificate], + _server_name: &rustls::ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: std::time::SystemTime, + ) -> Result { + Ok(rustls::client::ServerCertVerified::assertion()) + } +} + +pub fn get_certificates( + domain: String, + port: u16, + insecure: bool, +) -> Result, Box> { let mut tcp_stream = std::net::TcpStream::connect(format!("{}:{}", domain, port))?; - let mut root_store = RootCertStore::empty(); - root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { - OwnedTrustAnchor::from_subject_spki_name_constraints( - ta.subject, - ta.spki, - ta.name_constraints, - ) - })); - - let config = ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(root_store) - .with_no_client_auth(); + let config = if insecure { + ClientConfig::builder() + .with_safe_defaults() + .with_custom_certificate_verifier(SkipServerVerification::new()) + .with_no_client_auth() + } else { + let mut root_store = RootCertStore::empty(); + root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { + OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject, + ta.spki, + ta.name_constraints, + ) + })); + ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_store) + .with_no_client_auth() + }; let server_name = ServerName::try_from(domain.as_ref())?; diff --git a/src/x509-info/src/main.rs b/src/x509-info/src/main.rs index d9eed13..c318a3d 100644 --- a/src/x509-info/src/main.rs +++ b/src/x509-info/src/main.rs @@ -16,6 +16,10 @@ struct Args { #[clap(short, long, default_value_t = 443)] port: u16, + /// Accept invalid certificate + #[clap(short, long, default_value_t = false)] + insecure: bool, + #[clap(short, long,value_enum, default_value_t = output::OutputFormat::Short)] format: output::OutputFormat, } @@ -25,7 +29,7 @@ fn main() { let domain = args.domain; - let certs = client::get_certificates(domain, args.port); + let certs = client::get_certificates(domain, args.port, args.insecure); match certs { Ok(certs) => { diff --git a/src/x509-info/src/output.rs b/src/x509-info/src/output.rs index 7cfa13e..e38aff1 100644 --- a/src/x509-info/src/output.rs +++ b/src/x509-info/src/output.rs @@ -28,16 +28,30 @@ impl OutputFormat { let not_after = chrono::Local.timestamp(cert.validity().not_after.timestamp(), 0); let now: DateTime = 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(), - ); + + if remaining >= chrono::Duration::zero() { + println!( + "{}: {} ({} days left)", + cert.subject() + .iter_common_name() + .next() + .and_then(|cn| cn.as_str().ok()) + .unwrap_or(""), + not_after.to_rfc2822(), + remaining.num_days(), + ); + } else { + println!( + "{}: {} (it expired {} days ago)", + cert.subject() + .iter_common_name() + .next() + .and_then(|cn| cn.as_str().ok()) + .unwrap_or(""), + not_after.to_rfc2822(), + remaining.num_days(), + ); + } } fn to_text(self, cert: X509Certificate) { -- cgit v1.2.3