randombit / botan

Cryptography Toolkit
https://botan.randombit.net
BSD 2-Clause "Simplified" License
2.59k stars 568 forks source link

`./botan tls_server` is lacking a `--trusted-cas=` argument #4026

Open shilohshi opened 7 months ago

shilohshi commented 7 months ago

I'm trying to start a TLS server that requires the client to send certificates. I'm launching it using the following command:

botan tls_server server_cert.pem server_key.pem --port=443 --policy=policy.conf

where the content of policy.conf is:

require_client_certificate_authentication = true

Then I try to use the following command to let the client connect to the server:

botan tls_client 127.0.0.1 --port=443 --client-cert=client_cert.pem --client-cert-key=client_key.pem

At this point, both the client and the server are indicating that the client did not send a certificate:

//client
Certificate validation status: Cannot establish trust
Handshake complete, TLS v1.3 using CHACHA20_POLY1305_SHA256
Session ID 661E905B7D66DE48A1E04BFABFE9587B8C6D3E86DC1F1E459456733D89933D9C
Handshake complete
Alert: certificate_required

//server
TLS connection failed Policy requires client send a certificate, but it did not

I'd like to inquire whether my usage is not correct or if there's an issue with the corresponding implementation. Many thanks.

reneme commented 6 months ago

You're using it correctly. The problem lies in the botan CLI tool that, unfortunately, does not fully support client certificates at the moment.

You can enable it via the require_client_certificate_authentication = true policy setting, however, there's currently no way to specify one or more "trusted CAs" when starting a ./botan tls_server. A (hypothetical) ./botan tls_server --trusted-cas= argument would be needed here.

As a result, the server will request client-auth during the handshake (because of the policy setting), but won't indicate any acceptable CA. Additionally, the ./botan tls_client currently relies on this server-provided list of acceptable CAs to decide which client certificate to present. For an empty list, it will simply claim that it doesn't have a matching client certificate. The result is the behavior you describe.

Note, however, that the limitation is within Botan's CLI tool. The library does support client authentication even if a server does not indicate their acceptable CAs. Typically, users would override the Credentials_Manager::find_cert_chain() callback (also linked above) and add their client certificate selection logic as they see fit.

shilohshi commented 6 months ago

Many thanks for your explanation, hope this feature can be added in the future.

shilohshi commented 6 months ago

Hi, if I want to build a project that validates client certificates based on this project: https://github.com/reneme/botan-tls-testserver

which part of this project do I need to modify to achieve this? Thanks.

reneme commented 6 months ago

Note to self: we should add an example showcasing a server with client authentication.

Anyway, until then:

1. Extend Basic_Credentials_Manager

Add a member variable Botan::Certificate_Store_In_Memory m_trusted_roots and fill this with the issuer certificate(s) your server is supposed to trust, using .add_certificate(). You can do that in the constructor of Basic_Credentials_Manager, for instance. Right there, you'll also find some code to load certificates from PEM files.

Then, add an override of trusted_certificate_authorities(), roughly like that:

std::vector<Botan::Certificate_Store*> trusted_certificate_authorities(const std::string& type,
                                                                       const std::string& hostname) override {
   if(type == "tls-server") {
      return {&m_trusted_roots};
   } else {
      return {};
   }
}

2. Enable require_client_certificate_authentication in the Policy

In the relevant policy file (passed via ./testserver --policy), enable client authentication. Like you did before for the CLI tool:

require_client_certificate_authentication = true

3. Connect

Your server should now mandate client authentication.

4. (Bonus) Certificate Validation

In case you need fine-grained access to the client certificate validation your server is performing, call tls_ctx->set_verify_callback() in the do_listen() co-routine. Note, though, that no automatic certificate path validation will be done by the library in this case.

tls_ctx->set_verify_callback([](const std::vector<Botan::X509_Certificate>& cert_chain,
                                const std::vector<std::optional<Botan::OCSP::Response>>& ocsp_responses,
                                const std::vector<Botan::Certificate_Store*>& trusted_roots,
                                Botan::Usage_Type usage,
                                std::string_view hostname,
                                const Botan::TLS::Policy& policy) {
   // Check whatever you need here, and throw an exception if you're not
   // happy with the certificate chain presented by the client.
});
shilohshi commented 6 months ago

Hi, I followed the steps above to operate and recompile, but during testing, it still prompts Alert: certificate_required as usual. Would you mind upload a complete modified version or a website like https://pq.botan-crypto.org/ but built using this method to enable client cert authentication? Thank you very much.

reneme commented 6 months ago

Here's a complete example that worked for me.

1. Create Test Certificates

Here's a script that generates a some test certificates (using Botan's CLI tool):

./botan keygen --algo=ECDSA --params=secp256r1 --output=ca_priv.pem
./botan keygen --algo=ECDSA --params=secp256r1 --output=server_priv.pem
./botan keygen --algo=ECDSA --params=secp256r1 --output=client_priv.pem

./botan gen_self_signed ca_priv.pem "CA" --ca --country=DE --dns=ca.example --hash=SHA-384 --output=ca_cert.pem

./botan gen_pkcs10 server_priv.pem localhost --output=server_csr.pem
./botan gen_pkcs10 client_priv.pem client --output=client_csr.pem
./botan sign_cert ca_cert.pem ca_priv.pem server_csr.pem --output=server_cert.pem
./botan sign_cert ca_cert.pem ca_priv.pem client_csr.pem --output=client_cert.pem

2. Compile the Adapted Test Server

Here's a branch that allows for client authentication: https://github.com/reneme/botan-tls-testserver/tree/spike/client_auth

Compile it.

3. Run the Test Server

I'm assuming that the server is run from the repository's root directory and that the test certificates generated in step 1 are located in this folder as well. Please adapt the file paths as needed:

./testserver --cert server_cert.pem --key server_priv.pem --port 50443 --policy policies/clientauth.txt --client-auth-ca ca_cert.pem

4. Connect a Client

This uses Botan's CLI-tool to connect to the test server. In its outputs it should mention "Performing client authentication" before "Handshake complete". Again, I'm assuming that the test certificates of step 1 are available in the same directory.

./botan tls_client --port=50443 localhost --client-cert=client_cert.pem --client-cert-key=client_priv.pem --trusted-cas=ca_cert.pem
shilohshi commented 6 months ago

Yeah it works now, thanks for your help!