PKI at Home

By Connor Taffe | Published .

When running services at home, the proliferation of self-signed certificates and browser errors to click through can become a pain point. By creating our own internal Certificate Authority (CA) and loading that certificate onto relevant machines, we can access these services securely and simply.

To do this, I use Cloudflare's cfssl and follow a simple scheme I've encoded in my certs repo, orchestrated by the Makefile. This system is based on a pattern Rob Blackbourn wrote about.

Make

Our goal is to create a Certificate Authority (CA), Intermediate CA, and certificates signed by the Intermediate CA for each of our "servers" or services. If I want to construct a certificate just for my VMWare server, then my top-level goal could be:

.PHONY: certs
certs: \
	servers/vms/vms.home.arpa-server.pem \
	servers/vms/vms.home.arpa-server-key.pem

All this says is that there is a goal named certs that is not a file (.PHONY), and it requires two files: the public and private certificates, which I name after the fully qualfied domain of the service on my network: vms.home.arpa. Given this goal, make looks for a way to create the two named files.

To do this, I have a pattern rules which will construct set of public and private keys:

servers/%-server.pem servers/%-server-key.pem: servers/%.json intermediate-ca.pem intermediate-ca-key.pem cfssl.json
	cfssl gencert -ca intermediate-ca.pem -ca-key intermediate-ca-key.pem -config cfssl.json -profile=server $< | cfssljson -bare $(basename $<)-server

This rule requires a configuration file, servers/vms/vms.home.arpa.json along with keys for the Intermediate CA adn the global config. The two configs we will have to provide, and are illustrated below:

The file servers/vms/vms.home.arpa.json looks like:

{
    "CN": "vms.home.arpa",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "US",
            "ST": "Arkansas",
            "L": "Little Rock",
            "O": "Heavy Computer",
            "OU": "Heavy Computer Registry"
        }
    ],
    "hosts": [
        "vms.home.arpa",
        "localhost",
        "10.0.3.1"
    ]
}

For the CN I use the fully qualified internal domain. I also add it in hosts alongside the persistent IP address configured via DHCP and localhost for convenience and debugging purposes. The key section determines what size and algorithm the certificate will use, we use an RSA key of length 2048 which is the current NIST suggested minimum.1 The names section is not required to be accurate.

The file cfssl.json looks like:

{
    "signing": {
        "default": {
            "expiry": "87600h"
        },
        "profiles": {
            "intermediate-ca": {
                "usages": [
                    "signing",
                    "digital signature",
                    "key encipherment",
                    "cert sign",
                    "crl sign",
                    "server auth",
                    "client auth"
                ],
                "expiry": "87600h",
                "ca_constraint": {
                    "is_ca": true,
                    "max_path_len": 0,
                    "max_path_len_zero": true
                }
            },
            "peer": {
                "usages": [
                    "signing",
                    "digital signature",
                    "key encipherment",
                    "client auth",
                    "server auth"
                ],
                "expiry": "87600h"
            },
            "server": {
                "usages": [
                    "signing",
                    "digital signing",
                    "key encipherment",
                    "server auth"
                ],
                "expiry": "87600h"
            },
            "client": {
                "usages": [
                    "signing",
                    "digital signature",
                    "key encipherment",
                    "client auth"
                ],
                "expiry": "87600h"
            }
        }
    }
}

At the moment we are only using the server profile as indicated by -profile=server in our pattern, but we will later reference intermediate-ca. I use ten years for all the expirations, I am not concerned about man-in-the-middle attacks using leaked certificates; for your use-case set whatever is appropriate.

Intermediate CA

Now that we've covered the config files, our rule still needs an Intermediate CA, which is where the next rule comes in:

intermediate-ca.pem intermediate-ca-key.pem: ca.pem intermediate-ca.json cfssl.json
	cfssl gencert -initca intermediate-ca.json | cfssljson -bare intermediate-ca
	cfssl sign -ca ca.pem -ca-key ca-key.pem -config cfssl.json -profile intermediate-ca intermediate-ca.csr | cfssljson -bare intermediate-ca

This rule creates an Intermediate CA adn signs it with the CA. The rule requires the CA certificates, Intermediate CA configuration, and global configuration. The global configuration is covered above, so I'll reproduce intermediate-ca.json below:

{
    "CN": "Heavy Computer Intermediate CA",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "US",
            "ST": "Arkansas",
            "L": "Little Rock",
            "O": "Heavy Computer",
            "OU": "Heavy Computer Intermediate CA"
        }
    ],
    "ca": {
        "expiry": "87600h"
    }
}

You'll notice its very similar to the vms.home.arpa.json configuration above, but with a CA expiry field configured to ten years.

CA

The CA is constructed by another rule:

ca.pem ca-key.pme: ca.json
	cfssl gencert -initca ca.json | cfssljson -bare ca
	# Add to macOS keychain
	sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ca.pem

