Create your own Certificate Authority

This how-to is based on jamielinux.com.

Attachments can be downloaded here

Steps described in this document:

  • Creating a new, self-signed root CA from scratch
  • Create an intermediate CA using the root CA
  • Sign a server certificate
  • Sign a client certificate
  • Create a certificate revocation list (CRL)
  • Setup an OCSP responder behind an Apache proxy
  • Deploy certificates onto Apache webserver
  • Perform a number of tests using a browser

Setup Root CA

The root CA certificate only to sign other certificate authority certificates.

Prepare the Root CA directory

Setup a directory where everything related to the CA is managed.

mkdir -p /root/DemoCA/root
cd /root/DemoCA/root
mkdir certs csr crl newcerts private
chmod 700 private
touch index.txt
echo 1000 > serial
wget -O openssl.cnf "https://andre.bytesare.us/ca-demo-files/root-openssl.cnf"

Self-sign the root ca certificate

Using our openssl.cnf’s [ v3_ca ] extension:

cd /root/DemoCA/root
openssl genrsa -aes256 -out private/ca.key.pem 4096
openssl req -config openssl.cnf -key private/ca.key.pem -new -x509 -days 7300 -sha256 -extensions v3_ca -out certs/ca.cert.pem
# Subject: `C = AT, ST = Vienna, O = BytesAreUs GmbH, OU = BytesAreUs GmbH Certificate Authority, CN = BytesAreUs GmbH Root CA`

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

Setup an intermediate CA

Used for signing end-user certs.
Prepare the intermediate CA directory as below.

mkdir /root/DemoCA/intermediate-1
cd /root/DemoCA/intermediate-1
mkdir certs crl csr newcerts private
chmod 700 private
touch index.txt
touch index.txt.attr 
echo 1000 > serial
echo 1000 > crlnumber
wget -O openssl.cnf "https://andre.bytesare.us/ca-demo-files/intermediate-openssl.cnf"

Add/Modify these lines to the [ server_cert ] section in /root/ca/intermediate/openssl.cnf to specify CRL and OCSP Urls.

crlDistributionPoints = URI:http://ca-demo.bytesare.us/intermediate-1.crl.pem
authorityInfoAccess   = OCSP;URI:http://ca-demo.bytesare.us/ocsp/intermediate-1

Create the intermediate certificate

Using our openssl.cnf’s [ v3_intermediate_ca ] extension:

# Create csr:
cd /root/DemoCA/intermediate-1
openssl genrsa -aes256 -out private/intermediate-1.key.pem 4096
openssl req -config openssl.cnf -new -sha256 -key private/intermediate-1.key.pem -out csr/intermediate-1.csr.pem
# Subject: `C = AT, ST = Vienna, O = BytesAreUs GmbH, OU = BytesAreUs GmbH Certificate Authority, CN = BytesAreUs GmbH Intermediate CA 1`

# Sign csr with the root CA:
cd /root/DemoCA/root
openssl ca -config openssl.cnf -extensions v3_intermediate_ca -days 3650 -notext -md sha256 -in ../intermediate-1/csr/intermediate-1.csr.pem -out ../intermediate-1/certs/intermediate-1.cert.pem

# Check:
cd /root/DemoCA/intermediate-1
openssl x509 -noout -text -in certs/intermediate-1.cert.pem # check manually
openssl verify -CAfile ../root/certs/ca.cert.pem certs/intermediate-1.cert.pem
cat certs/intermediate-1.cert.pem ../root/certs/ca.cert.pem > certs/ca-chain.cert.pem

Create a server certificate

Using our openssl.cnf’s [ server_cert ] extension:

