diff options
| -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) { |
