Rustls UnknownIssuer with own CA

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(())

On the client side?

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 :man_shrugging: .

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.