This rule not only constructs the CA, but adds it to my MacBook's system keychain. It requires a CA configuration, ca.json is reproduced below:

{
    "CN": "Heavy Computer Root CA",
    "CA": {
        "expiry": "87600h"
    },
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "O": "Heavy Computer",
            "OU": "Heavy Computer Root CA",
            "L": "Little Rock",
            "ST": "Arkansas",
            "C": "US"
        }
    ]
}

You'll notice its almost identical to the Intermediate CA, except ca.expiry has moved to CA.expiry.

Sync

At the end of the Makefile, there's a sync rule:

\.synced: certs
	gsutil -q -m rsync -u -x '(?!^.*\.pem$$)' -r . gs://certs.connor.zip
	gsutil -q -m rsync -u -x '(?!^.*\.pem$$)' -r gs://certs.connor.zip .
	date -u +'%Y-%m-%dT%H:%M:%SZ' >$@

This rule copies all generated .pem files to a GCS bucket using gsutil rsync, then syncs any files from the bucket back to our local filesystem.

Usage

You can clone my certs repo and use it to generate your own certs. Simply:

  1. Delete or reconfigure the \.synced rule in the Makefile.
  2. If not on macOS, remove the sudo security add-trusted-cert ... line from the CA rule.
  3. Remove folders under clients/ and servers/ and replace them with services of your own.
  4. Edit the certs rule in the Makefile to reflect only your new folders, each must contain a config with the same prefix as your .pem files. For instance, servers/vms/vms.home.arpa.json matches servers/vms/vms.home.arpa-server.pem and servers/vms/vms.home.arpa-server-key.pem.
  5. Run brew install cfssl to install the utility.

Then just run make from the root of the repository to generate all required certificates.

Follow similar instructions except in clients to create client certificates like those for IRC.

Configuring Services

This section outlines instructions for some services I use on my network.

VMWare

To install a new certificate on VMWare 6.x, follow the instructions here.

  1. Navigate to the Web UI and from the Host page click Actions, Services, Enable Secure Shell (SSH)
  2. Create a combined certificates file:
    cat servers/vms/vms.home.arpa-server.pem intermediate-ca.pem ca.pem > vms.home.arpa-chain.pem
    
  3. Copy the certificates, using the hostname on your network:
    scp vms.home.arpa-chain.pem servers/vms/vms.home.arpa-server-key.pem vms.home.arpa:.
    
  4. Log into the node using SSH:
    ssh vms.home.arpa
    
  5. Move the existing certs to backup files:
    mv /etc/vmware/ssl/rui.crt /etc/vmware/ssl/orig.rui.crt
    mv /etc/vmware/ssl/rui.key /etc/vmware/ssl/orig.rui.key
    
  6. Copy our new certificates into position:
    mv vms.home.arpa-chain.pem /etc/vmware/ssl/rui.crt
    mv vms.home.arpa-server-key.pem /etc/vmware/ssl/rui.key
    
  7. Reboot VMWare

Once the machine comes back up, navigating to the UI should show no TLS errors from our browser, assuming our CA is in the system keychain.

IRC

Client certificates (generated under clients/) can be used for CertFP authentication with IRC networks.

To get the fingerprint for e.g. OFTC (see Automatically Identifying Using SSL + CertFP):

cat clients/irssi/irssi-client-key.pem clients/irssi/irssi-client.pem | openssl x509 -noout -fingerprint -sha1 | awk -F= '{gsub(":",""); print $2}'

Or for Libera (see Using CertFP), which uses SHA-512:

cat clients/irssi/irssi-client-key.pem clients/irssi/irssi-client.pem | openssl x509 -noout -fingerprint -sha512 | awk -F= '{gsub(":",""); print tolower($2)}'

To get a certificate to paste into ZNC's User Modules > Certificate form:

cat clients/irssi/irssi-client-key.pem clients/irssi/irssi-client.pem | pbcopy

For more information on ZNC, see my more in-depth article.


  1. NIST Special Publication 800-57 Part 3, Recommendation for Key Management summarizes recommendations in section 2.2.1 Recommended Key Sizes and Algorithms, Table 2-1. Reproduced below:

    Key Type Algorithms and Key Sizes
    Digital Signature keys used for authentication (for Users or Devices) RSA (2048 bits), ECDSA (Curve P-256)
    Digital Signature keys used for non-repudiation (for Users or Devices) RSA (2048 bits), ECDSA (Curves P-256 or P-384)
    CA and OCSP Responder Signing Keys RSA (2048 or 3072bits), ECDSA (Curves P-256 or P-384)
    Key Establishment keys (for Users or Devices) RSA (2048 bits), Diffie-Hellman (2048 bits), ECDH (Curves P-256 or P-384)
     ↩︎