ssl-root-cas.js/Painless-Self-Signed-Certif...

8.1 KiB

Painless Self Signed Certificates in node.js

Try the code

I made a complete, cloneable example:

https://git.coolaj86.com/coolaj86/nodejs-self-signed-certificate-example

TL;DR

If you don't like to read you can just copy and paste this into your terminal.

Create your Root CA and your Signed Certificate

STOP: There is one thing you need to change: Replace CN=local.ldsconnect.org with your domain.

HOWEVER, local.ldsconnect.org points to 127.0.0.1, so this example will work if you simply copy and paste with 0 modifications.

# make directories to work from
mkdir -p server/ client/ all/

# Create your very own Root Certificate Authority
openssl genrsa \
  -out all/my-private-root-ca.key.pem \
  2048

# Self-sign your Root Certificate Authority
# Since this is private, the details can be as bogus as you like
openssl req \
  -x509 \
  -new \
  -nodes \
  -key all/my-private-root-ca.key.pem \
  -days 1024 \
  -out all/my-private-root-ca.crt.pem \
  -subj "/C=US/ST=Utah/L=Provo/O=ACME Signing Authority Inc/CN=example.com"

# Create a Device Certificate for each domain,
# such as example.com, *.example.com, awesome.example.com
# NOTE: You MUST match CN to the domain name or ip address you want to use
openssl genrsa \
  -out all/my-server.key.pem \
  2048

# Create a request from your Device, which your Root CA will sign
openssl req -new \
  -key all/my-server.key.pem \
  -out all/my-server.csr.pem \
  -subj "/C=US/ST=Utah/L=Provo/O=ACME Tech Inc/CN=local.ldsconnect.org"

# Sign the request from Device with your Root CA
openssl x509 \
  -req -in all/my-server.csr.pem \
  -CA all/my-private-root-ca.crt.pem \
  -CAkey all/my-private-root-ca.key.pem \
  -CAcreateserial \
  -out all/my-server.crt.pem \
  -days 500

# Put things in their proper place
rsync -a all/my-server.{key,crt}.pem server/
rsync -a all/my-private-root-ca.crt.pem server/
rsync -a all/my-private-root-ca.crt.pem client/

The only 3 files you need on your server are these:

server
├── my-private-root-ca.crt.pem
├── my-server.crt.pem
└── my-server.key.pem

The 1 file you need on your clients is this:

client
└── my-private-root-ca.crt.pem

Your server

#!/usr/bin/env node
'use strict';

var https = require('https')
  , port = process.argv[2] || 4443
  , fs = require('fs')
  , path = require('path')
  , server
  , options
  ;

require('ssl-root-cas')
  .inject()
  .addFile(path.join(__dirname, 'server', 'my-private-root-ca.crt.pem'))
  ;

options = {
  key: fs.readFileSync(path.join(__dirname, 'server', 'my-server.key.pem'))
// You don't need to specify `ca`, it's done by `ssl-root-cas`
//, ca: [ fs.readFileSync(path.join(__dirname, 'server', 'my-private-root-ca.crt.pem'))]
, cert: fs.readFileSync(path.join(__dirname, 'server', 'my-server.crt.pem'))
};


function app(req, res) {
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello, encrypted world!');
}

server = https.createServer(options, app).listen(port, function () {
  port = server.address().port;
  console.log('Listening on https://127.0.0.1:' + port);
  console.log('Listening on https://' + server.address().address + ':' + port);
  console.log('Listening on https://local.ldsconnect.org:' + port);
});

Your client

#!/usr/bin/env node
var https = require('https')
  , fs = require('fs')
  , path = require('path')
  , ca = fs.readFileSync(path.join(__dirname, 'client', 'my-private-root-ca.crt.pem'))
  ;

var options = {
  host: 'local.ldsconnect.org',
  path: '/',
  ca: ca
};
options.agent = new https.Agent(options);

https.request(options, function(res) {
  res.pipe(process.stdout);
}).end();

What you need to know

I struggled for a bit with self-signed certificates until I found out that YOU'RE NOT SUPPOSED TO SERVE SELF-SIGNED CERTIFICATES.

Seriously. They're not supposed to be public-facing. And that's why you get some many various types of errors that are difficult to resolve such as SSL certificate problem: Invalid certificate chain and DEPTH_ZERO_SELF_SIGNED_CERT.

The purpose of self-signed certificates is for Root CAs to hide them away in a safe place and occasionally sign 2nd-tier certificates which sign 3rd-tier certificates which sign... and so forth until you pay just $24.97 for an Nth-tier certificate that only works in the most recent browsers and devices.

So here's how to win:

  • Create a CA - KEEP IT PRIVATE (your server never sees it except the .crt.pem)
  • Self-sign your CA
  • Create a server certificate
  • Sign your public-facing cert THIS IS PUBLIC (but none of it ever leaves your server and the clients never see any of it)

Create A Certificate Authority

Since this is for private use and testing it doesn't much matter if the information is correct or not.

If you want, you can just copy and paste this directly:

openssl genrsa \
  -out my-private-root-ca.key.pem \
  2048

That creates a key without a passphrase. If you want to protect this key with a passphrase, add the option -des3.

Sign your Certificate Authority with itself

Normally you have to create a signing request (csr.pem) and then have it signed. This does both in one step.

openssl req \
  -x509 \
  -new \
  -nodes \
  -key my-private-root-ca.key.pem \
  -days 1024 \
  -out my-private-root-ca.crt.pem \
  -subj "/C=US/ST=Utah/L=Provo/O=ACME Signing Authority Inc/CN=example.com"
  • -new means your generating a new signature, this is why you don't have to provide an infile
  • -key is the key you're using to sign it
  • -nodes means "no des" or "don't encrypt with a des cipher" or, most simply, "no password"

If you want to keep this cert in a safe place and sign lots of stuff with it, then you should put a passphrase on it.

Create your SERVER cert

Looks pretty familiar, eh?

openssl genrsa \
  -out my-server.key.pem \
  2048

Create a signing request (csr.pem)

It's important to note that the CN MUST match YOUR domain.

If I want to use this certificate with api.example.com then it must say exactly that, example.com will not work. And although *.example.com is a valid wildcard for api.example.com, it will not work for example.com (you would need two certificates, and SNL vhosting or the v3_req subjectAltName extension)

CN may also be an IP address.

openssl req -new \
  -key my-server.key.pem \
  -out my-server.csr.pem \
  -subj "/C=US/ST=Utah/L=Provo/O=ACME Tech Inc/CN=awesome.example.com"

I recommend using a different Organization name, just so that it's easier to spot and debug at-a-glance in browsers and whatnot.

Sign your server cert with your CA

openssl x509 \
  -req -in my-server.csr.pem \
  -CA my-private-root-ca.crt.pem \
  -CAkey my-private-root-ca.key.pem \
  -CAcreateserial \
  -out my-server.crt.pem \
  -days 500
  • -days should be fewer than you specified in your certificate authority

Appendix

pem: Just so you know, pem means plain-text format (but the acronym is something about email and mime types)

Other SSL Resources

Zero-Config clone 'n' run (tm) Repos:

Articles