OSCP or Online Certificate Status Protocol for friends, provides a centralized mean of checking certificate validation and revocation by a central authority in the form of a Certificate Authority that migh emit and revoke certificates.

Usually OSCP allow for SSL/TLS certificates validation, hence why usually self-signed certificates throw a browser warning when navigating to pages using self-signed certificates, because they can’t be validated against a recognized/trusted authority by a browser or application.

In this post we will learn how to create our own OSCP responder using openssl inside docker.

This can be used to allow for authentication of applications using self-signed certificates. This can be specially useful if you want to open some of your endpoints to your known certified (by you though) applications, that don’t require an expensive certificate, and are not exactly using a domain to emit a letsencrypt certificate for example.

Our Dockerfile

So let’s start by checking our oscp Dockerfile

FROM alpine:3.7
LABEL maintainer="André Ilhicas dos Santos andreilhicas@gmail.com"
RUN apk --update add openssl
COPY create_ca.sh create_ca.sh
COPY create_client.sh create_client
COPY revoke_client.sh revoke_client
COPY get_chain.sh get_chain
COPY get_cert.sh get_cert
COPY root-openssl.conf /root/ca/openssl.cnf
COPY intermediate-openssl.conf /root/ca/intermediate/openssl.cnf
RUN chmod +x create_ca.sh && \
    chmod +x create_client && \
    chmod +x revoke_client && \
    chmod +x get_chain && \
    chmod +x get_cert
RUN ./create_ca.sh
COPY docker-entrypoint.sh docker-entrypoint.sh
EXPOSE 2560
ENTRYPOINT [ "/bin/sh", "docker-entrypoint.sh" ]

We will be using alpine:3.7 as our base image. I must admit I’m biased towards alpine images, they are lightweight allowing you to save bandwith and disk, while providing many common libraries found on Ubuntu or CentOS, sometimes requiring only some translation.

Since we will be using openssl for our OSCP certificate authority generation etc, we must install openssl, and this will be the only package we will install in this image.

Moving on to our Certificate Authority creation script.

Creating CA and Intermediate CA

create_ca.sh

#!/bin/sh
#André Santos - based on https://jamielinux.com/docs/openssl-certificate-authority/create-the-root-pair.html with modifications regarding Docker | automation
cd /root/ca
mkdir /root/ca/certs /root/ca/crl /root/ca/newcerts private
#Read and write to root in private folder
chmod 700 private
touch /root/ca/index.txt
#Echo the user id
echo 1000 > /root/ca/serial
#Generating the root key for the Certificate Authority | For simplicity without passphrase for usage within docker
openssl genrsa -out /root/ca/private/ca.key.pem 4096
#Read-only rights to the running user , root in this cases, as there is no need for any changes to the Dockerfile to declare another user and simplicity
chmod 400 /root/ca/private/ca.key.pem
#Now let's create the certificate for the authority and pass along the subject as will be ran in non-interactive mode
openssl req -config /root/ca/openssl.cnf \
      -key /root/ca/private/ca.key.pem \
      -new -x509 -days 3650 -sha256 -extensions v3_ca \
      -out /root/ca/certs/ca.cert.pem \
      -subj "/C=US/ST=NY/L=NY/O=ILHICAS/OU=OSCP lda/CN=www.example.com/EMAIL=myemail@oscp.example.com"

echo "Created Root Certificate"
#Grant everyone reading rights
chmod 444 /root/ca/certs/ca.cert.pem

#Now that we created the root pair, we should use and intermediate one.
#This part is the same as above except for the folder
mkdir /root/ca/intermediate/certs /root/ca/intermediate/crl /root/ca/intermediate/csr /root/ca/intermediate/newcerts /root/ca/intermediate/private
chmod 700 /root/ca/intermediate/private
touch /root/ca/intermediate/index.txt
#We must create a serial file to add serial numbers to our certificates - This will be useful when revoking as well
echo 1000 > /root/ca/intermediate/serial
echo 1000 > /root/ca/intermediate/crlnumber
touch /root/ca/intermediate/certs.db