cd /root/DemoCA/intermediate-1
openssl genrsa -aes256 -out private/ca-demo.bytesare.us.key.pem 2048
openssl req -config openssl.cnf -key private/ca-demo.bytesare.us.key.pem -new -sha256 -out csr/ca-demo.bytesare.us.csr.pem
# Subject: `C = AT, ST = Vienna, O = BytesAreUs GmbH, OU = Web Services, CN = ca-demo.bytesare.us`
openssl ca -config openssl.cnf -extensions server_cert -days 375 -notext -md sha256 -in csr/ca-demo.bytesare.us.csr.pem -out certs/ca-demo.bytesare.us.cert.pem
# Check:
openssl x509 -noout -text -in certs/ca-demo.bytesare.us.cert.pem
openssl verify -CAfile certs/ca-chain.cert.pem certs/ca-demo.bytesare.us.cert.pem

(Optional) Create and revoke another server certificate

For testing purposes, create a new server certificate with the same
name like the existing one. It is needed later to test the OCSP responder.

cd /root/DemoCA/intermediate-1
openssl genrsa -aes256 -out private/revoked.ca-demo.bytesare.us.key.pem 2048
openssl req -config openssl.cnf -key private/revoked.ca-demo.bytesare.us.key.pem -new -sha256 -out csr/revoked.ca-demo.bytesare.us.csr.pem
# Subject: `C = AT, ST = Vienna, O = BytesAreUs GmbH, OU = Web Services, CN = ca-demo.bytesare.us`
openssl ca -config openssl.cnf -extensions server_cert -days 375 -notext -md sha256 -in csr/revoked.ca-demo.bytesare.us.csr.pem -out certs/revoked.ca-demo.bytesare.us.cert.pem
# Check:
openssl x509 -noout -text -in certs/revoked.ca-demo.bytesare.us.cert.pem
openssl verify -CAfile certs/ca-chain.cert.pem certs/revoked.ca-demo.bytesare.us.cert.pem

Revoke the server cert:

cd /root/DemoCA/intermediate-1
openssl ca -config openssl.cnf -revoke certs/revoked.ca-demo.bytesare.us.cert.pem

Sign a client certificate

The client cert should be created in three steps without sending the key over the wire:

  1. Client creates a CSR:

    # mkdir /home/carl
    cd /home/carl
    openssl genrsa -out This email address is being protected from spambots. You need JavaScript enabled to view it. 2048 
    openssl req -new -key This email address is being protected from spambots. You need JavaScript enabled to view it. -out This email address is being protected from spambots. You need JavaScript enabled to view it.
    Subject: `C= AT, ST = Tirol, L = Innsbruck, CN = Carl the tester, emailAddress = This email address is being protected from spambots. You need JavaScript enabled to view it.`
    cp This email address is being protected from spambots. You need JavaScript enabled to view it. /tmp/
    
  2. Then, client sends the CSR to the intermediate CA, who signs it.

    Using our openssl.cnf’s [ usr_cert ] extension:

    cd /root/DemoCA/intermediate-1
    cp /tmp/This email address is being protected from spambots. You need JavaScript enabled to view it. csr/
    openssl ca -config openssl.cnf -extensions usr_cert -notext -md sha256 -in csr/This email address is being protected from spambots. You need JavaScript enabled to view it. -out certs/This email address is being protected from spambots. You need JavaScript enabled to view it.
    cp certs/This email address is being protected from spambots. You need JavaScript enabled to view it. certs/ca-chain.cert.pem /tmp/
    
    # Check:
    openssl verify -CAfile certs/ca-chain.cert.pem certs/This email address is being protected from spambots. You need JavaScript enabled to view it.   
    
  3. CA returns certificate and chain to client

    Who converts it into pkcs12 format to import it to his browser:

    cd /home/carl
    cp /tmp/ca-chain.cert.pem /tmp/This email address is being protected from spambots. You need JavaScript enabled to view it. .
    cat This email address is being protected from spambots. You need JavaScript enabled to view it. ca-chain.cert.pem > This email address is being protected from spambots. You need JavaScript enabled to view it.
    openssl pkcs12 -export -inkey This email address is being protected from spambots. You need JavaScript enabled to view it. -in This email address is being protected from spambots. You need JavaScript enabled to view it. -out This email address is being protected from spambots. You need JavaScript enabled to view it.
    
    

