This project uses Node JS server and client to demonstrate mTLS and validation of additional certificate properties. It is intended to be used as examples / training material only.
There are 5 exercises to demonstrate mutual authentication certificate checks that can be done:
- Trusted issuer
- Common Name check
- Subject Alternative Name (SAN) check
- Public key fingerprint
- Certificate fingerprint
The below gives some context and motivation for use of mTLS and these examples. Also included is instructions for generating your own dummy CA and certificates should you wish to.
Contributions are encouraged, please see the contribution guidelines.
This project demonstrates the creation of mutual trust between client and server, using a shared certificate authority (CA). The CA is trusted by both client and server, and both use certificates that are signed by the CA. The certificates are used to establish identity between client and server (authenticate their respective identities).
The following shows this in an abstract way:
You may be wondering what the point of this is? Well, mTLS is really the only way to be certain (well, relatively certain) that the network path is as intended - that the communicating partners are in fact, who they claim to be.
The following shows the relative strengths of checks that can be performed using mTLS:
Ensure you have an install of:
- NodeJS 19 or later
- OpenSSL command line tools
openssl req -new -x509 -days 3650 -keyout ca-key.pem -out ca-crt.pem
Password defaults to cap@ssword*&
. Uses democa.tlsdemo.co.nz
for FQDN.
Generate Server Key:
openssl genrsa -out server-key.pem 4096
Generate Server certificate signing request:
openssl req -new -key server-key.pem -out server-csr.pem
Specify server Common Name, like 'localhost' or 'server.localhost'. The client will verify this, so make sure you have a valid DNS name for this. For this example, do not set the challenge password.
Alternatively, specify required information in single command, including subject alternative names:
openssl req -new -subj "/C=NZ/ST=Wellington/L=Wellington/O=Demo Servers Ltd/OU=Web Servers/CN=www.demoservers.co.nz/emailAddress=webmaster@demoservers.co.nz" -addext "subjectAltName=DNS:test.demoservers.co.nz,DNS:www.demoservers.co.nz" -key server-key.pem -out server-csr.pem
Note: OpenSSL does not copy subjectAltName
from CSR to certificate. See the extensions.cnf file for required configuration.
Optional: Print the CSR as text using:
openssl req -text -noout -verify -in server-csr.pem
Sign certificate using the CA:
openssl x509 -req -days 90 -in server-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out server-crt.pem
Optionally, sign including extensions:
openssl x509 -req -days 90 -in server-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out server-crt.pem -extfile .\extensions.cnf
- insert CA Password
Verify server certificate:
openssl verify -CAfile ca-crt.pem server-crt.pem
Generate server certificate public key ping-sha256 (works in Linux / Mac only):
openssl x509 -in server\-crt.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
Get server certificate fingerprint:
openssl x509 -noout -fingerprint -sha256 -inform pem -in server-crt.pem
Generate Client Key:
openssl genrsa -out client1-key.pem 4096
Generate Client certificate signing request:
openssl req -new -key client1-key.pem -out client1-csr.pem
Specify client Common Name, like 'client.localhost'. Must be a valid DNS name for this example.
Sign certificate using the CA:
openssl x509 -req -days 90 -in client1-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out client1-crt.pem
- insert CA Password
Verify client certificate:
openssl verify -CAfile ca-crt.pem client1-crt.pem
Generate client certificate public key pin-sha256 (works in Linux / Mac only):
openssl x509 -in client1-crt.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
Get client certificate fingerprint:
openssl x509 -noout -fingerprint -sha256 -inform pem -in client1-crt.pem
First run the server:
node server.js
Then run the client
node client.js
Adjust any of the tests in the body of the code to confirm behaviour
From the CA
directory:
curl -v --cacert ca-crt.pem --cert client1-crt.pem --key client1-key.pem -k https://localhost:8000
To include OCSP check:
curl -v --cacert ca-crt.pem --cert client1-crt.pem --key client1-key.pem -k --cert-status https://localhost:8000
This example returns a 200 OK
HTTP response for success, and 401 Not Authorised
on failure, and as required by RFC7230 a WWW-Authenticate
response header is included. However, no IANA registered authentication scheme is available, so a custom scheme TLS
with realm=tls=demo
is returned. This should not be interpreted as requiring the client certificate in an Authorization
header (which is the normal interpretation WWW-Authenticate
challenge).
It is also possible to use 403 Forbidden
to indicate a failure, however this is considered to be less friendly to clients, and less informative that authentication can be retried with different TLS credentials (a different certificate/key pair). Nevertheless, 403
is completely valid and both 401
and 403
are seen in practise.