Skip to content

Instantly share code, notes, and snippets.

@ryankurte
Last active March 6, 2024 03:38
Show Gist options
  • Save ryankurte/3a40c35f95d130ed3d9a8f3a63cd3e72 to your computer and use it in GitHub Desktop.
Save ryankurte/3a40c35f95d130ed3d9a8f3a63cd3e72 to your computer and use it in GitHub Desktop.
Node.js Client Certificate Validation with Pinning Example
#!/bin/bash
# https://gist.github.com/ryankurte/bc0d8cff6e73a6bb1950
set -e
if [ "$#" -ne 3 ] && [ "$#" -ne 4 ]; then
echo "Usage: $0 CA NAME ORG"
echo "CA - name of fake CA"
echo "NAME - name of fake client"
echo "ORG - organisation for both"
echo "[DIR] - directory for cert output"
exit
fi
CA=$1
NAME=$2
ORG=$3
if [ -z "$4" ]; then
DIR=./
else
DIR=$4
fi
if [ ! -d "$DIR" ]; then
mkdir -p $DIR
fi
LENGTH=4096
DAYS=1000
SUBJECT=/C=NZ/ST=AKL/L=Auckland/O=$ORG
if [ ! -f "$DIR/$CA.key" ]; then
echo Generating CA
openssl genrsa -out $DIR/$CA.key $LENGTH
echo Signing CA
openssl req -x509 -new -nodes -key $DIR/$CA.key -sha256 -days 1024 -out $DIR/$CA.crt -subj $SUBJECT/CN=$CA
openssl x509 -in $DIR/$CA.crt -out $DIR/$CA.pem -text
openssl x509 -sha1 -noout -in $DIR/$CA.pem -fingerprint | sed 's/SHA1 Fingerprint=//g' >> $DIR/$CA.fp
else
echo Located existing CA
fi
if [ ! -f "$DIR/$NAME.key" ]; then
echo Generating keys
openssl genrsa -out $DIR/$NAME.key $LENGTH
echo Generating CSR
openssl req -new -out $DIR/$NAME.csr -key $DIR/$NAME.key -subj $SUBJECT/CN=$NAME
echo Signing cert
openssl x509 -req -days $DAYS -in $DIR/$NAME.csr -out $DIR/$NAME.crt -CA $DIR/$CA.crt -CAkey $DIR/$CA.key -CAcreateserial
echo Generating PEM
openssl x509 -in $DIR/$NAME.crt -out $DIR/$NAME.pem -text
openssl x509 -sha1 -noout -in $DIR/$NAME.pem -fingerprint | sed 's/SHA1 Fingerprint=//g' > $DIR/$NAME.fp
echo Cleaning Up
rm $DIR/*.csr
else
echo Located existing client certificate
fi
echo Done
all: test
.PHONY: keys
keys:
./gencerts.sh evilca localhost evilcorp keys/
./gencerts.sh evilca client evilcorp keys/
test: keys
node test.js
clean:
rm -rf keys/
const fs = require('fs');
const https = require('https');
var options = {
port: process.env.PORT || 10201,
server_key: process.env.SERVER_KEY || __dirname + '/keys/localhost.key',
server_crt: process.env.SERVER_CRT || __dirname + '/keys/localhost.crt',
server_fp: process.env.SERVER_FP || __dirname + '/keys/localhost.fp',
client_key: process.env.CLIENT_KEY || __dirname + '/keys/client.key',
client_crt: process.env.CLIENT_CRT || __dirname + '/keys/client.crt',
client_fp: process.env.CLIENT_FP || __dirname + '/keys/client.fp',
ca: process.env.TLS_CA || __dirname + '/keys/evilca.crt'
}
// Load fingerprints
var clientFingerprints = [fs.readFileSync(options.server_fp).toString().replace('\n', '')];
var serverFingerprints = [fs.readFileSync(options.client_fp).toString().replace('\n', '')];
// Configure server
var serverOptions = {
key: fs.readFileSync(options.server_key),
cert: fs.readFileSync(options.server_crt),
ca: fs.readFileSync(options.ca),
requestCert: true,
rejectUnauthorized: true
}
function onRequest(req, res) {
console.log(new Date()+' '+
req.connection.remoteAddress+' '+
req.socket.getPeerCertificate().subject.CN+' '+
req.method+' '+req.url);
}
// Create TLS enabled server
var server = https.createServer(serverOptions, onRequest);
// Start Server
server.listen(options.port);
console.log("Listening on: " + options.port);
// Create TLS request
var requestOptions = {
hostname: 'localhost',
port: options.port,
path: '/',
method: 'GET',
key: fs.readFileSync(options.client_key),
cert: fs.readFileSync(options.client_crt),
ca: fs.readFileSync(options.ca),
requestCert: true,
rejectUnauthorized: true,
maxCachedSessions: 0
};
// Create agent (required for custom trust list)
requestOptions.agent = new https.Agent(requestOptions);
var req = https.request(requestOptions, (res) => {
console.log('statusCode:', res.statusCode);
});
req.end();
// Pin server certs
req.on('socket', socket => {
socket.on('secureConnect', () => {
var fingerprint = socket.getPeerCertificate().fingerprint;
// Check if certificate is valid
if(socket.authorized === false){
req.emit('error', new Error(socket.authorizationError));
return req.abort();
}
// Check if fingerprint matches
if(clientFingerprints.indexOf(fingerprint) === -1){
req.emit('error', new Error('Fingerprint does not match'));
return req.abort();
}
});
});
req.on('error', (e) => {
console.error(e);
process.exit(0);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment