Categories
Engineering Linux

Managing Your Own CA, Elliptic Curve Edition

Historically, I’ve used my main desktop to store system backups and provide other network services, but this is annoying because I dual-boot it to play video-games, so while I’m playing most services are offline. Additionally, electricity in San Diego is absurdly expensive (thanks SDGE), so the 180+W while idling adds up quite a bit. To fix both problems, I bought an HP Prodesk 600 G3 MT from Human-I-T for very reasonable prices, hoping that the idle power draw stays under 20 Watts. While setting up that server, I decided that I might as well figure out how to set up my own CA for all of the internal services that I’m setting up, since I was getting tired of getting warnings from Firefox about “unsecured” connections due to self-signed certificates.

Most of the information I used to set up my own CA came from the OpenSSL CA documentation, which can be found here: https://openssl-ca.readthedocs.io/en/latest/index.html. However, it is missing the details on how to use Elliptic Curve keys. This post is an attempt to fill in that gap.

The process can be summarized as follows:

  1. Make a root CA key and certificate
  2. Make an intermediate CA key and certificate by requesting signing by root CA, and create an intermediate certificate chain
  3. Make individual server certificates by requesting signing by intermediate CA, and extend the certificate chain

Safeguarding Keys

It is important to protect all private keys, as if they leak anyone with them can sign certificates that pass as valid when checked against your CA certificates. This matters a little less when all of your certs are meant for your local network, but meh, might as well try to do something about it.

What I decided to do was to use LUKS to encrypt some filesystems on disk. I used one to store the CA and intermediate CA, and a second one to store client keys.

# Create the Root CA/Intermediate CA file, and the file for server keys
dd if=/dev/urandom of=ca bs=1M count=100 status=progress
dd if=/dev/urandom of=server bs=1M count=100 status=progress

# it will ask for a password-- remember it or store it somewhere secure/safe/encrypted
cryptsetup luksFormat ca
cryptsetup luksFormat server

# now open the volume, and mount it for use. Opening it will ask for the volume password
cryptsetup open ca ca-keys
cryptsetup open server server-keys
mkdir ca-keys
mkdir server-keys
mount /dev/mapper/ca-keys ca-keys
mount /dev/mapper/server-keys server-keys

It’s probably a good idea to backup the LUKS headers, in case something happens to them:

cryptsetup luksHeaderBackup ca --header-backup-file ca-keys.header.luks
cryptsetup luksHeaderBackup server --header-backup-file server-keys.header.luks

Make Root CA and its Certificate

The general outline of the steps can be found here: https://openssl-ca.readthedocs.io/en/latest/create-the-root-pair.html

cd ca-keys/
mkdir root/
cd root/
mkdir certs crl newcerts private
chmod 700 private
touch index.txt
echo 1000 > serial

At this point a openssl.cnf file needs to be created that will include default settings. It can be copied from https://openssl-ca.readthedocs.io/en/latest/root-configuration-file.html#root-configuration-file and modified to meet needs. For example, on my local install, I tweaked the dir and private_key paths, and changed the defaults for many of the *_default fields (e.g. countryName_default to US). I also tweaked the req default_bits to 4096.

With that in place, now it’s time to create the key in the ca-keys/root/ directory:

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -pkeyopt ec_param_enc:named_curve -aes-256-cbc -out private/root-ca.key.pem
# This will now ask for a password, set one and keep it safe!
chmod 400 private/root-ca.key.pem

Wait, why is that so messy? Turns out that only some elliptic-curves are really used widely online (e.g. Chrome doesn’t support secp521r1). The two that seem to be the most supported are secp384r1 and secp256r1. So I just arbitrarily-ish picked secp384r1. Also, we are using the -aes-256-cbc option to enable password protection on the key. There are other options that may work (see the output of openssl enc -list for tentative options).

OK, now we have the key. Now we need a root certificate (we’re setting the expiry date far into the future, as recommended):

openssl req -config openssl.cnf -new -sha256 -extensions v3_ca -days 7300 -key private/root-ca.key.pem -out csr/ca.csr.pem
# This will request the password for the key, and then will ask for information about the certificate such as its common name, organization, etc.
chmod 444 certs/ca.cert.pem

To verify that the certificate looks ok:

openssl x509 -noout -text -in certs/ca.cert.pem

Make Intermediate CA and its Certificate

