Ian Spence
Posted on May 10, 2019
The online certificate status protocol, or OCSP for short, is a way for TLS clients (like your web browser) to check if a certificate has been revoked or not.
Certificate Revocation?
With asymmetrical encryption, your encryption is only as good as long as your private key remains private. This is often not the case and private keys end up exposed.
This is just one of the many possible reasons why a certificate may be revoked. And a certificate authority, or a certificate holder may revoke their certificate to mark it as untrusted. Since there's no way to change a certificate once its been created, status checks are used to see if its been revoked. This is where OCSP comes into play.
A basic example
Let's take a look at the certificate for dev.to, in it we'll see the URL for the OCSP server:
X509v3 extensions:
Authority Information Access:
CA Issuers - URI:http://secure.globalsign.com/cacert/cloudsslsha2g3.crt
OCSP - URI:http://ocsp2.globalsign.com/cloudsslsha2g3
If OCSP is enabled in your web browser (which it probably isn't, we'll get to that later), when you browsed to dev.to your browser made a quick HTTP request to ocsp2.globalsign.com/cloudsslsha2g3 with the SHA1 fingerprint of the certificate. Globalsign, the CA for dev.to's certificate, will check its database for that certificate and if its been revoked or not, and respond back with a signed response.
Depending on the response from the OCSP server, the browser may either continue on to the site or consider the certificate revoked and untrustworthy.
HTTP? SHA-1? What year is this?
Yup, you read that right. OCSP requests are all in good-old insecure HTTP, and most clients use typically end up using SHA-1 fingerprints.
The reason OCSP operates over HTTP is because if it operated over HTTPS, then we'd have to check the status of the certificate used for the OCSP server, then that server, and the next one, and so on and so on. We'd end up in a forever loop of checking certificates. OCSP responses are signed to prevent tampering.
The OCSP specification mandates that SHA-1 must be the minimum required hashing algorithm for servers and clients, giving room for more secure hashing algorithms to be used. However, since status checks are meant to be fast, TLS Clients often don't want to get stuck in a retry loop trying to negotiate a algorithm with the server. Therefor, most clients use the default of SHA-1 and just live with it.
Are there privacy concerns?
You bet! With OCSP, an extra third-party is made aware of you browsing to a website that typically wouldn't know otherwise.
With our above example, GlobalSign knows that you visited dev.to from a specific IP, with a specific browser, at a specific time. All because of OCSP.
Thankfully, there is a commonly implemented and accepted solution: OCSP Stapling.
With OCSP stapling, the web server itself makes an OCSP request on your behalf and includes it with the connection information. This means that the third-party does not know who connected to that site at that time, only that somebody did.
Getting the OCSP URL from a X.509 Certificate
The OCSP URL for a certificate in within the “Certificate Authority Information Access” extension. To get the URL of the responder, get the information access extension, then iterate through each possible item for that extension and locate the OCSP object. It’s a IA4 ASN1 string value, so you’ll need to decode it to use it as a regular string.
X509 * cert;
char * ocspURL;
AUTHORITY_INFO_ACCESS * info = X509_get_ext_d2i(cert, NID_info_access, NULL, NULL);
int len = sk_ACCESS_DESCRIPTION_num(info);
for (int i = 0; i < len; i++) {
// Look for the OCSP entry
ACCESS_DESCRIPTION * description = sk_ACCESS_DESCRIPTION_value(info, i);
if (OBJ_obj2nid(description->method) == NID_ad_OCSP) {
if (description->location->type == GEN_URI) {
// Hold on to this URL for later use
ocspURL = i2s_ASN1_IA5STRING(NULL, description->location->d.ia5);
}
}
}
Generating a OCSP request
An OCSP request is one or more "Key IDs", which is a fingerprint of the certificate. Most clients typically only include one certificate per request, and there are no restrictions on servers refusing to respond to requests with multiple certificates.
X509 * certificate;
X509 * issuer;
// Passing NULL as the first parameter will use SHA-1
OCSP_CERTID * certID = OCSP_cert_to_id(NULL, certificate, issuer);
OCSP_REQUEST * request = OCSP_REQUEST_new();
OCSP_request_add0_id(request, certid);
// Hold on to request_data and len for later use.
unsigned char * request_data = NULL;
int len = i2d_OCSP_REQUEST(request, &request_data);
Making the OCSP Request
The actual request is just a simple HTTP POST to the responder URL, with the body being a ASN-encoded OCSP request.
Like with all HTTP POST requests, you'll need to provide the Content-Type
and Accept
headers. Most OCSP servers will refuse to respond without the correct header values, so make sure you don't forget these:
Content-Type: application/ocsp-request
Accept: application/ocsp-response
Note: This post does not cover actually making a HTTP POST request. libCURL works well for this.
An OCSP response contains an outer response object, and at least one inner certificate result. (One result per certificate requested).
Once you've gotten a response back from the OCSP responder, decode the response body as a OCSP_RESPONSE
object.
const unsigned char * bytes; // Assuming this is the bytes of the HTTP response body
OCSP_RESPONSE * response = d2i_OCSP_RESPONSE(NULL, &bytes, data.length);
Then check to see if the OCSP request itself was successful. This does not indicate that the certificate is revoked or not:
int requestResponse = OCSP_response_status(ocspResponse);
if (requestResponse != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
// Uh oh!
}
Once you're sure that the OCSP request was successful, get the basic response object, which contains all of the certificate status results:
OCSP_BASICRESP * basicResp = OCSP_response_get1_basic(ocspResponse);
if (basicResp == NULL) {
// Something bad happened!
}
Least but not last, get the result of your certificate!
int status; // If the certificate is revoked or not
int reason; // A CRL reason (if revoked)
ASN1_GENERALIZEDTIME * revocationTime; // When the certificate was revoked (if revoked)
ASN1_GENERALIZEDTIME * thisUP; // The last time this certificate was updated
ASN1_GENERALIZEDTIME * nextUP; // The time of the next update (how long you can cache until
if (!OCSP_resp_find_status(resp, certID, &status, &reason, &revocationTime, &thisUP, &nextUP)) {
// There was a problem getting the status - probably the certificate wasnt found
}
Awesome, now we finally have the result of our certificate!
Verifying Certificate Status
Now that we have the status of our certificate, we need to see if our certificate is revoked or not.
With OCSP there are three possible status' for a certificate: GOOD
, REVOKED
, or UNKNOWN
.
- GOOD means that the certificate is not revoked according to this OCSP responder. It does not mean the certificate is valid otherwise (I.E. could still be expired).
- REVOKED means that the certificate is revoked.
- UNKNOWN means that the OCSP responder does not know about this certificate. You must be careful with unknown responses, as many applications consider UNKNOWN responses to be the same as GOOD.
If the certificate has a REVOKED
status, then you'll want to check the reason why it's revoked. OCSP uses the reason codes defined in the certificate revocation list (CRL) specification. The reasons are:
– unspecified: can be used to revoke certificates for reasons other than the specific codes.
– keyCompromise: is used in revoking an end-entity certificate; it indicates that it is known or suspected that the subject's private key, or other aspects of the subject validated in the certificate, have been compromised.
– cACompromise: is used in revoking a CA-certificate; it indicates that it is known or suspected that the subject's private key, or other aspects of the subject validated in the certificate, have been compromised.
– affiliationChanged: indicates that the subject's name or other information in the certificate has been modified but there is no cause to suspect that the private key has been compromised.
– superseded: indicates that the certificate has been superseded but there is no cause to suspect that the private key has been compromised.
– cessationOfOperation: indicates that the certificate is no longer needed for the purpose for which it was issued but there is no cause to suspect that the private key has been compromised.
– privilegeWithdrawn: indicates that a certificate (public-key or attribute certificate) was revoked because a privilege contained within that certificate has been withdrawn.
– aACompromise: indicates that it is known or suspected that aspects of the AA validated in the attribute certificate have been compromised.
All these reasons are well and good, and there's likely nothing your application has to do for any of them, but then there's two edge cases that really complicate matters:
- certificateHold: indicates that a certificate authority no longer vouches for this certificate, but that may change at a later date.
- removeFromCrl: indicates that a certificate was previously on hold, but is no longer on hold and considered not revoked.
So it's always important to check the reason, because even if the status is REVOKED, if the reason is removeFromCrl
, then it's not actually revoked.
If you're looking for easily verify your certificates on-the-go using your iOS mobile device, check out one of my projects: TLS Inspector. The worlds first Free & Libre X509 Certificate Inspector on iOS (GitHub).
Posted on May 10, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.