How to generate an HTTPS certificate with node-forge

A script that returns a self-signed certificate in Javascript

Author's image
Tamás Sallai
3 mins

Generating HTTPS certificates

Many times I need to generate an HTTPS certificate for a development server. For example, I can use the https module to start a webserver:

import https from "https";

// ... get a certificate somehow

// start HTTPS server
const app = https.createServer({key: cert.privateKey, cert: cert.cert}, async (req, res) => {
	res.setHeader("Content-Type", "text/html");
	res.writeHead(200);
	res.end("Hello world!");
});

app.listen(8081);

But the https.createServer needs a certificate. How to generate it?

The official documentation as well as most articles recommend running OpenSSL commands in advance:

openssl genrsa -out key.pem
openssl req -new -key key.pem -out csr.pem
openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem
rm csr.pem

Then on the Javascript-side, dealing with certificates is a matter of reading files:

const options = {
	key: fs.readFileSync('key.pem'),
	cert: fs.readFileSync('cert.pem')
};

But, as usual, I wanted something simpler: an in-process generator that requires no setup and just works when I run the Javascript code.

PEM

For this I used to use the pem library. It has a createCertificate function thats result the https.createServer can use:

import pem from "pem";

const keys = await pem.promisified.createCertificate({days: 1, selfSigned: true});

https.createServer({key: keys.clientKey, cert: keys.certificate}, (req, res) => {
	// ...
}).listen(8081);

This works great, but under the hood it simply calls OpenSSL. I always found it a possible problem.

And indeed it broke recently because of this. So I continued searching for some other solution.

Node-forge

During this search, I stumbled upon this article: https://www.techengineer.one/how-to-generate-x-509v3-self-signed-certificate-in-pem-format-with-node-js/. It describes the solution for my problem.

With a few extra tweaks, this is the solution I ended up with:

import forge from "node-forge";
import crypto from "crypto";

export const generateCert = ({altNameIPs, altNameURIs, validityDays}) => {
	const keys = forge.pki.rsa.generateKeyPair(2048);
	const cert = forge.pki.createCertificate();
	cert.publicKey = keys.publicKey;

	// NOTE: serialNumber is the hex encoded value of an ASN.1 INTEGER.
	// Conforming CAs should ensure serialNumber is:
	// - no more than 20 octets
	// - non-negative (prefix a '00' if your value starts with a '1' bit)
	cert.serialNumber = '01' + crypto.randomBytes(19).toString("hex"); // 1 octet = 8 bits = 1 byte = 2 hex chars
	cert.validity.notBefore = new Date();
	cert.validity.notAfter = new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * (validityDays ?? 1));
	const attrs = [{
		name: 'countryName',
		value: 'AU'
	}, {
		shortName: 'ST',
		value: 'Some-State'
	}, {
		name: 'organizationName',
		value: 'Internet Widgits Pty Ltd'
	}];
	cert.setSubject(attrs);
	cert.setIssuer(attrs);

	// add alt names so that the browser won't complain
	cert.setExtensions([{
		name: 'subjectAltName',
		altNames: [
			...(altNameURIs !== undefined ?
				altNameURIs.map((uri) => ({type: 6, value: uri})) :
				[]
			),
			...(altNameIPs !== undefined ?
				altNameIPs.map((uri) => ({type: 7, ip: uri})) :
				[]
			)
		]
	}]);
	// self-sign certificate
	cert.sign(keys.privateKey);

	// convert a Forge certificate and private key to PEM
	const pem = forge.pki.certificateToPem(cert);
	const privateKey = forge.pki.privateKeyToPem(keys.privateKey)

	return {
		cert: pem,
		privateKey
	};
}

This generates an https-compatible certificate:

const cert = generateCert({altNameIPs: ["127.0.0.1", "127.0.0.2"], validityDays: 2});

const app = https.createServer({key: cert.privateKey, cert: cert.cert}, async (req, res) => {
	res.setHeader("Content-Type", "text/html");
	res.writeHead(200);
	res.end("Hello world!");
});

app.listen(8081);

I added the ability to specify the alt name IP and domains, as well as a validity in days. But otherwise, this is the perfect solution: fully JS-based, no outside dependency, and does not require any additional setup.

How does it work?

It uses node-forge which is a pure JS implementation of all primitives related to TLS and other cryptographic tools. It can do a lot more than just generating and signing certificates, and its documentation contains a ton of examples.

October 25, 2022
In this article