I am trying to use HTTP self-signed certificate in Node.js.
I've generated the certificates using the following script that I've written:
generate.sh
#!/bin/bash
read -p "FQDN: " FQDN
# for easy testing
rm ROOT.*
rm SERVER.*
openssl genrsa 4096 > ROOT.key
openssl req -x509 -nodes -sha256 -new -key ROOT.key -days 365 -subj "/C=AU/CN=example" > ROOT.crt
openssl req -newkey rsa:4096 -nodes -sha256 -keyout SERVER.key -subj "/C=AU/CN=${FQDN}" > SERVER.csr
openssl x509 -days 365 -req -in SERVER.csr -CA ROOT.crt -CAkey ROOT.key -CAcreateserial > SERVER.crt
And I'm trying to test the certificates using the following:
test.js
let fs = require('fs')
let https = require('https')
// HTTP server
https.createServer({
key: fs.readFileSync('SERVER.key'),
cert: fs.readFileSync('SERVER.crt'),
ca: fs.readFileSync('ROOT.crt')
}, function (req, res) {
res.writeHead(200)
res.end('Hello world')
}).listen(4433)
// HTTP request
let req = https.request({
hostname: '127.0.0.1',
port: 4433,
path: '/',
method: 'GET',
ca: fs.readFileSync('ROOT.crt')
}, function (res) {
res.on('data', function (data) {
console.log(data.toString())
})
})
req.end()
However, upon testing:
> node test.js
Error: Hostname/IP doesn't match certificate's altnames: "IP: 127.0.0.1 is not in the cert's list: "
This seems odd, because if I print the certificates cert list, it shows that the IP is there?
If I use localhost
as the FQDN and host
(in the request), it does work.
What am I missing?
edit:
curl --cacert ROOT.crt https://127.0.0.1:4433
completes without error, so what am I missing in the Node.js code?
The easiest way to find your IP address in Node. js is to pull the client IP address from the incoming HTTP request object. If you are running Express in your Node app, this is very easy to do. Access the socket field of the Express request object, and then look up the remoteAddress property of that field.
Thanks to Kris Reeves for pointing me in the right direction.
The problem was as mentioned, the certificate was missing subjectAltName
s, and getting them in a compatible format for Node wasn't so simple (had to be X509v3 compatible).
The final Makefile
ended up as thus:
.PHONY: clean default
FQDN ?= 127.0.0.1
default: SERVER.crt
clean:
rm -f openssl.conf
rm -f ROOT.*
rm -f SERVER.*
openssl.conf:
cat /etc/ssl/openssl.cnf > openssl.conf
echo "[ san_env ]" >> openssl.conf
echo "subjectAltName=$$""{ENV::SAN}" >> openssl.conf
ROOT.key:
openssl genrsa 4096 > ROOT.key
ROOT.crt: ROOT.key
openssl req \
-new \
-x509 \
-nodes \
-sha256 \
-key ROOT.key \
-days 365 \
-subj "/C=AU/CN=example" \
-out ROOT.crt
SERVER.csr: openssl.conf
SAN=IP:$(FQDN) openssl req \
-reqexts san_env \
-config openssl.conf \
-newkey rsa:4096 \
-nodes -sha256 \
-keyout SERVER.key \
-subj "/C=AU/CN=$(FQDN)" \
-out SERVER.csr
SERVER.crt: openssl.conf ROOT.key ROOT.crt SERVER.csr
SAN=IP:$(FQDN) openssl x509 \
-req \
-extfile openssl.conf \
-extensions san_env \
-days 365 \
-in SERVER.csr \
-CA ROOT.crt \
-CAkey ROOT.key \
-CAcreateserial \
-out SERVER.crt
Hopefully this helps someone else who wants to use self signed certificates with only an IP.
$ make FQDN=127.0.0.1
I'm talking to you on IRC, but I'll leave the information here in case it helps someone else.
By the source code for node's tls module, we can see that it expects the altnames to be in a very specific format:
https://github.com/nodejs/node/blob/v5.0.0/lib/tls.js#L108-L145
Particularly the line:
var option = altname.match(/^(DNS|IP Address|URI):(.*)$/);
Later, it decides what format the host being checked is in, and if it is an IP address, it looks for it in the IP addresses it extracted from the altnames. So, the certificate will fail unless it has an altname of the format "IP Address:127.0.0.1", because that's the only way Node will put it in the list of IPs that it checks when it later discerns that it's testing an IP address.
I don't know if this is canon for the format of the altnames field of a certificate, but it should present you a path to generate a certificate that Node will accept.
Alternately, you can provide your own checkServerIdentity
function in the connect options for your socket or http request (should get passed to the socket):
https://github.com/nodejs/node/blob/82022a79b035c25f8a41df1f2a20793d356c1511/lib/_tls_wrap.js#L962-L969
This is how I've had to do it before, but I believe the altname matching may be newer than when I last encountered this problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With