feat(transport): Add support client mTLS (#77)
This commit adds a simple API for specifying the TLS certificate a GRPC client will present (via the same `Identity` wrapper as a server cert is configured). It also adds an API to specify which CA certificate client TLS certificates will be validated against for servers.
This commit is contained in:
committed by
Lucio Franco
parent
9079e0f66b
commit
335a373a40
@@ -44,6 +44,14 @@ path = "src/tls/client.rs"
|
|||||||
name = "tls-server"
|
name = "tls-server"
|
||||||
path = "src/tls/server.rs"
|
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]
|
[dependencies]
|
||||||
tonic = { path = "../tonic", features = ["rustls"] }
|
tonic = { path = "../tonic", features = ["rustls"] }
|
||||||
bytes = "0.4"
|
bytes = "0.4"
|
||||||
|
|||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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-----
|
||||||
@@ -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<dyn std::error::Error>> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
@@ -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<T> = Result<Response<T>, Status>;
|
||||||
|
type Stream = VecDeque<Result<EchoResponse, Status>>;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct EchoServer;
|
||||||
|
|
||||||
|
#[tonic::async_trait]
|
||||||
|
impl pb::server::Echo for EchoServer {
|
||||||
|
async fn unary_echo(&self, request: Request<EchoRequest>) -> EchoResult<EchoResponse> {
|
||||||
|
let message = request.into_inner().message;
|
||||||
|
Ok(Response::new(EchoResponse { message }))
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerStreamingEchoStream = Stream;
|
||||||
|
type BidirectionalStreamingEchoStream = Stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ use super::channel::Channel;
|
|||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
use super::{
|
use super::{
|
||||||
service::TlsConnector,
|
service::TlsConnector,
|
||||||
tls::{Certificate, TlsProvider},
|
tls::{Certificate, Identity, TlsProvider},
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http::uri::{InvalidUriBytes, Uri};
|
use http::uri::{InvalidUriBytes, Uri};
|
||||||
@@ -212,6 +212,7 @@ pub struct ClientTlsConfig {
|
|||||||
provider: TlsProvider,
|
provider: TlsProvider,
|
||||||
domain: Option<String>,
|
domain: Option<String>,
|
||||||
cert: Option<Certificate>,
|
cert: Option<Certificate>,
|
||||||
|
identity: Option<Identity>,
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
openssl_raw: Option<openssl1::ssl::SslConnector>,
|
openssl_raw: Option<openssl1::ssl::SslConnector>,
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
@@ -223,6 +224,9 @@ impl fmt::Debug for ClientTlsConfig {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("ClientTlsConfig")
|
f.debug_struct("ClientTlsConfig")
|
||||||
.field("provider", &self.provider)
|
.field("provider", &self.provider)
|
||||||
|
.field("domain", &self.domain)
|
||||||
|
.field("cert", &self.cert)
|
||||||
|
.field("identity", &self.identity)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,6 +250,7 @@ impl ClientTlsConfig {
|
|||||||
provider,
|
provider,
|
||||||
domain: None,
|
domain: None,
|
||||||
cert: None,
|
cert: None,
|
||||||
|
identity: None,
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
openssl_raw: None,
|
openssl_raw: None,
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
@@ -265,6 +270,12 @@ impl ClientTlsConfig {
|
|||||||
self
|
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.
|
/// Use options specified by the given `SslConnector` to configure TLS.
|
||||||
///
|
///
|
||||||
/// This overrides all other TLS options set via other means.
|
/// This overrides all other TLS options set via other means.
|
||||||
@@ -294,12 +305,20 @@ impl ClientTlsConfig {
|
|||||||
match self.provider {
|
match self.provider {
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
TlsProvider::OpenSsl => match &self.openssl_raw {
|
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),
|
Some(r) => TlsConnector::new_with_openssl_raw(r.clone(), domain),
|
||||||
},
|
},
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
TlsProvider::Rustls => match &self.rustls_raw {
|
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),
|
Some(c) => TlsConnector::new_with_rustls_raw(c.clone(), domain),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use super::service::{layer_fn, BoxedIo, ServiceBuilderExt};
|
|||||||
use super::{
|
use super::{
|
||||||
service::TlsAcceptor,
|
service::TlsAcceptor,
|
||||||
tls::{Identity, TlsProvider},
|
tls::{Identity, TlsProvider},
|
||||||
|
Certificate,
|
||||||
};
|
};
|
||||||
use crate::body::BoxBody;
|
use crate::body::BoxBody;
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
@@ -225,6 +226,7 @@ impl fmt::Debug for Server {
|
|||||||
pub struct ServerTlsConfig {
|
pub struct ServerTlsConfig {
|
||||||
provider: TlsProvider,
|
provider: TlsProvider,
|
||||||
identity: Option<Identity>,
|
identity: Option<Identity>,
|
||||||
|
client_ca_root: Option<Certificate>,
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
openssl_raw: Option<openssl1::ssl::SslAcceptor>,
|
openssl_raw: Option<openssl1::ssl::SslAcceptor>,
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
@@ -260,6 +262,7 @@ impl ServerTlsConfig {
|
|||||||
ServerTlsConfig {
|
ServerTlsConfig {
|
||||||
provider,
|
provider,
|
||||||
identity: None,
|
identity: None,
|
||||||
|
client_ca_root: None,
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
openssl_raw: None,
|
openssl_raw: None,
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
@@ -273,6 +276,12 @@ impl ServerTlsConfig {
|
|||||||
self
|
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.
|
/// Use options specified by the given `SslAcceptor` to configure TLS.
|
||||||
///
|
///
|
||||||
/// This overrides all other TLS options set via other means.
|
/// This overrides all other TLS options set via other means.
|
||||||
@@ -298,12 +307,18 @@ impl ServerTlsConfig {
|
|||||||
match self.provider {
|
match self.provider {
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
TlsProvider::OpenSsl => match &self.openssl_raw {
|
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()),
|
Some(acceptor) => TlsAcceptor::new_with_openssl_raw(acceptor.clone()),
|
||||||
},
|
},
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
TlsProvider::Rustls => match &self.rustls_raw {
|
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()),
|
Some(config) => TlsAcceptor::new_with_rustls_raw(config.clone()),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ use crate::transport::{Certificate, Identity};
|
|||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
use openssl1::{
|
use openssl1::{
|
||||||
pkey::PKey,
|
pkey::PKey,
|
||||||
ssl::{select_next_proto, AlpnError, SslAcceptor, SslConnector, SslMethod},
|
ssl::{select_next_proto, AlpnError, SslAcceptor, SslConnector, SslMethod, SslVerifyMode},
|
||||||
x509::X509,
|
x509::{store::X509StoreBuilder, X509},
|
||||||
};
|
};
|
||||||
use std::{fmt, sync::Arc};
|
use std::{fmt, sync::Arc};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
use tokio_rustls::{
|
use tokio_rustls::{
|
||||||
rustls::{internal::pemfile, ClientConfig, NoClientAuth, PrivateKey, ServerConfig, Session},
|
rustls::{ClientConfig, NoClientAuth, ServerConfig, Session},
|
||||||
webpki::DNSNameRef,
|
webpki::DNSNameRef,
|
||||||
TlsAcceptor as RustlsAcceptor, TlsConnector as RustlsConnector,
|
TlsAcceptor as RustlsAcceptor, TlsConnector as RustlsConnector,
|
||||||
};
|
};
|
||||||
@@ -58,6 +58,7 @@ impl TlsConnector {
|
|||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
pub(crate) fn new_with_openssl_cert(
|
pub(crate) fn new_with_openssl_cert(
|
||||||
cert: Option<Certificate>,
|
cert: Option<Certificate>,
|
||||||
|
identity: Option<Identity>,
|
||||||
domain: String,
|
domain: String,
|
||||||
) -> Result<Self, crate::Error> {
|
) -> Result<Self, crate::Error> {
|
||||||
let mut config = SslConnector::builder(SslMethod::tls())?;
|
let mut config = SslConnector::builder(SslMethod::tls())?;
|
||||||
@@ -68,6 +69,13 @@ impl TlsConnector {
|
|||||||
config.cert_store_mut().add_cert(ca)?;
|
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 {
|
Ok(Self {
|
||||||
inner: Connector::Openssl(config.build()),
|
inner: Connector::Openssl(config.build()),
|
||||||
domain: Arc::new(domain),
|
domain: Arc::new(domain),
|
||||||
@@ -87,14 +95,19 @@ impl TlsConnector {
|
|||||||
|
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
pub(crate) fn new_with_rustls_cert(
|
pub(crate) fn new_with_rustls_cert(
|
||||||
cert: Option<Certificate>,
|
ca_cert: Option<Certificate>,
|
||||||
|
identity: Option<Identity>,
|
||||||
domain: String,
|
domain: String,
|
||||||
) -> Result<Self, crate::Error> {
|
) -> Result<Self, crate::Error> {
|
||||||
let mut config = ClientConfig::new();
|
let mut config = ClientConfig::new();
|
||||||
config.set_protocols(&[Vec::from(&ALPN_H2[..])]);
|
config.set_protocols(&[Vec::from(&ALPN_H2[..])]);
|
||||||
|
|
||||||
if cert.is_some() {
|
if let Some(identity) = identity {
|
||||||
let cert = cert.unwrap();
|
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[..]);
|
let mut buf = std::io::Cursor::new(&cert.pem[..]);
|
||||||
config.root_store.add_pem_file(&mut buf).unwrap();
|
config.root_store.add_pem_file(&mut buf).unwrap();
|
||||||
}
|
}
|
||||||
@@ -192,7 +205,10 @@ enum Acceptor {
|
|||||||
|
|
||||||
impl TlsAcceptor {
|
impl TlsAcceptor {
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
pub(crate) fn new_with_openssl_identity(identity: Identity) -> Result<Self, crate::Error> {
|
pub(crate) fn new_with_openssl_identity(
|
||||||
|
identity: Identity,
|
||||||
|
client_ca_root: Option<Certificate>,
|
||||||
|
) -> Result<Self, crate::Error> {
|
||||||
let key = PKey::private_key_from_pem(&identity.key[..])?;
|
let key = PKey::private_key_from_pem(&identity.key[..])?;
|
||||||
let cert = X509::from_pem(&identity.cert.pem[..])?;
|
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)
|
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 {
|
Ok(Self {
|
||||||
inner: Acceptor::Openssl(config.build()),
|
inner: Acceptor::Openssl(config.build()),
|
||||||
})
|
})
|
||||||
@@ -220,50 +246,28 @@ impl TlsAcceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "rustls")]
|
#[cfg(feature = "rustls")]
|
||||||
fn load_rustls_private_key(
|
pub(crate) fn new_with_rustls_identity(
|
||||||
mut cursor: std::io::Cursor<&[u8]>,
|
identity: Identity,
|
||||||
) -> Result<PrivateKey, crate::Error> {
|
client_ca_root: Option<Certificate>,
|
||||||
// First attempt to load the private key assuming it is PKCS8-encoded
|
) -> Result<Self, crate::Error> {
|
||||||
if let Ok(mut keys) = pemfile::pkcs8_private_keys(&mut cursor) {
|
let (cert, key) = rustls_keys::load_identity(identity)?;
|
||||||
if keys.len() > 0 {
|
|
||||||
return Ok(keys.remove(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it not, try loading the private key as an RSA key
|
let mut config = match client_ca_root {
|
||||||
cursor.set_position(0);
|
None => ServerConfig::new(NoClientAuth::new()),
|
||||||
if let Ok(mut keys) = pemfile::rsa_private_keys(&mut cursor) {
|
Some(cert) => {
|
||||||
if keys.len() > 0 {
|
let mut cert = std::io::Cursor::new(&cert.pem[..]);
|
||||||
return Ok(keys.remove(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise we have a Private Key parsing problem
|
let mut client_root_cert_store = tokio_rustls::rustls::RootCertStore::empty();
|
||||||
Err(Box::new(TlsError::PrivateKeyParseError))
|
match client_root_cert_store.add_pem_file(&mut cert) {
|
||||||
}
|
Err(_) => return Err(Box::new(TlsError::CertificateParseError)),
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "rustls")]
|
let client_auth =
|
||||||
pub(crate) fn new_with_rustls_identity(identity: Identity) -> Result<Self, crate::Error> {
|
tokio_rustls::rustls::AllowAnyAuthenticatedClient::new(client_root_cert_store);
|
||||||
let cert = {
|
ServerConfig::new(client_auth)
|
||||||
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 Self::load_rustls_private_key(key) {
|
|
||||||
Ok(key) => key,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut config = ServerConfig::new(NoClientAuth::new());
|
|
||||||
|
|
||||||
config.set_single_cert(cert, key)?;
|
config.set_single_cert(cert, key)?;
|
||||||
config.set_protocols(&[Vec::from(&ALPN_H2[..])]);
|
config.set_protocols(&[Vec::from(&ALPN_H2[..])]);
|
||||||
|
|
||||||
@@ -338,3 +342,57 @@ impl fmt::Display for TlsError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error 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<PrivateKey, crate::Error> {
|
||||||
|
// 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<Certificate>, 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user