openssl genrsa -out /root/ca/intermediate/private/intermediate.key.pem 4096
chmod 400 /root/ca/intermediate/private/intermediate.key.pem

echo "Created Intermediate Private Key"

#Creating the intermediate certificate signing request using the intermediate ca config
openssl req -config intermediate/openssl.cnf \
      -key /root/ca/intermediate/private/intermediate.key.pem \
      -new -sha256 \
      -out /root/ca/intermediate/csr/intermediate.csr.pem \
      -subj "/C=US/ST=NY/L=NY/O=ILHICAS/OU=OSCP lda/CN=www.security.example.com/EMAIL=myemail@oscp.example.com"

echo "Created Intermediate CSR"

#Creating an intermediate certificate, by signing the previous csr with the CA key based on root ca config with the directive v3_intermediate_ca extension to sign the intermediate CSR
echo -e "y\ny\n" | openssl ca -config openssl.cnf -extensions v3_intermediate_ca \
      -days 3650 -notext -md sha256 \
      -in /root/ca/intermediate/csr/intermediate.csr.pem \
      -out /root/ca/intermediate/certs/intermediate.cert.pem

echo "Created Intermediate Certificate Signed by root CA"

#Grant everyone reading rights
chmod 444 /root/ca/intermediate/certs/intermediate.cert.pem


#Creating certificate chain with intermediate and root
cat /root/ca/intermediate/certs/intermediate.cert.pem \
      /root/ca/certs/ca.cert.pem > /root/ca/intermediate/certs/ca-chain.cert.pem
chmod 444 /root/ca/intermediate/certs/ca-chain.cert.pem


#Create a Certificate revocation list of the intermediate CA
openssl ca -config /root/ca/intermediate/openssl.cnf \
      -gencrl -out /root/ca/intermediate/crl/intermediate.crl.pem

#Create OSCP key pair
openssl genrsa \
      -out /root/ca/intermediate/private/oscp.key.pem 4096

#Create the OSCP CSR
openssl req -config /root/ca/intermediate/openssl.cnf -new -sha256 \
      -key /root/ca/intermediate/private/oscp.key.pem \
      -out /root/ca/intermediate/csr/oscp.csr.pem \
      -nodes \
      -subj "/C=US/ST=NY/L=NY/O=ILHICAS/OU=OSCP lda/CN=www.oscp.security.example.com/EMAIL=myemail@oscp.example.com"

#Sign it
echo -e "y\ny\n" | openssl ca -config /root/ca/intermediate/openssl.cnf \
      -extensions ocsp -days 375 -notext -md sha256 \
      -in /root/ca/intermediate/csr/oscp.csr.pem \
      -out /root/ca/intermediate/certs/oscp.cert.pem

As you can see in the above script, we first create a Certificate Authority and then an intermediate CA that will be responsible for signing our certificate signing requests

Creating certificates for both clients and servers

So how exactly do we create new certificates? Using the script of create_client.sh below

#!/bin/sh
#First generate the key for the client

openssl genrsa \
      -out /root/ca/intermediate/private/$1.key.pem 2048 &>/dev/null
chmod 400 /root/ca/intermediate/private/$1.key.pem

#Then create the certificate signing request
openssl req -config /root/ca/intermediate/openssl.cnf \
      -key /root/ca/intermediate/private/$1.key.pem \
      -new -sha256 -out /root/ca/intermediate/csr/$1.csr.pem \
      -subj "/C=US/ST=NY/L=NY/O=ILHICAS/OU=OSCP lda/CN=www.$1.oscp.security.example.com/EMAIL=$1@security.example.com" &>/dev/null

#Now sign it with the intermediate CA
echo -e "y\ny\n" | openssl ca -config /root/ca/intermediate/openssl.cnf \
      -extensions $2 -days 365 -notext -md sha256 \
      -in /root/ca/intermediate/csr/$1.csr.pem \
      -out /root/ca/intermediate/certs/$1.cert.pem &>/dev/null

chmod 444 /root/ca/intermediate/certs/$1.cert.pem
cat /root/ca/intermediate/certs/$1.cert.pem

So we create our client’s private key, emit a CSR, and then sign it using our intermediate Certificate Authority, all under the intermediate certificates folder for simplicity.

