Implementing Mutual TLS (mTLS) Authentication with OpenSSL: A Step-by-Step Guide
This article explores mutual Transport Layer Security (mTLS) authentication and how OpenSSL can facilitate its implementation. Also known as client-server authentication, mTLS is a robust security mechanism that requires both the client and server to present valid digital certificates before establishing a secure connection. This additional layer of authentication ensures that only trusted entities can access protected resources.
We will set up mutual TLS (mTLS) using two intermediate CAsโone for server certificates and another for client certificatesโboth signed by the same root CA.
Steps Overview:
- Create Root CA
- Create Two Intermediate CAs (one for server, one for client)
- Generate Server Certificate (signed by Server Intermediate CA)
- Generate Client Certificate (signed by Client Intermediate CA)
- Start OpenSSL Server with mTLS
- Test with curl
Step 1: Create the Root CA
A Root Certificate Authority (Root CA) is the top-level trust anchor in a Public Key Infrastructure (PKI) that issues and signs digital certificates. It is self-signed and is used to verify and establish trust in all certificates issued under it.
First commmand generate the private key as a file rootCA.key and second will creates a self-signed Root CA certificate (rootCA.crt) valid for 10 years.
openssl genrsa -out rootCA.key 4096
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.crt -subj "/C=US/ST=State/L=City/O=RootCA/OU=IT/CN=root-ca"
๐น genrsa โ Generates a RSA private key.
๐น -out rootCA.key โ Saves the key as rootCA.key.
๐น 4096 โ Specifies the key size (4096 bits) for strong security.
๐น req โ Runs OpenSSLโs Certificate Signing Request (CSR) command.
๐น -x509 โ Creates a self-signed certificate instead of a CSR.
๐น -new โ Generates a new certificate request.
๐น -nodes โ No password protection on the private key (useful for automation).
๐น -key rootCA.key โ Uses the previously generated private key.
๐น -sha256 โ Uses SHA-256 as the hashing algorithm for the signature.
๐น -days 3650 โ Sets the certificateโs validity period to 3650 days (10 years).
๐น -out rootCA.crt โ Saves the Root CA certificate as rootCA.crt.
๐น -subj โ/C=US/ST=State/L=City/O=RootCA/OU=IT/CN=root-caโ
/C=US โ Country (US)
/ST=State โ State name
/L=City โ City name
/O=RootCA โ Organization (Root CA)
/OU=IT โ Organizational Unit (IT)
/CN=root-ca โ Common Name (Root CAโs name)
Step 2: Create Two Intermediate CAs
creates a Server Intermediate Certificate Authority (CA) that is signed by the Root CA.
Server Intermediate CA
openssl genrsa -out server-intermediateCA.key 4096
openssl req -new -key server-intermediateCA.key -out server-intermediateCA.csr -subj "/C=US/ST=State/L=City/O=ServerCA/OU=IT/CN=server-intermediate-ca"
openssl x509 -req -in server-intermediateCA.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server-intermediateCA.crt -days 3650 -sha256
First, This private key will be used to sign server certificates issued by this intermediate CA.
Second, This step generates a CSR that will be signed by the Root CA.
Third, This step creates a Server Intermediate CA certificate, signed by the Root CA.
๐น server-intermediateCA.key โ Private key for the Server Intermediate CA.
๐น server-intermediateCA.csr > โ CSR for the Server Intermediate CA.
๐น server-intermediateCA.crt > โ Intermediate CA certificate signed by the Root CA.
๐น rootCA.srl > โ Serial number file for tracking issued certificates.
Client Intermediate CA
Creates a Client Intermediate Certificate Authority (CA) that is signed by the Root CA.
openssl genrsa -out client-intermediateCA.key 4096
openssl req -new -key client-intermediateCA.key -out client-intermediateCA.csr -subj "/C=US/ST=State/L=City/O=ClientCA/OU=IT/CN=client-intermediate-ca"
openssl x509 -req -in client-intermediateCA.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out client-intermediateCA.crt -days 3650 -sha256
๐น client-intermediateCA.key โ Private key for the Client Intermediate CA.
๐น client-intermediateCA.csr > โ CSR for the Client Intermediate CA.
๐น client-intermediateCA.crt > โ Intermediate CA certificate signed by the Root CA.
๐น rootCA.srl > โ Serial number file for tracking issued certificates.
Step 3: Generate Server Certificate (Signed by Server Intermediate CA)
Generates a server certificate signed by the Server Intermediate Certificate Authority (CA).
openssl genrsa -out server.key 4096
openssl req -new -key server.key -out server.csr -subj "/C=US/ST=State/L=City/O=MyServer/OU=IT/CN=server.local"
openssl x509 -req -in server.csr -CA server-intermediateCA.crt -CAkey server-intermediateCA.key -CAcreateserial -out server.crt -days 3650 -sha256
Server Certificate Chain:
Root CA
โโโ Server Intermediate CA
โโโ Server Certificate (server.crt)
Step 4: Generate Client Certificate (Signed by Client Intermediate CA)
Generates a client certificate signed by the Client Intermediate Certificate Authority (CA).
openssl genrsa -out client.key 4096
openssl req -new -key client.key -out client.csr -subj "/C=US/ST=State/L=City/O=MyClient/OU=IT/CN=client.local"
openssl x509 -req -in client.csr -CA client-intermediateCA.crt -CAkey client-intermediateCA.key -CAcreateserial -out client.crt -days 3650 -sha256
Client Certificate Chain:
Root CA
โโโ Client Intermediate CA
โโโ Client Certificate (client.crt)
Create CA bundle: Concating both Intermediate certs and root CA into a single file.
cat server-intermediateCA.crt client-intermediateCA.crt rootCA.crt > ca-bundle.crt
Verify certificates:
openssl verify -CAfile full-chain.crt server.crt
server.crt: OK
openssl verify -CAfile full-chain.crt client.crt
client.crt: OK
The output of these commands indicates that both server.crt and client.crt are valid and properly signed by a Certificate Authority (CA).
Step 5: Start OpenSSL Server with mTLS
Run the server, requiring client authentication:
openssl s_server -accept 8443 -cert server.crt -key server.key -CAfile ca-bundle.crt -Verify 2
๐น s_server โ Starts an SSL/TLS server.
๐น -accept 8443 โ The server listens on port 8443 for incoming TLS connections.
๐น -cert server.crt โ Uses server.crt as the serverโs certificate.
๐น -key server.key โ Uses server.key as the private key for the server.
๐น -CAfile ca-bundle.crt โ Specifies a file containing trusted CA certificates (intermediate + root CAs) to verify client certificates.
๐น -Verify 2 โ Requires mutual TLS (mTLS), meaning: The server will request a client certificate. The depth 2 means it will accept client certificates issued up to 2 levels deep in the CA hierarchy (e.g., Root CA โ Intermediate CA โ Client Certificate).
Step 6: Test with curl
Use curl with client authentication:
curl -v --cert client.crt --key client.key --cacert ca-bundle.crt https://server.local:8443
๐น -v โ Enables verbose mode, showing detailed SSL handshake and request/response details.
๐น โcert client.crt โ Specifies the client certificate to authenticate with the server.
๐น โkey client.key โ Specifies the private key for the client certificate.
๐น โcacert ca-bundle.crt โ Specifies the trusted CA certificate bundle to verify the serverโs certificate.
๐น https://server.local:8443 โ The target URL of the server, using HTTPS on port 8443.
openssl s_server -accept 8443 -cert server.crt -key server.key -CAfile full-chain.crt -Verify 2
verify depth is 2, must return a certificate
Using auto DH parameters
ACCEPT
depth=2 C = US, ST = State, L = City, O = RootCA, OU = IT, CN = root-ca
verify return:1
depth=1 C = US, ST = State, L = City, O = ClientCA, OU = IT, CN = client-intermediate-ca
verify return:1
depth=0 C = US, ST = State, L = City, O = MyClient, OU = IT, CN = client.local
verify return:1
Write BLOCK
GET / HTTP/1.1
Host: server.local:8443
User-Agent: curl/8.7.1
Accept: */*
DONE
shutting down SSL
CONNECTION CLOSED
ACCEPT
First, Server verified the Root CA (which issued the intermediate CA). The server verified the Client Intermediate CA. The clientโs certificate was issued by this intermediate CA.And, verify return:1 means verification was successful.
curl -v --cert client.crt --key client.key --cacert full-chain.crt https://server.local:8443
* Host server.local:8443 was resolved.
* IPv6: (none)
* IPv4: 127.0.0.1
* Trying 127.0.0.1:8443...
* Connected to server.local (127.0.0.1) port 8443
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* CAfile: full-chain.crt
* CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Request CERT (13):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Certificate (11):
* (304) (OUT), TLS handshake, CERT verify (15):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384 / [blank] / UNDEF
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* subject: C=US; ST=State; L=City; O=MyServer; OU=IT; CN=server.local
* start date: Mar 10 17:18:54 2025 GMT
* expire date: Mar 8 17:18:54 2035 GMT
* common name: server.local (matched)
* issuer: C=US; ST=State; L=City; O=ServerCA; OU=IT; CN=server-intermediate-ca
* SSL certificate verify ok.
* using HTTP/1.x
> GET / HTTP/1.1
> Host: server.local:8443
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
Curl command successfully established a mutual TLS (mTLS) connection with the server. mTLS was successfully established. The server authenticated itself to the client.The client authenticated itself to the server.A secure session was created, and data exchange began.
Comments