OPENSSL for Web-Security: verification of Certificate and Certificate Revocation List (in C/C++)

Monzurul Islam
5 min readJul 22, 2021

Digital certificates are instrumental to public key infrastructure (PKI) and web security. Using digital certificate, a client verifies the authenticity of the server. It’s like verifying “if the server is who it says it is”. Usually Digital certificates expire after one year. But if something goes wrong, for example, the certificate owners private key got lost, then the certificate needs to be revoked before expiration. Usually the CA revokes that certificate and add it to a blacklist (Certificate Revocation list). Thus It is important that CA publishes the approved certificates as well as Revoked certificate list.

A pictorial overview of verification of certificate and CRL.

Let’s take a quick walk through of the whole process. When a client sends a connection request to a server, the server sends back its own certificate. Now the client verifies if the certificate is indeed a valid certificate. The client also downloads a copy of the revocation list (CRL) from a CRL server (maintained by a CA). The client performs a lookup on the CRL to find out if the certificate is revoked or not.

So, this process includes 4 modules: a server app, a client app, a CRL server and a CA authority. But basically client app carries the burden of CRL download, CRL lookup and verification. In this article, I am going to show how to code the modules and simulate the whole process in your own machine.

Complete source code for this article can be found here

OpenSSL’s API for SSL is large and can be daunting to many programmers. As our first introduction to the SSL API, let’s start with covering 3 basic structure: SSL_METHOD, SSL_CTX and SSL. The SSL_METHOD structure specifies the protocol version of SSL/TLS. Using a SSL method object, we create a SSL context object SSL_CTX. An SSL_CTX object will act as a factory for producing the SSL connection object.

Let’s start with the smaller module: the server app. If you are familiar with basic socket programming, you already know that a basic server socket bind itself to a port on the local machine and keeps listening for incoming connection. And when there is a connection request from a client, the server does accept() the connection, creates a new file descriptor for the connection and starts to send() or recv() message using this new file descriptor. But in a openssl based server, following additional things happen:

To begin with, A SSL_CTX structure is created and set up at the InitServerCTX() function.

SSL_CTX *InitServerCTX(void){
const SSL_METHOD *method;
SSL_CTX *ctx;
OpenSSL_add_all_algorithms();
SSL_load_error_strings(); /* load all error messages */
method = TLSv1_2_server_method();/* create new server-metho instance */
ctx = SSL_CTX_new(method); /* create new context from method */
if (ctx == NULL)
{
ERR_print_errors_fp(stderr);
abort();
}
return ctx;
}

The server certificates are loaded into an SSL_CTX structure at LoadCertificates().

void LoadCertificates(SSL_CTX *ctx, char *CertFile, char *KeyFile){/* set the local certificate from CertFile */
if (SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
abort();
}
/* set the private key from KeyFile*/
if (SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
abort();
}
/* verify private key */
if (!SSL_CTX_check_private_key(ctx))
{
fprintf(stderr, "Private key does not match the public certificate\n");
abort();
}
}

Once the aforementioned two function has been performed and the accept() has returned a file descriptor, instead of calling our regular send()/recv() method, we create a SSL connection object and bind this with the accept() returned file descriptor. Then we use SSL_read() or SSL_write() method to receive or send messages. And that’s our minimal ssl server.

Please note that, in the code, for simplicity, only the server is sending its certificate to client, not the other way around. Also remember, you need to generate a pair of certificate and key for the server. There’s tons of examples available on Internet. Follow any of them. Just don’t forget to pass the http address of CRL distribution server in your openssl.conf (example below). More on this later!

[ ca ]
default_ca = exampleca
[ exampleca ]
x509_extensions = certificate_extensions
[ certificate_extensions ]
basicConstraints = CA:false
crlDistributionPoints = URI:http://10.0.2.15:3000/cdp.der

Now, lets write our client app. The client app will quite a few tasks. Besides it’s main job of connecting to the server, it verifies server certificate, downloads the CRL from the distribution point and performs CRL check. Let’s walk thru one by one.

After connecting the client app to the server, set peer certificate verification parameters like SSL_VERIFY_PEER, SSL_VERIFY_FAIL_IF_NO_PEER_CERT. We are also passing a callback for certificate verification. Inside this function (cert_verify_callback), we are printing the issuer, the subject line of erroneous certificate as well as a human readable error string for the error.

int cert_verify_callback(int ok, X509_STORE_CTX* store)
{
char data[256];
int last;
if(!ok){
...
X509* cert = X509_STORE_CTX_get_current_cert(store);
int depth = X509_STORE_CTX_get_error_depth(store);
int err = X509_STORE_CTX_get_error(store);
printf("%s X509 [Certificate Verify Fail]: Error with certificate at depth (%d)\n", __FUNCTION__, depth);
X509_NAME_oneline(X509_get_issuer_name(cert), data, 256);
printf("%s X509 [Certificate Verify Fail]: Issuer = %s\n", __FUNCTION__, data);
X509_NAME_oneline(X509_get_subject_name(cert), data, 256);
printf("%s X509 [Certificate Verify Fail]: Subject = %s\n", __FUNCTION__, data);
printf("%s X509 [Certificate Verify Fail]: Error (%d:%s)\n", __FUNCTION__, err, X509_verify_cert_error_string(err));
}
return ok;
}

In X509_STORE_set_lookup_crls_cb, we are defining the callback for CRL checking and download. At the crl_http_callback, first we extract the CRL distribution point. Then from the distribution point, we pullout the URL and using this URL, we download the CRL. We also store the CRL to a local cache.

STACK_OF(X509_CRL)* crl_http_callback(X509_STORE_CTX *store, X509_NAME *nm)
{
...
crldp = (STACK_OF(DIST_POINT) *)X509_get_ext_d2i(cert, NID_crl_distribution_points, NULL, NULL);
if (!crldp) {
debug_print(" No CRLDP... \n ");
goto out;
}
debug_print(" found crl dp \n ");
int i;
for (i = 0; i < sk_DIST_POINT_num(crldp); i++) {
DIST_POINT *dp = sk_DIST_POINT_value(crldp, i);
urlptr = get_dp_url(dp);
if (urlptr) {
printf(" Got URL \n");
debug_print(" Got URL \n ");
crl = load_HTTP_crl(urlptr);
break;
}
}
...
out:
return crls;
}

After all the steps of certificate verification occurs with success, the client establishes a secure connection performing SSL_connect() on its SSL connection object. Then we use SSL_read() or SSL_write() method to receive or send messages probably inside an infinite loop. That’s our ssl client.

Btw, remember to host the CRL file at the URL mentioned in the openssl.conf. A simple NodeJS server can do the trick (for a local test). The openssl based server and client code can be run as follows:

On two different terminal:
/crl_client 127.0.0.1 6001
sudo ./server 6000

If everything goes right, you will see a secure full duplex chat server-client system running.

--

--

Monzurul Islam

Embedded Software designer working in Networking Application domain