diff options
| author | Franck Cuny <franck@fcuny.net> | 2023-03-28 18:48:21 -0700 |
|---|---|---|
| committer | Franck Cuny <franck@fcuny.net> | 2023-03-28 18:48:21 -0700 |
| commit | 51640af5aa964b8eafa28e06e49528588b937a36 (patch) | |
| tree | 9246a48ee3ce60968c02f0923ab4c9fc6705af09 | |
| parent | doc: update README (diff) | |
| download | x-51640af5aa964b8eafa28e06e49528588b937a36.tar.gz | |
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.
| -rw-r--r-- | src/x509-info/Cargo.toml | 2 | ||||
| -rw-r--r-- | src/x509-info/README.md | 29 | ||||
| -rw-r--r-- | src/x509-info/src/client.rs | 59 | ||||
| -rw-r--r-- | src/x509-info/src/main.rs | 6 | ||||
| -rw-r--r-- | 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] <DOMAIN> +$ Usage: x509-info [OPTIONS] <DOMAIN> Arguments: <DOMAIN> @@ -20,6 +19,9 @@ Options: [default: 443] + -i, --insecure + Accept invalid certificate + -f, --format <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 +<no name>: 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<Vec<Certificate>, Box<dyn Error>> { +struct SkipServerVerification; + +impl SkipServerVerification { + fn new() -> Arc<Self> { + 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<Item = &[u8]>, + _ocsp_response: &[u8], + _now: std::time::SystemTime, + ) -> Result<rustls::client::ServerCertVerified, rustls::Error> { + Ok(rustls::client::ServerCertVerified::assertion()) + } +} + +pub fn get_certificates( + domain: String, + port: u16, + insecure: bool, +) -> Result<Vec<Certificate>, Box<dyn Error>> { 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> = 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("<no name>"), + 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("<no name>"), + not_after.to_rfc2822(), + remaining.num_days(), + ); + } } fn to_text(self, cert: X509Certificate) { |
