Last active
March 6, 2024 03:38
-
-
Save ryankurte/3a40c35f95d130ed3d9a8f3a63cd3e72 to your computer and use it in GitHub Desktop.
Node.js Client Certificate Validation with Pinning Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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