(Optional) Create and revoke another client cert

For negative testing the CRL later, sign another client cert and revoke it.

# mkdir /home/luzifer
cd /home/luzifer
openssl genrsa -out This email address is being protected from spambots. You need JavaScript enabled to view it. 2048 
openssl req -new -key This email address is being protected from spambots. You need JavaScript enabled to view it. -out This email address is being protected from spambots. You need JavaScript enabled to view it.
Subject: `C= AT, ST = Tirol, L = Innsbruck, CN = Luzifer the Evil, emailAddress = This email address is being protected from spambots. You need JavaScript enabled to view it.`
cp This email address is being protected from spambots. You need JavaScript enabled to view it. /tmp/

cd /root/DemoCA/intermediate-1
cp /tmp/This email address is being protected from spambots. You need JavaScript enabled to view it. csr/
openssl ca -config openssl.cnf -extensions usr_cert -notext -md sha256 -in csr/This email address is being protected from spambots. You need JavaScript enabled to view it. -out certs/This email address is being protected from spambots. You need JavaScript enabled to view it.
cp certs/This email address is being protected from spambots. You need JavaScript enabled to view it. certs/ca-chain.cert.pem /tmp/

# Check:
openssl verify -CAfile certs/ca-chain.cert.pem certs/This email address is being protected from spambots. You need JavaScript enabled to view it.   

cd /home/luzifer
cp /tmp/ca-chain.cert.pem /tmp/This email address is being protected from spambots. You need JavaScript enabled to view it. .
cat This email address is being protected from spambots. You need JavaScript enabled to view it. ca-chain.cert.pem > This email address is being protected from spambots. You need JavaScript enabled to view it.
openssl pkcs12 -export -inkey This email address is being protected from spambots. You need JavaScript enabled to view it. -in This email address is being protected from spambots. You need JavaScript enabled to view it. -out This email address is being protected from spambots. You need JavaScript enabled to view it.

Now, revoke luzifer’s certificate:

cd /root/DemoCA/intermediate-1
openssl ca -config openssl.cnf -revoke certs/This email address is being protected from spambots. You need JavaScript enabled to view it.

Create a CRL

CRL files are used to check if a certificate has been revoked (offline).

cd /root/DemoCA/intermediate-1
openssl ca -config openssl.cnf -gencrl -out crl/intermediate-1.crl.pem

# Check:
openssl crl -in crl/intermediate-1.crl.pem -text
cat certs/ca-chain.cert.pem crl/intermediate-1.crl.pem > /tmp/chain+crl.pem
openssl verify -crl_check -CAfile /tmp/chain+crl.pem certs/ca-demo.bytesare.us.cert.pem
openssl verify -crl_check -CAfile /tmp/chain+crl.pem certs/revoked.ca-demo.bytesare.us.cert.pem

Create an OCSP responder certificate

OCSP is used for online checking the revocation status of a certificate.
Create a signer certificate to sign the OCSP responses:

cd /root/DemoCA/intermediate-1
openssl genrsa -aes256 -out private/ocsp.key.pem 4096
openssl req -config openssl.cnf -new -sha256 -key private/ocsp.key.pem -out csr/ocsp.csr.pem
# Subject: `C = AT, ST = Vienna, O = BytesAreUs GmbH, OU = BytesAreUs GmbH Certificate Authority, CN = BytesAreUs OCSP Signer CA 1`
openssl ca -config openssl.cnf -extensions ocsp -days 375 -md sha256 -in csr/ocsp.csr.pem -out certs/ocsp.cert.pem

# Check:
openssl verify -CAfile certs/ca-chain.cert.pem certs/ocsp.cert.pem
openssl ocsp -url http://localhost:2560/ocsp -text -rmd sha256 -index index.txt -CA certs/ca-chain.cert.pem -rkey private/ocsp.key.pem -rsigner certs/ocsp.cert.pem -nrequest 2 |  grep -e "Serial Number:" -e "Cert Status:" -e "Update:"

