diff --git a/tonic-examples/Cargo.toml b/tonic-examples/Cargo.toml index d79c681be..0fd95c557 100644 --- a/tonic-examples/Cargo.toml +++ b/tonic-examples/Cargo.toml @@ -44,6 +44,14 @@ path = "src/tls/client.rs" name = "tls-server" path = "src/tls/server.rs" +[[bin]] +name = "tls-client-auth-server" +path = "src/tls_client_auth/server.rs" + +[[bin]] +name = "tls-client-auth-client" +path = "src/tls_client_auth/client.rs" + [dependencies] tonic = { path = "../tonic", features = ["rustls"] } bytes = "0.4" diff --git a/tonic-examples/data/tls/client1.key b/tonic-examples/data/tls/client1.key new file mode 100644 index 000000000..f4d8da275 --- /dev/null +++ b/tonic-examples/data/tls/client1.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCiiWrmzpENsI+c +Cz4aBpG+Pl8WOsrByfZx/ZnJdCZHO3MTYE6sCLhYssf0ygAEEGxvmkd4cxmfCfgf +xuT8u+D7Y5zQSoymkbWdU6/9jbNY6Ovtc+a96I1LGXOKROQw6KR3PuqLpUqEOJiB +l03qK+HMU0g56G1n31Od7HkJsDRvtePqy3I3LgpdcRps23sk46tCzZzhyfqIQ7Qf +J5qZx93tA+pfy+Xtb9XIUTIWKIp1/uyfh8Fp8HA0c9zJCSZzJOX2j3GH1TYqkVgP +egI2lhmdXhP5Q8vdhwy0UJaL28RJXA6UAg0tPZeWJe6pux9JiA81sI6My+Krrw8D +yibkGTTbAgMBAAECggEANCQhRym9HsclSsnQgkjZOE6J8nep08EWbjsMurOoE/He +WLjshAPIH6w6uSyUFLmwD51OkDVcYsiv8IG9s9YRtpOeGrPPqx/TQ0U1kAGFJ2CR +Tvt/aizQJudjSVgQXCBFontsgp/j58bAJdKEDDtHlGSjJvCJKGlcSa0ypwj/yVXt +frjROJNYzw9gMM7fN/IKF/cysdXSeLl/Q9RnHVIfC3jOFJutsILCK8+PC51dM8Fl +IOjmPmiZ080yV8RBcMRECwl53vLOE3OOpR3ZijfNCY1KU8zWi1oELJ1o6f4+cBye +7WPgFEoBew5XHXZ+ke8rh8cc0wth7ZTcC+xC/456AQKBgQDQr2EzBwXxYLF8qsN1 +R4zlzXILLdZN8a4bKfrS507/Gi1gDBHzfvbE7HfljeqrAkbKMdKNkbz3iS85SguH +jsM047xUGJg0PAcwBLHUedlSn1xDDcDHW6X8ginpA2Zz1+WAlhNz6XurA1wnjZmS +VcPxopH7QsuFCclqtt14MbBQ6QKBgQDHY3jcAVfQF+yhQ0YyM6GPLN342aTplgyJ +yz4uWVMeXacU4QzqGbf2L2hc9M2L28Xb37RWC3Q/by0vUefiC6qxRt+GJdRsOuQj +2F1uUibeWtAWp249fcfvxjLib276J+Eit18LI0s0mNR3ekK4GcjSe4NwSq5IrU8e +pBreet3dIwKBgQCxVuil4WkGd+I8jC0v5A7zVsR8hYZhlGkdgm45fgHevdMjlP5I +S3PPYxh8hj6O9o9L0k0Yq2nHfdgYujjUCNkQgBuR55iogv6kqsioRKgPE4fnH6/c +eqCy1bZh4tbUyPqqbF65mQfUCzXsEuQXvDSYiku+F0Q2mVuGCUJpmug3yQKBgEd3 +LeCdUp4xlQ0QEd74hpXM3RrO178pmwDgqj7uoU4m/zYKnBhkc3137I406F+SvE5c +1kRpApeh/64QS27IA7xazM9GS+cnDJKUgJiENY5JOoCELo03wiv8/EwQ6NQc6yMI +WrahRdlqVe0lEzjtdP+MacYb3nAKPmubIk5P96nFAoGAFAyrKpFTyXbNYBTw9Rab +TG6q7qkn+YTHN3+k4mo9NGGwZ3pXvmrKMYCIRhLMbqzsmTbFqCPPIxKsrmf8QYLh +xHYQjrCkbZ0wZdcdeV6yFSDsF218nF/12ZPE7CBOQMfZTCKFNWGL97uIVcmR6K5G +ojTkOvaUnwQtSFhNuzyr23I= +-----END PRIVATE KEY----- diff --git a/tonic-examples/data/tls/client1.pem b/tonic-examples/data/tls/client1.pem new file mode 100644 index 000000000..bb3b82c40 --- /dev/null +++ b/tonic-examples/data/tls/client1.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIQYbE9d1Rft5h4ku7FSAvWdzANBgkqhkiG9w0BAQsFADAn +MSUwIwYDVQQDExxUb25pYyBFeGFtcGxlIENsaWVudCBSb290IENBMB4XDTE5MTAx +NDEyMzkzNloXDTI0MTAxMjEyMzkzNlowEjEQMA4GA1UEAxMHY2xpZW50MTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKKJaubOkQ2wj5wLPhoGkb4+XxY6 +ysHJ9nH9mcl0Jkc7cxNgTqwIuFiyx/TKAAQQbG+aR3hzGZ8J+B/G5Py74PtjnNBK +jKaRtZ1Tr/2Ns1jo6+1z5r3ojUsZc4pE5DDopHc+6oulSoQ4mIGXTeor4cxTSDno +bWffU53seQmwNG+14+rLcjcuCl1xGmzbeyTjq0LNnOHJ+ohDtB8nmpnH3e0D6l/L +5e1v1chRMhYoinX+7J+HwWnwcDRz3MkJJnMk5faPcYfVNiqRWA96AjaWGZ1eE/lD +y92HDLRQlovbxElcDpQCDS09l5Yl7qm7H0mIDzWwjozL4quvDwPKJuQZNNsCAwEA +AaNGMEQwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAfBgNVHSME +GDAWgBQV1YOR+Jpl1fbujvWLSBEoRvsDhTANBgkqhkiG9w0BAQsFAAOCAQEAfTPu +KeHXmyVTSCUrYQ1X5Mu7VzfZlRbhoytHOw7bYGgwaFwQj+ZhlPt8nFC22/bEk4IV +AoCOli0WyPIB7Lx52dZ+v9JmYOK6ca2Aa/Dkw8Q+M3XA024FQWq3nZ6qANKC32/9 +Nk+xOcb1Qd/11stpTkRf2Oj7F7K4GnlFbY6iMyNW+RFXGKEbL5QAJDTDPIT8vw1x +oYeNPwmC042uEboCZPNXmuctiK9Wt1TAxjZT/cwdIBGGJ+xrW72abfJGs7bUcJfc +O4r9V0xVv+X0iKWTW0fwd9qjNfiEP1tFCcZb2XsNQPe/DlQZ+h98P073tZEsWI/G +KJrFspGX8vOuSdIeqw== +-----END CERTIFICATE----- diff --git a/tonic-examples/data/tls/client2.key b/tonic-examples/data/tls/client2.key new file mode 100644 index 000000000..32525fa03 --- /dev/null +++ b/tonic-examples/data/tls/client2.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCvpRVRx7joJVf3 +dIcDih5lY0Z+Y2MoDX83iwNF4QTfMibULyXffOnC5XcvayS/W8ToxaLweNE2bIwa +krl3K+R/4tdI9ZguSgHFBMRCNp33ZDGxD01XHduETXXdHXxKufxLNjCFtAWwM+GF +KbYzEdeyMmV9yYc3ihW3mg+7rzaUwU+hK7LjhTi/YW7TJF53UGIkEQuPKYUfBCha +01CWAJTJDI7hrH7asfIG690Wm3uWNj2wsFVk2ZtyRIis2aQOE4tknG3yTKjnLGBH +L+DQ+2UljuVyHi1pwCVN+zvoG/0RiiHWs6kW/zAompoxWhWPucKgssjC/JsWVKKp +TpfOfEy7AgMBAAECggEAFclTQKaWT+054Q7KJG1AYfETcF/hj7lE213Z1RQZJ5ov +6MfEWdlDoZIW24HduAKpBPpmwI8r3CVQp4cljBucpyQ68ejMrIkveQGjWlct9t6t +rzmnrTOd4+Y7xWZ/4UD6g1XAZQU0Y2u8AGlxGRqQd6D6p8SUihYNpY1tgCk2ivLO +znGZau0Gdt2f6Pv9C0cy5R2832a+3V1X9oCeeXVP8kM1dBKbWQTVxhYaxJxMZifn +qfJi3sjEJZZ05NEFKwKpYx7gUEtC1d/zvvL/VSjihcyCYk728jr40uoeBpBUiUsR +G0N1/xGNGHuRAGFjQSyrACtz1hRSPFmTyRhr6vrRGQKBgQDOFWKl6HjVB/WZ6ZC+ +BXMhUvCKz99lrOO1TbDAI8Tn26D+OSXhywXWPf++HhTZuND460I+79rPH+tJiFw4 +Y5z9JFce0MYPNcd/K7JBiV7zBBYof40UbBw8Eff7mWLaOXtI9uwItRXRBs+fIPRM +eCgVUid7pe//oAQ8npcmQEVxjwKBgQDaMEmrTo0+RpT3sS1m9pCQc2LjiKmhWP1L +N/IxZn5Ey9Ao+ibVIQzb69HMUBJ1EZGNZZkEcCguWICIov8rO10xIrD3m9rWgAVg +HlVFlcSTrg0/3cVd26msJKW4pjKA/GmYVhP8o027Zy5KtZ/2jJCh2UjSLsY+acEq ++Qr972BEFQKBgHOwAZ7NL/e27iKmwUBK4uSUIMBsDSaQtYtzv4M9ES5vVqMgBaoJ +RI+OYmChlmban0T9HEUkdJrNelHfIJXvJZPdsKJ15JlpQUKcjwbHTOvzIVU+tT3/ +qqH2HFW7N4j1t8WwB7Sjo0miHy9fWoUK9sVxRwTclCvV8krtZEBu2Az1AoGAGZoU +6t76v9X0YOQPWceQywJfFifRD7ercQoNhzJpmpT3xfckW1nXcm7HXVv/7nCzTY4g +WF74uAd2fZHysxXyJ3PUpBlLomO/PboRc2rReCqyL05MfGjsDeD2+SW3Q19a3J8t +FTXsRxMiYW3SaVGxHuyqGM+YP3aVTf+PBKD0AMkCgYEAyKtuxwBjPjO1pyOn195e +UY2oaZq4PfaK04Eu5uCmOD2vBcgJWZ8VJNXPlRL5vlCx9LrLB9vJDyXgSurqJAH6 +M8/7tR/xlyMix+Jq47209kfOXMMdSGf55/xERmN6vPuhLtPwtj0ZduJzq6gwnlvs +gG29TwA5xp9S4A1amFSrJBo= +-----END PRIVATE KEY----- diff --git a/tonic-examples/data/tls/client2.pem b/tonic-examples/data/tls/client2.pem new file mode 100644 index 000000000..2681b6145 --- /dev/null +++ b/tonic-examples/data/tls/client2.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCjCCAfKgAwIBAgIRAKD72aj29NawAKPAQkcm7QcwDQYJKoZIhvcNAQELBQAw +JzElMCMGA1UEAxMcVG9uaWMgRXhhbXBsZSBDbGllbnQgUm9vdCBDQTAeFw0xOTEw +MTQxMjM5MzZaFw0yNDEwMTIxMjM5MzZaMBIxEDAOBgNVBAMTB2NsaWVudDIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvpRVRx7joJVf3dIcDih5lY0Z+ +Y2MoDX83iwNF4QTfMibULyXffOnC5XcvayS/W8ToxaLweNE2bIwakrl3K+R/4tdI +9ZguSgHFBMRCNp33ZDGxD01XHduETXXdHXxKufxLNjCFtAWwM+GFKbYzEdeyMmV9 +yYc3ihW3mg+7rzaUwU+hK7LjhTi/YW7TJF53UGIkEQuPKYUfBCha01CWAJTJDI7h +rH7asfIG690Wm3uWNj2wsFVk2ZtyRIis2aQOE4tknG3yTKjnLGBHL+DQ+2UljuVy +Hi1pwCVN+zvoG/0RiiHWs6kW/zAompoxWhWPucKgssjC/JsWVKKpTpfOfEy7AgMB +AAGjRjBEMBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHwYDVR0j +BBgwFoAUFdWDkfiaZdX27o71i0gRKEb7A4UwDQYJKoZIhvcNAQELBQADggEBAF5F +2A3V2GGZo7AdztxWaPd6Nu/8VbQWGEeZeWFpQEloNiur96KDcndXXrS6GAyL31d9 +vfV2IA2yrB/2przFVRjfNnTj7+xNEtp23iMW8qhMIeHQs1IYu+HqHWDPEJfphTPd +4Tu5M+ciE7KSZgPsV15piPgst2dqDvghBqxE6t3UnR1wQ5b3wWCt1O9NO+obeV9a +1lzlYw2NjdHxmrqXLS9I5eYqEo3JtRQrOu3LUd7LqdlEO0jHMaxblxTgwJDoj/9C +CnXp3sNewqsNHW17dsKavMJ2LFXukqLjonvz8NjxeetD09OzMEsg8Tx/1nOQPUBw +rtVdBkQ/TxiNTlTtdZc= +-----END CERTIFICATE----- diff --git a/tonic-examples/data/tls/client_ca.pem b/tonic-examples/data/tls/client_ca.pem new file mode 100644 index 000000000..aa483b931 --- /dev/null +++ b/tonic-examples/data/tls/client_ca.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIRAMNWpWRu6Q1txEYUyrkyXKEwDQYJKoZIhvcNAQELBQAw +JzElMCMGA1UEAxMcVG9uaWMgRXhhbXBsZSBDbGllbnQgUm9vdCBDQTAeFw0xOTEw +MTQxMjM5MzZaFw0yOTEwMTExMjM5MzZaMCcxJTAjBgNVBAMTHFRvbmljIEV4YW1w +bGUgQ2xpZW50IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCv8Nj4XJbMI0wWUvLbmCf7IEvJFnomodGnDurh8Y5AGMPJ8cGdZC1yo2Lgah+D +IhXdsd72Wp7MhdntJAyPrMCDBfDrFiuj6YHDgt3OhPQSYl7EWG7QjFK3B2sp1K5D +h16G5zfwUKDj9Jp3xuPGuqNFQHL02nwbhtDilqHvaTfOJKVjsFCoU8Z77mfwXSwn +sPXpPB7oOO4mWfAtcwU11rTMiHFSGFlFhgbHULU/y90DcpfRQEpEiBoiK13gkyoP +zHT9WAg3Pelwb6K7c7kJ7mp4axhbf7MkwFhDQIjbBWqus2Eu3b0mf86ALfDbAaNC +wBi8xbNH2vWaDjiwLDY5uMZDAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwICBDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQV1YOR+Jpl1fbujvWLSBEoRvsDhTANBgkq +hkiG9w0BAQsFAAOCAQEAaXmM29TYkFUzZUsV7TSonAK560BjxDmbg0GJSUgLEFUJ +wpKqa9UKOSapG45LEeR2wwAmVWDJomJplkuvTD/KOabAbZKyPEfp+VMCaBUnILQF +Cxv5m7kQ3wmPS/rEL8FD809UGowW9cYqnZzUy5i/r263rx0k3OPjkkZN66Mh6+3H +ibNdaxf7ITO0JVb/Ohq9vLC9qf7ujiB1atMdJwkOWsZrLJXLygpx/D0/UhBT4fFH +OlyVOmuR27qaMbPgOs2l8DznkJY/QUfnET8iOQhFgb0Dt/Os4PYFhSDRIrgl5dJ7 +L/zZVQfZYpdxlBHJlDC1/NzVQl/1MgDnSgPGStZKPQ== +-----END CERTIFICATE----- diff --git a/tonic-examples/src/tls_client_auth/client.rs b/tonic-examples/src/tls_client_auth/client.rs new file mode 100644 index 000000000..8864e7738 --- /dev/null +++ b/tonic-examples/src/tls_client_auth/client.rs @@ -0,0 +1,38 @@ +pub mod pb { + tonic::include_proto!("grpc.examples.echo"); +} + +use pb::{client::EchoClient, EchoRequest}; +use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let server_root_ca_cert = tokio::fs::read("tonic-examples/data/tls/ca.pem").await?; + let server_root_ca_cert = Certificate::from_pem(server_root_ca_cert); + let client_cert = tokio::fs::read("tonic-examples/data/tls/client1.pem").await?; + let client_key = tokio::fs::read("tonic-examples/data/tls/client1.key").await?; + let client_identity = Identity::from_pem(client_cert, client_key); + + let tls = ClientTlsConfig::with_openssl() + .domain_name("localhost") + .ca_certificate(server_root_ca_cert) + .identity(client_identity) + .clone(); + + let channel = Channel::from_static("http://[::1]:50051") + .tls_config(&tls) + .clone() + .channel(); + + let mut client = EchoClient::new(channel); + + let request = tonic::Request::new(EchoRequest { + message: "hello".into(), + }); + + let response = client.unary_echo(request).await?; + + println!("RESPONSE={:?}", response); + + Ok(()) +} diff --git a/tonic-examples/src/tls_client_auth/server.rs b/tonic-examples/src/tls_client_auth/server.rs new file mode 100644 index 000000000..1dddf98de --- /dev/null +++ b/tonic-examples/src/tls_client_auth/server.rs @@ -0,0 +1,52 @@ +pub mod pb { + tonic::include_proto!("grpc.examples.echo"); +} + +use std::collections::VecDeque; + +use pb::{EchoRequest, EchoResponse}; +use tonic::transport::{Certificate, Identity, Server, ServerTlsConfig}; +use tonic::{Request, Response, Status}; + +type EchoResult = Result, Status>; +type Stream = VecDeque>; + +#[derive(Default)] +pub struct EchoServer; + +#[tonic::async_trait] +impl pb::server::Echo for EchoServer { + async fn unary_echo(&self, request: Request) -> EchoResult { + let message = request.into_inner().message; + Ok(Response::new(EchoResponse { message })) + } + + type ServerStreamingEchoStream = Stream; + type BidirectionalStreamingEchoStream = Stream; +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let cert = tokio::fs::read("tonic-examples/data/tls/server.pem").await?; + let key = tokio::fs::read("tonic-examples/data/tls/server.key").await?; + let server_identity = Identity::from_pem(cert, key); + + let client_ca_cert = tokio::fs::read("tonic-examples/data/tls/client_ca.pem").await?; + let client_ca_cert = Certificate::from_pem(client_ca_cert); + + let addr = "[::1]:50051".parse().unwrap(); + let server = EchoServer::default(); + + let tls = ServerTlsConfig::with_rustls() + .identity(server_identity) + .client_ca_root(client_ca_cert) + .clone(); + + Server::builder() + .tls_config(&tls) + .clone() + .serve(addr, pb::server::EchoServer::new(server)) + .await?; + + Ok(()) +} diff --git a/tonic/src/transport/endpoint.rs b/tonic/src/transport/endpoint.rs index 2d3de4387..b2112c709 100644 --- a/tonic/src/transport/endpoint.rs +++ b/tonic/src/transport/endpoint.rs @@ -2,7 +2,7 @@ use super::channel::Channel; #[cfg(feature = "tls")] use super::{ service::TlsConnector, - tls::{Certificate, TlsProvider}, + tls::{Certificate, Identity, TlsProvider}, }; use bytes::Bytes; use http::uri::{InvalidUriBytes, Uri}; @@ -212,6 +212,7 @@ pub struct ClientTlsConfig { provider: TlsProvider, domain: Option, cert: Option, + identity: Option, #[cfg(feature = "openssl")] openssl_raw: Option, #[cfg(feature = "rustls")] @@ -223,6 +224,9 @@ impl fmt::Debug for ClientTlsConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ClientTlsConfig") .field("provider", &self.provider) + .field("domain", &self.domain) + .field("cert", &self.cert) + .field("identity", &self.identity) .finish() } } @@ -246,6 +250,7 @@ impl ClientTlsConfig { provider, domain: None, cert: None, + identity: None, #[cfg(feature = "openssl")] openssl_raw: None, #[cfg(feature = "rustls")] @@ -265,6 +270,12 @@ impl ClientTlsConfig { self } + /// Sets the client identity to present to the server. + pub fn identity(&mut self, identity: Identity) -> &mut Self { + self.identity = Some(identity); + self + } + /// Use options specified by the given `SslConnector` to configure TLS. /// /// This overrides all other TLS options set via other means. @@ -294,12 +305,20 @@ impl ClientTlsConfig { match self.provider { #[cfg(feature = "openssl")] TlsProvider::OpenSsl => match &self.openssl_raw { - None => TlsConnector::new_with_openssl_cert(self.cert.clone(), domain), + None => TlsConnector::new_with_openssl_cert( + self.cert.clone(), + self.identity.clone(), + domain, + ), Some(r) => TlsConnector::new_with_openssl_raw(r.clone(), domain), }, #[cfg(feature = "rustls")] TlsProvider::Rustls => match &self.rustls_raw { - None => TlsConnector::new_with_rustls_cert(self.cert.clone(), domain), + None => TlsConnector::new_with_rustls_cert( + self.cert.clone(), + self.identity.clone(), + domain, + ), Some(c) => TlsConnector::new_with_rustls_raw(c.clone(), domain), }, } diff --git a/tonic/src/transport/server.rs b/tonic/src/transport/server.rs index 74dae0055..702571c4f 100644 --- a/tonic/src/transport/server.rs +++ b/tonic/src/transport/server.rs @@ -5,6 +5,7 @@ use super::service::{layer_fn, BoxedIo, ServiceBuilderExt}; use super::{ service::TlsAcceptor, tls::{Identity, TlsProvider}, + Certificate, }; use crate::body::BoxBody; use futures_core::Stream; @@ -225,6 +226,7 @@ impl fmt::Debug for Server { pub struct ServerTlsConfig { provider: TlsProvider, identity: Option, + client_ca_root: Option, #[cfg(feature = "openssl")] openssl_raw: Option, #[cfg(feature = "rustls")] @@ -260,6 +262,7 @@ impl ServerTlsConfig { ServerTlsConfig { provider, identity: None, + client_ca_root: None, #[cfg(feature = "openssl")] openssl_raw: None, #[cfg(feature = "rustls")] @@ -273,6 +276,12 @@ impl ServerTlsConfig { self } + /// Sets a certificate against which to validate client TLS certificates. + pub fn client_ca_root(&mut self, cert: Certificate) -> &mut Self { + self.client_ca_root = Some(cert); + self + } + /// Use options specified by the given `SslAcceptor` to configure TLS. /// /// This overrides all other TLS options set via other means. @@ -298,12 +307,18 @@ impl ServerTlsConfig { match self.provider { #[cfg(feature = "openssl")] TlsProvider::OpenSsl => match &self.openssl_raw { - None => TlsAcceptor::new_with_openssl_identity(self.identity.clone().unwrap()), + None => TlsAcceptor::new_with_openssl_identity( + self.identity.clone().unwrap(), + self.client_ca_root.clone(), + ), Some(acceptor) => TlsAcceptor::new_with_openssl_raw(acceptor.clone()), }, #[cfg(feature = "rustls")] TlsProvider::Rustls => match &self.rustls_raw { - None => TlsAcceptor::new_with_rustls_identity(self.identity.clone().unwrap()), + None => TlsAcceptor::new_with_rustls_identity( + self.identity.clone().unwrap(), + self.client_ca_root.clone(), + ), Some(config) => TlsAcceptor::new_with_rustls_raw(config.clone()), }, } diff --git a/tonic/src/transport/service/tls.rs b/tonic/src/transport/service/tls.rs index ab3f5b9d6..22f4cb44e 100644 --- a/tonic/src/transport/service/tls.rs +++ b/tonic/src/transport/service/tls.rs @@ -3,14 +3,14 @@ use crate::transport::{Certificate, Identity}; #[cfg(feature = "openssl")] use openssl1::{ pkey::PKey, - ssl::{select_next_proto, AlpnError, SslAcceptor, SslConnector, SslMethod}, - x509::X509, + ssl::{select_next_proto, AlpnError, SslAcceptor, SslConnector, SslMethod, SslVerifyMode}, + x509::{store::X509StoreBuilder, X509}, }; use std::{fmt, sync::Arc}; use tokio::net::TcpStream; #[cfg(feature = "rustls")] use tokio_rustls::{ - rustls::{internal::pemfile, ClientConfig, NoClientAuth, PrivateKey, ServerConfig, Session}, + rustls::{ClientConfig, NoClientAuth, ServerConfig, Session}, webpki::DNSNameRef, TlsAcceptor as RustlsAcceptor, TlsConnector as RustlsConnector, }; @@ -58,6 +58,7 @@ impl TlsConnector { #[cfg(feature = "openssl")] pub(crate) fn new_with_openssl_cert( cert: Option, + identity: Option, domain: String, ) -> Result { let mut config = SslConnector::builder(SslMethod::tls())?; @@ -68,6 +69,13 @@ impl TlsConnector { config.cert_store_mut().add_cert(ca)?; } + if let Some(identity) = identity { + let key = PKey::private_key_from_pem(&identity.key[..])?; + let cert = X509::from_pem(&identity.cert.pem[..])?; + config.set_certificate(&cert)?; + config.set_private_key(&key)?; + } + Ok(Self { inner: Connector::Openssl(config.build()), domain: Arc::new(domain), @@ -87,14 +95,19 @@ impl TlsConnector { #[cfg(feature = "rustls")] pub(crate) fn new_with_rustls_cert( - cert: Option, + ca_cert: Option, + identity: Option, domain: String, ) -> Result { let mut config = ClientConfig::new(); config.set_protocols(&[Vec::from(&ALPN_H2[..])]); - if cert.is_some() { - let cert = cert.unwrap(); + if let Some(identity) = identity { + let (client_cert, client_key) = rustls_keys::load_identity(identity)?; + config.set_single_client_cert(client_cert, client_key); + } + + if let Some(cert) = ca_cert { let mut buf = std::io::Cursor::new(&cert.pem[..]); config.root_store.add_pem_file(&mut buf).unwrap(); } @@ -192,7 +205,10 @@ enum Acceptor { impl TlsAcceptor { #[cfg(feature = "openssl")] - pub(crate) fn new_with_openssl_identity(identity: Identity) -> Result { + pub(crate) fn new_with_openssl_identity( + identity: Identity, + client_ca_root: Option, + ) -> Result { let key = PKey::private_key_from_pem(&identity.key[..])?; let cert = X509::from_pem(&identity.cert.pem[..])?; @@ -205,6 +221,16 @@ impl TlsAcceptor { select_next_proto(ALPN_H2_WIRE, alpn).ok_or(AlpnError::NOACK) }); + if let Some(cert) = client_ca_root { + let ca_cert = X509::from_pem(&cert.pem[..])?; + let mut store = X509StoreBuilder::new()?; + store.add_cert(ca_cert.clone())?; + + config.add_client_ca(&ca_cert)?; + config.set_verify_cert_store(store.build())?; + config.set_verify(SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT); + } + Ok(Self { inner: Acceptor::Openssl(config.build()), }) @@ -220,50 +246,28 @@ impl TlsAcceptor { } #[cfg(feature = "rustls")] - fn load_rustls_private_key( - mut cursor: std::io::Cursor<&[u8]>, - ) -> Result { - // First attempt to load the private key assuming it is PKCS8-encoded - if let Ok(mut keys) = pemfile::pkcs8_private_keys(&mut cursor) { - if keys.len() > 0 { - return Ok(keys.remove(0)); - } - } - - // If it not, try loading the private key as an RSA key - cursor.set_position(0); - if let Ok(mut keys) = pemfile::rsa_private_keys(&mut cursor) { - if keys.len() > 0 { - return Ok(keys.remove(0)); - } - } + pub(crate) fn new_with_rustls_identity( + identity: Identity, + client_ca_root: Option, + ) -> Result { + let (cert, key) = rustls_keys::load_identity(identity)?; - // Otherwise we have a Private Key parsing problem - Err(Box::new(TlsError::PrivateKeyParseError)) - } + let mut config = match client_ca_root { + None => ServerConfig::new(NoClientAuth::new()), + Some(cert) => { + let mut cert = std::io::Cursor::new(&cert.pem[..]); - #[cfg(feature = "rustls")] - pub(crate) fn new_with_rustls_identity(identity: Identity) -> Result { - let cert = { - let mut cert = std::io::Cursor::new(&identity.cert.pem[..]); - match pemfile::certs(&mut cert) { - Ok(certs) => certs, - Err(_) => return Err(Box::new(TlsError::CertificateParseError)), - } - }; + let mut client_root_cert_store = tokio_rustls::rustls::RootCertStore::empty(); + match client_root_cert_store.add_pem_file(&mut cert) { + Err(_) => return Err(Box::new(TlsError::CertificateParseError)), + _ => (), + }; - let key = { - let key = std::io::Cursor::new(&identity.key[..]); - match Self::load_rustls_private_key(key) { - Ok(key) => key, - Err(e) => { - return Err(e); - } + let client_auth = + tokio_rustls::rustls::AllowAnyAuthenticatedClient::new(client_root_cert_store); + ServerConfig::new(client_auth) } }; - - let mut config = ServerConfig::new(NoClientAuth::new()); - config.set_single_cert(cert, key)?; config.set_protocols(&[Vec::from(&ALPN_H2[..])]); @@ -338,3 +342,57 @@ impl fmt::Display for TlsError { } impl std::error::Error for TlsError {} + +#[cfg(feature = "rustls")] +mod rustls_keys { + use tokio_rustls::rustls::{internal::pemfile, Certificate, PrivateKey}; + + use crate::transport::service::tls::TlsError; + use crate::transport::Identity; + + fn load_rustls_private_key( + mut cursor: std::io::Cursor<&[u8]>, + ) -> Result { + // First attempt to load the private key assuming it is PKCS8-encoded + if let Ok(mut keys) = pemfile::pkcs8_private_keys(&mut cursor) { + if keys.len() > 0 { + return Ok(keys.remove(0)); + } + } + + // If it not, try loading the private key as an RSA key + cursor.set_position(0); + if let Ok(mut keys) = pemfile::rsa_private_keys(&mut cursor) { + if keys.len() > 0 { + return Ok(keys.remove(0)); + } + } + + // Otherwise we have a Private Key parsing problem + Err(Box::new(TlsError::PrivateKeyParseError)) + } + + pub(crate) fn load_identity( + identity: Identity, + ) -> Result<(Vec, PrivateKey), crate::Error> { + let cert = { + let mut cert = std::io::Cursor::new(&identity.cert.pem[..]); + match pemfile::certs(&mut cert) { + Ok(certs) => certs, + Err(_) => return Err(Box::new(TlsError::CertificateParseError)), + } + }; + + let key = { + let key = std::io::Cursor::new(&identity.key[..]); + match load_rustls_private_key(key) { + Ok(key) => key, + Err(e) => { + return Err(e); + } + } + }; + + Ok((cert, key)) + } +}