From d7193af4e6a9e5483d709601d38ccc163ab008c0 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Fri, 7 Oct 2022 23:47:10 +0900 Subject: [PATCH 01/13] temporarily implemented client authentication using client certificates (mTLS) --- Cargo.toml | 11 ++- README.md | 58 +++++++++++- TODO.md | 2 +- config-example.toml | 5 +- example-certs/client.ca.crt | 12 +++ example-certs/client.ca.key | 5 + example-certs/client.crt | 13 +++ example-certs/client.csr | 8 ++ example-certs/client.ext | 4 + example-certs/client.key | 5 + localhost.crt => example-certs/server.crt | 0 localhost.key => example-certs/server.key | 0 h3 | 2 +- src/backend/mod.rs | 110 +++++++++++++++++++++- src/config/parse.rs | 10 +- src/config/toml.rs | 1 + src/proxy/mod.rs | 1 + src/proxy/proxy_client_cert.rs | 45 +++++++++ src/proxy/proxy_h3.rs | 23 ++++- src/proxy/proxy_tls.rs | 49 +++++++--- src/utils/bytes_name.rs | 2 +- 21 files changed, 326 insertions(+), 40 deletions(-) create mode 100644 example-certs/client.ca.crt create mode 100644 example-certs/client.ca.key create mode 100644 example-certs/client.crt create mode 100644 example-certs/client.csr create mode 100644 example-certs/client.ext create mode 100644 example-certs/client.key rename localhost.crt => example-certs/server.crt (100%) rename localhost.key => example-certs/server.key (100%) create mode 100644 src/proxy/proxy_client_cert.rs diff --git a/Cargo.toml b/Cargo.toml index eb814db..d828e7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rpxy" -version = "0.1.0" +version = "0.1.1" authors = ["Jun Kurihara"] homepage = "https://github.com/junkurihara/rust-rpxy" repository = "https://github.com/junkurihara/rust-rpxy" @@ -18,7 +18,7 @@ http3 = ["quinn", "h3", "h3-quinn"] [dependencies] env_logger = "0.9.1" anyhow = "1.0.65" -clap = { version = "3.2.22", features = ["std", "cargo", "wrap_help"] } +clap = { version = "4.0.4", features = ["std", "cargo", "wrap_help"] } futures = { version = "0.3.24", features = ["alloc", "async-await"] } hyper = { version = "0.14.20", default-features = false, features = [ "server", @@ -27,7 +27,7 @@ hyper = { version = "0.14.20", default-features = false, features = [ "stream", ] } log = "0.4.17" -tokio = { version = "1.21.1", default-features = false, features = [ +tokio = { version = "1.21.2", default-features = false, features = [ "net", "rt-multi-thread", "parking_lot", @@ -41,7 +41,7 @@ rustls = { version = "0.20.6", default-features = false } rand = "0.8.5" toml = { version = "0.5.9", default-features = false } rustc-hash = "1.1.0" -serde = { version = "1.0.144", default-features = false, features = ["derive"] } +serde = { version = "1.0.145", default-features = false, features = ["derive"] } hyper-rustls = { version = "0.23.0", default-features = false, features = [ "tokio-runtime", "webpki-tokio", @@ -52,7 +52,8 @@ bytes = "1.2.1" quinn = { version = "0.8.5", optional = true } h3 = { path = "./h3/h3/", optional = true } h3-quinn = { path = "./h3/h3-quinn/", optional = true } -thiserror = "1.0.35" +thiserror = "1.0.37" +x509-parser = "0.14.0" [target.'cfg(not(target_env = "msvc"))'.dependencies] diff --git a/README.md b/README.md index 4ec9d1b..5d2c6eb 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ listen_port_tls = 443 [apps."app_name"] server_name = 'app1.example.com' -tls = { tls_cert_path = 'localhost.crt', tls_cert_key_path = 'localhost.key' } +tls = { tls_cert_path = 'server.crt', tls_cert_key_path = 'server.key' } reverse_proxy = [{ upstream = [{ location = 'app1.local:8080' }] }] ``` @@ -141,7 +141,7 @@ We should note that the private key specified by `tls_cert_key_path` must be *in In the current Web, we believe it is common to serve everything through HTTPS rather than HTTP, and hence *https redirection* is often used for HTTP requests. When you specify both `listen_port` and `listen_port_tls`, you can enable an option of such redirection by making `https_redirection` true. ```toml -tls = { https_redirection = true, tls_cert_path = 'localhost.crt', tls_cert_key_path = 'localhost.key' } +tls = { https_redirection = true, tls_cert_path = 'server.crt', tls_cert_key_path = 'server.key' } ``` If it is true, `rpxy` returns the status code `301` to the cleartext request with new location `https:///` served over TLS. @@ -155,7 +155,7 @@ listen_port_tls = 443 [apps.app1] server_name = 'app1.example.com' -tls = { https_redirection = true, tls_cert_path = 'localhost.crt', tls_cert_key_path = 'localhost.key' } +tls = { https_redirection = true, tls_cert_path = 'server.crt', tls_cert_key_path = 'server.key' } [[apps.app1.reverse_proxy]] upstream = [ @@ -235,13 +235,63 @@ If you obtain certificates and private keys from [Let's Encrypt](https://letsenc The easiest way is to use `openssl` by ```bash -openssl pkcs8 -topk8 -nocrypt \ +$ openssl pkcs8 -topk8 -nocrypt \ -in yoru_domain_from_le.key \ -inform PEM \ -out your_domain_pkcs8.key.pem \ -outform PEM ``` +### Client Authentication using Client Certificate Signed by Your Own Root CA + +First, you need to prepare a CA certificate used to verify client certificate. If you do not have one, you can generate CA key and certificate by OpenSSL commands as follows. *Note that `rustls` accepts X509v3 certificates and reject SHA-1, and that `rpxy` relys on Version 3 extension fields of `KeyID`s of `Subject Key Identifier` and `Authority Key Identifier`.* + +1. Generate CA key of `secp256v1`, CSR, and then generate CA certificate that will be set for `tls.client_ca_cert_path` for each server app in `config.toml`. + + ```bash + $ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out client.ca.key + + $ openssl req -new -key client.ca.key -out client.ca.csr + ... + ----- + Country Name (2 letter code) []: ... + State or Province Name (full name) []: ... + Locality Name (eg, city) []: ... + Organization Name (eg, company) []: ... + Organizational Unit Name (eg, section) []: ... + Common Name (eg, fully qualified host name) []: + Email Address []: ... + + $ openssl x509 -req -days 3650 -sha256 -in client.ca.csr -signkey client.ca.key -out client.ca.crt -extfile client.ca.ext + ``` + +2. Generate a client key of `secp256v1` and certificate signed by CA key. + + ```bash + $ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out client.key + + $ openssl req -new -key client.key -out client.csr + ... + ----- + Country Name (2 letter code) []: + State or Province Name (full name) []: + Locality Name (eg, city) []: + Organization Name (eg, company) []: + Organizational Unit Name (eg, section) []: + Common Name (eg, fully qualified host name) []: + Email Address []: + + $ openssl x509 -req -days 365 -sha256 -in client.csr -CA client.ca.crt -CAkey client.ca.key -CAcreateserial -out client.crt -extfile client.ext + ``` + + Now you have a client key `client.key` and certificate `client.crt` (version 3). `p12` file can be retrieved as + + ```bash + $ openssl pkcs12 -export -inkey client.key -in client.crt -certfile client.ca.crt -out client.pfx + ``` + + All of sample certificate files are found in `./example-certs/` directory. + ### (Work Around) Deployment on Ubuntu 22.04LTS using docker behind `ufw` Basically, docker automatically manage your iptables if you use the port-mapping option, i.e., `--publish` for `docker run` or `ports` in `docker-compose.yml`. This means you do not need to manually expose your port, e.g., 443 TCP/UDP for HTTPS, using `ufw`-like management command. diff --git a/TODO.md b/TODO.md index 6493f9c..c994cb1 100644 --- a/TODO.md +++ b/TODO.md @@ -7,5 +7,5 @@ - Options to serve custom http_error page. - Prometheus metrics - Documentation -- Client certificate +- Client certificate -> support intermediate certificate. Currently, only supports client certificates directly signed by root CA. - etc. diff --git a/config-example.toml b/config-example.toml index d8a9ecd..1310315 100644 --- a/config-example.toml +++ b/config-example.toml @@ -37,8 +37,9 @@ default_app = 'another_localhost' server_name = 'localhost' # Domain name # Optional: TLS setting. if https_port is specified and tls is true above, this must be given. -tls = { https_redirection = true, tls_cert_path = '/certs/localhost.crt', tls_cert_key_path = '/certs/localhost.key' } # for docker volume mounted certs -#tls = { https_redirection = true, tls_cert_path = './localhost.crt', tls_cert_key_path = './localhost.key' } # for local +tls = { https_redirection = true, tls_cert_path = '/certs/server.crt', tls_cert_key_path = '/certs/server.key' } # for docker volume mounted certs +#tls = { https_redirection = true, tls_cert_path = './server.crt', tls_cert_key_path = './server.key' } # for local +#tls = { https_redirection = true, tls_cert_path = './server.crt', tls_cert_key_path = './server.key', client_ca_cert_path = './client_cert.ca.crt' } # for local with client_cert ## TODO # allowhosts = ['127.0.0.1', '::1', '192.168.10.0/24'] # TODO diff --git a/example-certs/client.ca.crt b/example-certs/client.ca.crt new file mode 100644 index 0000000..0912812 --- /dev/null +++ b/example-certs/client.ca.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIB0DCCAXegAwIBAgIUeW2Vdqq6y9H0TFvClTW9YkwNlcMwCgYIKoZIzj0EAwIw +PjELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMQ0wCwYDVQQHDARDaHVvMRAw +DgYDVQQKDAdaZXR0YW50MB4XDTIyMTAwMzE0MTAxM1oXDTMyMDkzMDE0MTAxM1ow +PjELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMQ0wCwYDVQQHDARDaHVvMRAw +DgYDVQQKDAdaZXR0YW50MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuLuDStaw +D2jTIDUUFDnT2X01kNPAv2UK5QAzwjQPqu61koNHsRSe1GuhkC2jFolCaapOTWnJ +E7EeesOXtihI4KNTMFEwHQYDVR0OBBYEFBHfSGDdI6/YEwGrfPUuFAFxO5ejMB8G +A1UdIwQYMBaAFBHfSGDdI6/YEwGrfPUuFAFxO5ejMA8GA1UdEwEB/wQFMAMBAf8w +CgYIKoZIzj0EAwIDRwAwRAIgBfmM5qivBXQbLOH9+XI4D8ah0nrNjZvTYMS0V32d +888CIF33NCYYf+LB/edqkQeyU/Xuw4pOx72MD3GPJG1lYWkW +-----END CERTIFICATE----- diff --git a/example-certs/client.ca.key b/example-certs/client.ca.key new file mode 100644 index 0000000..4f8bd88 --- /dev/null +++ b/example-certs/client.ca.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgj6mxDmE5gPgJ5yQY +pJByP2UL67EwcBHJEVed77CHmRuhRANCAAS4u4NK1rAPaNMgNRQUOdPZfTWQ08C/ +ZQrlADPCNA+q7rWSg0exFJ7Ua6GQLaMWiUJpqk5NackTsR56w5e2KEjg +-----END PRIVATE KEY----- diff --git a/example-certs/client.crt b/example-certs/client.crt new file mode 100644 index 0000000..5756314 --- /dev/null +++ b/example-certs/client.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB3jCCAYSgAwIBAgIUJg74LEgATwFv6xAvbcILjHAx2k4wCgYIKoZIzj0EAwIw +PjELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMQ0wCwYDVQQHDARDaHVvMRAw +DgYDVQQKDAdaZXR0YW50MB4XDTIyMTAwMzE0MTEwM1oXDTIzMTAwMjE0MTEwM1ow +RDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMQ8wDQYDVQQHDAZOZXJpbWEx +FDASBgNVBAoMC1pldHRhbnQgRGV2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE +C8CxHow7SH0ZuRzFmquRl8lvwlwAuARZBPnT3u44BPwqDerI97555JkKquk35F6g +mFLPB28ljIvMicBDqjzS56NaMFgwHwYDVR0jBBgwFoAUEd9IYN0jr9gTAat89S4U +AXE7l6MwCQYDVR0TBAIwADALBgNVHQ8EBAMCBPAwHQYDVR0OBBYEFBuXDF84bxZt +qK7Y/ngSgm5JHgC2MAoGCCqGSM49BAMCA0gAMEUCIQD2yl6pYXuPnOSne4+yHOw3 +PdhPlyARxQqhrWM2LITP4AIgMv+exuURpaVj4ykhmlGS7ut05qZBpVgH4E+gamn2 +ZW8= +-----END CERTIFICATE----- diff --git a/example-certs/client.csr b/example-certs/client.csr new file mode 100644 index 0000000..750b84c --- /dev/null +++ b/example-certs/client.csr @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBADCBpgIBADBEMQswCQYDVQQGEwJKUDEOMAwGA1UECAwFVG9reW8xDzANBgNV +BAcMBk5lcmltYTEUMBIGA1UECgwLWmV0dGFudCBEZXYwWTATBgcqhkjOPQIBBggq +hkjOPQMBBwNCAAQLwLEejDtIfRm5HMWaq5GXyW/CXAC4BFkE+dPe7jgE/CoN6sj3 +vnnkmQqq6TfkXqCYUs8HbyWMi8yJwEOqPNLnoAAwCgYIKoZIzj0EAwIDSQAwRgIh +AJ0KUTO7x6YvavdLHllW9HWiSyeztquAQrqqHzO7sAHmAiEAitDM1Jv3xHbeK83R +ihWMGj/8y+QMeaL7cPBY/dfwIis= +-----END CERTIFICATE REQUEST----- diff --git a/example-certs/client.ext b/example-certs/client.ext new file mode 100644 index 0000000..e6d5f0f --- /dev/null +++ b/example-certs/client.ext @@ -0,0 +1,4 @@ +authorityKeyIdentifier=keyid:always +basicConstraints=CA:FALSE +keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment + diff --git a/example-certs/client.key b/example-certs/client.key new file mode 100644 index 0000000..602b5bf --- /dev/null +++ b/example-certs/client.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgQ70UolUBJK41lMWU +4fid9INB08+kWF5NgXmLr3VknvahRANCAAQLwLEejDtIfRm5HMWaq5GXyW/CXAC4 +BFkE+dPe7jgE/CoN6sj3vnnkmQqq6TfkXqCYUs8HbyWMi8yJwEOqPNLn +-----END PRIVATE KEY----- diff --git a/localhost.crt b/example-certs/server.crt similarity index 100% rename from localhost.crt rename to example-certs/server.crt diff --git a/localhost.key b/example-certs/server.key similarity index 100% rename from localhost.key rename to example-certs/server.key diff --git a/h3 b/h3 index 27ef38d..92b6726 160000 --- a/h3 +++ b/h3 @@ -1 +1 @@ -Subproject commit 27ef38d3940eb35205a6d8b1dd7c3f8d278d77b4 +Subproject commit 92b67269c2a24c0beeac0bbdd0c886dd886c3ad6 diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 3d042fe..83b47ce 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -5,7 +5,8 @@ use crate::{ log::*, utils::{BytesName, PathNameBytesExp, ServerNameBytesExp}, }; -use rustc_hash::FxHashMap as HashMap; +use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; +use rustls::OwnedTrustAnchor; use std::{ fs::File, io::{self, BufReader, Cursor, Read}, @@ -19,6 +20,7 @@ use tokio_rustls::rustls::{ }; pub use upstream::{ReverseProxy, Upstream, UpstreamGroup}; pub use upstream_opts::UpstreamOption; +use x509_parser::prelude::*; /// Struct serving information to route incoming connections, like server name to be handled and tls certs/keys settings. pub struct Backend { @@ -30,6 +32,7 @@ pub struct Backend { pub tls_cert_path: Option, pub tls_cert_key_path: Option, pub https_redirection: Option, + pub client_ca_cert_path: Option, } impl Backend { @@ -105,6 +108,66 @@ impl Backend { })?; Ok(CertifiedKey::new(certs, signing_key)) } + + fn read_client_ca_certs(&self) -> io::Result<(Vec, HashSet>)> { + debug!("Read CA certificate for client authentication"); + // Reads client certificate and returns client + let client_ca_cert_path = { + if let Some(c) = self.client_ca_cert_path.as_ref() { + c + } else { + return Err(io::Error::new(io::ErrorKind::Other, "Invalid certs and keys paths")); + } + }; + let certs: Vec<_> = { + let certs_path_str = client_ca_cert_path.display().to_string(); + let mut reader = BufReader::new(File::open(client_ca_cert_path).map_err(|e| { + io::Error::new( + e.kind(), + format!("Unable to load the client certificates [{}]: {}", certs_path_str, e), + ) + })?); + rustls_pemfile::certs(&mut reader) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Unable to parse the client certificates"))? + } + .drain(..) + .map(Certificate) + .collect(); + + let owned_trust_anchors: Vec<_> = certs + .iter() + .map(|v| { + let trust_anchor = tokio_rustls::webpki::TrustAnchor::try_from_cert_der(&v.0).unwrap(); + rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( + trust_anchor.subject, + trust_anchor.spki, + trust_anchor.name_constraints, + ) + }) + .collect(); + + let subject_key_identifiers: HashSet<_> = certs + .iter() + .filter_map(|v| { + // retrieve ca key id (subject key id) + let cert = parse_x509_certificate(&v.0).unwrap().1; + let subject_key_ids = cert + .iter_extensions() + .filter_map(|ext| match ext.parsed_extension() { + ParsedExtension::SubjectKeyIdentifier(skid) => Some(skid), + _ => None, + }) + .collect::>(); + if !subject_key_ids.is_empty() { + Some(subject_key_ids[0].0.to_owned()) + } else { + None + } + }) + .collect(); + + Ok((owned_trust_anchors, subject_key_identifiers)) + } } /// HashMap and some meta information for multiple Backend structs. @@ -113,12 +176,20 @@ pub struct Backends { pub default_server_name_bytes: Option, // for plaintext http } +pub type SniKeyIdsMap = HashMap>>; +pub struct ServerCrypto { + pub inner: Arc, + pub server_name_client_ca_keyids_map: Arc, +} + impl Backends { - pub async fn generate_server_crypto_with_cert_resolver(&self) -> Result { + pub async fn generate_server_crypto_with_cert_resolver(&self) -> Result { let mut resolver = ResolvesServerCertUsingSni::new(); + let mut client_ca_roots = rustls::RootCertStore::empty(); + let mut client_ca_key_ids: SniKeyIdsMap = HashMap::default(); // let mut cnt = 0; - for (_, backend) in self.apps.iter() { + for (server_name_bytes_exp, backend) in self.apps.iter() { if backend.tls_cert_key_path.is_some() && backend.tls_cert_path.is_some() { match backend.read_certs_and_key() { Ok(certified_key) => { @@ -137,14 +208,40 @@ impl Backends { warn!("Failed to add certificate for {}: {}", backend.server_name.as_str(), e); } } + // add client certificate if specified + if backend.client_ca_cert_path.is_some() { + match backend.read_client_ca_certs() { + Ok((owned_trust_anchors, subject_key_ids)) => { + // TODO: ここでSubject Key ID (CA Key ID)を記録しておく。認証後にpeer certificateのauthority key idとの一貫性をチェック。 + // v3 x509前提で特定のkey id extが入ってなければ使えない前提 + client_ca_roots.add_server_trust_anchors(owned_trust_anchors.into_iter()); + client_ca_key_ids.insert(server_name_bytes_exp.to_owned(), subject_key_ids); + } + Err(e) => { + warn!( + "Failed to add client ca certificate for {}: {}", + backend.server_name.as_str(), + e + ); + } + } + } } } // debug!("Load certificate chain for {} server_name's", cnt); + ////////////// + // TODO: Client Certs + let client_certs_verifier = rustls::server::AllowAnyAnonymousOrAuthenticatedClient::new(client_ca_roots); + // No ClientCert or WithClientCert + // let client_certs_verifier = rustls::server::AllowAnyAuthenticatedClient::new(client_ca_roots); + let mut server_config = ServerConfig::builder() .with_safe_defaults() - .with_no_client_auth() + // .with_no_client_auth() + .with_client_cert_verifier(client_certs_verifier) .with_cert_resolver(Arc::new(resolver)); + ////////////////////////////// #[cfg(feature = "http3")] { @@ -160,6 +257,9 @@ impl Backends { server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; } - Ok(server_config) + Ok(ServerCrypto { + inner: Arc::new(server_config), + server_name_client_ca_keyids_map: Arc::new(client_ca_key_ids), + }) } } diff --git a/src/config/parse.rs b/src/config/parse.rs index 8efc839..93406fd 100644 --- a/src/config/parse.rs +++ b/src/config/parse.rs @@ -18,12 +18,12 @@ pub fn parse_opts(globals: &mut Globals) -> std::result::Result<(), anyhow::Erro Arg::new("config_file") .long("config") .short('c') - .takes_value(true) + .value_name("FILE") .help("Configuration file path like \"./config.toml\""), ); let matches = options.get_matches(); - let config = if let Some(config_file_path) = matches.value_of("config_file") { + let config = if let Some(config_file_path) = matches.get_one::("config_file") { ConfigToml::new(config_file_path)? } else { // Default config Toml @@ -93,9 +93,9 @@ pub fn parse_opts(globals: &mut Globals) -> std::result::Result<(), anyhow::Erro let server_name_string = app.server_name.as_ref().unwrap(); // TLS settings - let (tls_cert_path, tls_cert_key_path, https_redirection) = if app.tls.is_none() { + let (tls_cert_path, tls_cert_key_path, https_redirection, client_ca_cert_path) = if app.tls.is_none() { ensure!(globals.http_port.is_some(), "Required HTTP port"); - (None, None, None) + (None, None, None, None) } else { let tls = app.tls.as_ref().unwrap(); ensure!(tls.tls_cert_key_path.is_some() && tls.tls_cert_path.is_some()); @@ -109,6 +109,7 @@ pub fn parse_opts(globals: &mut Globals) -> std::result::Result<(), anyhow::Erro ensure!(globals.https_port.is_some()); // only when both https ports are configured. tls.https_redirection }, + tls.client_ca_cert_path.as_ref().map(PathBuf::from), ) }; if globals.http_port.is_none() { @@ -130,6 +131,7 @@ pub fn parse_opts(globals: &mut Globals) -> std::result::Result<(), anyhow::Erro tls_cert_path, tls_cert_key_path, https_redirection, + client_ca_cert_path, }, ); info!("Registering application: {} ({})", app_name, server_name_string); diff --git a/src/config/toml.rs b/src/config/toml.rs index 5f31ba6..258094e 100644 --- a/src/config/toml.rs +++ b/src/config/toml.rs @@ -47,6 +47,7 @@ pub struct TlsOption { pub tls_cert_path: Option, pub tls_cert_key_path: Option, pub https_redirection: Option, + pub client_ca_cert_path: Option, } #[derive(Deserialize, Debug, Default)] diff --git a/src/proxy/mod.rs b/src/proxy/mod.rs index 3e21ea6..82d775b 100644 --- a/src/proxy/mod.rs +++ b/src/proxy/mod.rs @@ -1,3 +1,4 @@ +mod proxy_client_cert; #[cfg(feature = "http3")] mod proxy_h3; mod proxy_main; diff --git a/src/proxy/proxy_client_cert.rs b/src/proxy/proxy_client_cert.rs new file mode 100644 index 0000000..14459f5 --- /dev/null +++ b/src/proxy/proxy_client_cert.rs @@ -0,0 +1,45 @@ +use crate::{error::*, log::*}; +use rustc_hash::FxHashSet as HashSet; +use rustls::Certificate; +use x509_parser::extensions::ParsedExtension; +use x509_parser::prelude::*; + +// TODO: consider move this function to the layer of handle_request (L7) to return 403 +pub(super) fn check_client_authentication( + client_certs: Option<&[Certificate]>, + client_certs_setting_for_sni: Option<&HashSet>>, +) -> Result<()> { + if let Some(client_ca_keyids_set) = client_certs_setting_for_sni { + if let Some(client_certs) = client_certs { + debug!("Incoming TLS client is (temporarily) authenticated via client cert"); + // Check client certificate key ids + + let mut client_certs_parsed_iter = client_certs.iter().filter_map(|d| parse_x509_certificate(&d.0).ok()); + let match_server_crypto_and_client_cert = client_certs_parsed_iter.any(|c| { + let mut filtered = c.1.iter_extensions().filter_map(|e| { + if let ParsedExtension::AuthorityKeyIdentifier(key_id) = e.parsed_extension() { + key_id.key_identifier.as_ref() + } else { + None + } + }); + + filtered.any(|id| client_ca_keyids_set.contains(id.0)) + }); + if !match_server_crypto_and_client_cert { + // TODO: return 403 here + error!("Inconsistent client certificate for given server name"); + return Err(RpxyError::Proxy( + "Inconsistent client certificate for given server name".to_string(), + )); + } + } else { + // TODO: return 403 here + error!("Client certificate is needed for given server name"); + return Err(RpxyError::Proxy( + "Client certificate is needed for given server name".to_string(), + )); + } + } + Ok(()) +} diff --git a/src/proxy/proxy_h3.rs b/src/proxy/proxy_h3.rs index c7ad3d3..63369cf 100644 --- a/src/proxy/proxy_h3.rs +++ b/src/proxy/proxy_h3.rs @@ -1,9 +1,9 @@ -use super::Proxy; -use crate::{error::*, log::*, utils::ServerNameBytesExp}; +use super::{proxy_client_cert::check_client_authentication, Proxy}; +use crate::{backend::SniKeyIdsMap, error::*, log::*, utils::ServerNameBytesExp}; use bytes::{Buf, Bytes}; use h3::{quic::BidiStream, server::RequestStream}; use hyper::{client::connect::Connect, Body, Request, Response}; -use std::net::SocketAddr; +use std::{net::SocketAddr, sync::Arc}; use tokio::time::{timeout, Duration}; impl Proxy @@ -14,11 +14,28 @@ where self, conn: quinn::Connecting, tls_server_name: ServerNameBytesExp, + sni_cc_map: Arc, ) -> Result<()> { let client_addr = conn.remote_address(); match conn.await { Ok(new_conn) => { + // Check client certificates + // TODO: consider move this function to the layer of handle_request (L7) to return 403 + let cc = { + // https://docs.rs/quinn/latest/quinn/struct.Connection.html + let client_certs_setting_for_sni = sni_cc_map.get(&tls_server_name); + let client_certs = match new_conn.connection.peer_identity() { + Some(peer_identity) => peer_identity + .downcast::>() + .ok() + .map(|p| p.into_iter().collect::>()), + None => None, + }; + (client_certs, client_certs_setting_for_sni) + }; + check_client_authentication(cc.0.as_ref().map(AsRef::as_ref), cc.1)?; + let mut h3_conn = h3::server::Connection::<_, bytes::Bytes>::new(h3_quinn::Connection::new(new_conn)).await?; info!( "QUIC/HTTP3 connection established from {:?} {:?}", diff --git a/src/proxy/proxy_tls.rs b/src/proxy/proxy_tls.rs index 9a49df6..8a71491 100644 --- a/src/proxy/proxy_tls.rs +++ b/src/proxy/proxy_tls.rs @@ -1,5 +1,14 @@ -use super::proxy_main::{LocalExecutor, Proxy}; -use crate::{constants::*, error::*, log::*, utils::BytesName}; +use super::{ + proxy_client_cert::check_client_authentication, + proxy_main::{LocalExecutor, Proxy}, +}; +use crate::{ + backend::{ServerCrypto, SniKeyIdsMap}, + constants::*, + error::*, + log::*, + utils::BytesName, +}; use hyper::{client::connect::Connect, server::conn::Http}; use rustls::ServerConfig; use std::sync::Arc; @@ -19,7 +28,7 @@ impl Proxy where T: Connect + Clone + Sync + Send + 'static, { - async fn cert_service(&self, server_crypto_tx: watch::Sender>>) { + async fn cert_service(&self, server_crypto_tx: watch::Sender>>) { info!("Start cert watch service"); loop { if let Ok(server_crypto) = self.globals.backends.generate_server_crypto_with_cert_resolver().await { @@ -38,21 +47,23 @@ where async fn listener_service( &self, server: Http, - mut server_crypto_rx: watch::Receiver>>, + mut server_crypto_rx: watch::Receiver>>, ) -> Result<()> { let tcp_listener = TcpListener::bind(&self.listening_on).await?; info!("Start TCP proxy serving with HTTPS request for configured host names"); // let mut server_crypto: Option> = None; let mut tls_acceptor: Option = None; + let mut sni_client_ca_keyid_map: Option> = None; loop { tokio::select! { tcp_cnx = tcp_listener.accept() => { - if tls_acceptor.is_none() || tcp_cnx.is_err() { + if tls_acceptor.is_none() || tcp_cnx.is_err() || sni_client_ca_keyid_map.is_none() { continue; } let (raw_stream, client_addr) = tcp_cnx.unwrap(); let acceptor = tls_acceptor.clone().unwrap(); + let sni_cc_map = sni_client_ca_keyid_map.clone().unwrap(); let server_clone = server.clone(); let self_inner = self.clone(); @@ -70,6 +81,13 @@ where if server_name.is_none(){ Err(RpxyError::Proxy("No SNI is given".to_string())) } else { + ////////////////////////////// + // Check client certificate + // TODO: consider move this function to the layer of handle_request (L7) to return 403 + let client_certs = conn.peer_certificates(); + let client_certs_setting_for_sni = sni_cc_map.get(&server_name.clone().unwrap()); + check_client_authentication(client_certs, client_certs_setting_for_sni)?; + ////////////////////////////// // this immediately spawns another future to actually handle stream. so it is okay to introduce timeout for handshake. self_inner.client_serve(stream, server_clone, client_addr, server_name); // TODO: don't want to pass copied value... Ok(()) @@ -95,7 +113,8 @@ where break; } let server_crypto = server_crypto_rx.borrow().clone().unwrap(); - tls_acceptor = Some(TlsAcceptor::from(server_crypto)); + tls_acceptor = Some(TlsAcceptor::from(server_crypto.inner.clone())); + sni_client_ca_keyid_map = Some(server_crypto.server_name_client_ca_keyids_map.clone()); } else => break } @@ -104,10 +123,10 @@ where } #[cfg(feature = "http3")] - async fn listener_service_h3(&self, mut server_crypto_rx: watch::Receiver>>) -> Result<()> { + async fn listener_service_h3(&self, mut server_crypto_rx: watch::Receiver>>) -> Result<()> { info!("Start UDP proxy serving with HTTP/3 request for configured host names"); // first set as null config server - let server_crypto = ServerConfig::builder() + let rustls_server_config = ServerConfig::builder() .with_safe_defaults() .with_no_client_auth() .with_cert_resolver(Arc::new(tokio_rustls::rustls::server::ResolvesServerCertUsingSni::new())); @@ -117,16 +136,17 @@ where .max_concurrent_bidi_streams(self.globals.h3_max_concurrent_bidistream) .max_concurrent_uni_streams(self.globals.h3_max_concurrent_unistream); - let mut server_config_h3 = QuicServerConfig::with_crypto(Arc::new(server_crypto)); + let mut server_config_h3 = QuicServerConfig::with_crypto(Arc::new(rustls_server_config)); server_config_h3.transport = Arc::new(transport_config_quic); server_config_h3.concurrent_connections(self.globals.h3_max_concurrent_connections); let (endpoint, mut incoming) = Endpoint::server(server_config_h3, self.listening_on)?; - let mut server_crypto: Option> = None; + let mut server_crypto: Option> = None; + let mut sni_client_ca_keyid_map: Option> = None; loop { tokio::select! { new_conn = incoming.next() => { - if server_crypto.is_none() || new_conn.is_none() { + if server_crypto.is_none() || new_conn.is_none() || sni_client_ca_keyid_map.is_none() { continue; } let mut conn = new_conn.unwrap(); @@ -152,7 +172,7 @@ where ); // TODO: server_nameをここで出してどんどん深く投げていくのは効率が悪い。connecting -> connectionsの後でいいのでは? // TODO: 通常のTLSと同じenumか何かにまとめたい - let fut = self.clone().connection_serve_h3(conn, new_server_name); + let fut = self.clone().connection_serve_h3(conn, new_server_name, sni_client_ca_keyid_map.clone().unwrap()); self.globals.runtime_handle.spawn(async move { // Timeout is based on underlying quic if let Err(e) = fut.await { @@ -166,7 +186,8 @@ where } server_crypto = server_crypto_rx.borrow().clone(); if server_crypto.is_some(){ - endpoint.set_server_config(Some(QuicServerConfig::with_crypto(server_crypto.clone().unwrap()))); + endpoint.set_server_config(Some(QuicServerConfig::with_crypto(server_crypto.clone().unwrap().inner.clone()))); + sni_client_ca_keyid_map = Some(server_crypto.clone().unwrap().server_name_client_ca_keyids_map.clone()); } } else => break @@ -177,7 +198,7 @@ where } pub async fn start_with_tls(self, server: Http) -> Result<()> { - let (tx, rx) = watch::channel::>>(None); + let (tx, rx) = watch::channel::>>(None); #[cfg(not(feature = "http3"))] { select! { diff --git a/src/utils/bytes_name.rs b/src/utils/bytes_name.rs index 0858f48..80bc0f0 100644 --- a/src/utils/bytes_name.rs +++ b/src/utils/bytes_name.rs @@ -20,7 +20,7 @@ impl PathNameBytesExp { where I: std::slice::SliceIndex<[u8]>, { - (&self.0).get(index) + self.0.get(index) } pub fn starts_with(&self, needle: &Self) -> bool { self.0.starts_with(&needle.0) From 333083e26469d8a64a738beb9cc6a0353492a778 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Fri, 7 Oct 2022 23:48:41 +0900 Subject: [PATCH 02/13] deps --- h3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h3 b/h3 index 92b6726..90ef1f7 160000 --- a/h3 +++ b/h3 @@ -1 +1 @@ -Subproject commit 92b67269c2a24c0beeac0bbdd0c886dd886c3ad6 +Subproject commit 90ef1f7183640f3bc0779fd598e4dd0b621d0753 From b479a38166b501b78d1aa8bdf6a7828ea3e1be98 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Fri, 7 Oct 2022 23:49:06 +0900 Subject: [PATCH 03/13] deps --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d828e7d..965c9f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ http3 = ["quinn", "h3", "h3-quinn"] [dependencies] env_logger = "0.9.1" anyhow = "1.0.65" -clap = { version = "4.0.4", features = ["std", "cargo", "wrap_help"] } +clap = { version = "4.0.10", features = ["std", "cargo", "wrap_help"] } futures = { version = "0.3.24", features = ["alloc", "async-await"] } hyper = { version = "0.14.20", default-features = false, features = [ "server", From 62fe6a0b49cc1d6b318817a31bf95a4815a67ef3 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Tue, 11 Oct 2022 15:35:46 +0900 Subject: [PATCH 04/13] deps and refactor --- h3 | 2 +- src/backend/mod.rs | 2 +- src/proxy/proxy_client_cert.rs | 59 ++++++++++++++++++++-------------- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/h3 b/h3 index 90ef1f7..720da6d 160000 --- a/h3 +++ b/h3 @@ -1 +1 @@ -Subproject commit 90ef1f7183640f3bc0779fd598e4dd0b621d0753 +Subproject commit 720da6d652c41a5ac2b56c9bf602b756bd0032d3 diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 83b47ce..c62b27a 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -110,7 +110,7 @@ impl Backend { } fn read_client_ca_certs(&self) -> io::Result<(Vec, HashSet>)> { - debug!("Read CA certificate for client authentication"); + debug!("Read CA certificates for client authentication"); // Reads client certificate and returns client let client_ca_cert_path = { if let Some(c) = self.client_ca_cert_path.as_ref() { diff --git a/src/proxy/proxy_client_cert.rs b/src/proxy/proxy_client_cert.rs index 14459f5..a0f5faf 100644 --- a/src/proxy/proxy_client_cert.rs +++ b/src/proxy/proxy_client_cert.rs @@ -9,37 +9,48 @@ pub(super) fn check_client_authentication( client_certs: Option<&[Certificate]>, client_certs_setting_for_sni: Option<&HashSet>>, ) -> Result<()> { - if let Some(client_ca_keyids_set) = client_certs_setting_for_sni { - if let Some(client_certs) = client_certs { + let client_ca_keyids_set = match client_certs_setting_for_sni { + Some(c) => c, + None => { + // No client cert settings for given server name + return Ok(()); + } + }; + + let client_certs = match client_certs { + Some(c) => { debug!("Incoming TLS client is (temporarily) authenticated via client cert"); - // Check client certificate key ids - - let mut client_certs_parsed_iter = client_certs.iter().filter_map(|d| parse_x509_certificate(&d.0).ok()); - let match_server_crypto_and_client_cert = client_certs_parsed_iter.any(|c| { - let mut filtered = c.1.iter_extensions().filter_map(|e| { - if let ParsedExtension::AuthorityKeyIdentifier(key_id) = e.parsed_extension() { - key_id.key_identifier.as_ref() - } else { - None - } - }); - - filtered.any(|id| client_ca_keyids_set.contains(id.0)) - }); - if !match_server_crypto_and_client_cert { - // TODO: return 403 here - error!("Inconsistent client certificate for given server name"); - return Err(RpxyError::Proxy( - "Inconsistent client certificate for given server name".to_string(), - )); - } - } else { + c + } + None => { // TODO: return 403 here error!("Client certificate is needed for given server name"); return Err(RpxyError::Proxy( "Client certificate is needed for given server name".to_string(), )); } + }; + + // Check client certificate key ids + let mut client_certs_parsed_iter = client_certs.iter().filter_map(|d| parse_x509_certificate(&d.0).ok()); + let match_server_crypto_and_client_cert = client_certs_parsed_iter.any(|c| { + let mut filtered = c.1.iter_extensions().filter_map(|e| { + if let ParsedExtension::AuthorityKeyIdentifier(key_id) = e.parsed_extension() { + key_id.key_identifier.as_ref() + } else { + None + } + }); + filtered.any(|id| client_ca_keyids_set.contains(id.0)) + }); + + if !match_server_crypto_and_client_cert { + // TODO: return 403 here + error!("Inconsistent client certificate for given server name"); + return Err(RpxyError::Proxy( + "Inconsistent client certificate for given server name".to_string(), + )); } + Ok(()) } From 8115bbf8661d14e0d819fc65636f6ad69938ca1c Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Tue, 11 Oct 2022 16:17:50 +0900 Subject: [PATCH 05/13] refactor --- Cargo.toml | 2 +- src/proxy/proxy_client_cert.rs | 4 +-- src/proxy/proxy_tls.rs | 62 +++++++++++++++++++--------------- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 965c9f9..7f16166 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ http3 = ["quinn", "h3", "h3-quinn"] [dependencies] env_logger = "0.9.1" anyhow = "1.0.65" -clap = { version = "4.0.10", features = ["std", "cargo", "wrap_help"] } +clap = { version = "4.0.12", features = ["std", "cargo", "wrap_help"] } futures = { version = "0.3.24", features = ["alloc", "async-await"] } hyper = { version = "0.14.20", default-features = false, features = [ "server", diff --git a/src/proxy/proxy_client_cert.rs b/src/proxy/proxy_client_cert.rs index a0f5faf..c77b0f9 100644 --- a/src/proxy/proxy_client_cert.rs +++ b/src/proxy/proxy_client_cert.rs @@ -46,9 +46,9 @@ pub(super) fn check_client_authentication( if !match_server_crypto_and_client_cert { // TODO: return 403 here - error!("Inconsistent client certificate for given server name"); + error!("Inconsistent client certificate was provided for SNI"); return Err(RpxyError::Proxy( - "Inconsistent client certificate for given server name".to_string(), + "Inconsistent client certificate was provided for SNI".to_string(), )); } diff --git a/src/proxy/proxy_tls.rs b/src/proxy/proxy_tls.rs index 8a71491..153c6cd 100644 --- a/src/proxy/proxy_tls.rs +++ b/src/proxy/proxy_tls.rs @@ -70,40 +70,46 @@ where // spawns async handshake to avoid blocking thread by sequential handshake. let handshake_fut = async move { // timeout is introduced to avoid get stuck here. - match timeout(Duration::from_secs(TLS_HANDSHAKE_TIMEOUT_SEC), acceptor.accept(raw_stream)).await { - Ok(x) => match x { - Ok(stream) => { - // Retrieve SNI - let (_, conn) = stream.get_ref(); - let server_name = conn.sni_hostname(); - debug!("HTTP/2 or 1.1: SNI in ClientHello: {:?}", server_name); - let server_name = server_name.map_or_else(|| None, |v| Some(v.to_server_name_vec())); - if server_name.is_none(){ - Err(RpxyError::Proxy("No SNI is given".to_string())) - } else { - ////////////////////////////// - // Check client certificate - // TODO: consider move this function to the layer of handle_request (L7) to return 403 - let client_certs = conn.peer_certificates(); - let client_certs_setting_for_sni = sni_cc_map.get(&server_name.clone().unwrap()); - check_client_authentication(client_certs, client_certs_setting_for_sni)?; - ////////////////////////////// - // this immediately spawns another future to actually handle stream. so it is okay to introduce timeout for handshake. - self_inner.client_serve(stream, server_clone, client_addr, server_name); // TODO: don't want to pass copied value... - Ok(()) - } - }, - Err(e) => { - Err(RpxyError::Proxy(format!("Failed to handshake TLS: {}", e))) - } - }, + let accepted = match timeout(Duration::from_secs(TLS_HANDSHAKE_TIMEOUT_SEC), acceptor.accept(raw_stream)).await { + Ok(a) => a, Err(e) => { - Err(RpxyError::Proxy(format!("Timeout to handshake TLS: {}", e))) + return Err(RpxyError::Proxy(format!("Timeout to handshake TLS: {}", e))); } + }; + let stream = match accepted { + Ok(s) => s, + Err(e) => { + return Err(RpxyError::Proxy(format!("Failed to handshake TLS: {}", e))); + } + }; + // Retrieve SNI + let (_, conn) = stream.get_ref(); + let server_name = conn.sni_hostname(); + debug!("HTTP/2 or 1.1: SNI in ClientHello: {:?}", server_name); + let server_name = server_name.map_or_else(|| None, |v| Some(v.to_server_name_vec())); + if server_name.is_none(){ + // conn.send_close_notify(); + Err(RpxyError::Proxy("No SNI is given".to_string())) + } else { + ////////////////////////////// + // Check client certificate + // TODO: consider move this function to the layer of handle_request (L7) to return 403 + let client_certs = conn.peer_certificates(); + let client_certs_setting_for_sni = sni_cc_map.get(&server_name.clone().unwrap()); + check_client_authentication(client_certs, client_certs_setting_for_sni)?; + // if let Err(e) = check_client_authentication(client_certs, client_certs_setting_for_sni){ + // conn.send_close_notify(); + // return Err(e); + // } + ////////////////////////////// + // this immediately spawns another future to actually handle stream. so it is okay to introduce timeout for handshake. + self_inner.client_serve(stream, server_clone, client_addr, server_name); // TODO: don't want to pass copied value... + Ok(()) } }; self.globals.runtime_handle.spawn( async move { if let Err(e) = handshake_fut.await { + error!("{}", e); } }); From a030e1186161c3f231841d38c9338940a7ba28c9 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Tue, 11 Oct 2022 18:44:54 +0900 Subject: [PATCH 06/13] refactor. todo: move the consistency check between client certificate and sni to http layer and emit 400 --- src/proxy/proxy_tls.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/proxy/proxy_tls.rs b/src/proxy/proxy_tls.rs index 153c6cd..6e3200c 100644 --- a/src/proxy/proxy_tls.rs +++ b/src/proxy/proxy_tls.rs @@ -88,7 +88,6 @@ where debug!("HTTP/2 or 1.1: SNI in ClientHello: {:?}", server_name); let server_name = server_name.map_or_else(|| None, |v| Some(v.to_server_name_vec())); if server_name.is_none(){ - // conn.send_close_notify(); Err(RpxyError::Proxy("No SNI is given".to_string())) } else { ////////////////////////////// @@ -97,10 +96,6 @@ where let client_certs = conn.peer_certificates(); let client_certs_setting_for_sni = sni_cc_map.get(&server_name.clone().unwrap()); check_client_authentication(client_certs, client_certs_setting_for_sni)?; - // if let Err(e) = check_client_authentication(client_certs, client_certs_setting_for_sni){ - // conn.send_close_notify(); - // return Err(e); - // } ////////////////////////////// // this immediately spawns another future to actually handle stream. so it is okay to introduce timeout for handshake. self_inner.client_serve(stream, server_clone, client_addr, server_name); // TODO: don't want to pass copied value... @@ -109,7 +104,6 @@ where }; self.globals.runtime_handle.spawn( async move { if let Err(e) = handshake_fut.await { - error!("{}", e); } }); From 87b6c8121176092173b703419296943362cc18de Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Tue, 11 Oct 2022 18:59:29 +0900 Subject: [PATCH 07/13] add client.ca.ext --- example-certs/client.ca.ext | 3 +++ example-certs/client_pass=foobar.p12 | Bin 0 -> 1648 bytes 2 files changed, 3 insertions(+) create mode 100644 example-certs/client.ca.ext create mode 100644 example-certs/client_pass=foobar.p12 diff --git a/example-certs/client.ca.ext b/example-certs/client.ca.ext new file mode 100644 index 0000000..5f04aff --- /dev/null +++ b/example-certs/client.ca.ext @@ -0,0 +1,3 @@ +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer +basicConstraints=critical,CA:TRUE diff --git a/example-certs/client_pass=foobar.p12 b/example-certs/client_pass=foobar.p12 new file mode 100644 index 0000000000000000000000000000000000000000..37a3a4ea33f0a17602f3595da00cbe586b60c7f8 GIT binary patch literal 1648 zcmai!dpOgJAIJCIzWZ)+TUgqYgqT~_SAH%dzh5YuvaDF5lU$2&$|IpLEV(x2*F`iA z&O}9WXkotP8470(fKPsr04c4wl)kpxbE)_o-0QUZF(5mo$*z|{0 zfptJ^?g{0Y=ZdSJkVq^7R)Mg8eM2gt5%5+>$v=$>grGpc48&P0X%r{7gw^I48e(JL zb_$c(;2!t9-DS%cHQegS24nRr?y)rF(=)JS`Rv2mO4rRQIXglJ{`;u5+A58X*j`e3 z=A-N2dHv)NR0lJE;DB0P3Zwn4F?+USs3f2UXOfk5ALU}Pme|=|K*`W@_Bm^IUA=W^ zV1g8W*);5PxYYG!%wgmEb>wDKX}YJ=b@NrUzR&)VW|u&uMTEz-%OPC}dieHV(`$Pq zFATRe9e}+|QfIpli{y48q5)}@;SE<#c^t0I_?gCmoxJ6TFEUHDeV>ViV8&Riq%m8( zW~5!|qUM6%+G;$}n7@*Di%YLG0xXfi--z+9YbWE5L7nlZYBron^7N9i&@H^7ifHR+ z0!4*#uAM9vhjDBsFF`kruMt*s(1F5_>hisewgx!~SvupXoS zC9Cq=sM*%{2FrxRagDlg!BkHP&c-X7YPXg0AORX(bh8aO@;GjhG^Z(klYP)puW#Gr zoR;-rqtG%z6mPRqs$Sl{$V}gj0Fv|SeOrjL-ES{sEy%C#E2~fAFufxHeT_=DNfZW8 zR&S>GrQF)AEe?{17achtBC#L6@@T6JdxPP_bbemD2+F;8LRF&JY0|HJIz!Do`i~%= zC=LN}_K%bGrdxuAMU2tuCbOxi#GyN~JfW@qiT`{EjGWiPDpY5P+w|DnLpAMb%)MVG z{Dsa1L-ko(aF>!oH<&(BTkPW(xaH!ze{pWP$ZS&f{&Y(*$Q3CzS>f3-H3DUn!>eiC zBU)k)nor|F;9PuU)l39{@W>^ly)d-GJHdnv9?tH$@r z)ow=2d-nFKED}#)lsN8sWqFGoZ=+iBCsMjz1Bo>eW*0E;|9YQW+XD6W*u8$w{>DJ` z%glM*R0Sbsqm%$;=0z@8rJ**|YRmDz>5EbRD(Z}eC)lEcUx88XAfDxuv7gRIWnIu* zfSNJo=B0%;tclOAtwb9xY9u5|2O2&n!T9$ga}Y&mKzTxSx-IfX0Lb(xgxP@8cOLx- zIA9mZ21x6%+4>P7<^Qu!WdndLPML#Kq<@Dq4W}(WG@2~uZ*jKs%3A7kpJJ!%1`}Nd z;U7I<%2iFqjuRp>!=27+Uc9iW>q)SEIj1`zunT4W>f~K%yd2Y~eCkX8^ugz2WeMN3 zq62dSoaaBgWxfy{9Y<* Date: Tue, 11 Oct 2022 19:01:43 +0900 Subject: [PATCH 08/13] fix $ -> % --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5d2c6eb..a986892 100644 --- a/README.md +++ b/README.md @@ -235,7 +235,7 @@ If you obtain certificates and private keys from [Let's Encrypt](https://letsenc The easiest way is to use `openssl` by ```bash -$ openssl pkcs8 -topk8 -nocrypt \ +% openssl pkcs8 -topk8 -nocrypt \ -in yoru_domain_from_le.key \ -inform PEM \ -out your_domain_pkcs8.key.pem \ @@ -249,9 +249,9 @@ First, you need to prepare a CA certificate used to verify client certificate. I 1. Generate CA key of `secp256v1`, CSR, and then generate CA certificate that will be set for `tls.client_ca_cert_path` for each server app in `config.toml`. ```bash - $ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out client.ca.key + % openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out client.ca.key - $ openssl req -new -key client.ca.key -out client.ca.csr + % openssl req -new -key client.ca.key -out client.ca.csr ... ----- Country Name (2 letter code) []: ... @@ -262,15 +262,15 @@ First, you need to prepare a CA certificate used to verify client certificate. I Common Name (eg, fully qualified host name) []: Email Address []: ... - $ openssl x509 -req -days 3650 -sha256 -in client.ca.csr -signkey client.ca.key -out client.ca.crt -extfile client.ca.ext + % openssl x509 -req -days 3650 -sha256 -in client.ca.csr -signkey client.ca.key -out client.ca.crt -extfile client.ca.ext ``` 2. Generate a client key of `secp256v1` and certificate signed by CA key. ```bash - $ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out client.key + % openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -out client.key - $ openssl req -new -key client.key -out client.csr + % openssl req -new -key client.key -out client.csr ... ----- Country Name (2 letter code) []: @@ -281,13 +281,13 @@ First, you need to prepare a CA certificate used to verify client certificate. I Common Name (eg, fully qualified host name) []: Email Address []: - $ openssl x509 -req -days 365 -sha256 -in client.csr -CA client.ca.crt -CAkey client.ca.key -CAcreateserial -out client.crt -extfile client.ext + % openssl x509 -req -days 365 -sha256 -in client.csr -CA client.ca.crt -CAkey client.ca.key -CAcreateserial -out client.crt -extfile client.ext ``` Now you have a client key `client.key` and certificate `client.crt` (version 3). `p12` file can be retrieved as ```bash - $ openssl pkcs12 -export -inkey client.key -in client.crt -certfile client.ca.crt -out client.pfx + % openssl pkcs12 -export -inkey client.key -in client.crt -certfile client.ca.crt -out client.pfx ``` All of sample certificate files are found in `./example-certs/` directory. From c765da33db8192a16520bf38c78a2e4961c946cc Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Wed, 12 Oct 2022 15:16:40 +0900 Subject: [PATCH 09/13] update response to invalid client certificate or no client certificate --- Cargo.toml | 2 +- src/error.rs | 9 +++++++++ src/handler/handler_main.rs | 15 +++++++++++++++ src/proxy/proxy_client_cert.rs | 12 +++++------- src/proxy/proxy_h3.rs | 15 ++++++++++++--- src/proxy/proxy_main.rs | 6 +++++- src/proxy/proxy_tls.rs | 9 +++++---- 7 files changed, 52 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7f16166..b64f78e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ http3 = ["quinn", "h3", "h3-quinn"] [dependencies] env_logger = "0.9.1" anyhow = "1.0.65" -clap = { version = "4.0.12", features = ["std", "cargo", "wrap_help"] } +clap = { version = "4.0.13", features = ["std", "cargo", "wrap_help"] } futures = { version = "0.3.24", features = ["alloc", "async-await"] } hyper = { version = "0.14.20", default-features = false, features = [ "server", diff --git a/src/error.rs b/src/error.rs index 3771627..7a39c9e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -43,3 +43,12 @@ pub enum RpxyError { #[error(transparent)] Other(#[from] anyhow::Error), } + +#[derive(Debug, Error, Clone)] +pub enum ClientCertsError { + #[error("TLS Client Certificate is Required for Given SNI: {0}")] + ClientCertRequired(String), + + #[error("Inconsistent TLS Client Certificate for Given SNI: {0}")] + InconsistentClientCert(String), +} diff --git a/src/handler/handler_main.rs b/src/handler/handler_main.rs index b6d0146..251f898 100644 --- a/src/handler/handler_main.rs +++ b/src/handler/handler_main.rs @@ -35,11 +35,26 @@ where listen_addr: SocketAddr, tls_enabled: bool, tls_server_name: Option, + tls_client_auth_result: Option>, ) -> Result> { //////// let mut log_data = MessageLog::from(&req); log_data.client_addr(&client_addr); ////// + // First check client auth result if exist + if let Some(res) = tls_client_auth_result { + match res { + Err(ClientCertsError::ClientCertRequired(_)) => { + // Client cert is required for the TLS server name + return self.return_with_error_log(StatusCode::FORBIDDEN, &mut log_data); + } + Err(ClientCertsError::InconsistentClientCert(_)) => { + // Client cert provided was inconsistent to the TLS server name + return self.return_with_error_log(StatusCode::BAD_REQUEST, &mut log_data); + } + _ => (), + } + } // Here we start to handle with server_name let server_name = if let Ok(v) = req.parse_host() { diff --git a/src/proxy/proxy_client_cert.rs b/src/proxy/proxy_client_cert.rs index c77b0f9..aa212c1 100644 --- a/src/proxy/proxy_client_cert.rs +++ b/src/proxy/proxy_client_cert.rs @@ -7,9 +7,9 @@ use x509_parser::prelude::*; // TODO: consider move this function to the layer of handle_request (L7) to return 403 pub(super) fn check_client_authentication( client_certs: Option<&[Certificate]>, - client_certs_setting_for_sni: Option<&HashSet>>, -) -> Result<()> { - let client_ca_keyids_set = match client_certs_setting_for_sni { + client_ca_keyids_set_for_sni: Option<&HashSet>>, +) -> std::result::Result<(), ClientCertsError> { + let client_ca_keyids_set = match client_ca_keyids_set_for_sni { Some(c) => c, None => { // No client cert settings for given server name @@ -23,9 +23,8 @@ pub(super) fn check_client_authentication( c } None => { - // TODO: return 403 here error!("Client certificate is needed for given server name"); - return Err(RpxyError::Proxy( + return Err(ClientCertsError::ClientCertRequired( "Client certificate is needed for given server name".to_string(), )); } @@ -45,9 +44,8 @@ pub(super) fn check_client_authentication( }); if !match_server_crypto_and_client_cert { - // TODO: return 403 here error!("Inconsistent client certificate was provided for SNI"); - return Err(RpxyError::Proxy( + return Err(ClientCertsError::InconsistentClientCert( "Inconsistent client certificate was provided for SNI".to_string(), )); } diff --git a/src/proxy/proxy_h3.rs b/src/proxy/proxy_h3.rs index 63369cf..d0aaf5d 100644 --- a/src/proxy/proxy_h3.rs +++ b/src/proxy/proxy_h3.rs @@ -21,7 +21,6 @@ where match conn.await { Ok(new_conn) => { // Check client certificates - // TODO: consider move this function to the layer of handle_request (L7) to return 403 let cc = { // https://docs.rs/quinn/latest/quinn/struct.Connection.html let client_certs_setting_for_sni = sni_cc_map.get(&tls_server_name); @@ -34,7 +33,8 @@ where }; (client_certs, client_certs_setting_for_sni) }; - check_client_authentication(cc.0.as_ref().map(AsRef::as_ref), cc.1)?; + // TODO: pass this value to the layer of handle_request (L7) to return 403 + let tls_client_auth_result = check_client_authentication(cc.0.as_ref().map(AsRef::as_ref), cc.1); let mut h3_conn = h3::server::Connection::<_, bytes::Bytes>::new(h3_quinn::Connection::new(new_conn)).await?; info!( @@ -61,10 +61,17 @@ where let self_inner = self.clone(); let tls_server_name_inner = tls_server_name.clone(); + let tls_client_auth_result_inner = tls_client_auth_result.clone(); self.globals.runtime_handle.spawn(async move { if let Err(e) = timeout( self_inner.globals.proxy_timeout + Duration::from_secs(1), // timeout per stream are considered as same as one in http2 - self_inner.stream_serve_h3(req, stream, client_addr, tls_server_name_inner), + self_inner.stream_serve_h3( + req, + stream, + client_addr, + tls_server_name_inner, + tls_client_auth_result_inner, + ), ) .await { @@ -90,6 +97,7 @@ where stream: RequestStream, client_addr: SocketAddr, tls_server_name: ServerNameBytesExp, + tls_client_auth_result: std::result::Result<(), ClientCertsError>, ) -> Result<()> where S: BidiStream + Send + 'static, @@ -141,6 +149,7 @@ where self.listening_on, self.tls_enabled, Some(tls_server_name), + Some(tls_client_auth_result), ) .await?; diff --git a/src/proxy/proxy_main.rs b/src/proxy/proxy_main.rs index 964ad70..8501902 100644 --- a/src/proxy/proxy_main.rs +++ b/src/proxy/proxy_main.rs @@ -51,6 +51,7 @@ where server: Http, peer_addr: SocketAddr, tls_server_name: Option, + tls_client_auth_result: Option>, ) where I: AsyncRead + AsyncWrite + Send + Unpin + 'static, { @@ -74,6 +75,7 @@ where self.listening_on, self.tls_enabled, tls_server_name.clone(), + tls_client_auth_result.clone(), ) }), ) @@ -92,7 +94,9 @@ where let tcp_listener = TcpListener::bind(&self.listening_on).await?; info!("Start TCP proxy serving with HTTP request for configured host names"); while let Ok((stream, _client_addr)) = tcp_listener.accept().await { - self.clone().client_serve(stream, server.clone(), _client_addr, None); + self + .clone() + .client_serve(stream, server.clone(), _client_addr, None, None); } Ok(()) as Result<()> }; diff --git a/src/proxy/proxy_tls.rs b/src/proxy/proxy_tls.rs index 6e3200c..b90c8a3 100644 --- a/src/proxy/proxy_tls.rs +++ b/src/proxy/proxy_tls.rs @@ -92,13 +92,14 @@ where } else { ////////////////////////////// // Check client certificate - // TODO: consider move this function to the layer of handle_request (L7) to return 403 let client_certs = conn.peer_certificates(); - let client_certs_setting_for_sni = sni_cc_map.get(&server_name.clone().unwrap()); - check_client_authentication(client_certs, client_certs_setting_for_sni)?; + let client_ca_keyids_set_for_sni = sni_cc_map.get(&server_name.clone().unwrap()); + // TODO: pass this value to the layer of handle_request (L7) to return 403 + let client_certs_auth_result = check_client_authentication(client_certs, client_ca_keyids_set_for_sni); ////////////////////////////// // this immediately spawns another future to actually handle stream. so it is okay to introduce timeout for handshake. - self_inner.client_serve(stream, server_clone, client_addr, server_name); // TODO: don't want to pass copied value... + // TODO: don't want to pass copied value... + self_inner.client_serve(stream, server_clone, client_addr, server_name, Some(client_certs_auth_result)); Ok(()) } }; From 7e4f4d3488481d3005aff56e1208ba8ae4396dc4 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Wed, 12 Oct 2022 15:40:56 +0900 Subject: [PATCH 10/13] workaround --- TODO.md | 4 +++- src/backend/mod.rs | 24 +++++++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/TODO.md b/TODO.md index c994cb1..2a10f18 100644 --- a/TODO.md +++ b/TODO.md @@ -7,5 +7,7 @@ - Options to serve custom http_error page. - Prometheus metrics - Documentation -- Client certificate -> support intermediate certificate. Currently, only supports client certificates directly signed by root CA. +- Client certificate + - support intermediate certificate. Currently, only supports client certificates directly signed by root CA. + - split rustls::server::ServerConfig for SNIs - etc. diff --git a/src/backend/mod.rs b/src/backend/mod.rs index c62b27a..bc701e0 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -231,16 +231,22 @@ impl Backends { // debug!("Load certificate chain for {} server_name's", cnt); ////////////// - // TODO: Client Certs - let client_certs_verifier = rustls::server::AllowAnyAnonymousOrAuthenticatedClient::new(client_ca_roots); - // No ClientCert or WithClientCert - // let client_certs_verifier = rustls::server::AllowAnyAuthenticatedClient::new(client_ca_roots); + let mut server_config = if client_ca_key_ids.is_empty() { + ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_cert_resolver(Arc::new(resolver)) + } else { + // TODO: Client Certs + // No ClientCert or WithClientCert + // let client_certs_verifier = rustls::server::AllowAnyAuthenticatedClient::new(client_ca_roots); + let client_certs_verifier = rustls::server::AllowAnyAnonymousOrAuthenticatedClient::new(client_ca_roots); + ServerConfig::builder() + .with_safe_defaults() + .with_client_cert_verifier(client_certs_verifier) + .with_cert_resolver(Arc::new(resolver)) + }; - let mut server_config = ServerConfig::builder() - .with_safe_defaults() - // .with_no_client_auth() - .with_client_cert_verifier(client_certs_verifier) - .with_cert_resolver(Arc::new(resolver)); ////////////////////////////// #[cfg(feature = "http3")] From 42c0e9474ecfa743a01a7ce4e23c3201e424667a Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Thu, 13 Oct 2022 18:12:22 +0900 Subject: [PATCH 11/13] deps --- Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4dfc8cc..be24326 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,11 +18,7 @@ http3 = ["quinn", "h3", "h3-quinn"] [dependencies] env_logger = "0.9.1" anyhow = "1.0.65" -<<<<<<< HEAD -clap = { version = "4.0.13", features = ["std", "cargo", "wrap_help"] } -======= clap = { version = "4.0.14", features = ["std", "cargo", "wrap_help"] } ->>>>>>> main futures = { version = "0.3.24", features = ["alloc", "async-await"] } hyper = { version = "0.14.20", default-features = false, features = [ "server", From 512690fce5643cc104da05227ced58e0250799da Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Fri, 14 Oct 2022 22:45:13 +0900 Subject: [PATCH 12/13] changed how to support multiple domains and support client authentication directly by rustls using split server_config --- README.md | 31 ++++++- TODO.md | 4 +- example-certs/client_pass=foobar.p12 | Bin 1648 -> 0 bytes example-certs/client_pass=foobar.pfx | Bin 0 -> 1510 bytes src/backend/mod.rs | 121 ++++++++++++++++----------- src/error.rs | 1 + src/handler/handler_main.rs | 43 +++++----- src/proxy/proxy_client_cert.rs | 1 + src/proxy/proxy_h3.rs | 100 ++++++++++------------ src/proxy/proxy_main.rs | 6 +- src/proxy/proxy_tls.rs | 95 ++++++++++----------- 11 files changed, 218 insertions(+), 184 deletions(-) delete mode 100644 example-certs/client_pass=foobar.p12 create mode 100644 example-certs/client_pass=foobar.pfx diff --git a/README.md b/README.md index a986892..09991d5 100644 --- a/README.md +++ b/README.md @@ -226,6 +226,33 @@ Other than them, all you need is to mount your `config.toml` as `/etc/rpxy.toml` [`./bench`](./bench/) directory could be a very simple example of configuration of `rpxy`. This can also be an example of an example of docker use case. +## Experimental Features and Caveats + +### HTTP/3 + +`rpxy` can serves HTTP/3 requests thanks to `quinn` and `hyperium/h3`. To enable this experimental feature, add an entry `experimental.h3` in your `config.toml` like follows. Any values in the entry like `alt_svc_max_age` are optional. + +```toml +[experimental.h3] +alt_svc_max_age = 3600 +request_max_body_size = 65536 +max_concurrent_connections = 10000 +max_concurrent_bidistream = 100 +max_concurrent_unistream = 100 +``` + +### Client Authentication via Client Certificates + +Client authentication is enabled when `apps."app_name".tls.client_ca_cert_path` is set for the domain specified by `"app_name"` like + +```toml +[apps.localhost] +server_name = 'localhost' # Domain name +tls = { https_redirection = true, tls_cert_path = './server.crt', tls_cert_key_path = './server.key', client_ca_cert_path = './client_cert.ca.crt' } +``` + + However, currently we have a limitation on HTTP/3 support for applications that enables client authentication. If an application is set with client authentication, HTTP/3 doesn't work for the application. + ## TIPS ### Using Private Key Issued by Let's Encrypt @@ -284,12 +311,14 @@ First, you need to prepare a CA certificate used to verify client certificate. I % openssl x509 -req -days 365 -sha256 -in client.csr -CA client.ca.crt -CAkey client.ca.key -CAcreateserial -out client.crt -extfile client.ext ``` - Now you have a client key `client.key` and certificate `client.crt` (version 3). `p12` file can be retrieved as + Now you have a client key `client.key` and certificate `client.crt` (version 3). `pfx` (`p12`) file can be retrieved as ```bash % openssl pkcs12 -export -inkey client.key -in client.crt -certfile client.ca.crt -out client.pfx ``` + Note that on MacOS, a `pfx` generated by `OpenSSL 3.0.6` cannot be imported to MacOS KeyChain Access. We generated the sample `pfx` using `LibreSSL 2.8.3` instead `OpenSSL`. + All of sample certificate files are found in `./example-certs/` directory. ### (Work Around) Deployment on Ubuntu 22.04LTS using docker behind `ufw` diff --git a/TODO.md b/TODO.md index 2a10f18..dcbd9ee 100644 --- a/TODO.md +++ b/TODO.md @@ -9,5 +9,7 @@ - Documentation - Client certificate - support intermediate certificate. Currently, only supports client certificates directly signed by root CA. - - split rustls::server::ServerConfig for SNIs + - Currently, we took the following approach (caveats) + - For Http2 and 1.1, prepare `rustls::ServerConfig` for each domain name and hence client CA cert is set for each one. + - For Http3, use aggregated `rustls::ServerConfig` for multiple domain names except for ones requiring client-auth. So, if a domain name is set with client authentication, http3 doesn't work for the domain. - etc. diff --git a/example-certs/client_pass=foobar.p12 b/example-certs/client_pass=foobar.p12 deleted file mode 100644 index 37a3a4ea33f0a17602f3595da00cbe586b60c7f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1648 zcmai!dpOgJAIJCIzWZ)+TUgqYgqT~_SAH%dzh5YuvaDF5lU$2&$|IpLEV(x2*F`iA z&O}9WXkotP8470(fKPsr04c4wl)kpxbE)_o-0QUZF(5mo$*z|{0 zfptJ^?g{0Y=ZdSJkVq^7R)Mg8eM2gt5%5+>$v=$>grGpc48&P0X%r{7gw^I48e(JL zb_$c(;2!t9-DS%cHQegS24nRr?y)rF(=)JS`Rv2mO4rRQIXglJ{`;u5+A58X*j`e3 z=A-N2dHv)NR0lJE;DB0P3Zwn4F?+USs3f2UXOfk5ALU}Pme|=|K*`W@_Bm^IUA=W^ zV1g8W*);5PxYYG!%wgmEb>wDKX}YJ=b@NrUzR&)VW|u&uMTEz-%OPC}dieHV(`$Pq zFATRe9e}+|QfIpli{y48q5)}@;SE<#c^t0I_?gCmoxJ6TFEUHDeV>ViV8&Riq%m8( zW~5!|qUM6%+G;$}n7@*Di%YLG0xXfi--z+9YbWE5L7nlZYBron^7N9i&@H^7ifHR+ z0!4*#uAM9vhjDBsFF`kruMt*s(1F5_>hisewgx!~SvupXoS zC9Cq=sM*%{2FrxRagDlg!BkHP&c-X7YPXg0AORX(bh8aO@;GjhG^Z(klYP)puW#Gr zoR;-rqtG%z6mPRqs$Sl{$V}gj0Fv|SeOrjL-ES{sEy%C#E2~fAFufxHeT_=DNfZW8 zR&S>GrQF)AEe?{17achtBC#L6@@T6JdxPP_bbemD2+F;8LRF&JY0|HJIz!Do`i~%= zC=LN}_K%bGrdxuAMU2tuCbOxi#GyN~JfW@qiT`{EjGWiPDpY5P+w|DnLpAMb%)MVG z{Dsa1L-ko(aF>!oH<&(BTkPW(xaH!ze{pWP$ZS&f{&Y(*$Q3CzS>f3-H3DUn!>eiC zBU)k)nor|F;9PuU)l39{@W>^ly)d-GJHdnv9?tH$@r z)ow=2d-nFKED}#)lsN8sWqFGoZ=+iBCsMjz1Bo>eW*0E;|9YQW+XD6W*u8$w{>DJ` z%glM*R0Sbsqm%$;=0z@8rJ**|YRmDz>5EbRD(Z}eC)lEcUx88XAfDxuv7gRIWnIu* zfSNJo=B0%;tclOAtwb9xY9u5|2O2&n!T9$ga}Y&mKzTxSx-IfX0Lb(xgxP@8cOLx- zIA9mZ21x6%+4>P7<^Qu!WdndLPML#Kq<@Dq4W}(WG@2~uZ*jKs%3A7kpJJ!%1`}Nd z;U7I<%2iFqjuRp>!=27+Uc9iW>q)SEIj1`zunT4W>f~K%yd2Y~eCkX8^ugz2WeMN3 zq62dSoaaBgWxfy{9Y<*j_lzV@oezt#FFsO2tS zTqme4FWpAH7qt+%zt#?`TlW7N%c-H#ZWfWWew_D<*PSkL{H5+NPYp>22enR!87A>; zF!DQrA{2x>5Dmc*A z@tVLW9$}^5r*rEXxcO^Gl|RMQ>!%{!Y1ZJF<9n}eo!gZeS2n@L)bzEN?o-?Ei2+k1d%y7a#a5CB*VP?wFZ1GX}Ie13{+Ub+hij$G*PQ-bDCs` zA7o&J8OZ?9FoFRB1_>&LNQU{A9Sm;zL%veF1NuNs0EeZ*RH~Dp z@ntl)GLxyQPz7Rt=&23=!}ZJ55#q?pT(t9@EodiRfBh8&aIw@a#v=V{MBUl*h z2{_t?)ep}UzfQ<$xmNCfgnOKp;@T52B`_lf2`Yw2hW8Bt2^BFG1QgJ&Nf4$mNXGYJ zzN{G)aBS-FV*@ZTFd;Ar1_dh)0|FWa00b0ulXQ;o!!b<%#Rj0UXF|YK8hwid2>qe9 M!o5b{?*ak{07rbV;s5{u literal 0 HcmV?d00001 diff --git a/src/backend/mod.rs b/src/backend/mod.rs index bc701e0..c6a2842 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -6,7 +6,7 @@ use crate::{ utils::{BytesName, PathNameBytesExp, ServerNameBytesExp}, }; use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; -use rustls::OwnedTrustAnchor; +use rustls::{OwnedTrustAnchor, RootCertStore}; use std::{ fs::File, io::{self, BufReader, Cursor, Read}, @@ -146,6 +146,7 @@ impl Backend { }) .collect(); + // TODO: SKID is not used currently let subject_key_identifiers: HashSet<_> = certs .iter() .filter_map(|v| { @@ -176,82 +177,104 @@ pub struct Backends { pub default_server_name_bytes: Option, // for plaintext http } -pub type SniKeyIdsMap = HashMap>>; +pub type SniServerCryptoMap = HashMap>; pub struct ServerCrypto { - pub inner: Arc, - pub server_name_client_ca_keyids_map: Arc, + // For Quic/HTTP3, only servers with no client authentication + pub inner_global_no_client_auth: Arc, + // For TLS over TCP/HTTP2 and 1.1, map of SNI to server_crypto for all given servers + pub inner_local_map: Arc, } impl Backends { - pub async fn generate_server_crypto_with_cert_resolver(&self) -> Result { - let mut resolver = ResolvesServerCertUsingSni::new(); - let mut client_ca_roots = rustls::RootCertStore::empty(); - let mut client_ca_key_ids: SniKeyIdsMap = HashMap::default(); + pub async fn generate_server_crypto(&self) -> Result { + let mut resolver_global = ResolvesServerCertUsingSni::new(); + let mut server_crypto_local_map: SniServerCryptoMap = HashMap::default(); - // let mut cnt = 0; for (server_name_bytes_exp, backend) in self.apps.iter() { if backend.tls_cert_key_path.is_some() && backend.tls_cert_path.is_some() { match backend.read_certs_and_key() { Ok(certified_key) => { - if let Err(e) = resolver.add(backend.server_name.as_str(), certified_key) { + let mut resolver_local = ResolvesServerCertUsingSni::new(); + let mut client_ca_roots_local = RootCertStore::empty(); + + // add server certificate and key + if let Err(e) = resolver_local.add(backend.server_name.as_str(), certified_key.to_owned()) { error!( "{}: Failed to read some certificates and keys {}", backend.server_name.as_str(), e ) - } else { - // debug!("Add certificate for server_name: {}", backend.server_name.as_str()); - // cnt += 1; } + + if backend.client_ca_cert_path.is_none() { + // aggregated server config for no client auth server for http3 + if let Err(e) = resolver_global.add(backend.server_name.as_str(), certified_key) { + error!( + "{}: Failed to read some certificates and keys {}", + backend.server_name.as_str(), + e + ) + } + } else { + // add client certificate if specified + match backend.read_client_ca_certs() { + Ok((owned_trust_anchors, _subject_key_ids)) => { + client_ca_roots_local.add_server_trust_anchors(owned_trust_anchors.into_iter()); + } + Err(e) => { + warn!( + "Failed to add client CA certificate for {}: {}", + backend.server_name.as_str(), + e + ); + } + } + } + + let mut server_config_local = if client_ca_roots_local.is_empty() { + // with no client auth, enable http1.1 -- 3 + let mut sc = ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_cert_resolver(Arc::new(resolver_local)); + #[cfg(feature = "http3")] + { + sc.alpn_protocols = vec![b"h3".to_vec(), b"hq-29".to_vec()]; // TODO: remove hq-29 later? + } + sc + } else { + // with client auth, enable only http1.1 and 2 + // let client_certs_verifier = rustls::server::AllowAnyAnonymousOrAuthenticatedClient::new(client_ca_roots); + let client_certs_verifier = rustls::server::AllowAnyAuthenticatedClient::new(client_ca_roots_local); + ServerConfig::builder() + .with_safe_defaults() + .with_client_cert_verifier(client_certs_verifier) + .with_cert_resolver(Arc::new(resolver_local)) + }; + server_config_local.alpn_protocols.push(b"h2".to_vec()); + server_config_local.alpn_protocols.push(b"http/1.1".to_vec()); + + server_crypto_local_map.insert(server_name_bytes_exp.to_owned(), Arc::new(server_config_local)); } Err(e) => { warn!("Failed to add certificate for {}: {}", backend.server_name.as_str(), e); } } - // add client certificate if specified - if backend.client_ca_cert_path.is_some() { - match backend.read_client_ca_certs() { - Ok((owned_trust_anchors, subject_key_ids)) => { - // TODO: ここでSubject Key ID (CA Key ID)を記録しておく。認証後にpeer certificateのauthority key idとの一貫性をチェック。 - // v3 x509前提で特定のkey id extが入ってなければ使えない前提 - client_ca_roots.add_server_trust_anchors(owned_trust_anchors.into_iter()); - client_ca_key_ids.insert(server_name_bytes_exp.to_owned(), subject_key_ids); - } - Err(e) => { - warn!( - "Failed to add client ca certificate for {}: {}", - backend.server_name.as_str(), - e - ); - } - } - } } } // debug!("Load certificate chain for {} server_name's", cnt); ////////////// - let mut server_config = if client_ca_key_ids.is_empty() { - ServerConfig::builder() - .with_safe_defaults() - .with_no_client_auth() - .with_cert_resolver(Arc::new(resolver)) - } else { - // TODO: Client Certs - // No ClientCert or WithClientCert - // let client_certs_verifier = rustls::server::AllowAnyAuthenticatedClient::new(client_ca_roots); - let client_certs_verifier = rustls::server::AllowAnyAnonymousOrAuthenticatedClient::new(client_ca_roots); - ServerConfig::builder() - .with_safe_defaults() - .with_client_cert_verifier(client_certs_verifier) - .with_cert_resolver(Arc::new(resolver)) - }; + let mut server_crypto_global = ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_cert_resolver(Arc::new(resolver_global)); ////////////////////////////// #[cfg(feature = "http3")] { - server_config.alpn_protocols = vec![ + server_crypto_global.alpn_protocols = vec![ b"h3".to_vec(), b"hq-29".to_vec(), // TODO: remove later? b"h2".to_vec(), @@ -264,8 +287,8 @@ impl Backends { } Ok(ServerCrypto { - inner: Arc::new(server_config), - server_name_client_ca_keyids_map: Arc::new(client_ca_key_ids), + inner_global_no_client_auth: Arc::new(server_crypto_global), + inner_local_map: Arc::new(server_crypto_local_map), }) } } diff --git a/src/error.rs b/src/error.rs index 7a39c9e..aa679f8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -44,6 +44,7 @@ pub enum RpxyError { Other(#[from] anyhow::Error), } +#[allow(dead_code)] #[derive(Debug, Error, Clone)] pub enum ClientCertsError { #[error("TLS Client Certificate is Required for Given SNI: {0}")] diff --git a/src/handler/handler_main.rs b/src/handler/handler_main.rs index 251f898..fbc5161 100644 --- a/src/handler/handler_main.rs +++ b/src/handler/handler_main.rs @@ -1,6 +1,12 @@ // Highly motivated by https://github.com/felipenoris/hyper-reverse-proxy use super::{utils_headers::*, utils_request::*, utils_synth_response::*}; -use crate::{backend::UpstreamGroup, error::*, globals::Globals, log::*, utils::ServerNameBytesExp}; +use crate::{ + backend::{Backend, UpstreamGroup}, + error::*, + globals::Globals, + log::*, + utils::ServerNameBytesExp, +}; use hyper::{ client::connect::Connect, header::{self, HeaderValue}, @@ -35,26 +41,11 @@ where listen_addr: SocketAddr, tls_enabled: bool, tls_server_name: Option, - tls_client_auth_result: Option>, ) -> Result> { //////// let mut log_data = MessageLog::from(&req); log_data.client_addr(&client_addr); ////// - // First check client auth result if exist - if let Some(res) = tls_client_auth_result { - match res { - Err(ClientCertsError::ClientCertRequired(_)) => { - // Client cert is required for the TLS server name - return self.return_with_error_log(StatusCode::FORBIDDEN, &mut log_data); - } - Err(ClientCertsError::InconsistentClientCert(_)) => { - // Client cert provided was inconsistent to the TLS server name - return self.return_with_error_log(StatusCode::BAD_REQUEST, &mut log_data); - } - _ => (), - } - } // Here we start to handle with server_name let server_name = if let Ok(v) = req.parse_host() { @@ -133,7 +124,7 @@ where if res_backend.status() != StatusCode::SWITCHING_PROTOCOLS { // Generate response to client - if self.generate_response_forwarded(&mut res_backend).is_ok() { + if self.generate_response_forwarded(&mut res_backend, backend).is_ok() { log_data.status_code(&res_backend.status()).output(); return Ok(res_backend); } else { @@ -191,7 +182,11 @@ where //////////////////////////////////////////////////// // Functions to generate messages - fn generate_response_forwarded(&self, response: &mut Response) -> Result<()> { + fn generate_response_forwarded( + &self, + response: &mut Response, + chosen_backend: &Backend, + ) -> Result<()> { let headers = response.headers_mut(); remove_connection_header(headers); remove_hop_header(headers); @@ -199,7 +194,8 @@ where #[cfg(feature = "http3")] { - if self.globals.http3 { + // TODO: Workaround for avoid h3 for client authentication + if self.globals.http3 && chosen_backend.client_ca_cert_path.is_none() { if let Some(port) = self.globals.https_port { add_header_entry_overwrite_if_exist( headers, @@ -210,6 +206,15 @@ where ), )?; } + } else { + // remove alt-svc to disallow requests via http3 + headers.remove(header::ALT_SVC.as_str()); + } + } + #[cfg(not(feature = "http3"))] + { + if let Some(port) = self.globals.https_port { + headers.remove(header::ALT_SVC.as_str()); } } diff --git a/src/proxy/proxy_client_cert.rs b/src/proxy/proxy_client_cert.rs index aa212c1..adac4b7 100644 --- a/src/proxy/proxy_client_cert.rs +++ b/src/proxy/proxy_client_cert.rs @@ -4,6 +4,7 @@ use rustls::Certificate; use x509_parser::extensions::ParsedExtension; use x509_parser::prelude::*; +#[allow(dead_code)] // TODO: consider move this function to the layer of handle_request (L7) to return 403 pub(super) fn check_client_authentication( client_certs: Option<&[Certificate]>, diff --git a/src/proxy/proxy_h3.rs b/src/proxy/proxy_h3.rs index d0aaf5d..d5a6c88 100644 --- a/src/proxy/proxy_h3.rs +++ b/src/proxy/proxy_h3.rs @@ -1,9 +1,9 @@ -use super::{proxy_client_cert::check_client_authentication, Proxy}; -use crate::{backend::SniKeyIdsMap, error::*, log::*, utils::ServerNameBytesExp}; +use super::Proxy; +use crate::{error::*, log::*, utils::ServerNameBytesExp}; use bytes::{Buf, Bytes}; use h3::{quic::BidiStream, server::RequestStream}; use hyper::{client::connect::Connect, Body, Request, Response}; -use std::{net::SocketAddr, sync::Arc}; +use std::net::SocketAddr; use tokio::time::{timeout, Duration}; impl Proxy @@ -14,28 +14,11 @@ where self, conn: quinn::Connecting, tls_server_name: ServerNameBytesExp, - sni_cc_map: Arc, ) -> Result<()> { let client_addr = conn.remote_address(); match conn.await { Ok(new_conn) => { - // Check client certificates - let cc = { - // https://docs.rs/quinn/latest/quinn/struct.Connection.html - let client_certs_setting_for_sni = sni_cc_map.get(&tls_server_name); - let client_certs = match new_conn.connection.peer_identity() { - Some(peer_identity) => peer_identity - .downcast::>() - .ok() - .map(|p| p.into_iter().collect::>()), - None => None, - }; - (client_certs, client_certs_setting_for_sni) - }; - // TODO: pass this value to the layer of handle_request (L7) to return 403 - let tls_client_auth_result = check_client_authentication(cc.0.as_ref().map(AsRef::as_ref), cc.1); - let mut h3_conn = h3::server::Connection::<_, bytes::Bytes>::new(h3_quinn::Connection::new(new_conn)).await?; info!( "QUIC/HTTP3 connection established from {:?} {:?}", @@ -43,43 +26,46 @@ where ); // TODO: Is here enough to fetch server_name from NewConnection? // to avoid deep nested call from listener_service_h3 - while let Some((req, stream)) = match h3_conn.accept().await { - Ok(opt_req) => opt_req, - Err(e) => { - warn!("HTTP/3 failed to accept incoming connection: {}", e); - return Ok(h3_conn.shutdown(0).await?); - } - } { - // We consider the connection count separately from the stream count. - // Max clients for h1/h2 = max 'stream' for h3. - let request_count = self.globals.request_count.clone(); - if request_count.increment() > self.globals.max_clients { - request_count.decrement(); - return Ok(h3_conn.shutdown(0).await?); - } - debug!("Request incoming: current # {}", request_count.current()); - - let self_inner = self.clone(); - let tls_server_name_inner = tls_server_name.clone(); - let tls_client_auth_result_inner = tls_client_auth_result.clone(); - self.globals.runtime_handle.spawn(async move { - if let Err(e) = timeout( - self_inner.globals.proxy_timeout + Duration::from_secs(1), // timeout per stream are considered as same as one in http2 - self_inner.stream_serve_h3( - req, - stream, - client_addr, - tls_server_name_inner, - tls_client_auth_result_inner, - ), - ) - .await - { - error!("HTTP/3 failed to process stream: {}", e); + loop { + // this routine follows hyperium/h3 examples https://github.com/hyperium/h3/blob/master/examples/server.rs + match h3_conn.accept().await { + Ok(None) => { + break; } - request_count.decrement(); - debug!("Request processed: current # {}", request_count.current()); - }); + Err(e) => { + warn!("HTTP/3 error on accept incoming connection: {}", e); + match e.get_error_level() { + h3::error::ErrorLevel::ConnectionError => break, + h3::error::ErrorLevel::StreamError => continue, + } + } + Ok(Some((req, stream))) => { + // We consider the connection count separately from the stream count. + // Max clients for h1/h2 = max 'stream' for h3. + let request_count = self.globals.request_count.clone(); + if request_count.increment() > self.globals.max_clients { + request_count.decrement(); + h3_conn.shutdown(0).await?; + break; + } + debug!("Request incoming: current # {}", request_count.current()); + + let self_inner = self.clone(); + let tls_server_name_inner = tls_server_name.clone(); + self.globals.runtime_handle.spawn(async move { + if let Err(e) = timeout( + self_inner.globals.proxy_timeout + Duration::from_secs(1), // timeout per stream are considered as same as one in http2 + self_inner.stream_serve_h3(req, stream, client_addr, tls_server_name_inner), + ) + .await + { + error!("HTTP/3 failed to process stream: {}", e); + } + request_count.decrement(); + debug!("Request processed: current # {}", request_count.current()); + }); + } + } } } Err(err) => { @@ -97,7 +83,6 @@ where stream: RequestStream, client_addr: SocketAddr, tls_server_name: ServerNameBytesExp, - tls_client_auth_result: std::result::Result<(), ClientCertsError>, ) -> Result<()> where S: BidiStream + Send + 'static, @@ -149,7 +134,6 @@ where self.listening_on, self.tls_enabled, Some(tls_server_name), - Some(tls_client_auth_result), ) .await?; diff --git a/src/proxy/proxy_main.rs b/src/proxy/proxy_main.rs index 8501902..964ad70 100644 --- a/src/proxy/proxy_main.rs +++ b/src/proxy/proxy_main.rs @@ -51,7 +51,6 @@ where server: Http, peer_addr: SocketAddr, tls_server_name: Option, - tls_client_auth_result: Option>, ) where I: AsyncRead + AsyncWrite + Send + Unpin + 'static, { @@ -75,7 +74,6 @@ where self.listening_on, self.tls_enabled, tls_server_name.clone(), - tls_client_auth_result.clone(), ) }), ) @@ -94,9 +92,7 @@ where let tcp_listener = TcpListener::bind(&self.listening_on).await?; info!("Start TCP proxy serving with HTTP request for configured host names"); while let Ok((stream, _client_addr)) = tcp_listener.accept().await { - self - .clone() - .client_serve(stream, server.clone(), _client_addr, None, None); + self.clone().client_serve(stream, server.clone(), _client_addr, None); } Ok(()) as Result<()> }; diff --git a/src/proxy/proxy_tls.rs b/src/proxy/proxy_tls.rs index b90c8a3..dcd7a58 100644 --- a/src/proxy/proxy_tls.rs +++ b/src/proxy/proxy_tls.rs @@ -1,9 +1,6 @@ -use super::{ - proxy_client_cert::check_client_authentication, - proxy_main::{LocalExecutor, Proxy}, -}; +use super::proxy_main::{LocalExecutor, Proxy}; use crate::{ - backend::{ServerCrypto, SniKeyIdsMap}, + backend::{ServerCrypto, SniServerCryptoMap}, constants::*, error::*, log::*, @@ -17,7 +14,6 @@ use tokio::{ sync::watch, time::{sleep, timeout, Duration}, }; -use tokio_rustls::TlsAcceptor; #[cfg(feature = "http3")] use futures::StreamExt; @@ -31,7 +27,7 @@ where async fn cert_service(&self, server_crypto_tx: watch::Sender>>) { info!("Start cert watch service"); loop { - if let Ok(server_crypto) = self.globals.backends.generate_server_crypto_with_cert_resolver().await { + if let Ok(server_crypto) = self.globals.backends.generate_server_crypto().await { if let Err(_e) = server_crypto_tx.send(Some(Arc::new(server_crypto))) { error!("Failed to populate server crypto"); break; @@ -52,61 +48,61 @@ where let tcp_listener = TcpListener::bind(&self.listening_on).await?; info!("Start TCP proxy serving with HTTPS request for configured host names"); - // let mut server_crypto: Option> = None; - let mut tls_acceptor: Option = None; - let mut sni_client_ca_keyid_map: Option> = None; + let mut server_crypto_map: Option> = None; loop { tokio::select! { tcp_cnx = tcp_listener.accept() => { - if tls_acceptor.is_none() || tcp_cnx.is_err() || sni_client_ca_keyid_map.is_none() { + if tcp_cnx.is_err() || server_crypto_map.is_none() { continue; } let (raw_stream, client_addr) = tcp_cnx.unwrap(); - let acceptor = tls_acceptor.clone().unwrap(); - let sni_cc_map = sni_client_ca_keyid_map.clone().unwrap(); + let sc_map_inner = server_crypto_map.clone(); let server_clone = server.clone(); let self_inner = self.clone(); // spawns async handshake to avoid blocking thread by sequential handshake. let handshake_fut = async move { - // timeout is introduced to avoid get stuck here. - let accepted = match timeout(Duration::from_secs(TLS_HANDSHAKE_TIMEOUT_SEC), acceptor.accept(raw_stream)).await { - Ok(a) => a, - Err(e) => { - return Err(RpxyError::Proxy(format!("Timeout to handshake TLS: {}", e))); - } - }; - let stream = match accepted { + let acceptor = tokio_rustls::LazyConfigAcceptor::new(rustls::server::Acceptor::new().unwrap(), raw_stream).await; + if let Err(e) = acceptor { + return Err(RpxyError::Proxy(format!("Failed to handshake TLS: {}", e))); + } + let start = acceptor.unwrap(); + let client_hello = start.client_hello(); + let server_name = client_hello.server_name(); + debug!("HTTP/2 or 1.1: SNI in ClientHello: {:?}", server_name); + let server_name = server_name.map_or_else(|| None, |v| Some(v.to_server_name_vec())); + if server_name.is_none(){ + return Err(RpxyError::Proxy("No SNI is given".to_string())); + } + let server_crypto = sc_map_inner.as_ref().unwrap().get(server_name.as_ref().unwrap()); + if server_crypto.is_none() { + return Err(RpxyError::Proxy(format!("No TLS serving app for {:?}", "xx"))); + } + let stream = match start.into_stream(server_crypto.unwrap().clone()).await { Ok(s) => s, Err(e) => { return Err(RpxyError::Proxy(format!("Failed to handshake TLS: {}", e))); } }; - // Retrieve SNI - let (_, conn) = stream.get_ref(); - let server_name = conn.sni_hostname(); - debug!("HTTP/2 or 1.1: SNI in ClientHello: {:?}", server_name); - let server_name = server_name.map_or_else(|| None, |v| Some(v.to_server_name_vec())); - if server_name.is_none(){ - Err(RpxyError::Proxy("No SNI is given".to_string())) - } else { - ////////////////////////////// - // Check client certificate - let client_certs = conn.peer_certificates(); - let client_ca_keyids_set_for_sni = sni_cc_map.get(&server_name.clone().unwrap()); - // TODO: pass this value to the layer of handle_request (L7) to return 403 - let client_certs_auth_result = check_client_authentication(client_certs, client_ca_keyids_set_for_sni); - ////////////////////////////// - // this immediately spawns another future to actually handle stream. so it is okay to introduce timeout for handshake. - // TODO: don't want to pass copied value... - self_inner.client_serve(stream, server_clone, client_addr, server_name, Some(client_certs_auth_result)); - Ok(()) - } + self_inner.client_serve(stream, server_clone, client_addr, server_name); + Ok(()) }; + self.globals.runtime_handle.spawn( async move { - if let Err(e) = handshake_fut.await { - error!("{}", e); - } + // timeout is introduced to avoid get stuck here. + match timeout( + Duration::from_secs(TLS_HANDSHAKE_TIMEOUT_SEC), + handshake_fut + ).await { + Ok(a) => { + if let Err(e) = a { + error!("{}", e); + } + }, + Err(e) => { + error!("Timeout to handshake TLS: {}", e); + } + }; }); } _ = server_crypto_rx.changed() => { @@ -114,8 +110,7 @@ where break; } let server_crypto = server_crypto_rx.borrow().clone().unwrap(); - tls_acceptor = Some(TlsAcceptor::from(server_crypto.inner.clone())); - sni_client_ca_keyid_map = Some(server_crypto.server_name_client_ca_keyids_map.clone()); + server_crypto_map = Some(server_crypto.inner_local_map.clone()); } else => break } @@ -143,11 +138,10 @@ where let (endpoint, mut incoming) = Endpoint::server(server_config_h3, self.listening_on)?; let mut server_crypto: Option> = None; - let mut sni_client_ca_keyid_map: Option> = None; loop { tokio::select! { new_conn = incoming.next() => { - if server_crypto.is_none() || new_conn.is_none() || sni_client_ca_keyid_map.is_none() { + if server_crypto.is_none() || new_conn.is_none() { continue; } let mut conn = new_conn.unwrap(); @@ -173,7 +167,7 @@ where ); // TODO: server_nameをここで出してどんどん深く投げていくのは効率が悪い。connecting -> connectionsの後でいいのでは? // TODO: 通常のTLSと同じenumか何かにまとめたい - let fut = self.clone().connection_serve_h3(conn, new_server_name, sni_client_ca_keyid_map.clone().unwrap()); + let fut = self.clone().connection_serve_h3(conn, new_server_name); self.globals.runtime_handle.spawn(async move { // Timeout is based on underlying quic if let Err(e) = fut.await { @@ -187,8 +181,7 @@ where } server_crypto = server_crypto_rx.borrow().clone(); if server_crypto.is_some(){ - endpoint.set_server_config(Some(QuicServerConfig::with_crypto(server_crypto.clone().unwrap().inner.clone()))); - sni_client_ca_keyid_map = Some(server_crypto.clone().unwrap().server_name_client_ca_keyids_map.clone()); + endpoint.set_server_config(Some(QuicServerConfig::with_crypto(server_crypto.clone().unwrap().inner_global_no_client_auth.clone()))); } } else => break From 634686b8450368d6d4a96ae89ad315fbb7bbe413 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Fri, 14 Oct 2022 23:03:41 +0900 Subject: [PATCH 13/13] deps --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index be24326..de3db2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ http3 = ["quinn", "h3", "h3-quinn"] [dependencies] env_logger = "0.9.1" anyhow = "1.0.65" -clap = { version = "4.0.14", features = ["std", "cargo", "wrap_help"] } +clap = { version = "4.0.15", features = ["std", "cargo", "wrap_help"] } futures = { version = "0.3.24", features = ["alloc", "async-await"] } hyper = { version = "0.14.20", default-features = false, features = [ "server",