Saturday, August 29, 2020

Mutual TLS Authentication


Summary

This post will cover the mutual TLS/client-side certificate approach to API authentication.  This is an authentication scheme that's suitable for machine to machine authentication of a limited number of clients.  I'll demonstrate the approach with Node.js implementations of the server and client using self-signed certificates.

Message Flow

Diagram  below depicting the message exchanges (from a CURL session) for a mutual TLS authentication.



Generating Self-Signed Certs

Below is a sequence of OpenSSL commands to generate the server-side private key and self-signed public certificate, the client-side private key and certificate signing request, and finally client-side certificate signing by the server-side cert.

  1. #generate server private key and cert
  2. openssl req -x509 -newkey rsa:4096 -keyout serverKey.pem -out serverCert.pem -nodes -days 365 -subj "/CN=localhost"
  3.  
  4. #generate client private key and cert signing request
  5. openssl req -newkey rsa:4096 -keyout clientKey.pem -out clientCsr.pem -nodes -subj "/CN=Client"
  6.  
  7. #sign client cert
  8. openssl x509 -req -in clientCsr.pem -CA serverCert.pem -CAkey serverKey.pem -out clientCert.pem -set_serial 01 -days 365

Server Snippet

Node.js implementation of a REST server that will request mutual TLS/client-side cert. Highlighted areas are of note for mutual TLS.

  1. const https = require('https');
  2. const express = require('express');
  3. const fs = require('fs');
  4.  
  5. const port = 8443;
  6. const key = fs.readFileSync('serverKey.pem');
  7. const cert = fs.readFileSync('serverCert.pem');
  8. const options = {
  9. key: key,
  10. cert: cert,
  11. requestCert: true,
  12. ca: [cert]
  13. };
  14.  
  15. let kvpStore = {};
  16.  
  17. const app = express();
  18. app.use(express.json());
  19.  
  20. //create
  21. app.post('/kvp', (req, res) => {
  22. const key = req.body.key;
  23. const value = req.body.value;
  24. if (key && value) {
  25. if (key in kvpStore) {
  26. res.status(400).json({error: 'kvp already exists'});
  27. }
  28. else {
  29. kvpStore[key] = value;
  30. res.status(201).json({key: key, value: kvpStore[key]});
  31. }
  32. }
  33. else {
  34. res.status(400).json({error: 'missing key value pair'});
  35. }
  36. });

CURL Client Commands

  1. #CREATE
  2. curl -v -k --key clientKey.pem --cert clientCert.pem -H "Content-Type: application/json" -d '{"key":"1", "value":"abc"}' https://localhost:8443/kvp
  3.  
  4. #RETRIEVE
  5. curl -v -k --key clientKey.pem --cert clientCert.pem https://localhost:8443/kvp/1
  6.  
  7. #UPDATE
  8. curl -v -k --key clientKey.pem --cert clientCert.pem -X PUT -H "Content-Type: application/json" -d '{"key":"1", "value":"def"}' https://localhost:8443/kvp
  9.  
  10. #DELETE
  11. curl -v -k --key clientKey.pem --cert clientCert.pem -X DELETE https://localhost:8443/kvp/1

Client Snippet

Node.js implementation of REST client supporting mutual TLS.  Again, highlighted areas depicting where mutual TLS-specific configuration is necessary.

  1. const https = require('https');
  2. const fs = require('fs');
  3. const fetch = require('node-fetch');
  4.  
  5. const key = fs.readFileSync('clientKey.pem');
  6. const cert = fs.readFileSync('clientCert.pem');
  7. const options = {
  8. key: key,
  9. cert: cert,
  10. rejectUnauthorized: false
  11. };
  12. const url = 'https://localhost:8443/kvp/';
  13. const tlsAgent = new https.Agent(options)
  14.  
  15. async function create(kvp) {
  16. const response = await fetch(url, {
  17. method: 'POST',
  18. body: JSON.stringify(kvp),
  19. headers: {'Content-Type': 'application/json'},
  20. agent: tlsAgent
  21. });
  22.  
  23. const json = await response.json();
  24. console.log(`CREATE - ${response.status} ${JSON.stringify(json)}`);
  25. }

Source

https://github.com/joeywhelan/mutualtls

Copyright ©1993-2024 Joey E Whelan, All rights reserved.