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.
So let’s start by checking our oscp Dockerfile
FROM alpine:3.7 LABEL maintainer="André Ilhicas dos Santos email@example.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
#!/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/EMAILfirstname.lastname@example.org" 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/EMAILemail@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/EMAILfirstname.lastname@example.org" #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
#!/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/EMAILemail@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
$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
#!/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:
The other two scripts [ get_chain.sh && get_cert.sh ] are just for simplicity using
docker exec -it
And here are their contents
#!/bin/sh cat /root/ca/intermediate/certs/$1.cert.pem
To get a client or server certificate in pem format
And to output our
#!/bin/sh cat /root/ca/intermediate/certs/ca-chain.cert.pem
Direct usage contains no arguments
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
#!/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
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