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.

#generate server private key and cert
openssl req -x509 -newkey rsa:4096 -keyout serverKey.pem -out serverCert.pem -nodes -days 365 -subj "/CN=localhost"

#generate client private key and cert signing request
openssl req -newkey rsa:4096 -keyout clientKey.pem -out clientCsr.pem -nodes -subj "/CN=Client"

#sign client cert
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.

const https = require('https');
const express = require('express');
const fs = require('fs');

const port = 8443;
const key = fs.readFileSync('serverKey.pem');
const cert = fs.readFileSync('serverCert.pem');
const options = {
        key: key,
        cert: cert,
        requestCert: true,
        ca: [cert]
};

let kvpStore = {};

const app = express();
app.use(express.json());

//create
app.post('/kvp', (req, res) => {
    const key = req.body.key;
    const value = req.body.value;
    if (key && value) {
        if (key in kvpStore) {
            res.status(400).json({error: 'kvp already exists'});
        }
        else {
            kvpStore[key] = value;
            res.status(201).json({key: key, value: kvpStore[key]});
        }
    } 
    else {
        res.status(400).json({error: 'missing key value pair'});
    }   
});

CURL Client Commands

#CREATE
curl -v -k --key clientKey.pem --cert clientCert.pem -H "Content-Type: application/json" -d '{"key":"1", "value":"abc"}' https://localhost:8443/kvp

#RETRIEVE
curl -v -k --key clientKey.pem --cert clientCert.pem https://localhost:8443/kvp/1

#UPDATE
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

#DELETE
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.

const https = require('https');
const fs = require('fs');
const fetch = require('node-fetch');

const key = fs.readFileSync('clientKey.pem');
const cert = fs.readFileSync('clientCert.pem');
const options = {
        key: key,
        cert: cert,
        rejectUnauthorized: false
};
const url = 'https://localhost:8443/kvp/';
const tlsAgent = new https.Agent(options)

async function create(kvp) {
    const response = await fetch(url, {
        method: 'POST',
        body: JSON.stringify(kvp),
        headers: {'Content-Type': 'application/json'},
        agent: tlsAgent
    });

    const json = await response.json();
    console.log(`CREATE - ${response.status} ${JSON.stringify(json)}`);
}

Source

https://github.com/joeywhelan/mutualtls

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