Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot use IP in Node.js for self-signed certificate

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?

like image 768
deceleratedcaviar Avatar asked Nov 06 '15 01:11

deceleratedcaviar


People also ask

How do I find my IP address in node JS?

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.


2 Answers

Thanks to Kris Reeves for pointing me in the right direction.

The problem was as mentioned, the certificate was missing subjectAltNames, 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

like image 94
deceleratedcaviar Avatar answered Sep 19 '22 18:09

deceleratedcaviar


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.

like image 20
Kris Reeves Avatar answered Sep 22 '22 18:09

Kris Reeves