temporarily implemented client authentication using client certificates (mTLS)

This commit is contained in:
Jun Kurihara 2022-10-07 23:47:10 +09:00
commit d7193af4e6
No known key found for this signature in database
GPG key ID: 48ADFD173ED22B03
21 changed files with 326 additions and 40 deletions

View file

@ -1,3 +1,4 @@
mod proxy_client_cert;
#[cfg(feature = "http3")]
mod proxy_h3;
mod proxy_main;

View file

@ -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<Vec<u8>>>,
) -> 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(())
}

View file

@ -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<T> Proxy<T>
@ -14,11 +14,28 @@ where
self,
conn: quinn::Connecting,
tls_server_name: ServerNameBytesExp,
sni_cc_map: Arc<SniKeyIdsMap>,
) -> 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::<Vec<rustls::Certificate>>()
.ok()
.map(|p| p.into_iter().collect::<Vec<_>>()),
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 {:?} {:?}",

View file

@ -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<T> Proxy<T>
where
T: Connect + Clone + Sync + Send + 'static,
{
async fn cert_service(&self, server_crypto_tx: watch::Sender<Option<Arc<ServerConfig>>>) {
async fn cert_service(&self, server_crypto_tx: watch::Sender<Option<Arc<ServerCrypto>>>) {
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<LocalExecutor>,
mut server_crypto_rx: watch::Receiver<Option<Arc<ServerConfig>>>,
mut server_crypto_rx: watch::Receiver<Option<Arc<ServerCrypto>>>,
) -> 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<Arc<ServerConfig>> = None;
let mut tls_acceptor: Option<TlsAcceptor> = None;
let mut sni_client_ca_keyid_map: Option<Arc<SniKeyIdsMap>> = 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<Option<Arc<ServerConfig>>>) -> Result<()> {
async fn listener_service_h3(&self, mut server_crypto_rx: watch::Receiver<Option<Arc<ServerCrypto>>>) -> 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<Arc<ServerConfig>> = None;
let mut server_crypto: Option<Arc<ServerCrypto>> = None;
let mut sni_client_ca_keyid_map: Option<Arc<SniKeyIdsMap>> = 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<LocalExecutor>) -> Result<()> {
let (tx, rx) = watch::channel::<Option<Arc<ServerConfig>>>(None);
let (tx, rx) = watch::channel::<Option<Arc<ServerCrypto>>>(None);
#[cfg(not(feature = "http3"))]
{
select! {