Getting an SSL certificate these days has become much easier than it was in the past, with the availability of free Certificate Authorities (CAs) like Let's Encrypt. But even so, there are scenarios when you need a certificate that couldn't be issued by them: longer term certificates, complex wildcards, local addresses within your environment, and even routers that are accessed by IP instead of a dns name. Some of these could be issued by a paid CA, others aren't even an option. Code signing certificates are also great, but not cheap, while encryption and authentication certs are generally only issued in enterprise environments.
Getting a self-signed certificate is pretty easy - most routers will generate their own certificates, and it's pretty straightforward to create your own certificate using openssl or similar tools. The problem with self-signed certificates is that they won't be trusted by default. You still get the benefit of your connection being encrypted, but there won't be a guarantee that nobody intercepted your data, altered it and signed it with their own untrusted cert, unless you check the certificate every time. You could always add your certificate to your local trust store, but you'd have to do that for every single certificate you create, on every device you access them, which will quickly become cumbersome.
The solution is simple - you can create your own private CA and add it to your trust store. Any certificates created by that CA would be trusted as well, which makes managing this considerably easier! You wouldn't use these certs on your public website, but they'd be perfect for internal services or your home lab.
Taking one step further, you could also create intermediary CAs, creating a trust chain - the end device certificates would be created by your intermediary CA. If your intermediary CA keys get compromised, you could just revoke them and create a new intermediary, and won't need to update the trust store on your machines.
In these articles I'll put down what I learned while creating my own CA. I've decided to break this down into several parts, to make it easier to digest and manage:
- Part 1: Building your own root and intermediate certificate authorities
- Part 2: Issuing certificates
- Part 3: Storage and security
- Part 4: Additional options and features
The main requirements for setting up and managing your CA would be an installation of OpenSSL, and having the correct time and date set on your machine. Once ready, create a folder structure for your CAs. In my case, I created a separate folder for each CA - inside you have everything you need for that CA - configuration, private keys, certificates etc.
I also recommend initialising a local git repo in each CA path, especially while experimenting - it will help you keep track of your changes, and revert any steps you made if you made any mistakes! You don't have to do it, but I found it useful for myself. If you do, make sure you don't push your repo to a public server and expose your private keys. I just kept it as a local repository to keep track of the file/CA changes.
First we set up a folder structure for our root CA, add the folders and initial files we'll be using later:
mkdir /root/caw cd /root/ca mkdir newcerts certs crl private requests touch index.txt touch index.txt.attr
The folders created will be used for:
newcerts- will store ALL the historically generated certificate files
certs- will be used to store the certificates, and I also store my
crl- will store the Certificate Revocation List (CRL) files
privatewill store the private keys for our certificates
requestswill be used to store the certificate request files (
index.txt is the "database" used by OpenSSL to manage the CA
Once we have the basic structure, we can configure our CA. You can use my root openssl.conf as a baseline, copy it to your CA folder, and at the very least customise entries marked with
Once everything is configured - we can create our private key and root certificate! (don't forget to adjust the file names) In my case, I created the root CA with an expiration of 25 years (9125 days)
openssl genrsa -aes256 -out private\root.key Generating RSA private key, 2048 bit long modulus (2 primes) .......+++++ .......................................+++++ e is 65537 (0x010001) Enter pass phrase for private\root.key: Verifying - Enter pass phrase for private\root.key: openssl req -config openssl.conf -new -x509 -extensions v3_ca -key private\root.key -out root.crt -days 9125 Enter pass phrase for private\root.key:
Creating the intermediate CA is a similar affair. Initialise the folder structure and the files you'll need:
mkdir /root/interm cd /root/interm mkdir newcerts certs crl private requests touch index.txt touch index.txt.attr
In a similar fashion, you need a configuration file - note that the intermediary CA doesn't have to be the same as the root CA. In my case, they are a number of differences - you can use my intermediate openssl.conf as your baseline, and adjust it accordingly.
Once configured - it's time to create your private key, and intermediate CA request:
openssl genrsa -aes256 -out private\interm.key Generating RSA private key, 2048 bit long modulus (2 primes) ...............................+++++ ..................+++++ e is 65537 (0x010001) Enter pass phrase for private\interm.key: Verifying - Enter pass phrase for private\interm.key: openssl req -config openssl.conf -new -key private\interm.key -out ..\ca\requests\interm.csr Enter pass phrase for private\interm.key:
With your CSR created, we're going back to our CA and will sign this request.
One thing to note - each certificate you create will have a unique serial number. In my case, I wanted them to be unique and nondeterministic - for this reason before generating a certificate, I write a new hash into a file called
serial. If you instead want the certificate serial numbers to be ordinal, you could just create a
serial file with just one line for your initial serial value. Each time a certificate is created, the value in the serial file will be auto-incremented.
The output below is from my intermediate certificate:
cd /root/ca openssl rand -hex 20 > serial openssl ca -config openssl.conf -extensions v3_intermediate_ca -notext -in requests\interm.csr -out certs\interm.crt Using configuration from openssl.conf Enter pass phrase for /root/ca/private/interm.key: Check that the request matches the signature Signature ok Certificate Details: Serial Number: aa:cb:1c:d1:f0:8a:ef:d2:8a:7c:33:f2:29:53:18:0f:1f:f4:53:a9 Validity Not Before: May 12 12:31:29 2019 GMT Not After : May 10 12:31:29 2024 GMT Subject: countryName = GB localityName = London organizationName = FlexLabs Ltd commonName = FlexLabs Ltd Test Web CA emailAddress = [email protected] X509v3 extensions: X509v3 Subject Key Identifier: BB:8F:1A:71:F8:70:47:FE:30:02:DB:CF:C9:05:CF:42:78:2E:E3:56 X509v3 Authority Key Identifier: keyid:9C:2B:69:88:30:B8:D6:C0:92:66:A5:5B:7E:FB:AB:65:2C:13:38:FB X509v3 Basic Constraints: critical CA:TRUE, pathlen:0 X509v3 Key Usage: critical Digital Signature, Certificate Sign, CRL Sign X509v3 CRL Distribution Points: Full Name: URI:https://ca.flexlabs.org/testroot.crl Authority Information Access: CA Issuers - URI:https://ca.flexlabs.org/testroot.crt OCSP - URI:https://ca.flexlabs.org/ocsp/testroot Certificate is to be certified until May 10 12:31:29 2024 GMT (1825 days) Sign the certificate? [y/n]:y 1 out of 1 certificate requests certified, commit? [y/n]y Write out database with 1 new entries Data Base Updated
Once a CA signs a certificate, it is also copied to the
newcerts folder and is named after it's serial number. The
index.txt database is also updated, and get a new row for the generated certificate, containing the status, time of issue, time of revocation (if revoked), serial and the subject:
V 240510123129Z AACB1CD1F08AEFD28A7C33F22953180F1FF453A9 unknown /C=GB/L=London/O=FlexLabs Ltd/CN=FlexLabs Ltd Test Web CA/[email protected]
You can copy the generated certificate to the root of your intermediary CA. I also create a chain file, containing both the intermediary CA and it's parent:
copy certs\interm.crt ..\interm\interm.crt copy certs\interm.crt ..\interm\interm.chain.crt echo root.crt >> ..\interm\interm.chain.crt
Installing the root certificate
Finally, don't forget to install your root certificate into your trusted certificate store, to make sure your certificates are recognised as trusted!
On a Windows machine, you can double click your root certificate file, and click on "Install Certificate" which will start the "Certificate Import Wizard". Make sure you pick "Local Machine" in the "Store Location" field. When asked for the "Certificate Store", pick the "Place all certificates in the following store" and select the "Trusted Root Certification Authorities" store.
In the second part I'll discuss creating certificates that can be used on web servers, e-mail encryption, code signing and so on.