In Mutual TLS both Client and Server have a certificate and both sides authenticate using their public/private key pair. Below are the handshake steps in simplistic way.

  • Client connects to server
  • Server presents its TLS certificate
  • Client verifies the server’s certificate
  • Client presents its TLS certificate
  • Server verifies the client’s certificate
  • Server grants access
  • Client and server exchange information over encrypted TLS connection

Mutual TLS

Below are the steps and commands


Generate Server Certificate

Server Details

  • Organization Name: Bobby Dreamer Inc.,
  • Organizational Unit Name: Crypto

Generate Certification Authority (CA) certificate for server

  • Password: super
  • Common Name(CN): localhost
openssl req -new -x509 -days 365 -keyout server-ca-key.pem -out server-ca-crt.pem
 
# Output
Generating a RSA private key
....+++++
.............................................+++++
writing new private key to 'server-ca-key.pem'    
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:IN
State or Province Name (full name) [Some-State]:TN
Locality Name (eg, city) []:Chennai
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Bobby Dreamer Inc., 
Organizational Unit Name (eg, section) []:Crypto
Common Name (e.g. server FQDN or YOUR name) []:localhost
Email Address []:

Generating a private key

openssl genrsa -out server-key.pem 4096
 
# Output
Generating RSA private key, 4096 bit long modulus (2 primes)
.............................++++
..............................++++
e is 65537 (0x010001)

Generate Certificate Signing Request (CSR)

  • Common Name(CN): server.localhost
  • do not insert the challenge password
openssl req -new -sha256 -key server-key.pem -out server-csr.pem
 
# Output
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:IN
State or Province Name (full name) [Some-State]:TN
Locality Name (eg, city) []:Chennai
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Bobby Dreamer Inc., 
Organizational Unit Name (eg, section) []:Crypto
Common Name (e.g. server FQDN or YOUR name) []:server.localhost
Email Address []:
 
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

Generate the server certificate from CSR

  • Type in the CA Password:super
openssl x509 -req -days 365 -in server-csr.pem -CA server-ca-crt.pem -CAkey server-ca-key.pem -CAcreateserial -out server-crt.pem
 
# Output
Signature ok
subject=C = IN, ST = TN, L = Chennai, O = "Bobby Dreamer Inc., ", OU = Crypto, CN = server.localhost
Getting CA Private Key
Enter pass phrase for server-ca-key.pem:

Verify certificate signature

Using the below command you can verify the certificate signature against the CA

openssl verify -CAfile server-ca-crt.pem server-crt.pem
 
# Output
server-crt.pem: OK

Generate Client Certificate

Client details

  • Organization Name: Browsers Inc.,
  • Organizational Unit Name: Security

Generate Certification Authority (CA) certificate for client

  • Password: client
  • Common Name(CN): browser.com
openssl req -new -x509 -days 365 -keyout client-ca-key.pem -out client-ca-crt.pem
 
# Output
Generating a RSA private key
......+++++
............+++++
writing new private key to 'client-ca-key.pem'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:IN
State or Province Name (full name) [Some-State]:TN
Locality Name (eg, city) []:Chennai
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Browsers Inc.,
Organizational Unit Name (eg, section) []:Security
Common Name (e.g. server FQDN or YOUR name) []:browser.com
Email Address []:

Generate a private key

openssl genrsa -out client-key.pem 4096
 
# Output
Generating RSA private key, 4096 bit long modulus (2 primes)
...............................................................................................................................++++
..............................++++
e is 65537 (0x010001)

Generate Certificate Signing Request (CSR)

  • Common Name(CN): client.browser.com
  • do not insert the challenge password
openssl req -new -sha256 -key client-key.pem -out client-csr.pem
 
# Output
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:IN
State or Province Name (full name) [Some-State]:TN
Locality Name (eg, city) []:Chennai
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Browsers Inc.,
Organizational Unit Name (eg, section) []:Security
Common Name (e.g. server FQDN or YOUR name) []:client.browser.com
Email Address []:
 
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

Generate client certificate from CSR

  • Type in the CA Password:client
openssl x509 -req -days 365 -in client-csr.pem -CA client-ca-crt.pem -CAkey client-ca-key.pem -CAcreateserial -out client-crt.pem
 
# Output
Signature ok
subject=C = IN, ST = TN, L = Chennai, O = "Browsers Inc.,", OU = Security, CN = client.browser.com
Getting CA Private Key
Enter pass phrase for client-ca-key.pem:

Verify certificate signature

openssl verify -CAfile client-ca-crt.pem client-crt.pem
 
# Output 
client-crt.pem: OK

Test

Code is available here in Github

  • Start the server.js script node server.js
  • In another terminal window, start the node client.js

Errors you might get

D:\BigData\14.Nodejs\17.Certificates\b.client-server_certificate2>node client.js
TLS Socket ERROR (Error: getaddrinfo ENOTFOUND server.localhost)

Solution

In Windows, go to C:\Windows\System32\drivers\etc

  • Backup the existing hosts file
  • Edit the file and add the below two lines
127.0.0.1 server.localhost
127.0.0.1 client.browser.com

It should look like below Mutual TLS

Rerun the client script node client.js

D:\BigData\14.Nodejs\17.Certificates\b.client-server_certificate2>node client.js
TLS Connection established successfully!
Response statusCode:  200
Response headers:  {
  date: 'Mon, 30 Aug 2021 15:25:24 GMT',
  connection: 'close',
  'transfer-encoding': 'chunked'
}
Server Host Name: server.localhost
Received message: OK!
 
TLS Connection closed!

In the server side, you will get response like below,

D:\BigData\14.Nodejs\17.Certificates\b.client-server_certificate2>node server.js
Mon Aug 30 2021 20:55:23 GMT+0530 (India Standard Time) ::ffff:127.0.0.1 GET /

CURL

Tried to connect to server using CURL it did not work. Did not attempt to do any further investigating as it looks like another whole subject to learn.

curl -k https://127.0.0.1:8888 -v --cacert ./certs/server-ca-crt.pem --key ./certs/client-key.pem --cert ./certs/client-crt.pem
 
# Output 
* Rebuilt URL to: https://127.0.0.1:8888/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8888 (#0)
* schannel: SSL/TLS connection with 127.0.0.1 port 8888 (step 1/3)
* schannel: disabled server certificate revocation checks
* schannel: verifyhost setting prevents Schannel from comparing the supplied target name with the subject names in server certificates.
* schannel: using IP address, SNI is not supported by OS.
* schannel: sending initial handshake data: sending 153 bytes...
* schannel: sent initial handshake data: sent 153 bytes
* schannel: SSL/TLS connection with 127.0.0.1 port 8888 (step 2/3)
* schannel: failed to receive handshake, need more data
* schannel: SSL/TLS connection with 127.0.0.1 port 8888 (step 2/3)
* schannel: encrypted data got 1970
* schannel: encrypted data buffer: offset 1970 length 4096
* schannel: a client certificate has been requested
* schannel: SSL/TLS connection with 127.0.0.1 port 8888 (step 2/3)
* schannel: encrypted data buffer: offset 1970 length 4096
* schannel: sending next handshake data: sending 100 bytes...
* schannel: SSL/TLS connection with 127.0.0.1 port 8888 (step 2/3)
* schannel: encrypted data got 7
* schannel: encrypted data buffer: offset 7 length 4096
* schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.
* Closing connection 0
* schannel: shutting down SSL/TLS connection with 127.0.0.1 port 8888
* schannel: clear security context handle
curl: (35) schannel: next InitializeSecurityContext failed: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.

References