[ Team LiB ] Previous Section Next Section

10.12 Checking Revocation Status via OCSP with OpenSSL

10.12.1 Problem

You have a certificate that you want to verify, as well as the certificate used to issue it (and any others that may be in the certification path), but you need to check that the certificates have not been revoked. One way to do this is to download the CRL from the issuing CA, but an alternative is to check an OCSP responder for an immediate response. Using OCSP allows you to avoid the overhead of downloading a potentially very large CRL file.

10.12.2 Solution

Most CAs publish CRLs, but most do not run OCSP responders. A number of public OCSP responders collect CRLs from a number of different CAs and are capable of responding for each of them. Such responders are known as chain responders, and they should only be trusted if their certificate can be verified or if it is trusted and it contains the extKeyUsage extension with the OCSPSigning bit enabled. A reasonably up-to-date list of these public responders is available from http://www.openvalidation.org. For those CAs that run their own OCSP responders, it's best to contact them directly rather than relying on a chain responder, because the information from a CA's responder is more likely to be the most up-to-date.

In Recipe 10.10, we built a lookup table of various CAs that contains information about where their CRLs can be found. You will notice that OCSP responder information is also present for those CAs that have their own. At the time of this writing, the only CA that has its own responder (so far as we have been able to determine) is VeriSign.

10.12.3 Discussion

Checking a certificate's revocation status using an OCSP responder requires three things: the address of the OCSP responder, the certificate to be checked, and the certificate that issued the certificate you want to check. With these three items, OpenSSL makes quick work of communicating with an OCSP responder. A number of tunable variables that affect the verification process are supported, so we have created a data structure to hold this information:

#include <openssl/ocsp.h>
#include <openssl/ssl.h>
   
typedef struct {
  char            *url;
  X509            *cert;
  X509            *issuer;
  spc_x509store_t *store;
  X509            *sign_cert;
  EVP_PKEY        *sign_key;
  long            skew;
  long            maxage;
} spc_ocsprequest_t;

The fields in this structure are as follows:

url

Address of the OCSP responder to which to connect; this should always be a URL that specifies either HTTP or HTTPS as the service. For example, VeriSign's OCSP responder address is http://ocsp.verisign.com.

cert

Pointer to the certificate whose revocation status you want to check. In many cases, this will likely come from the peer when establishing or renegotiating an SSL session.

issuer

Pointer to the certificate that issued the certificate whose revocation status you want to check. This should be a trusted root certificate.

store

Any information required for building an X509_STORE object internally. This object will be used for verifying the OCSP responder's certificate. A full discussion of this object can be found in Recipe 10.5, but basically it contains trusted certificates and CRLs that OpenSSL can use to verify the validity of the certificate received from the OCSP responder.

sign_cert

An OCSP request can optionally be signed. Some servers require signed requests. Any server will accept a signed request provided that the server is able to verify the signature. If you want the request to be signed, this field should be non-NULL and should be a pointer to the certificate to use to sign the request. If you are going to sign your request, you should use a certificate that has been issued by a CA that is trusted by the OCSP responder so that the responder will be able to verify its validity.

sign_key

If the sign_cert member is non-NULL, this member must be filled in with a pointer to the private key to use in signing the request. It is ignored if the sign_cert member is NULL.

skew

An OCSP response contains three time fields: thisUpdate, nextUpdate, and producedAt. These fields must be checked to determine how reliable the results from the responder are. For example, under no circumstance should thisUpdate ever be greater than nextUpdate. However, it is likely that there will be some amount of clock skew between the server and the client. skew defines an acceptable amount of skew in units of seconds. It should be set to a reasonably low value. In most cases, five seconds should work out fine.

maxage

