diff --git a/Cargo.lock b/Cargo.lock index d3851e2..dd5edd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -294,6 +294,45 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "asn1-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf6690c370453db30743b373a60ba498fc0d6d83b11f4abfd87a84a075db5dd4" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atty" version = "0.2.14" @@ -524,6 +563,26 @@ dependencies = [ "typenum", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + +[[package]] +name = "der-parser" +version = "8.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d4bc9b0db0a0df9ae64634ac5bdefb7afcb534e182275ca0beadbe486701c1" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -547,6 +606,17 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "displaydoc" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "encoding_rs" version = "0.8.31" @@ -1000,6 +1070,12 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.5.3" @@ -1039,6 +1115,46 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.1" @@ -1058,6 +1174,15 @@ dependencies = [ "libc", ] +[[package]] +name = "oid-registry" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d4bda43fd1b844cbc6e6e54b5444e2b1bc7838bce59ad205902cccbb26d6761" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.13.1" @@ -1144,6 +1269,15 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" +[[package]] +name = "pem" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" +dependencies = [ + "base64", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -1347,6 +1481,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustls" version = "0.20.6" @@ -1563,6 +1706,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "tcp_relay_client" version = "0.1.0" @@ -1594,10 +1749,12 @@ dependencies = [ "env_logger", "futures", "log", + "pem", "rustls", "serde", "tokio", "webpki", + "x509-parser", ] [[package]] @@ -1846,6 +2003,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-xid" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" + [[package]] name = "untrusted" version = "0.7.1" @@ -2082,6 +2245,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "x509-parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0ecbeb7b67ce215e40e3cc7f2ff902f94a223acf44995934763467e7b1febc8" +dependencies = [ + "asn1-rs", + "base64", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror", + "time", +] + [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" diff --git a/tcp_relay_server/Cargo.toml b/tcp_relay_server/Cargo.toml index 82df512..5632f09 100644 --- a/tcp_relay_server/Cargo.toml +++ b/tcp_relay_server/Cargo.toml @@ -16,4 +16,6 @@ serde = { version = "1.0.144", features = ["derive"] } tokio = { version = "1", features = ["full"] } futures = "0.3.24" rustls = "0.20.6" -webpki = "0.22.0" \ No newline at end of file +webpki = "0.22.0" +x509-parser = "0.14.0" +pem = "1.1.0" \ No newline at end of file diff --git a/tcp_relay_server/src/server_config.rs b/tcp_relay_server/src/server_config.rs index 3c6c68e..70e655c 100644 --- a/tcp_relay_server/src/server_config.rs +++ b/tcp_relay_server/src/server_config.rs @@ -48,6 +48,10 @@ pub struct ServerConfig { /// This option automatically enable TLS client authentication #[clap(long)] pub tls_client_auth_root_cert: Option, + + /// TLS client authentication revocation list (CRL file) + #[clap(long)] + pub tls_revocation_list: Option, } impl ServerConfig { diff --git a/tcp_relay_server/src/tls_cert_client_verifier.rs b/tcp_relay_server/src/tls_cert_client_verifier.rs index 36f3148..5b50ceb 100644 --- a/tcp_relay_server/src/tls_cert_client_verifier.rs +++ b/tcp_relay_server/src/tls_cert_client_verifier.rs @@ -1,8 +1,10 @@ use std::sync::Arc; use std::time::SystemTime; +use rustls::internal::msgs::enums::AlertDescription; use rustls::server::{AllowAnyAuthenticatedClient, ClientCertVerified, ClientCertVerifier}; use rustls::{Certificate, DistinguishedNames, Error, RootCertStore}; +use x509_parser::prelude::{CertificateRevocationList, FromDer, X509Certificate}; use base::cert_utils::parse_pem_certificates; @@ -10,10 +12,12 @@ use crate::server_config::ServerConfig; pub struct CustomCertClientVerifier { upstream_cert_verifier: Box>, + crl: Option>, } impl CustomCertClientVerifier { pub fn new(conf: Arc) -> Self { + // Build root certifications list let cert_path = conf .tls_client_auth_root_cert .as_deref() @@ -36,8 +40,20 @@ impl CustomCertClientVerifier { .expect("Failed to add certificate to root store"); } + // Parse CRL file (if any) + let crl = if let Some(crl_file) = &conf.tls_revocation_list { + let crl_file = std::fs::read(crl_file).expect("Failed to open / read CRL file!"); + + let parsed_crl = pem::parse(crl_file).expect("Failed to decode CRL file!"); + + Some(parsed_crl.contents) + } else { + None + }; + Self { upstream_cert_verifier: Box::new(AllowAnyAuthenticatedClient::new(store)), + crl, } } } @@ -61,6 +77,26 @@ impl ClientCertVerifier for CustomCertClientVerifier { intermediates: &[Certificate], now: SystemTime, ) -> Result { + // Check the certificates sent by the client has been revoked + if let Some(crl) = &self.crl { + let (_rem, crl) = + CertificateRevocationList::from_der(crl).expect("Failed to read CRL!"); + + let (_rem, cert) = + X509Certificate::from_der(&end_entity.0).expect("Failed to read certificate!"); + + for revoked in crl.iter_revoked_certificates() { + if revoked.user_certificate == cert.serial { + log::error!( + "Client attempted to use a revoked certificate! Serial={:?} Subject={}", + cert.serial, + cert.subject + ); + return Err(Error::AlertReceived(AlertDescription::CertificateRevoked)); + } + } + } + self.upstream_cert_verifier .verify_client_cert(end_entity, intermediates, now) }