Self-Signing Certificate Authorities
Introduction
If you run a website which receives or displays personal information, passwords or other secrets, you need to encrypt your connections using SSL or TLS. This is what puts the "S" into HTTPS, FTPS, IMAPS, POPS etc, and requires private keys and public certificates. Your browser (or other SSL/TLS client) trusts certain CAs (certificate authorities), and they in turn are willing to trust you by issuing you a certificate, if you throw money at them.
This is necessary for public-facing production deployments, and these days the cheapest certificates don't cost the earth - for example, Namecheap's start at £6/$9, and Let's Encrypt should also be launching this summer, providing free domain-validated certificates. However, cheap certificates are domain validated, where the CA checks that you own the domain using automated tests; these can be slow and laborious, so it's often not practical or possible to get these for internal services or development environments.
The alternative is to set yourself up as a self-signing CA. You won't be trusted by the public, but it's a good option if you're in the position where you're not public-facing, or where you can tell your users to install your root CA before they use your service. You'll be able to issue as many certificates as you want, and your connection should be just as secure as with a "proper" certificate.
The easy way
I've written caman which will do everything for you - it's a bash script with all the commands I'm about to describe in excruciating detail, so if all you want to do is jump to getting your certificates, go over to the project page and start following its instructions.
Read on if you want to know how to do it yourself.
What we're aiming for
We're going to create a directory which contains your CA, your host keys, and the certificates you generate. The structure will be:
ca/ Your CA information
caconfig.cnf CA configuration (modify this)
ca.key.pem CA private key (keep this safe)
ca.crt.pem CA public certificate (distribute this)
ca.crl.pem List of revoked keys (publish this)
serial Next serial number
index.txt Registered certificates
crlnumber Next CRL number
newcerts/ A copy of each certificate signed
store/ Your certificates
host.domain.tld/ One folder per host
config.cnf Config for the given host
YYYY-MM-DD/ Build date for cert set
host.domain.tld.key.pem Key
host.domain.tld.csr Signing request
host.domain.tld.crt.pem Certificate
host.domain.tld.keycrt.pem Key + cert combo
Prepare your CA
Before we start, you'll need openssl
to be installed on your system. In Ubuntu, that would be:
sudo apt-get install openssl
Now we'll create the CA dir, and start the serial number at 1:
cd my_ca
mkdir ca store
mkdir ca/certs ca/private
echo '01' > ca/serial
touch ca/index.txt
You will now need to create your ca/caconfig.cnf
file. You can use the caman template:
curl https://raw.githubusercontent.com/radiac/caman/master/ca/caconfig.cnf.default > ca/caconfig.cnf
You will need to change some lines - look for the comments starting # >>
.
-
Change the 6 values under
[ req_distinguished_name ]
:countryName
: your two-character country codestateOrProvinceName
: your state or provinceorganizationName
: the name of your organisationorganizationUnitName
: your department in the organisationcommonName
: the name of your organisationemailAddress
: your e-mail address
-
Change the CRL distribution points URL under
[ usr_cert ]
and[ v3_ca ]
:crlDistributionPoints
: URL where you will publish yourca.crl.pem
- The CRL is a list of revoked certificates which you'll need to update each time you revoke a host certificate; if you don't want to be bothered with this, you can just comment these lines out, as well as
crl_extensions
andcrlnumber
under[ CA_default ]
.
- You can ignore the value for
default_days
here - caman uses it, but OpenSSL won't; we'll be passing it directly on the command line.
Create the root certificate
Your CA is identified by its root certificate. First we need to create the private CA key:
openssl genrsa -aes256 -out ca/ca.key.pem 4096
The openssl genrsa
command generates an RSA key, and the other options tell it to encrypt it with AES 256 (-aes256
), to use 4096 bits, and to write it to the file ca/ca.key.pem
.
When you run the command, it will ask you for a password - keep it safe, you'll need it every time you want to generate and sign a new certificate.
This will generate the private CA key, ca/ca.key.pem
. This is important:
- Do not lose this key. Without it, certificates can't be signed or renewed
-
Do not disclose this key to anyone. If it is compromised, others will be able to impersonate the CA.
Now we can use the key to generate the public CA certificate:
openssl req -x509 -new -key ca/ca.key.pem -days 36500 -out ca/ca.crt.pem -config ca/caconfig.cnf
The openssl req
command with -x509
tells OpenSSL to generate a self-signed certificate; the other options tell it to generate a new certificate (-new
) and how long the certificate should be valid for - in this case, -days 36500
means about 100 years. It then uses the caconfig.cnf
which we configured earlier, and puts the certificate in the file ca/ca.crt.pem
.
Publishing your CA Certificate and CRL
To get clients to trust certificates signed by the new self-signing authority, they must install the root certificate, ca.crt.pem
.
The easiest way to do this is to put it on your web server as a normal file for download, called ca.crt
. Most browsers will know what to do with it from there.
If you're using Apache to serve your page, it may need to know the MIME type - add this to your configuration or .htaccess
:
AddType application/x-x509-ca-cert .crt
You can then generate your CRL with the following command:
openssl ca -gencrl -out ca/ca.crl.pem -config ca/caconfig.cnf
To publish it, just put it at the URL you defined in your caconfig.cnf
above. Remember to update it each time you renew or revoke a certificate.
Preparing for a new host
Before we start creating hosts, lets set up a directory to store them.
In this article we'll be creating a certificate for my.example.com
:
mkdir store/my.example.com
To make a wildcard certificate to cover multiple hosts, just use an asterisk - for example, *.example.com
would catch any subdomain of example.com.
You will now need to create a configuration for this host. Again, you can use the caman template:
curl https://raw.githubusercontent.com/radiac/caman/master/ca/host.cnf.default > store/my.example.com/config.cnf
You will need to change some lines - look for the comments starting # >>
.
-
Change 4 of the values under
[ host_distinguished_name ]
:countryName
: the two-character country code for this hoststateOrProvinceName
: the state or province for this hostorganizationName
: the name of the organisation for this hostemailAddress
: the e-mail address for the admin for this host- Do not change
commonName
ororganizationUnitName
- these are placeholders which will be set by caman
- You can ignore the value for
default_days
here - caman uses it, but OpenSSL won't; we'll be passing it directly on the command line.
You're now ready to make the certificate itself.
Creating a host certificate
First create a new sub-directory to store this set of files - there will be a key, a CSR (certificate signing request) and a certificate. I like to use the date I'm generating the certificate:
mkdir store/my.example.com/2015-05-18
Now we'll create a new private key and CSR with this command:
openssl req -sha256 \
-newkey rsa:2048 -nodes \
-keyout store/my.example.com/2015-05-18/my.example.com.key.pem \
-new -out store/my.example.com/2015-05-18/my.example.com.csr \
-config store/my.example.com/config.cnf
This again calls openssl req
, but without -x509
this time because we don't want it to be self-signed - we want to sign it using our own CA. We then tell it to use SHA256 (-sha256
), and generate a new 2048 bit RSA private key (-newkey rsa:2048
) which is not encrypted (-nodes
- that's "no DES", not "nodes") in the file ending .key.pem
. It will then use this to create a new CSR (-new
) in the file ending .csr
. It uses the host config we created earlier to provide any additional settings.
Next we need to sign the CSR to generate the certificate:
openssl ca \
-in store/my.example.com/2015-05-18/my.example.com.csr \
-out store/my.example.com/2015-05-18/my.example.com.crt.pem \
-days 3650 -config ca/caconfig.cnf
This calls openssl ca
to use the certificate authority to sign the CSR (-in
) and generate the certificate (-out
). Here it will be valid for 3650 days (-days
), or approximately 10 years. We give it the CA config this time, so it knows where to find all the settings for our CA.
You will now have a private key ending .key.pem
, and a public certificate ending .crt.pem
. In most cases you'll use these separately, but some services may want them merged together in one file (such as Apache wth the SSLCertificateFile
directive), so we'll concatenate them just in case it's needed:
cat store/my.example.com/2015-05-18/my.example.com.key.pem \
store/my.example.com/2015-05-18/my.example.com.crt.pem \
> store/my.example.com/2015-05-18/my.example.com.keycrt.pem
You can now copy those three files to the server (you won't need the .csr
) and configure your services to use them. That's a bit beyond the scope of this article, but may be something I'll follow up with in a later one.
Revoking and renewing certificates
If a host's certificate has expired, is no longer needed, or has been compromised, you must revoke the current certificate for that host, and generate and publish a new CRL.
First check ca/index.txt
for the index corresponding to the host you're revoking:
more index.txt | grep my.example.com
You will see something like this:
R 250513123511Z 150516124528Z 01 unknown /C=CN/ST=State/O=MyOrg/OU=my.example.com/CN=my.example.com/emailAddress=email@example.com
R 250513124545Z 150516124604Z 02 unknown /C=CN/ST=State/O=MyOrg/OU=my.example.com/CN=my.example.com/emailAddress=email@example.com
V 250513124606Z 03 unknown /C=CN/ST=State/O=MyOrg/OU=my.example.com/CN=my.example.com/emailAddress=email@example.com
If a line starts with an R
it means that certificate has been revoked; look for the one starting V
with CN=
which matches your host; it will almost certainly be the last one. Then take the short number in the fourth column; in the example above, the index is 03
.
Now you have the index, you can revoke that certificate:
openssl ca -revoke ca/newcerts/03.pem" -config ca/caconfig.cnf
replacing 03
with the index for that certificate.
If you are replacing the certificate, you can now create a new host certificate exactly as you did in the Creating a host certificate section above.
Don't forget to update and publish a new CRL, as described under Publishing your CA Certificate and CRL.
Comments
Exactly what I needed. Awesome! Thank you.
I don't really like the CA model, and believe anyone should be able to setup its own CA, just like GPG keys. Peers should be able to trust the CA's they want easily.
Thanks for the comment and contributions!
I agree, the current model isn't that great - I understand the need for a widely-accepted set of root CAs for the modern web to work, but the ability to add new CAs for trusted peers is essential. Unfortunately we're starting to see places where unofficial CAs aren't as trusted - eg if you install your CA in an Android device, you'll get a "Network may be monitored" warning every time you turn it on.
Leave a comment
- Add a comment - it's quick, easy and anonymous