Creating a local certificate authority
This post explains how to setup a local CA (certificate authority) from which to issue server certificates for a local network.
Our Setup
At home we also want to have SSL certificates for our servers, so we can leverage encryption of http traffic and turn it into https. Also, some infrastructure servers (i.e. GIT) will rely on https, so it will be a good idea to be prepared. Our setup will consist of three layers:
Root CA <-> Intermediate CA <-> Servers
The general reason we don’t issue server certificates directly from the root CA is that it is 1) rather uncommon to do so and 2) we might end up with the need for more than one CA to issue server certificates in our network (i.e. using lets encrypt)
Root CA
The first thing we need to do is to setup a root CA which more or less is nothing more than a structure of directories on any host. The host actually doesn’t need to be a server and literally can be your laptop. No services need to be started and in reality the root CA often is an air gapped computer.
mkdir /root/ca cd /root/ca mkdir certs crl newcerts private chmod 700 private touch index.txt echo 1000 > serial
Now we need to create a config file for openssl
touch caconfig.cnf
The file content is shown below. Please note that the root directory in line 7 of the file needs to match the directory on your drive. If you decide to create the directory somewhere else, please adapt line 7. The same goes for the subdirectories between line 8 and line 21.
[ ca ] # `man ca` default_ca = CA_default [ CA_default ] # Directory and file locations. dir = /root/ca certs = $dir/certs crl_dir = $dir/crl new_certs_dir = $dir/newcerts database = $dir/index.txt serial = $dir/serial RANDFILE = $dir/private/.rand # The root key and root certificate. private_key = $dir/private/ca.key.pem certificate = $dir/certs/ca.cert.pem # For certificate revocation lists. crlnumber = $dir/crlnumber crl = $dir/crl/ca.crl.pem 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 policy = policy_strict [ policy_strict ] # The root CA should only sign intermediate certificates that match. # See the POLICY FORMAT section of `man ca`. countryName = match stateOrProvinceName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional [ policy_loose ] # Allow the intermediate CA to sign a more diverse range of certificates. # See the POLICY FORMAT section of the `ca` man page. countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional 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 . 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 = Saxony localityName_default = Dresden 0.organizationName_default = Example organizationalUnitName_default = Example Certificate Authority emailAddress_default = john.doe@example.com [ v3_ca ] # Extensions for a typical CA (`man x509v3_config`). subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer basicConstraints = critical, CA:true keyUsage = critical, digitalSignature, cRLSign, keyCertSign [ v3_intermediate_ca ] # Extensions for a typical intermediate CA (`man x509v3_config`). subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer basicConstraints = critical, CA:true keyUsage = critical, digitalSignature, cRLSign, keyCertSign [ v3_intermediate_ca ] # Extensions for a typical intermediate 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 [ 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
Now it is time to create the root key. Because we are creating a private key for a root CA we should use 4096 bit encryption.
cd /root/ca openssl genrsa -aes256 -out private/ca.key.pem 4096
You will be prompted to give a password:
Enter pass phrase for ca.key.pem: mysecretpassword Verifying - Enter pass phrase for ca.key.pem: mysecretpassword
After the certificate is created we should make sure nobody else can read it on the filesystem.
chmod 400 private/ca.key.pem
Now we create a signing request for a certificate that is valid for 20 years. We also implicitly self sign the request:
cd /root/ca openssl req -config caconfig.cnf -key private/ca.key.pem -new -x509 -days 7300 -sha256 -extensions v3_ca -out certs/ca.cert.pem
When we are being prompted for the password, we shall give the private key password from above.
Enter pass phrase for ca.key.pem: mysecretpassword You are about to be asked to enter information that will be incorporated into your certificate request. ----- Country Name (2 letter code) [XX]:DE State or Province Name []:Saxony Locality Name []: Dresden Organization Name []:Example Organizational Unit Name []:Example Certificate Authority Common Name []:Example Root CA Email Address []:john.doe@example.com
The public certificate can be read by everybody, as long as nobody can write it. If desired we can also use openssl to marvel at our fresh certificate and verify it.
chmod 444 certs/ca.cert.pem openssl x509 -noout -text -in certs/ca.cert.pem
Intermediate CA
Same as for the root CA, we now setup an intermediate CA. The intermediate CA could be at the same host, but it really doesn’t have to be though.
mkdir /root/ca/ca-intermediate cd /root/ca/ca-intermediate mkdir certs crl csr newcerts private chmod 700 private touch index.txt echo 1000 > serial echo 1000 > crlnumber
Again, we need to create a config file for openssl
touch ca-intermediate.cnf
The file content is shown below. Not much has changed compared to the root CA. The policy we apply is now loose (see line 32) and we copy possible extensions (see line 35). Again, the root directory in line 7 of the file needs to match the directory on your drive. If you decide to create the directory somewhere else, please adapt line 7. The same goes for the subdirectories between line 8 and line 21.
[ ca ] # `man ca` default_ca = CA_default [ CA_default ] # Directory and file locations. dir = /root/ca/ca-intermediate certs = $dir/certs crl_dir = $dir/crl new_certs_dir = $dir/newcerts database = $dir/index.txt serial = $dir/serial RANDFILE = $dir/private/.rand # The root key and root certificate. private_key = $dir/private/intermediate.key.pem certificate = $dir/certs/intermediate.cert.pem # For certificate revocation lists. crlnumber = $dir/crlnumber crl = $dir/crl/intermediate.crl.pem 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 policy = policy_loose # <<< I M P O R T A N T >>> copy_extensions = copy [ policy_strict ] # The root CA should only sign intermediate certificates that match. # See the POLICY FORMAT section of `man ca`. countryName = match stateOrProvinceName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional [ policy_loose ] # Allow the intermediate CA to sign a more diverse range of certificates. # See the POLICY FORMAT section of the `ca` man page. countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional 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 . 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 = Saxony localityName_default = Dresden 0.organizationName_default = Example organizationalUnitName_default = Example Certificate Authority emailAddress_default = john.doe@example.com [ v3_ca ] # Extensions for a typical CA (`man x509v3_config`). subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer basicConstraints = critical, CA:true keyUsage = critical, digitalSignature, cRLSign, keyCertSign [ v3_intermediate_ca ] # Extensions for a typical intermediate 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 [ 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
Next we can create the intermediate private key (please note that we are still in the ca-intermediate folder):
openssl genrsa -aes256 -out private/intermediate.key.pem 4096
We are being prompted to give a password:
Enter pass phrase for intermediate.key.pem: anothersecretpassword Verifying - Enter pass phrase for intermediate.key.pem: anothersecretpassword
We should make sure that nobody can read the private key:
chmod 400 private/intermediate.key.pem
As a next step we now create a signing request:
openssl req -config ca-intermediate.cnf -new -sha256 -key private/intermediate.key.pem -out csr/intermediate.csr.pem
As we do so, we are being prompted for the password that protects the private key of our intermediate certificate authority.
Enter pass phrase for intermediate.key.pem: anothersecretpassword You are about to be asked to enter information that will be incorporated into your certificate request. ----- Country Name (2 letter code) [XX]:DE State or Province Name []:Saxony Locality Name []:Dresden Organization Name []:Example Organizational Unit Name []:Example Certificate Authority Common Name []:Example Intermediate CA Email Address []:john.doeh@example.com
We have successfully created a certificate signing request. Now let’s go back to the Root CA and sign the request. Let’s make sure we use the Root CA’s openssl config for doing that:
cd /root/ca openssl ca -config caconfig.cnf -extensions v3_intermediate_ca -days 3650 -notext -md sha256 -in ca-intermediate/csr/intermediate.csr.pem -out ca-intermediate/certs/intermediate.cert.pem
The password we are being prompted for is the password of the Root CA – makes sense, because the Root CA is asked to sign something.
Enter pass phrase for ca.key.pem: mysecretpassword Sign the certificate? [y/n]: y
It should be allowed that everybody can read the public certificate. At the same time nobody should be allowed to write it.
chmod 444 ca-intermediate/certs/intermediate.cert.pem
We could verify the intermediate certificate with the following command:
openssl x509 -noout -text -in ca-intermediate/certs/intermediate.cert.pem
However, so much more important is to verify the chain of the intermediate certificate against the root certificate:
openssl verify -CAfile certs/ca.cert.pem ca-intermediate/certs/intermediate.cert.pem intermediate.cert.pem: OK
On that note… let’s create a certificate chain that we can later on hand out to web servers (i.e. apache has an option for that):
cat ca-intermediate/certs/intermediate.cert.pem certs/ca.cert.pem > ca-intermediate/certs/ca-chain.cert.pem chmod 444 ca-intermediate/certs/ca-chain.cert.pem
Server Certificate
Finally, we can issue a server certificate from our intermediate CA. At first, create the server key (make sure it is 2048 bit). As we are creating a certificate for a service (i.e. Nginx or Apache) we should skip the -aes256 option as we otherwise would create a private key with a password and would need to enter the password every time we start the service.
cd /root/ca/ca-intermediate openssl genrsa -out private/myserver.example.com.key.pem 2048 chmod 400 private/myserver.example.com.key.pem
It makes a lot of sense to create a config file for openssl!
cd /root/ca/ca-intermediate touch myserver.cnf
And here is why: look at lines 72, 96 and 99 until 101. Our server will have more than just one simple domain name service entry. It might be available under the full name myserver.example.com but also under the short name myserver. We need alternative DNS entries for the server certificate.
[ ca ] # `man ca` default_ca = CA_default [ CA_default ] # Directory and file locations. dir = /root/ca/ca-intermediate certs = $dir/certs crl_dir = $dir/crl new_certs_dir = $dir/newcerts database = $dir/index.txt serial = $dir/serial RANDFILE = $dir/private/.rand # The root key and root certificate. private_key = $dir/private/intermediate.key.pem certificate = $dir/certs/intermediate.cert.pem # For certificate revocation lists. crlnumber = $dir/crlnumber crl = $dir/crl/intermediate.crl.pem 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 policy = policy_loose # <<< I M P O R T A N T >>> # Extension copying option: use with caution. copy_extensions = copy [ policy_strict ] # The root CA should only sign intermediate certificates that match. # See the POLICY FORMAT section of `man ca`. countryName = match stateOrProvinceName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional [ policy_loose ] # Allow the intermediate CA to sign a more diverse range of certificates. # See the POLICY FORMAT section of the `ca` man page. countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional 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 # <<< I M P O R T A N T >>> req_extensions = v3_req [ req_distinguished_name ] # See . 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 = Saxony localityName_default = Dresden 0.organizationName_default = Example organizationalUnitName_default = Example Web Services emailAddress_default = john.doe@example.com [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment # <<< I M P O R T A N T >>> subjectAltName = @alt_names # <<< I M P O R T A N T >>> [ alt_names ] DNS.0 = myserver.example.com DNS.1 = myserver [ v3_ca ] # Extensions for a typical CA (`man x509v3_config`). subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer basicConstraints = critical, CA:true keyUsage = critical, digitalSignature, cRLSign, keyCertSign [ v3_intermediate_ca ] # Extensions for a typical intermediate 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 [ 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
Now we create the signing request (make sure you’re in the intermediate ca folder and use the special server config as it contains the SAN):
openssl req -config myserver.cnf -key private/myserver.example.com.key.pem -new -sha256 -out csr/myserver.example.com.csr.pem
There should be no password prompt, as our private key is unprotected.
You are about to be asked to enter information that will be incorporated into your certificate request. ----- Country Name (2 letter code) [XX]:DE State or Province Name []:Saxony Locality Name []:Dresden Organization Name []:Example Organizational Unit Name []:Example Web Services Common Name []:myserver.example.com Email Address []:john.doe@example.com
Now we sign the request with the intermediate CA and write protect it:
openssl ca -config ca-intermediate.cnf -extensions server_cert -days 375 -notext -md sha256 -in csr/myserver.example.com.csr.pem -out certs/myserver.example.com.cert.pem chmod 444 certs/myserver.example.com.cert.pem
You can verify the certificate:
openssl x509 -noout -text -in certs/myserver.example.com.cert.pem
However, very important would be to create a bundle that chains the intermediate certificate with the server certificate:
cat certs/myserver.example.com.cert.pem certs/intermediate.cert.pem > certs/myserver.example.com.bundle.pem
Last but not least lets verify the chain of the server certificate against the root certificate:
cd /root/ca openssl verify -CAfile certs/ca.cert.pem ca-intermediate/certs/myserver.example.com.bundle.pem myserver.example.com.cert.pem: OK
…all done!
