Fun with X.509 certificates

Keywords:

PKI for private environments

This short tutorial will show you how to create CA, server, and client certificates, and how to use certificate signing requests (CSR) to safely sign public keys. The only tool required will be GnuTLS's certtool.

You should not in any circumstance roll your own PKI unless you understand the potential risks and are able to secure your infrastructure properly. Invest in a hardware security module or smart cards to keep your private keys safe. Put a secure passphrase on your private keys at the very least.

Private keys should be stored securely at all times. A compromised private key means any trust put in that certificate (be it identity verification, signing, &c) is now completely of no value. A compromised CA may even be used to create trusted (!) certificates for systems you did not expect to be signed by your CA.

I do not intend to use this in an environment that expects tangible security, and I will not be making any recommendations regarding e.g. how many bits a private key should be or what ciphers you should use. Use the defaults suggested by GnuTLS unless you have reason to do otherwise.

Known issues

You may run into these issues with older versions of GnuTLS:

  • To work with encrypted private keys in combination with template files, you must either pass the password on the command line or put it in the template file. (This may leak the password to other users on your system!) In recent version of GnuTLS, use --ask-pass.
  • Older versions do not support name constraints for the CA certificate. This would allow you to limit the scope in which the CA can issue certificates.

See also

Certificate Authority

The certificate authority (CA) is an important trust anchor that validates the authenticity of the subsidiary certificates in your PKI. It will be used to sign all our certificates.

⚠ Trusting the CA means trusting all the certificates it has issued (even other subsidiary CAs, potentially) as trusted. You can limit this by setting a path length (maximum depth of subsidiary CAs) and name constraints (hostnames the CA can issue).

A CA certificate is (for the most part) just a regular X.509 certificate with "I am a CA" flag set. It will not be automatically trusted by the systems you want to use it on. You will have to install the CA certificate (ca.pem) on the systems you want to trust the CA on.

Generate your certificate authority key and certificate.

# Generate CA key -- ensure it is well secured!
# Use a HSM or read-protected smart card instead if you can.
# The PKCS#8 option password-protects the private key.
certtool --pkcs8 --generate-privkey --sec-param high --outfile ca-key.pem

# Self-sign the CA to create our public certificate
certtool --generate-self-signed --load-privkey ca-key.pem \
    --template ca.cfg --outfile ca.pem

Example template:

# This template file specifies that the certificate is a certificate authority
# that will be used to sign other certificates, and certificate revocations.
#
# Set additional properties (e.g. a CRL URI) as appropriate.

# Certificate authority template
organization = "Example"
cn = "Your CA name here"
expiration_days = 3650

# This CA can not issue subsidiary CAs
path_len = 0

# Is a CA
ca

# Signs certificates and certificate revocation lists
cert_signing_key
crl_signing_key

# Name constraints (recommended); new in GnuTLS 3.3.x
# Setting this will allow this CA to only issue certificates for this domain
nc_permit_dns = "example.com"
#nc_exclude_dns = "test.example.com"

It's also possible to create subsidiary CAs by simply signing the subsidiary CA with your root CA. Alternatively, you can have your CA certificate signed by an already trusted CA.

Notes

  • Certificates have a serial number that uniquely identifies a certificate under that CA. You can assign incremental or random serial numbers, just make sure to never issue the same serial twice. (GnuTLS uses a time-based serial if you do not specify one manually.)
  • Keep a list of certificates/serials you have issued. This will make it easier to track and revoke certificates.

Signing certificates

In a private environment, you often want to generate both a private key and a certificate in one step. In this example we have the CA generate and sign a key pair.

Server certificate example

Server certificates are used by connecting client to verify that the service they are connecting to matches the expected hostname (or IP address, or …). The server's identifying information (often: one or more hostnames) is encoded in the certificate, which is then signed by the CA.

