Subsections

2017-09-22 Adding Certificate Revocation Lists (CRLs) to an Existing OpenSSL Implementation

I recently took it upon myself to add Certificate Revocation List (CRL) functionality to an SSL-enabled program (InspIRCd). This blog details both the code that I used and some unexpected functionality regarding how CRLs behave. Hopefully this blog will prove useful to someone else who needs to tread this path.

Code Itself

The code itself is actually quite simple, consisting of two basic steps: loading the CRL files and then enabling CRL checking. In order to load the CRL files, begin by getting the X509_STORE * from the SSL_CTX * by calling SSL_CTX_get_cert_store(3), then load the CRL files into the X509_STORE * by calling X509_STORE_load_locations() with the specified CRL file and path locations. The definition of the functions can be found in openssl/x509_vfy.h in your system's include file directory. The code looks something like:
	SSL_CTX *ctx = sslctx; // Defined elsewhere.
	X509_STORE *store;
	char *crlfile = "/path/to/single/file.pem";
	char *crlpath = "/path/to/entire/directory";

	if (!(store = SSL_CTX_get_cert_store(ctx))) {
		fprintf(stderr, "No cert store found\n");
		return -1;
	}
	if (!X509_STORE_load_locations(store, crlfile, crlpath)) {
		fprintf(stderr, "Unable to load CRL files\n");
		return -1;
	}
This will load the CRL files, but will not enable CRL checking; this has to be done by setting a flag value in the X509_STORE *. There at least two possible options: one is to check only the leaf certificate, the other is to check all certificates in the chain. The former option can be accomplished by setting only X509_V_FLAG_CRL_CHECK while the latter can be accomplished by setting two flags X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL. The latter option (the entire chain) is almost certainly what you want.
	char* crlmode; // Defined elsewhere.
	int crlflags;

	if (!strcmp(crlmode, "chain")) {
		crlflags = X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL;
	} else if (!strcmp(crlmode, "leaf")) {
		crlflags = X509_V_FLAG_CRL_CHECK;
	} else {
		fprintf(stderr, "Unknown flag mode '%s'\n", crlmode);
		return -1;
	}
	if (X509_STORE_set_flags(store, crlflags) != 1) {
		fprintf(stderr, "Unable to set flags\n");
		return -1;
	}
...and that's it! CRLs should now be enabled for your program. However, do not rest easily just yet, as CRLs offer several pitfalls for the unwary, as detailed below.

Missing CRL Files

Consider the following, simple certificate chain:
Figure: Simple certificate chain, where A and B are CAs and C is the client.
\fbox{
\begin{tikzpicture}
% Styles.
[ca/.style={rectangle,draw=green!60!black...
...{Client};
\node [rectangle,draw,fit=(legend) (l1) (l2)] {};
\end{tikzpicture}}
The server trusts Certificate Authorities (CAs) A and B to issue valid certificates, thus C's certificate will be accepted. Without CRLs this scenario will work as expected, C will be able to connect to the server without a problem. Now, assume that the server enables CRLs, generates a CRL for A, and then configures itself to use A's CRL; a CRL for B is not generated as B has not revoked any certificate and thus there doesn't seem to be any need to generate a CRL for B. As you can probably guess by now, C will not be able to connect in this scenario, because the default CRL-checking method requires every CA in the chain to have a CRL file loaded, thus C cannot connect unless both A and B generate CRL files, even if they have no revoked certificates.
Figure: Each CA is required to have a CRL file.
\fbox{
\begin{tikzpicture}
% Styles.
[ca/.style={rectangle,draw=green!60!black...
...{Client};
\node [rectangle,draw,fit=(legend) (l1) (l2)] {};
\end{tikzpicture}}

The consequence of this is that enabling CRL-checking requires gathering CRL files, a task that is likely non-trivial. The alternative is to override OpenSSL's verify callback by defining your own verify function and then telling OpenSSL to use said function with X509_STORE_set_verify_cb(3). I, however, do not intend to touch that with a 10-foot pole.

Using a CRL Path

Suppose that you wish to use a directory to store CRL files separately for each CA (instead of one big CRL file), then have OpenSSL load in each CRL file from this directory. An obvious benefit of this approach is that it allows you to create a separate CRL file for each CA; using the certificate chain from the previous section, this would mean a directory containing both crl/a.pem and crl/b.pem. One would think that it would be enough to place each CRL file in said directory, then point OpenSSL to said directory, but that actually doesn't work.

While OpenSSL indeed expects each CA to have its own CRL file, it expects the CA file to be named in a very specific manner; it expects the file to be named as the concatenation of the first part of the hash of its issuer name and .rX where X starts as 0 and is incremented for each hash collision that occurs. For example, suppose the hash of CA B is deadbeef, then its CRL filename is expected to be deadbeef.r0. Arguably, the easiest way to support this hash-based filename is to create the actual CRL file with the name of its respective issuer, then create the symbolic link for OpenSSL, which can be done by something like:

	for crl in `ls *.pem`; do
		hash=$(openssl crl -hash -in "${crl}" -noout)
		# Just assume no collisions for simplicity.
		ln -s "${crl}" "${hash%$'\n'}.r0"
	done
...which generates a hash for each file suffixed with .pem in the current directory (WARNING: does not take into account hash collisions). With the symbolic links created, you should now be able to use a CRL path.

Figure: Each CA requires its own CRL file. The CRL file name is based on the hash of its issuer name. Note that the issuer of the CRL file is the subject of its respective CA.
\fbox{
\begin{tikzpicture}
% Styles.
[ca/.style={rectangle,draw=green!60!black...
...{Client};
\node [rectangle,draw,fit=(legend) (l1) (l2)] {};
\end{tikzpicture}}

One more caveat: OpenSSL does not seem to report any error if the CRL path directory does not exist. Figures.


Generated using LaTeX2html: Source