OK, now we can work on making the intermediate CA, which is the one that will actually be used to sign all of the certificates for the websites, etc. This should look familiar as it’s similar to what we did with the root certificate:

cd ../
mkdir intermediate
cd intermediate
mkdir certs crl csr newcerts private
chmod 700 private
touch index.txt
echo 1000 > serial
echo 1000 > crlnumber

Now, we can copy the base openssl.cnf for the intermediate certificate from https://openssl-ca.readthedocs.io/en/latest/intermediate-configuration-file.html#intermediate-configuration-file. Once saved, just as with the root openssl.cnf, variables can be tweaked (such as dir, *_default, default_bits).

Now it is time to create the intermediate CA key:

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -pkeyopt ec_param_enc:named_curve -aes-256-cbc -out private/intermediate.key.pem
# This will now ask for a password, set one and keep it safe!
chmod 400 private/intermediate.key.pem

And now to create and sign the intermediate certificate:

openssl req -config openssl.cnf -new -sha256 -keyprivate/intermediate.key.pem -out csr/intermediate.csr.pem
# This will ask for the intermediate key password, and other information to fill into the certificate

cd ../root
openssl ca -config openssl.cnf -extensions v3_intermediate_ca -days 3650 -notext -md sha256 -in ../intermediate/csr/intermediate.csr.pem -out ../intermediate/certs/intermediate.cert.pem
# This will ask for the root CA password
chmod 444 ../intermediate/certs/intermediate.cert.pem

And to verify the newly created certificate:

openssl x509 -noout -text -in ../intermediate/certs/intermediate.cert.pem
# and check it against the root CA
openssl verify -CAfile ./certs/ca.cert.pem ../intermediate/certs/intermediate.cert.pem

Create the Certificate Chain

This is straight-ish forward (the order matters!). We need to concatenate the certificates of the root and the intermediate together to form the start of the certificate chain (intermediate needs to go first, the root):

# Changing directory to the root of the certs encrypted partition
cd ../
cat ./intermediate/certs/intermediate.cert.pem root/certs/ca.cert.pem > ./intermediate/certs/ca-chain.cert.pem

Great! At this point all of the initial setup is completed. From here on is a matter of creating site specific certificates, and installing the root certificate to all computers that need it.

Creating a Certificate for an Internal Site

Now we need to go into the server-certs encrypted folder we created earlier, as this is where we will store the certificates and keys for the individual sites as we create them (arguably, the better approach is to have each server create its own private keys and just provide the CSR to the signing server):

cd server-keys
mkdir site1.my.example
# Now create the key, no password
# If password is desired, add `-aes256 -pass stdin`
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -pkeyopt ec_param_enc:named_curve -out site1.my.example/site1.key.pem
chmod 400 site1.my.example/site1.key.pem

With the private key in place (which should probably securely copied to the server for this domain), we now need to prepare the certificate signing request (CSR). We need the openssl.cnf file from here again (the intermediate CA). subjectAltName are now effectively required by modern browsers if the certificate is to authenticate a website. Multiple names can be provided, separated by commas (e.g. DNS:site1.my.example,DNS:site1.local).

openssl req -config openssl.cnf -new -sha256 -key site1.my.example/site1.key.pem -out site1.my.example/site1.csr.pem -addext "subjectAltName = DNS:site1.my.example"

With the CSR in hand, we now need to go back to the intermediate CA to create the certificate:

cd ../ca-keys/intermediate/
openssl ca -config openssl.cnf -extensions server_cert -days 365 -notext -md sha256 -in ../../server-keys/site1.my.example/site1.csr.pem -out ../../server-keys/site1.my.example/site1.cert.pem

And with that we should now have a signed server certificate! The last thing to do is to create the certificate chain with this new certificate:

cd ../../server-keys/site1.my.example/
cat site1.cert.pem ../../ca-keys/intermediate/certs/ca-chain.cert.pem > site1.chain.pem

And finally, with that we should have all of the certificates and chains needed to set up properly signed certificates at home or other locations. The only thing really left to do is to install the root CA certificate on any machines and browsers that should recognize any certificates signed by the intermediate CA as authentic.

Safeguarding Keys, Again: Unmount and Lock

After being done signing and copying certificates and chains, remember to unmount and lock the encrypted storages!

# cd out of server-keys and ca-keys
umount server-keys
umount ca-keys
cryptsetup close ca
cryptsetup close server

Leave a Reply

Your email address will not be published. Required fields are marked *