Build with Naz : TLS (Transport Layer Security) in Rust with tokio, rustls, CFSSL
- Introduction
- TLS primer
- Rust and TLS primer
- YouTube videos for this article
- First, create the certificates by running gen-certs.fish
- Second, write and run the code
- Build with Naz video series on developerlife.com YouTube channel
Introduction #
This repo contains code for a simple server and client program written in Rust that
communicate over TLS using the tokio
and rustls
libraries.
- TLS is used to secure the communication between the server and client.
- It is an added layer on top of the TCP connection.
TLS primer #
TLS is a cryptographic protocol designed to provide secure communication over a computer network. It ensures:
- Confidentiality: Data is encrypted so that only the intended recipient can read it.
- Integrity: Data cannot be altered without detection.
- Authentication: The identities of the parties involved can be verified.
It consists of both symmetric and asymmetric encryption algorithms. Here’s a brief overview of both.
Symmetric Encryption
- Definition: Uses the same key for both encryption and decryption.
- Examples: AES (Advanced Encryption Standard), DES (Data Encryption Standard).
- Benefits:
- Faster than asymmetric encryption.
- Suitable for encrypting large amounts of data.
- Drawbacks:
- Key distribution can be a challenge; both parties must securely share the key. So sharing the key between both parties can either happen out of band, or using some other mechanism (like asymmetric encryption).
Asymmetric Encryption
- Definition: Uses a pair of keys (public and private) for encryption and decryption.
- Examples: RSA, ECC (Elliptic Curve Cryptography).
- Benefits:
- Solves the key distribution problem; the public key can be shared openly.
- Provides authentication through digital signatures.
- Drawbacks:
- Slower than symmetric encryption.
- Not suitable for encrypting large amounts of data directly.
TLS uses a combination of both symmetric and asymmetric encryption. It uses asymmetric encryption to establish a secure connection and symmetric encryption to encrypt the data transferred over the connection.
Additionally the following are required to make the communication secure between the client and server:
- The client needs to have the CA certificate in case you are using self-signed certificates.
- The server needs to have both the server certificate and the private key.
Here’s an overview of how TLS works:
- Handshake - The client and server perform a handshake to establish a secure
connection. During this process:
- The client and server agree on the TLS version and cipher suites to use.
- The server presents its digital certificate, which contains its public key.
- The client verifies the server’s certificate against trusted Certificate Authorities (CAs).
- The client generates a random session key, encrypts it with the server’s public key, and sends it to the server.
- Session Key - Once the server receives the encrypted session key, it decrypts it using its private key. Both parties now have the same session key, which is used for symmetric encryption of the data transmitted during the session.
- Data Transmission - All data sent between the client and server is encrypted using the session key, ensuring confidentiality and integrity.
Rust and TLS primer #
Now that we know more about TLS, how do we access it in Rust? Rust has 2 main implementations for TLS:
-
rustls
: A modern, safe, and fast TLS library written in Rust. This does not have any dependencies on OpenSSL, or any C code, or any OS specific code. It is a pure Rust implementation. -
native-tls
: A thin wrapper around the platform’s native TLS implementation. It uses OpenSSL on Unix-like systems and SChannel on Windows.
YouTube videos for this article #
If you like to learn via video, please watch the companion video on the developerlife.com YouTube channel where I live code the entire program from scratch. You can follow along there, step by step if you like, in addition to this article and repo.
The code in the video and this tutorial are all in this GitHub repo.
First, create the certificates by running gen-certs.fish #
All the scripts and certificate related files are in the certs
folder:
- The main script is
gen-certs.fish
. It generates the CA and server certificates. It also uses the script below to get the CFSSL binaries. - The
get-cfssl-binaries.fish
script downloads the CFSSL binaries if needed. If they are already downloaded, it does nothing.
Tools used by the scripts (CFSSL) #
- The CFSSL tool is used to generate the certificates.
- Learn more about the tool in this blog post.
- You can get the prebuilt binaries here.
- This video goes over the process of setting up TLS with CFSSL.
Configuration files deep dive #
There are 3 JSON files that are used to generate the certificates:
`ca-config.json`: The configuration for the CA.
- The main node is
signing
which has theprofiles
node. You can have multiple profiles. In this case, I create a single profile namedserver
, which is a name I just made up.- The node named
server
, which is a made up name of a profile, is used to generate the server certificate. This is a name that I created, it is not a reserved keyword, it has no special meaning. It is used in thecfssl gencert ... -profile=server server-csr.json
command and used to tie all the generated files together.- The
expiry
node sets the expiration date for a certificate. I changed it 10 years or87600h
. - The
usages
node sets the key usage for the certificate. I set it tosigning
,key encipherment
,server auth
, andclient auth
.
- The
- Here’s an example:
{ "signing": { "default": { "expiry": "87600h" }, "profiles": { "server": { "expiry": "87600h", "usages": ["signing", "key encipherment", "server auth"] } } } }
- The node named
`server-csr.json`: The configuration for the server certificate. This is related to the
server
profile above. The CA will sign the server certificate using the server
profile.
- The
CN
node is the Common Name for this certificate. I set it toserver
. This has no special meaning. It is set to ensure that thecfssl gencert -ca ca.pem ...
commands to generate the certificates work and can find the information related to theserver
, which matches the profile name. - The
key
node sets the key size and type. I set it to2048
bits andrsa
. This is important. - The
hosts
node sets the DNS names and IP addresses for the certificate. This is really important. The client will use aServerName
in Rust code to connect to the server. That name must match whatever is in thehosts
array. You can just add another name there which can be parsed as a DNS name or an IP address. In my case, I havelocalhost
andr3bl.com
(which is just made up). However, in the Rust client code to connect to the server, I can create aServerName
using either"localhost"
or"r3bl.com"
. - Here’s an example:
{ "CN": "server", "hosts": ["localhost", "r3bl.com"], "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "ST": "Texas", "L": "Austin" } ] }
`ca-csr.json`: The Certificate Signing Request (CSR) for the CA.
- The
CN
node is the Common Name for the CA. I set it toca
. This has no special meaning. It is just to make sure that thecfssl gencert -initca ca-csr.json
commands to generate the certificates work and can find the information related to the CA. - The
key
node sets the key size and type. I set it to2048
bits andrsa
. This is important. - Here’s an example:
{ "CN": "ca", "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "ST": "Texas", "L": "Austin" } ] }
Each of these files are modified from some default values to the desired values. They all started life using the following commands:
./cfssl print-defaults config > ca-config.json
./cfssl print-defaults csr > ca-csr.json
./cfssl print-defaults csr > server-csr.json
Run the scripts and generate the certificates #
Run the following commands to generate the certificates in the certs/generated
folder:
cd certs
./gen-certs.fish
Running this script will generate the following files:
- Generate root certificate (CA) and sign it. The
ca
string in the filenames comes from thecfssl gencert ... | cfssljson -bare ca
command. If you change the stringca
in the command, it will change the filenames that are produced.
File | Description |
---|---|
ca.csr |
Certificate signing request |
ca-key.pem |
Private key |
ca.pem |
Public key; used in the Rust client code |
- Generate server certificate (and private key) and sign it with the CA. The
server
string in the filenames comes from thecfssl gencert ... | cfssljson -bare server
command. If you change the stringserver
in the command, it will change the filenames that are produced.
File | Description |
---|---|
server.csr |
Certificate signing request |
server-key.pem |
Private key; used in the Rust server code |
server.pem |
Public key; used in the Rust server code |
Examine the generated certificates #
- Look in the
certs/generated/
folder to see the generated certificates. You can examine them using theopenssl
command:
openssl x509 -noout -text -in generated/ca.pem
Look for the following lines which confirm that this is a CA certificate, and some other
configuration properties provided in the ca-config.json
file:
Field | Description |
---|---|
Issuer: C=US, ST=TX, L=Austin, CN=ca |
The CA’s own details, from ca-config.json |
Not After: ... |
Expiration date |
Public-Key: (2048 bit) |
Key size and type from ca-csr.json |
CA:TRUE |
This is a CA (root certificate) |
- Look in the
certs/generated
folder to see the server certificates. You can examine them using theopenssl
command:
openssl x509 -noout -text -in generated/server.pem
Look for the following lines which confirm that this is a server certificate, and some
other configuration properties provided in the server-csr.json
file:
Field | Description |
---|---|
Issuer: C=US, ST=Texas, L=Austin, CN=ca |
Issued by the CA above |
Subject: C=US, ST=Texas, L=Austin, CN=server |
The server’s own details |
Not After : ... |
Expiration date |
CA:FALSE |
Not a root certificate |
TLS Web Server Authentication |
Extended Key Usage for server authentication |
DNS:localhost, IP Address:127.0.0.1 |
This is from server-csr.json . The Rust client code uses this in ServerName to make a TLS connection |
- Finally verify the server certificate against the CA certificate:
openssl verify -CAfile generated/ca.pem generated/server.pem
If the certificate is valid, you will see the following output: generated/server.pem: OK
Second, write and run the code #
Once the certificates are generated, the next step is to write the server and client code. Here’s the mental model for doing this.
-
Client code
-
Certificate concerns:
- The client code will need to load the root certificate store, inside of which will
reside the CA (certificate authority) certificate chain, that we have generated (the
ca.pem
file). - The client will also need to know the server’s hostname, which is used to verify the
server’s certificate. This has to match the
hosts
entry in theserver-csr.json
config file. This entry has to be in the form of aServerName
in the Rust code, which is a DNS or IP address parsable format.
- The client code will need to load the root certificate store, inside of which will
reside the CA (certificate authority) certificate chain, that we have generated (the
-
Code concerns:
- The certificate and key files above is used to generate a
ClientConfig
struct, from therustls
crate. It is then used to create aTlsConnector
struct. - The unsecure connection of type
TcpStream
will be created as per usual usingTcpStream::connect()
. However, this will then be wrapped in aTlsConnector
which will make it a secure connection. The reader and writer halves are split from thisTlsStream
struct. And the reader and writer halves are used as per usual.
- The certificate and key files above is used to generate a
-
-
Server code
-
Certificate concerns:
- The server code will need to load the server’s certificate and private key, which we
have generated (the
server.pem
andserver-key.pem
files).- This server certificate is signed by the CA certificate. Since we are using
self-signed certificates, only the client will need to load the CA certificate to
verify the server certificate. And not the server.
- This is because the server is self-signed and doesn’t need to verify any incoming certificates.
- If we weren’t using self-signed certificates, the client would just have to load the root certificate store that’s available publicly (like Mozilla root certificates).
- The server will not need to load the root certificate store, inside of which will
reside the CA certificate chain, that we have generated (the
ca.pem
file).
- This server certificate is signed by the CA certificate. Since we are using
self-signed certificates, only the client will need to load the CA certificate to
verify the server certificate. And not the server.
- The server code will need to load the server’s certificate and private key, which we
have generated (the
-
Code concerns:
- The certificate and key files above are used to generate a
ServerConfig
struct, from therustls
crate. It is then used to create aTlsAcceptor
struct. - The server will create a
TcpListener
and accept incoming connections. Each connection will be wrapped in aTlsAcceptor
which will make it a secure connection. The reader and writer halves are split from thisTlsStream
struct. And the reader and writer halves are used as per usual.
- The certificate and key files above are used to generate a
-
Here’s some more information about mapping the Rust code to the TLS files:
For details on the actual, code, here are some files from the tls
repo:
Here are the files for the TLS configuration and certificate generation:
Build with Naz video series on developerlife.com YouTube channel #
If you have comments and feedback on this content, or would like to request new content (articles & videos) on developerlife.com, please join our discord server.
You can watch a video series on building this crate with Naz on the developerlife.com YouTube channel.
- YT channel
- Playlists
👀 Watch Rust 🦀 live coding videos on our YouTube Channel.
📦 Install our useful Rust command line apps usingcargo install r3bl-cmdr
(they are from the r3bl-open-core project):
- 🐱
giti
: run interactive git commands with confidence in your terminal- 🦜
edi
: edit Markdown with style in your terminalgiti in action
edi in action