# (using a 2nd terminal)
cd /root/DemoCA/intermediate-1
openssl ocsp -CAfile certs/ca-chain.cert.pem -url http://localhost:2560/ocsp -issuer certs/intermediate-1.cert.pem -cert certs/ca-demo.bytesare.us.cert.pem
openssl ocsp -CAfile certs/ca-chain.cert.pem -url http://localhost:2560/ocsp -issuer certs/intermediate-1.cert.pem -cert certs/revoked.ca-demo.bytesare.us.cert.pem

Deploy and test the setup

To demonstrate the use of our CA, setup Apache with

  • the server certificate
  • an OCSP responder proxy for checking the server certificate
  • a location that requires a client certificate
  • a CRL file to check client certificates

Apache Configuration

Copy certificate material to a directory that apache user can access.

cd /root/DemoCA/intermediate-1
mkdir /etc/ssl/test-certs
cp certs/ca-chain.cert.pem certs/ca-demo.bytesare.us.cert.pem certs/revoked.ca-demo.bytesare.us.cert.pem private/ca-demo.bytesare.us.key.pem private/revoked.ca-demo.bytesare.us.key.pem crl/intermediate-1.crl.pem /etc/ssl/test-certs/

Apache needs the private key converted without a password,
otherwise the password has to be entered at every start.

cd /etc/ssl/test-certs/
openssl rsa -in ca-demo.bytesare.us.key.pem  -out ca-demo.bytesare.us.nopass.key.pem
openssl rsa -in revoked.ca-demo.bytesare.us.key.pem -out revoked.ca-demo.bytesare.us.nopass.key.pem
chown root:www-data -R .
chmod 640 *
cp intermediate-1.crl.pem /var/www/html/

Setup your local system

We pretend to be ca-demo.bytesare.us, and try it on the local machine.
Add the following line to your hosts file:

127.0.0.1 ca-demo.bytesare.us

Setup Apache

The proxy for the OCSP responder is set up on port 80
in /etc/apache2/sites-enabled/000-default.conf:

<VirtualHost *:80>
    ServerName ca-demo.bytesare.us
    DocumentRoot /var/www/html

    LogLevel info
    ErrorLog ${APACHE_LOG_DIR}/demo-ca.http.error.log
    CustomLog ${APACHE_LOG_DIR}/demo-ca.http.access.log combined

    <Location /ocsp/intermediate-1>
        ProxyPass http://127.0.0.1:2560/
    </Location>
</VirtualHost>

The server certificate and the client crl and trusted ca is configured
in /etc/apache2/sites-enabled/default-ssl.conf:

<IfModule mod_ssl.c>
<VirtualHost *:443>
    ServerName ca-demo.bytesare.us
    DocumentRoot /var/www/html-ssl

    LogLevel info ssl:debug
    ErrorLog ${APACHE_LOG_DIR}/demo-ca.ssl.error.log
    CustomLog ${APACHE_LOG_DIR}/demo-ca.ssl.access.log combined

    # Server certificate configuration:
    SSLEngine on
    SSLCertificateChainFile /etc/ssl/test-certs/ca-chain.cert.pem
    #SSLCertificateFile      /etc/ssl/test-certs/ca-demo.bytesare.us.cert.pem
    #SSLCertificateKeyFile   /etc/ssl/test-certs/ca-demo.bytesare.us.nopass.key.pem
    SSLCertificateFile     /etc/ssl/test-certs/revoked.ca-demo.bytesare.us.cert.pem
    SSLCertificateKeyFile  /etc/ssl/test-certs/revoked.ca-demo.bytesare.us.nopass.key.pem

    # Client certificate configuration:
    SSLCACertificateFile     /etc/ssl/test-certs/ca-chain.cert.pem
    SSLCARevocationFile      /etc/ssl/test-certs/intermediate-1.crl.pem
    SSLCARevocationCheck     leaf
    # Require a client cert (only) in /protected-area:
    SSLVerifyClient none
    <Location /protected-area>
        SSLVerifyClient require
        SSLVerifyDepth  10
    </Location>