Generate the key and sign the certificate, then securely transport them to and install them on the target server. Since server keys are often not password protected (you'd have to enter the password every time the server restarts), make sure the file is only readable by the services that need access to this key.

# Generate key
certtool --pkcs8 --generate-privkey --sec-param normal --outfile server-key.pem

# Create signed PEM
certtool --generate-certificate --load-ca-certificate ca.pem \
    --load-ca-privkey ca-key.pem --load-privkey server-key.pem \
    --template server.cfg --outfile server.pem

Example template:

# This template file specifies that this certificate will be used as a (TLS)
# server certificate.
#
# Make sure the ``cn`` matches the primary hostname of the service and
# use ``dns_name`` to add any alternative names.  (The common name does not
# have to be a hostname, but plenty of software will assume it is.)

organization = "Example"
cn = "example.com"
expiration_days = 365

# You can add an arbitrary number of (sub)domains
dns_name = "example.com"
dns_name = "www.example.com"

tls_www_server
signing_key
encryption_key

Certificate signing requests

If you want a certificate authority to sign your certificate without it ever having access to your private key you should use a certificate signing request. The CSR includes your public key, and optionally any attributes you want to include in the certificate, signed with your private key. The CSR is then sent to the CA, and in return we get our signed certificate.

As a CA you still have full control over the attributes of the certificate. Be careful with what you sign and always verify that the requested certificate properties are what you expect. An easy way to do this is to also share the certificate template used to create the CSR with the CA.

It's also possible to require the submitted CSR to include a challenge phrase. Because the challenge will be signed with the key of the CSR, you can be sure the request was legitimate.

Any type of certificate can be created using this approach.

Client certificates example

With a client certificate you can securely identify yourself to a service without the risk of having the (potentially compromised) service steal your login credentials (since it will only know your identity, not your private key). You will still have to be careful: vulnerabilities in e.g. your browser could still result in an attacker stealing your private key.

Generate a private key and a certificate signing request:

# Generate key
certtool --pkcs8 --generate-privkey --sec-param normal \
    --outfile client-key.pem

# Generate sign request
certtool --generate-request \
    --load-privkey client-key.pem \
    --template client.cfg \
    --outfile client.csr

As the CA, use the certificate signing request to issue the certificate:

# Inspect the certificate request
certtool --crq-info --infile client.csr

# Generate the signed certificate
# This step is done by the CA
certtool --generate-certificate --load-ca-certificate ca.pem \
    --load-ca-privkey ca-key.pem \
    --load-request client.csr \
    --template client.cfg \
    --outfile client.pem

The signed certificate (client-johndoe.pem in this case) can then be sent back to the person issueing the request.

Using a key/certificate (PKCS #12)

On the client side, we'll have to do some extra work to get various applications (e.g. Mozilla Firefox) to use our certificate. We combine the private key and the certificate using the PKCS #12 format. You will be asked for a key name (a textual description; just use yourname@example.com) and a password to encrypt the file with for extra security.

# Piece it all together
certtool --to-p12 \
    --load-certificate client.pem \
    --load-privkey client-key.pem \
    --outraw --outfile client.p12

Example template:

# There are (commonly) three types of client certificate:
#
# - Authentication
# - Confidentiality (encryption)
# - Non-repudiation (signing)
#
# Each type would have their own private key.
#
# This template file specifies a client authentication certificate.

organization = "Example"
cn = "johndoe@example.com"
expiration_days = 365

# Web client example:
tls_www_client
signing_key

# Optional: valid for any purpose
#key_purpose_oid = "2.5.29.37.0"


# Example for email (S/MIME)
#
# Notes:
# - For S/MIME you probably want one encryption key and one signing key.
# - certtool does not currently allow you to set non-repudiation

#email = "johndoe@example.com"

# Email protection
#key_purpose_oid = "1.3.6.1.5.5.7.3.4"

#signing_key
#encryption_key

Further improvements

  • Consider the infrastructure you need to manage revocation of certificates.
    • Certificate revocation list (CRL)
    • Online certificate status protocol deamon (OCSP)
  • Create a "registration authority" that collects, verifies, and processes keys, identity, certificate requests, and so on.
  • Ideally you should only ever sign certificates that were verified by your registration authority.
  • Create a subsidiary CA that has a shorter lifespan than your root CA. This way, even if the subsidiary CA is compromised or lost, you can simply revoke it and create a new one. No need to install a new root on every client.
  • Make sure your root CA is completely offline, and your subsidiary CAs as "offline as possible".