Rust mTLS example
This is an example of how to support mTLS (two-way authentication, with client authentication) in Rust.
Implementation is quite short and includes both client and server side.
To run the example:
- Generate all required keys and certificates following the instructions below.
- Use
cd ..
to go back to the project root folder. - Start the server by running
cargo run -- server
(note the white space). - (While the server is running) start another prompt and run
cargo run -- client
(note the white space). - If you see
"Hello, mTLS World!"
, your configuration is probably correct. Congratulations!
To run the example with rustls-tls
instead of native-tls
, replace the command in step 2/3 above with:
cargo run --no-default-features --features rustls-tls -- server
cargo run --no-default-features --features rustls-tls -- client
Requirements:
- Rust & Cargo
- openssl (>=2019)
- (optional) curl (>=2019)
Notes:
-
To successfully run the executable, if you do not need two CAs in your configuration, you should replace all "second_ca" with "ca" in
main.rs
. The second_ca in the code only targets to demonstrate the option to configure different CAs separately and you can find more information in (13.) below. -
To use this in a real-world scenario, please replace
localhost
with a meaningful domain name. Do not forget to also change it in the SAN (subjectAltName). Also do not forget to fill in meaningful information while generating CSRs. -
To test the mTLS server in your browser (e.g. Chrome), please see this nice post or search "manual install certification {your browser name}". However, please note that
- To make the https work (make the small lock icon green), the
localhost.bundle.crt
mentioned below need to be installed in thetrusted root certifications
category. - To make the client authentication work, just import the client's
client_0.p12
file mentioned below intoPersonal
category. In the import window, you might need to change the extension name to find the right file.
- To make the https work (make the small lock icon green), the
Setup your own Certificate Authority
CA
-
Create a folder for all related files
mkdir ca cd ca
-
Generate a key for the CA
openssl genrsa -out ca.key 2048
-
Generate a self signed certificate for the CA
# enter detailed information when necessary openssl req -new -x509 -key ca.key -out ca.crt
Server
-
Generate an RSA key for the domain (
localhost
here)openssl genrsa -out localhost.key 2048 # optional: inspect the key openssl rsa -in localhost.key -noout -text # optional: extract pubkey openssl rsa -in localhost.key -pubout -out localhost.pubkey
-
Generate a Certificate Signing Request (CSR)
# enter detailed information when necessary (please make sure you enter COMMON NAME) openssl req -new -key localhost.key -addext "subjectAltName = DNS:localhost" -out localhost.csr # optional: inspect the csr (note: while inspecting, make sure your Signature Algorithm is not MD5 which is not accepted by many sites, upgrade your openssl if necessary) openssl req -in localhost.csr -noout -text
-
Sign the domain certificate
openssl x509 -req -in localhost.csr -CA ca.crt -CAkey ca.key -CAcreateserial -extfile <(printf "subjectAltName=DNS:localhost") -out localhost.crt # optional: to exam the output crt openssl x509 -in localhost.crt -noout -text
-
Create another file that contains the domain certificate and the ca certificate
cat localhost.crt ca.crt > localhost.bundle.crt
Client
-
Generate an RSA key for the client
openssl genrsa -out client_0.key 2048
-
Generate a Certificate Signing Request (CSR), please note that the SAN extension is NECESSARY
# enter detailed information when necessary (please make sure you enter COMMON NAME) openssl req -new -key client_0.key -addext "subjectAltName = DNS:localhost" -out client_0.csr
-
Use CA key to sign it
openssl x509 -req -in client_0.csr -CA ca.crt -CAkey ca.key -CAcreateserial -extfile <(printf "subjectAltName=DNS:localhost") -out client_0.crt
-
Generate pem file to test with curl & browser
# generate pem file cat client_0.crt client_0.key > client_0.pem # optional: test command (after starting the server) using .pem file curl -L https://localhost:3030/ --cacert ca.crt --cert client_0.pem -v # generate cert file to use with browser (setting password to be 123456 for example) openssl pkcs12 -export -in client_0.pem -out client_0.p12 -name "client_0" # optional: test command (after starting the server) using .p12 file curl -L https://localhost:3030/ --cacert ca.crt --cert-type P12 --cert client_0.p12:123456 -v
Optional
-
Create certificates for more clients
export CLIENT_NAME=client_1 openssl genrsa -out ${CLIENT_NAME}.key 2048 openssl req -new -key ${CLIENT_NAME}.key -addext "subjectAltName = DNS:localhost" -out ${CLIENT_NAME}.csr
# after answering the prompt above openssl x509 -req -in ${CLIENT_NAME}.csr -CA ca.crt -CAkey ca.key -CAcreateserial -extfile <(printf "subjectAltName=DNS:localhost") -out ${CLIENT_NAME}.crt cat ${CLIENT_NAME}.crt ${CLIENT_NAME}.key > ${CLIENT_NAME}.pem openssl pkcs12 -export -in ${CLIENT_NAME}.pem -out ${CLIENT_NAME}.p12 -name "${CLIENT_NAME}" # enter a password (e.g. 123456 (plz don't use weak password in real-world deployment))
-
Generate multiple CAs
# generate a second CA openssl genrsa -out second_ca.key 2048 openssl req -new -x509 -key second_ca.key -out second_ca.crt # answer the prompt export CLIENT_NAME=second_client openssl genrsa -out ${CLIENT_NAME}.key 2048 openssl req -new -key ${CLIENT_NAME}.key -addext "subjectAltName = DNS:localhost" -out ${CLIENT_NAME}.csr # answer the prompt openssl x509 -req -in ${CLIENT_NAME}.csr -CA second_ca.crt -CAkey second_ca.key -CAcreateserial -extfile <(printf "subjectAltName=DNS:localhost") -out ${CLIENT_NAME}.crt cat ${CLIENT_NAME}.crt ${CLIENT_NAME}.key > ${CLIENT_NAME}.pem openssl pkcs12 -export -in ${CLIENT_NAME}.pem -out ${CLIENT_NAME}.p12 -name "${CLIENT_NAME}" # enter a password (e.g. 123456 (plz don't use weak password in real-world deployment))
Reference:
- A SUPER useful gist: https://gist.github.com/Soarez/9688998
- A Rust mTLS example using openssl and Actix: https://github.com/sjolicoeur/rust-mtls-example-server/blob/master/bin/create_certs.sh
- OpenSSL official doc: https://www.openssl.org/docs/man1.1.1/man1/
- Reqwest doc related to TLS client authentication: https://docs.rs/reqwest/0.11.4/reqwest/struct.ClientBuilder.html#method.identity
- Warp doc related to TLS client authentication: https://docs.rs/warp/0.3.1/warp/struct.TlsServer.html#method.client_auth_required_path
Contributing
There is still an unclear issue:
- Why the certificate for the client must include the extension fields of SAN?
If you know the answers or have better solutions, please feel free to share your thoughts or send issues/PRs. Contributions are greatly appreciated.