RFC 2560 OCSP responders are allowed to precompute responses to improve response time by eliminating the need to sign a response for every request. There are obvious security implications if a server opts to do this, as we discussed in Recipe 10.1. The producedAt field in the response will contain the time at which the response was computed, whether or not it was precomputed. The maxage member specifies the maximum age in seconds of responses that should be considered acceptable. Setting maxage to 0 will effectively cause the producedAt field in the response to be ignored and any otherwise acceptable response to be accepted, regardless of its age. OpenSSL's command-line ocsp command defaults to ignoring the producedAt field. However, we think it is too risky to accept precomputed responses. Unfortunately, there is no way to completely disable the acceptance of precomputed responses. The closest we can get is to set this value to one second, which is what we recommend you do.

Querying an OCSP responder is actually a complex operation, even though we are effectively reducing the amount of work necessary for you to a single function call. Because of the complexity of the operation, a number of things can go wrong, and so we have defined a sizable number of possible error codes. In some cases, we have lumped a number of finer-grained errors into a single error code, but the code presented here can easily be expanded to provide more detailed error information.

typedef enum {
  SPC_OCSPRESULT_ERROR_INVALIDRESPONSE   = -12,
  SPC_OCSPRESULT_ERROR_CONNECTFAILURE    = -11,
  SPC_OCSPRESULT_ERROR_SIGNFAILURE       = -10,
  SPC_OCSPRESULT_ERROR_BADOCSPADDRESS    = -9,
  SPC_OCSPRESULT_ERROR_OUTOFMEMORY       = -8,
  SPC_OCSPRESULT_ERROR_UNKNOWN           = -7,
  SPC_OCSPRESULT_ERROR_UNAUTHORIZED      = -6,
  SPC_OCSPRESULT_ERROR_SIGREQUIRED       = -5,
  SPC_OCSPRESULT_ERROR_TRYLATER          = -3,
  SPC_OCSPRESULT_ERROR_INTERNALERROR     = -2,
  SPC_OCSPRESULT_ERROR_MALFORMEDREQUEST  = -1,
  SPC_OCSPRESULT_CERTIFICATE_VALID       = 0,
  SPC_OCSPRESULT_CERTIFICATE_REVOKED     = 1
} spc_ocspresult_t;

You will notice that any nonzero result code is an error of some kind—whether it is an error resulting in a failure to obtain the revocation status of the certificate in question, or one indicating that the certificate has been revoked. When checking the error codes, do not assume that zero means failure, as is the norm. You should always use these constants, instead of simple boolean tests, when checking the result of an OCSP operation.

The following result codes have special meaning:

SPC_OCSPRESULT_ERROR_MALFORMEDREQUEST through SPC_OCSPRESULT_ERROR_UNKNOWN

Result codes starting with SPC_OCSPRESULT_ERROR_MALFORMEDREQUEST and ending with SPC_OCSPRESULT_ERROR_UNKNOWN come directly from the OCSP responder. If you receive any of these error codes, you can assume that communications with the OCSP responder were successfully established, but the responder was unable to satisfy the request for one of the reasons given.

SPC_OCSPRESULT_ERROR_INVALIDRESPONSE

Indicates that there was some failure in verifying the response received from the OCSP responder. In this case, it is a good idea not to trust the certificate for which you were attempting to discover the revocation status. It is safe to assume that communications with the OCSP responder were never established if you receive any of the other error codes.

SPC_OCSPRESULT_CERTIFICATE_VALID or SPC_OCSPRESULT_CERTIFICATE_REVOKED

If the request was successfully sent to the OCSP responder, and a valid response was received, the result code will be one of these codes.

Once an spc_ocsprequest_t structure is created and appropriately initialized, communicating with the OCSP responder is a simple matter of calling spc_verify_via_ocsp( ) and checking the result code.

