5

How to generate an HTTPS certificate with node-forge

 1 year ago
source link: https://advancedweb.hu/how-to-generate-an-https-certificate-with-node-forge/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

How to generate an HTTPS certificate with node-forge

A script that returns a self-signed certificate in Javascript

5c27b3-c500220ac01bac82650cfbb66ce0b60abec5812a61774a5188975b343af905d7.jpg
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.

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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK