Search code examples
javascriptnode.jshttpsopensslself-signed

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?


Solution

  • 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