</VirtualHost>
</IfModule>

Restart apache

After the configuration has been changed.

apachectl configtest 
systemctl restart apache2

Start the OCSP Responder process

And test the responder using the ca-demo.bytesare.us domain routed through the Apache listenting on localhost.

cd /root/DemoCA/intermediate-1
openssl ocsp -url http://localhost:2560/ocsp -text -rmd sha256 -index index.txt -CA certs/ca-chain.cert.pem -rkey private/ocsp.key.pem -rsigner certs/ocsp.cert.pem > /var/log/apache2/demo-ca.ocsp.log

# Check if apache proxies ocsp correctly (in a second terminal)
cd /root/DemoCA/intermediate-1
openssl ocsp -CAfile /etc/ssl/test-certs/ca-chain.cert.pem -url http://ca-demo.bytesare.us/ocsp/intermediate-1 -issuer /etc/ssl/test-certs/ca-chain.cert.pem -cert /etc/ssl/test-certs/ca-demo.bytesare.us.cert.pem 

Import the Intermediate CA certificate to a browser

In a company environment, the client administrator sets the root CA as trusted,
in our test case we will do this by hand in our browser only.

Copy cert chain to a readable location:

cd /root/DemoCA/intermediate-1
cp certs/ca-chain.cert.pem /tmp/
chmod +r /tmp/ca-chain.cert.pem

In firefox, open Settings, goto Privacy and Security, click on View Certificates and open the tab Authorities.
Then, import the ca-chain.pem file to accept our CA. Check if the Intermediate CA has been added to the list after the import.

Import the client certificate to a browser

Copy client certs to a readable location:

cp /home/carl/This email address is being protected from spambots. You need JavaScript enabled to view it. /home/luzifer/This email address is being protected from spambots. You need JavaScript enabled to view it. /tmp/
chmod +r /tmp/*.cert.p12

In firefox, open Settings, goto Privacy and Security, click on View Certificates and open the tab Your Certificates.
Then, import the This email address is being protected from spambots. You need JavaScript enabled to view it. file. Check if the client cert has been added to the list after the import.
Optionally, import the This email address is being protected from spambots. You need JavaScript enabled to view it. file. Check if the client cert has been added to the list after the import.

Showtime: Perform connection tests with your browser

Look at the Apache logs while doing this:

tail -fn0 /var/log/apache2/demo-ca*.log /var/log/apache2/ocsp.log

1. Check the browser connection to the https page

Open up the test page, it should load without errors.
Check the server certificate next to the address bar visually.

You should see in your apache logs, that the browser has also sent an OCSP request to our responder.

==> /var/log/apache2/demo-ca.http.access.log <==
127.0.0.1 - - [22/Mar/2019:20:11:17 +0100] "POST /ocsp/intermediate-1 HTTP/1.1" 200 2594 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"

==> /var/log/apache2/demo-ca.ssl.access.log <==
127.0.0.1 - - [22/Mar/2019:20:11:17 +0100] "GET / HTTP/1.1" 200 6163 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"

2. Check browser connection with client cert

Open up the location that requires client authentication and use the imported client certificate named carl.

The logs show the successful CRL check:

==> /var/log/apache2/demo-ca.ssl.error.log <==
[Fri Mar 22 20:32:09.087400 2019] [ssl:debug] [pid 30421:tid 140481960511232] ssl_engine_kernel.c(1585): [client 127.0.0.1:36704] AH02275: Certificate Verification, depth 0, CRL checking mode: leaf (1) [subject: emailAddress=This email address is being protected from spambots. You need JavaScript enabled to view it.,CN=Carl der Tester,L=Innsbruck,ST=Tirol,C=AT / issuer: CN=BytesAreUs GmbH Intermediate CA 1,OU=BytesAreUs GmbH Certificate Authority,O=BytesAreUs GmbH,ST=Vienna,C=AT / serial: 1001 / notbefore: Mar 22 16:13:37 2019 GMT / notafter: Mar 31 16:13:37 2020 GMT]

==> /var/log/apache2/demo-ca.ssl.access.log <==
127.0.0.1 - - [22/Mar/2019:20:32:06 +0100] "GET /protected-area/ HTTP/1.1" 200 13479 "https://ca-demo.bytesare.us/" "Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"

3. Check browser connection with a revoked client cert

Open up the location that requires client authentication and use the imported client certificate named luzifer.

The Browser refuses access indicating the revocation:

Secure Connection Failed

An error occurred during a connection to ca-demo.bytesare.us.
SSL peer rejected your certificate as revoked.
Error code: SSL_ERROR_REVOKED_CERT_ALERT

The server logs show an error 403 and the revocation error:

==> /var/log/apache2/demo-ca.ssl.error.log <==
[Fri Mar 22 20:36:53.040334 2019] [ssl:info] [pid 30421:tid 140481728079616] [client 127.0.0.1:36716] AH02276: Certificate Verification: Error (23): certificate revoked [subject: emailAddress=This email address is being protected from spambots. You need JavaScript enabled to view it.,CN=Luzifer der Teufel,L=Innsbruck,ST=Tirol,C=AT / issuer: CN=BytesAreUs GmbH Intermediate CA 1,OU=BytesAreUs GmbH Certificate Authority,O=BytesAreUs GmbH,ST=Vienna,C=AT / serial: 1002 / notbefore: Mar 22 16:20:39 2019 GMT / notafter: Mar 31 16:20:39 2020 GMT]

[Fri Mar 22 20:36:53.040622 2019] [ssl:error] [pid 30421:tid 140481728079616] SSL Library Error: error:1417C086:SSL routines:tls_process_client_certificate:certificate verify failed

==> /var/log/apache2/demo-ca.ssl.access.log <==
127.0.0.1 - - [22/Mar/2019:20:36:49 +0100] "GET /protected-area/ HTTP/1.1" 403 11114 "https://ca-demo.bytesare.us/" "Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"

4. Check browser connection to a page with a revoked server cert

If you use the revoked server certificate on apache,
(just uncomment the two revoked. lines in the apache config file and restart the server)
the browser will reject access because of the negative OCSP query.

Secure Connection Failed

An error occurred during a connection to ca-demo.bytesare.us.
Peer’s Certificate has been revoked.
Error code: SEC_ERROR_REVOKED_CERTIFICATE

The server logs show the start of a TLS handshake, then the negative OCSP Response.
No actual https response shows up, because the browser terminated the handshake:

==> /var/log/apache2/demo-ca.ssl.error.log <==
[Fri Mar 22 20:50:17.207959 2019] [ssl:info] [pid 32219:tid 140240362641152] [client 127.0.0.1:36828] AH01964: Connection to child 8 established (server ca-demo.bytesare.us:443)

[Fri Mar 22 20:50:17.224830 2019] [ssl:debug] [pid 32219:tid 140240362641152] ssl_engine_kernel.c(2067): [client 127.0.0.1:36828] AH02041: Protocol: TLSv1.2, Cipher: ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)

==> /var/log/apache2/demo-ca.http.access.log <==
127.0.0.1 - - [22/Mar/2019:20:50:17 +0100] "POST /ocsp/intermediate-1 HTTP/1.1" 200 2612 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"

==> /var/log/apache2/demo-ca.ocsp.log <==
OCSP Response Data:
    Produced At: Mar 22 19:50:17 2019 GMT
    Certificate ID:
      Serial Number: 1003
    Cert Status: revoked
    Revocation Time: Mar 22 16:25:49 2019 GMT

==> /var/log/apache2/demo-ca.ssl.error.log <==
[Fri Mar 22 20:50:17.262845 2019] [ssl:debug] [pid 32219:tid 140240362641152] ssl_engine_io.c(1103): [client 127.0.0.1:36828] AH02001: Connection closed to child 8 with standard shutdown (server ca-demo.bytesare.us:443)