Self-signed SSL certificates

This article describes, how to use OpenSSL to create a self-signed certificate authority and sign certificates with it.

The first thing you need, is a directory structure somewhere in your file system, where the certificate authority (CA) can place its files. It is recommended to put this in a safe place, like on a USB drive or at least a folder where only the root user has access to.

I wrote a short shell script, that creates the directory structure and some empty files in it. To use it, create a new file, put the shell script below in it and make it executable.

The script expects the directory path where to create the root folder of the CA directory structure as single parameter. If you want to use the current directory of the script file, use a “.” (dot) as directory path.

#!/bin/bash
 
# Create directory structure for local CA
 
if [ "${1}" == '' ]; then
  echo "ERROR: CA path needed."
  exit 1
fi

CA_PATH=${1} 
CA_NAME=rootca
CA_DIR=${CA_PATH}/${CA_NAME}
 
mkdir -p ${CA_DIR}
mkdir -p ${CA_DIR}/certs
mkdir -p ${CA_DIR}/new
mkdir -p ${CA_DIR}/db
touch ${CA_DIR}/db/${CA_NAME}.db
echo 1000 > ${CA_DIR}/db/${CA_NAME}.serial
mkdir -p ${CA_DIR}/private
chmod 700 ${CA_DIR}/private
mkdir -p ${CA_DIR}/crl
echo 1000 > ${CA_DIR}/crl/${CA_NAME}-crl.num
mkdir -p ${CA_DIR}/csr
 
echo "Created new, empty CA directory structure at:"
echo "${CA_DIR}"

After creating the directory structure, the next thing you need, is a configuration file for OpenSSL. The file goes into the root directory of your CA’s directory structure. You may want to provide some default values for the different fields you get asked every time you create a new CSR.

To create the configuration file, copy the listing below to a new file named openssl.cnf, if you want to follow along the example.

# OpenSSL root CA configuration file.

[ ca ]
# 'man ca'
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
ca                = rootca
dir               = .
certs             = $dir/certs
new_certs_dir     = $dir/new
database          = $dir/db/$ca.db
serial            = $dir/db/$ca.serial

# The root key and root certificate.
private_key       = $dir/private/$ca-key.pem
certificate       = $certs/$ca-cert.pem

# For certificate revocation lists.
crl_dir           = $dir/crl
crl               = $crl_dir/$ca-crl.pem
crlnumber         = $crl_dir/$ca-crl.num
crl_extensions    = crl_ext
default_crl_days  = 30

# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no

# The policy section consists of a set of variables corresponding to
# certificate DN fields. If the value is "match" then the field value
# must match the same field in the CA certificate. If the value is
# "supplied" then it must be present. If the value is "optional" then it
# may be present. Any fields not mentioned in the policy section are
# silently deleted, unless the -preserveDN option is set but this can be
# regarded more of a quirk than intended behaviour.

policy            = policy_default

copy_extensions   = copy

[ policy_default ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the 'ca' man page.
countryName             = match
stateOrProvinceName     = match
localityName            = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# Options for the 'req' tool ('man req').
default_bits        = 2048
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha256

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = DE
stateOrProvinceName_default     = BW
localityName_default            = Stuttgart
0.organizationName_default      = ITS
organizationalUnitName_default  = IT
emailAddress_default            = user@example.com

[ v3_ca ]
# Extensions for a typical CA ('man x509v3_config').
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
# Extensions for client certificates ('man x509v3_config').
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# Extensions for server certificates ('man x509v3_config').
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ example1_server_cert ]
# Extensions for server certificates ('man x509v3_config').
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:example1, DNS:example1.local.net, IP:192.168.100.20

[ example2_server_cert ]
# Extensions for server certificates ('man x509v3_config').
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:example2, DNS:example.local.net, IP:192.168.100.50

[ example3_server_cert ]
# Extensions for server certificates ('man x509v3_config').
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:example3, DNS:example3.local.net, IP:192.168.100.30

[ example4_server_cert ]
# Extensions for server certificates ('man x509v3_config').
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:example4, DNS:example4.local.net

[ example5_server_cert ]
# Extensions for server certificates ('man x509v3_config').
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:example5, DNS:example5.local.net

[ crl_ext ]
# Extension for CRLs ('man x509v3_config').
authorityKeyIdentifier=keyid:always

[ ocsp ]
# Extension for OCSP signing certificates ('man ocsp').
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning

If you look at the end of the configuration, you’ll notice different sections, which provide configuration details for different clients in my network. This way, you can provide an IP-address and additional names, besides the common name. You don’t need to use them, though.

Now go to the root folder of your CA’s directory structure.
First we need to generate a key for your root CA and protect the created file. Whoever has this private key, can sign certificates in the name of your CA. All the other certificates will be signed with this key, which means you should set a passphrase. Every time you sign a certificate later, you will need to enter this passphrase. The command below will create a 4096 Bit long key and ask you for a passphrase to protect the key. This key is the most important file of the CA and should only be accessible by the root user.

