Newbie to Rust but have implemented mTLS in several other languages previously.
My ultimate objective is to get a Rust gRPC client to talk to an existing (production) Python gRPC server using mTLS. I have spent a number of hours banging my head against TLS errors. Generated a fresh x509v3 stack as below, getting invalid peer certificate: UnknownIssuer
. For some reason it isn't liking the (self-signed) CA which I signed the client and/or server certificate with. Help please!
Certs stack:
- In /etc/ssl/openssl.cnf add:
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical,CA:true
nsComment = "OpenSSL Generated Certificate"
- openssl req -nodes -days 3650 -new -x509 -keyout ca-v3.key -out ca-v3.crt -extensions "v3_ca"
- openssl x509 -outform pem -in ca-v3.crt -out ca-v3.pem
- In v3.ext put:
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
- openssl req -nodes -days 3650 -new -keyout localhost-v3.key -out localhost-v3.csr
- openssl x509 -req -days 3650 -in localhost-v3.csr -CA ca-v3.pem -CAkey ca-v3.key -CAcreateserial -extfile v3.ext -out localhost-v3.crt
- openssl x509 -outform pem -in localhost-v3.crt -out localhost-v3.pem
- openssl verify -verbose -CAfile ca-v3.pem localhost-v3.pem
client
let id: Identity = Identity::from_pem(include_str!("tls/localhost.pem").as_bytes(),
include_str!("tls/localhost.key").as_bytes());
let ca: Certificate = Certificate::from_pem(include_str!("tls/ca.pem").as_bytes());
let tls_config: ClientTlsConfig = ClientTlsConfig::new()
.identity(id)
.ca_certificate(ca);
let channel = Channel::from_static("http://localhost:50051")
.tls_config(tls_config)
.unwrap()
.connect()
.await?;
server
let id: Identity = Identity::from_pem(include_str!("tls/localhost-v3.pem").as_bytes(),
include_str!("tls/localhost-v3.key").as_bytes());
let ca: Certificate = Certificate::from_pem(include_str!("tls/ca-v3.pem").as_bytes());
let tls_config: ServerTlsConfig = ServerTlsConfig::new()
.identity(id)
.client_ca_root(ca);
Server::builder()
.tls_config(tls_config)
.unwrap()
.add_service(GreeterServer::new(MyGreeter::default()))
.serve("[::1]:50051".parse()?)
.await?;
Ok(())
What library are you using? It looks like those tls config types aren't directly from rustls
On the client side?
Yes. I'm getting zero logging server-side and I haven't figured out how to enable more verbose logging. I think the issue is with the client validating the server certificate - it is signed by the CA which is being passed into the client TLS config so in theory it should work:
% openssl verify -verbose -CAfile ca-v3.pem localhost-v3.pem
localhost-v3.pem: OK
What library are you using? It looks like those tls config types aren't directly from rustls
Correct, I'm using tonic
, which is a gRPC library using rustls
under the hood. I have, however, used the raw rustls client tlsclient-mio
and got the same result:
cargo run --bin tlsclient-mio -- -p 50051 --auth-key localhost-v3.key --auth-certs localhost-v3.pem --http --verbose --cafile ca-v3.pem localhost
...
[2023-02-01T14:48:23Z WARN rustls::conn] Sending fatal alert BadCertificate
TLS error: InvalidCertficate(UnknownIssuer)
(which also doesn't work if I point it at the proven Python server using the same cert chain)
I think from a rustls perspective you need to add your self signed CA as a trusted root as shown in the getting started section on the rustls docs with the RootCertStore
I'm not sure how that interacts with tonic though
1 Like
Thanks @semicoleon. Your suggestion isn't the solution but it led me down a useful path, which I'll document here in case it's useful to anyone. tonic
is ultimately wrapping RootCertStore
when it sets up its config. I stepped through tonic, tokio-rustls, and rustls to verify that the client was indeed checking the certificate it received from the server against the CA cert that I specified. I believe I am correctly configuring the client/server in Rust.
This then led me to find that the original error being thrown was UnsupportedSignatureAlgorithm
, which is getting swallowed further up the chain and being displayed, somewhat unhelpfully, as UnknownIssuer
. Further digging suggests that my homespun CA is signing with an algorithm which rustls doesn't support (rustls/verify.rs at 411a65d7367bed6e5c90ed4bfa000334536e68ae · rustls/rustls · GitHub).
I haven't yet figured out the right invocation to openssl
to produce a signed cert which satisfies this check, but I'm working on it (help welcomed :-).
5 Likes
You might try specifying the generated key type and size to make sure that matches a supported option. You could also try the -digest
flag on req
. Unfortunately it's been awhile since I've used the openssl CLI so that's all I've got
I've given up trying to modify my openssl CA stack to satisfy rustls, after burning yet more time on that. I've instead picked up their example stack from rustls/test-ca at main · rustls/rustls · GitHub and started using that, and the client & server are happily communicating. I haven't yet attempted to figure out why that stack succeeds where mine fails
.
1 Like