This script takes two arguments:

$1 -> The name of our client

And

$2 -> The type of certificate we are generating if its for client usage or server usage-  usr_crt || server_crt

Other arguments for $2 will result in failure, but its out of scope to add more complexity to our shell script and its validation.

Direct usage would be

./create_client.sh ilhicas-server server_crt
#or
./create_client.sh ilhicas-client usr_crt

So now that we have both CA creation, and Client creation, we need to have a way to revoke our certificates.

Revoke clients script

And we create revoke_client.sh with:

#!/bin/sh
#Add client to the CRL (certificate revocation list)
openssl ca -config /root/ca/intermediate/openssl.cnf \
      -revoke /root/ca/intermediate/certs/$1.cert.pem

And the usage is as above create_client.sh, except it takes only one argument, the name of the client/certificate we created before.

Direct usage would be:

./revoke_client.sh ilhicas-client

The other two scripts [ get_chain.sh && get_cert.sh ] are just for simplicity using docker exec -it

And here are their contents

get_cert.sh

#!/bin/sh
cat /root/ca/intermediate/certs/$1.cert.pem

Direct usage

./get_cert.sh ilhicas-client

To get a client or server certificate in pem format

And to output our CA chain

get_chain.sh

#!/bin/sh
cat /root/ca/intermediate/certs/ca-chain.cert.pem

Direct usage contains no arguments

./get_chain.sh

To output our certificate chain in pem format.

But since this post is about Online Certificate Status Protocol, where is our remote/online certificate validation script.

And that is part of our entrypoint, since that’s our container main function, that’s where it should live.

And here is the contents of our docker-entrypoint.sh

#!/bin/sh
#This entrypoint is responsible for leaving the OSCP running to accept requests
openssl ocsp -port 0.0.0.0:2560 -text -sha256 \
      -index /root/ca/intermediate/index.txt \
      -CA /root/ca/intermediate/certs/ca-chain.cert.pem \
      -rkey /root/ca/intermediate/private/intermediate.key.pem \
      -rsigner /root/ca/intermediate/certs/intermediate.cert.pem

We use port 2560 internally mapped as that is the default port for oscp.

Running as Docker Container

To start build the image:

docker build --tag oscp .

To run it

docker run -d --name oscp -p 2560:2560 oscp

To create a client certificate (this command is not idempotent, so second run with same client name results in exception, will not be treated as its not under the scope of this project) - To run again please run revokation first

docker exec -it oscp ./create_client ilhicas usr_crt

To revoke a client certificate

docker exec -it oscp ./revoke_client ilhicas

To validate a certificate

Grab the cert , the chain and create a client

docker exec -it oscp ./get_chain > example_chain.pem &&\
docker exec -it oscp ./get_cert > example_cert.pem &&\
docker exec -it oscp ./create_client ilhicas_new > example_client.pem

Now validate the certificate of the client against the OSCP responder

openssl ocsp -CAfile example_chain.pem \
  -url http://127.0.0.1:2560 \
  -issuer example_cert.pem \
  -cert example_client.pem

This is not a production grade oscp - restart the oscp to be aware of new certs

docker restart oscp

Should end up with something like besides all the certificate output and verbosity: Response verify OK example_client.pem: good

Now revoke it docker exec -it oscp ./revoke_client ilhicas_new

We need to restart again

docker restart oscp

Rerun the validation command

Response verify OK
example_client.pem: revoked
This Update: Mar 18 19:36:07 2018 GMT
Revocation Time: Mar 18 19:35:49 2018 GMT

To clean things up

docker rm -f oscp
docker rmi oscp

Now I’ve built and pushed the image previously, not automated in hub.docker as I’ve not set up a git repository for this yet. You may instead of building the image your self

Just do

docker pull andreilhicas/oscp

You may tag it to use the same values as above

docker tag andreilhicas/oscp:latest oscp

And that’s it. Hope you find it helpful

André Ilhicas dos Santos

Devops Padawan, curious about systems automation, learning new languages, paradigms tools each day.

ilhicas ilhicas


Published