spc_ocspresult_t spc_verify_via_ocsp(spc_ocsprequest_t *data) {
  BIO                   *bio = 0;
  int                   rc, reason, ssl, status;
  char                  *host = 0, *path = 0, *port = 0;
  SSL_CTX               *ctx = 0;
  X509_STORE            *store = 0;
  OCSP_CERTID           *id;
  OCSP_REQUEST          *req = 0;
  OCSP_RESPONSE         *resp = 0;
  OCSP_BASICRESP        *basic = 0;
  spc_ocspresult_t      result;
  ASN1_GENERALIZEDTIME  *producedAt, *thisUpdate, *nextUpdate;
   
  result = SPC_OCSPRESULT_ERROR_UNKNOWN;
  if (!OCSP_parse_url(data->url, &host, &port, &path, &ssl)) {
    result = SPC_OCSPRESULT_ERROR_BADOCSPADDRESS;
    goto end;
  }
  if (!(req = OCSP_REQUEST_new(  ))) {
    result = SPC_OCSPRESULT_ERROR_OUTOFMEMORY;
    goto end;
  }
   
  id = OCSP_cert_to_id(0, data->cert, data->issuer);
  if (!id || !OCSP_request_add0_id(req, id)) goto end;
  OCSP_request_add1_nonce(req, 0, -1);
   
  /* sign the request */
  if (data->sign_cert && data->sign_key &&
      !OCSP_request_sign(req, data->sign_cert, data->sign_key, EVP_sha1(  ), 0, 0)) {
    result = SPC_OCSPRESULT_ERROR_SIGNFAILURE;
    goto end;
  }
   
  /* establish a connection to the OCSP responder */
  if (!(bio = spc_connect(host, atoi(port), ssl, data->store, &ctx))) {
    result = SPC_OCSPRESULT_ERROR_CONNECTFAILURE;
    goto end;
  }
   
  /* send the request and get a response */
  resp = OCSP_sendreq_bio(bio, path, req);
  if ((rc = OCSP_response_status(resp)) != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
    switch (rc) {
      case OCSP_RESPONSE_STATUS_MALFORMEDREQUEST:
        result = SPC_OCSPRESULT_ERROR_MALFORMEDREQUEST; break;
      case OCSP_RESPONSE_STATUS_INTERNALERROR:
        result = SPC_OCSPRESULT_ERROR_INTERNALERROR;    break;
      case OCSP_RESPONSE_STATUS_TRYLATER:
        result = SPC_OCSPRESULT_ERROR_TRYLATER;         break;
      case OCSP_RESPONSE_STATUS_SIGREQUIRED:
        result = SPC_OCSPRESULT_ERROR_SIGREQUIRED;      break;
      case OCSP_RESPONSE_STATUS_UNAUTHORIZED:
        result = SPC_OCSPRESULT_ERROR_UNAUTHORIZED;     break;
    }
    goto end;
  }
  
  /* verify the response */
  result = SPC_OCSPRESULT_ERROR_INVALIDRESPONSE;
  if (!(basic = OCSP_response_get1_basic(resp))) goto end;
  if (OCSP_check_nonce(req, basic) <= 0) goto end;
  if (data->store && !(store = spc_create_x509store(data->store))) goto end;
  if ((rc = OCSP_basic_verify(basic, 0, store, 0)) <= 0) goto end;
   
  if (!OCSP_resp_find_status(basic, id, &status, &reason, &producedAt,
                             &thisUpdate, &nextUpdate))
    goto end;
  if (!OCSP_check_validity(thisUpdate, nextUpdate, data->skew, data->maxage))
    goto end;
  
  /* All done.  Set the return code based on the status from the response. */
  if (status =  = V_OCSP_CERTSTATUS_REVOKED)
    result = SPC_OCSPRESULT_CERTIFICATE_REVOKED;
  else
    result = SPC_OCSPRESULT_CERTIFICATE_VALID;
  
end:
  if (bio) BIO_free_all(bio);
  if (host) OPENSSL_free(host);
  if (port) OPENSSL_free(port);
  if (path) OPENSSL_free(path);
  if (req) OCSP_REQUEST_free(req);
  if (resp) OCSP_RESPONSE_free(resp);
  if (basic) OCSP_BASICRESP_free(basic);
  if (ctx) SSL_CTX_free(ctx);
  if (store) X509_STORE_free(store);
  return result;
}

10.12.4 See Also

    [ Team LiB ] Previous Section Next Section