# openssl genrsa -aes256 -out private/rootca-key.pem 4096
# chmod 400 private/rootca-key.pem

Create a new signing request and sign it (create a certificate). Enter “rootca” for Common Name. Also protect the certificate with appropriate file permissions. This is the point where trust just comes out of thin air. 😉

# openssl req -config openssl.cnf -key private/rootca-key.pem -new -x509 -days 36500 -sha256 -extensions v3_ca -out certs/rootca-cert.pem
# chmod 444 certs/rootca-cert.pem

OPTIONAL: You may verify the information for the certificate you just created.

# openssl x509 -noout -text -in certs/rootca-cert.pem

To sign a certificate, you need a certificate sign request (CSR) first. However, to create a CSR, you need a private key first. If you use a existing CSR, generated from another system or service, the private key which was used to create this CSR, will never leave the system. Which is kind of the idea of a private key and in moste cases the usual way to do it.
But since this is your own CA, you may create the key directly wherever your root CA is and later put the key, including the signed certificate, on the system you want. Keys can be protected by a passphrase (like the key of the CA). But keep in mind, that every time the key is used, the passphrase needs to be provided, too. Because of that, private keys on most servers oder server applications aren’t protected by a passphrase.

If you want to create a new key without a passphrase (maybe for a server):
Don’t forget to replace <common_name> with the actual hostname of the system. If you want to use extensions to provide multiple names, use the main hostname as Common Name.

# openssl genrsa -out private/<common_name>-key.pem 2048
# chmod 400 private/<common_name>-key.pem

If you want to create a new key with a passphrase (maybe for a client):
Again, don’t forget to use the actual Common Name.

# openssl genrsa -aes256 -out private/<common_name>-key.pem 2048
# chmod 400 private/<common_name>-key.pem

Now you will create your certificate signing request (CSR) using the private key, you created before. If your key is protected with a passphrase, you will need to type that in, too.

In case you already have a CSR, for example, from some kind of appliance that creates a CSR for you, you may go straight to signing and skip this this part. The CA doesn’t care where the CSR did come from, but it needs the CSR to create a new certificate. Again, replace the <common_name> with you actual hostname.

# openssl req -config openssl.cnf -key private/<common_name>-key.pem -new -sha256 -out csr/<common_name>-csr.pem

Now your certificate authority (CA) will sign the CSR and create an actual certificate. If you want to use a custom extension section in your openssl.cnf file, you need to put the name of this section after the -extensions parameter. Otherwise, use “server_cert” or “usr_cert” (client certificate) as value for the “-extensions” parameter.

You’ll also need to provide a period of time in days, after which the certificate will expire. In this example, the created certificate will expire after about on year.

# openssl ca -config openssl.cnf -extensions <name_in_config> -days 375 -notext -md sha256 -in csr/<common_name>-csr.pem -out certs/<common_name>-cert.pem

If everything works out as expected, OpenSSL will ask you to confirm signing the certificate and to commit the change.

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

OPTIONAL: If you like, you can verify the information of the created client or server certificate:

# openssl x509 -noout -text -in certs/<common_name>-cert.pem

That’s it! You may now deploy your newly created certificates to different hosts and services in your local network. Keep in mind, that you’ll need to add the certificate of you root CA to all systems or software, like printers or browsers. Without that, the certificate won’t be recognized as valid. The good news is: You only need to do that once. If you renew the certificates later, you’ll only need to deploy the new certificate files.

Renew or replace existing certificates

If an existing certificate needs to be renewed, because it expired, the usual way would be to create a new CSR and then create/sign the certificate with the CA and the new CSR. But it is also possible, to use the existing CSR to create/sign a new valid certificate. Normally OpenSSL will complain, that there’s already a certificate with the same serial number. It will also show the serial number of the existing certificate.

It’s possible to revoke the existing certificate and then create a new and valid one with the same “old” CSR. To revoke the certificate in question, the correct serial number is needed. The db-file in the CAs directory structure contains all issued certificates and their serial numbers, which are stored in the “new” subdirectory.

# openssl ca -config openssl.cnf -revoke new/<serial_number>.pem

After removing the certificate with the revoke option, OpenSSL shouldn’t complain anymore if a new cert is signed with the existing CSR.

Using self-signed certificates with printers, network devices and other appliances

Usually there are two ways to achieve this. The first is to create a CSR on the system (i.e. a printer web interface) and download it. Then use this CSR with your root CA to create a signed certificate. This way, the private key stays on the target system.

The second way is to import the private key and the signed certificate. Many devices offer this as an alternative. In this case the private key and a CSR will be created on another system.

Create PKCS12 file format

Some applications need this file format. It contains the certificate and the private key and is therefore usually protected with a passphrase. The following command creates this file format and asks for a passphrase to set.

# openssl pkcs12 -config openssl.cnf -export -out <common_name>.pfx -inkey private/<common_name>-key.pem -in certs/<common_name>-cert.pem

Leave a Reply

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