Create your own certificate authority (for testing)
Published 2015-7-2This is an adaptation of my previous articles How to create Peer TLS Certificates and How to create and test a CSR for HTTPS.
For super quick testing with https, you may be interested in telebit.js.
For a "one and done" solution on the server, you may like greenlock.js
WARNING: Again, don't ever try to use a truly self-signed certificate (Root CA) directly, even in testing. Always use a certificate which has been signed by your self-created Root CA.
Why? Because Self-Signed Certificates are a LIE!!
A "self-signed" certificate is otherwise known as a "Root Certificate Authority". Many browsers and tools will reject these not just with warnings, but with actual errors.
A proper "self-signed" certificate would be one that you sign with a Root CA that you create - that's what we'll be doing here.
WARNING: Some clients have different underlying behaviors when instructed to use
invalid certificates. For example curl
on OS X does not send SNI when using the
--insecure
(-k
) option. But if you have your own Root CA and supply it, curl behaves.
Scope
We'll discuss how to create certificates for example.com
domains.
First you'll need sudo vim /etc/hosts
to add a few domains.
/etc/hosts
:
127.0.0.1 example.com
127.0.0.1 foo.example.com
127.0.0.1 bar.example.com
127.0.0.1 baz.example.com
127.0.0.1 ssh.example.com
127.0.0.1 vpn.example.com
127.0.0.1 localhost.example.com
Create a Certificate Authority
You need to create an authority for yourself. This is a true "self-signed certificate" or, in other words, a Root Certificate Authority (Root CA).
(Yes, I know I'm repeating myself about the Root CA vs "self-signed" bit. Hopefully it'll get through to you.)
create-root-ca.sh
:
#!/bin/bash
# make directories to work from
mkdir -p certs/ca
# Create your very own Root Certificate Authority
openssl genrsa \
-out certs/ca/my-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 certs/ca/my-root-ca.key.pem \
-days 9131 \
-out certs/ca/my-root-ca.crt.pem \
-subj "/C=US/ST=Utah/L=Provo/O=ACME Signing Authority Inc/CN=example.net"
Create *.example.com Private Keys and CSRs
In this step we're going to generate the private key, which will be tied to the server's identity by way of the certificate.
create-key-and-csr.sh
:
#!/bin/bash
# Change to be whatever
FQDN="$1"
# make directories to work from
mkdir -p certs/{servers,tmp}
# Create Certificate for this domain,
mkdir -p "certs/servers/${FQDN}"
openssl genrsa \
-out "certs/servers/${FQDN}/privkey.pem" \
2048
# Create the CSR
openssl req -new \
-key "certs/servers/${FQDN}/privkey.pem" \
-out "certs/tmp/${FQDN}.csr.pem" \
-subj "/C=US/ST=Utah/L=Provo/O=ACME Service/CN=${FQDN}"
You would use this script like so:
bash create-key-and-csr.sh foo.example.com
Sign the Certificate
Since we're doing development / testing stuff, I won't blame you if you do cheat a little but, technically, you should be creating each private key on the server which uses it, transferring just the CSR over to the server which is signing it, and then transfer the server certificate as well as the signing certificates (not keys) back with it.
sign-certificate-request.sh
:
#!/bin/bash
FQDN="$1"
# Sign the request from Server with your Root CA
openssl x509 \
-req -in certs/tmp/${FQDN}.csr.pem \
-CA certs/ca/my-root-ca.crt.pem \
-CAkey certs/ca/my-root-ca.key.pem \
-CAcreateserial \
-out certs/servers/${FQDN}/cert.pem \
-days 9131
# If you already have a serial file, you would use that (in place of CAcreateserial)
# -CAserial certs/ca/my-root-ca.srl
You would use this script like so:
bash sign-certificate-request.sh foo.example.com
Create Bundles
Some web servers are happy to have your private key separate from your certificate and the chain responsible for it. Others like them to be bundled in a certain way.
create-bundles.sh
:
#!/bin/bash
FQDN="$1"
echo ""
echo "PRIVATE server bundle: certs/servers/${FQDN}/server.pem"
echo "(keep it secret, keep it safe - just like privkey.pem)"
echo ""
echo ""
cat \
"certs/servers/${FQDN}/privkey.pem" \
"certs/servers/${FQDN}/cert.pem" \
> "certs/servers/${FQDN}/server.pem"
echo "chain: certs/servers/${FQDN}/chain.pem"
echo "(contains Intermediates and Root CA in least-authoritative first manner)"
echo ""
echo ""
# if there were an intermediate, it would be concatonated before the Root CA
cat \
"certs/ca/my-root-ca.crt.pem" \
> "certs/servers/${FQDN}/chain.pem"
# TODO
#
# The Convention for Full Chain is one of these:
# root + intermediates + cert
# root + intermediates
# intermediates + cert
#
# ... but I don't remember which
# I may be wrong about chain as well...
echo "fullchain: certs/servers/${FQDN}/fullchain.pem"
echo "(contains Server CERT, Intermediates and Root CA)"
echo ""
echo ""
cat \
"certs/servers/${FQDN}/cert.pem" \
"certs/ca/my-root-ca.crt.pem" \
> "certs/servers/${FQDN}/fullchain.pem"
echo "All Done"
You would use this script like so:
bash create-bundles.sh foo.example.com
How to use with servers
haproxy
I don't think it matters how you concatonate them, but haproxy needs your intermeditate certs, server cert, and server key.
It should NOT contain the Root CA (it'll get confused by it)
frontend foo_ft
mode http
bind 0.0.0.0:80
bind 0.0.0.0:443 ssl crt chain.pem crt server.pem
use_backend foo_bk
backend foo_bk
mode http
server 127.0.0.1:8080
node.js
See the example below in the client section; the server takes the same options as the client.
How to use with clients
cURL
Instead of using --insecure
(-k
) you can now specify --cacert
or --capath
.
curl https://foo.example.com/api/endpoint --cacert certs/servers/foo.example.com/chain.pem
You cannot use a Root CA as a self-signed cert with curl's --cecert
option.
If you try it'll throw errors because there will be no chain to follow.
It won't be able to validate the leaf node.
If you were instead to use --capath
, it would need to point to a directory that had files such as
intermediate.crt.pem
my-root-ca.crt.pem
curl https://foo.example.com/api/endpoint --capath certs/ca
node.js
'use strict';
var request = require('request');
var agentOptions = {
host: 'foo.example.com'
, port: '443'
, path: '/'
, rejectUnauthorized: true
// note, you cannot use chain.pem, you must specify files individually
, ca: [
fs.readFileSync('certs/ca/my-root-ca.pem')
//, fs.readFileSync('certs/ca/intermediate.pem')
]
};
var agent = new https.Agent(agentOptions);
request({
url: "https://foo.example.com/api/endpoint"
, method: 'GET'
, agent: agent
}, function (err, resp, body) {
// ...
});
What about intermediates?
When you purchase a certificate for $10 at name.com you can't turn around and use it to sign other certificates for yourself... but why not?
They're doing it, right? They are issuing from an intermediate, not a root cert.
Obviously these intermediates have been signed in such a way that they are allowed to sign again themselves. I don't really understand that process, but you can learn more about it from someone who does:
See https://jamielinux.com/docs/openssl-certificate-authority/create-the-intermediate-pair.html
By AJ ONeal
Did I make your day?
Buy me a coffee
(you can learn about the bigger picture I'm working towards on my patreon page )