Fixing Certificate Revocation

This is the third in a series of posts about setting up Play WS as a TLS client for a "secure by default" setup and configuration through text files, along with the research and thinking behind the setup. (TL;DR – if you're looking for a better revocation protocol, you may be happier reading Fixing Revocation for Web Browsers on the Internet and PKI: It's Not Dead, Only Resting.)

Previous posts are:

This post is all about certificate revocation using OCSP and CRL, what it is, how useful it is, and how to configure it in JSSE.

Certificate Revocation (and its Discontents)

The previous post talked about X.509 certificates that had been compromised in some way. Compromised certificates can be a big problem, especially if those certificates have the ability to sign other certificates. If certificates have been broken or forged, then in theory it should be possible for a certificate authority to let a client know as soon as possible which certificates are invalid and should not be used.

There have been two attempts to do certificate revocation: Certificate Revocation Lists (CRLs). CRLs – lists of bad certificates – were huge and hard to manage.

As an answer to CRLs, Online Certificate Status Protocol was invented. OCSP involves contacting the remote CA server and going through verification of the certificate there before it will start talking to the server. According to Cloudflare, this can make TLS up to 33% slower. Part of it may be because OCSP responders are slow, but it's clear that OCSP is not well loved.

In fact, most browsers don't even bother with OCSP. Adam Langley explains why OCSP is disabled in Chrome:

While the benefits of online revocation checking are hard to find, the costs are clear: online revocation checks are slow and compromise privacy. The median time for a successful OCSP check is ~300ms and the mean is nearly a second. This delays page loading and discourages sites from using HTTPS. They are also a privacy concern because the CA learns the IP address of users and which sites they're visiting.

On this basis, we're currently planning on disabling online revocation checks in a future version of Chrome. (There is a class of higher-security certificate, called an EV certificate, where we haven't made a decision about what to do yet.)

– "Revocation checking and Chrome's CRL"

Adding insult to injury, OCSP also has security issues:

Alas, there was a problem — and not just “the only value people are adding is republishing the data from the CA”. No, this concept doesn’t work at all, because OCSP assumes a CA never loses control of its keys. I repeat, the system in place to handle a CA losing its keys, assumes the CA never loses the keys.

Dan Kaminsky

and:

OCSP is actually much, much worse than you describe. The status values are, as you point out, broken. Even if you fix that (as some CAs have proposed, after being surprised to find out how OCSP really worked – yes, some of the folks charged with running OCSP don’t actually know how it really works) it doesn’t help, given OCSP’s broken IDs an attacker can trivially work around this. And if you fix those, given the replay-attack-enabled “high-performance” optimisation an attacker can work around that. And if you fix that, given that half the response is unauthenticated, an attacker can go for that. To paraphrase Lucky Green, OCSP is multiple-redundant broken, by design. If you remove the bits that don’t work (the response status, the cert ID, nonces, and the unauthenticated portions of the response) there is literally nothing left. There’s an empty ASN.1 shell with no actual content. There is not one single bit of OCSP that actually works as it’s supposed to (or at least “as a reasonable person would expect it to”, since technically it does work exactly as the spec says it should).

Peter Gutmann, replying to Dan Kaminsky

And to drive the point home, if you have someone sitting on your network with a copy of sslsniff, they can trivially fake out a response:

As an attacker, it is thus possible for us to intercept any OCSP request and send a tryLater response without having to generate a signature of any kind. The composition of the response is literally just the OCSPResponseStatus for the tryLater condition, which is simply the single-byte ASCII character '3'.

Most OCSP implementations will accept this response without generating any kind of warning to the user that something might be amiss, thus defeating the protocol.

Defeating OCSP With The Character '3'

Given all of this, it's hard to say OCSP is worthwhile. However, it's important to note that all of the above comments are talking about public revocation checking against browsers and mobile devices in the wild.

If you're using web services in an internal network, OCSP actually sounds useful. Privacy is less of an issue, you're running on an internal network, you can make your OCSP responder fast enough, and using a hard-fail approach for a web service is reasonable. The research on the use of OCSP in web services is thin: I found one article. Presumably, OCSP gets rolled into PKI enterprise management solutions.

I also haven't heard of any exploits in the wild, perhaps because OCSP is so rarely used. This is not to say that OCSP is secure… but even speed bumps can be effective sometimes.

Certificate Revocation in JSSE

Certificate Revocation in JSSE is disabled by default, because of the performance issues. I decided to leave this disabled out of the box, but did what I could to make it easier to configure.

The implementation is… convoluted. The details are spelled out in Appendix C of the PKI Guide and Enable OCSP checking, but it's still incomplete.

You need to set up the system properties, on the command line:

java -Dcom.sun.security.enableCRLDP=true -Dcom.sun.net.ssl.checkRevocation=true

You need to do this because the system properties set up private static final fields internally:

If you're calling this in a running JVM, you need to ensure that nothing's loaded up those classes already, or you'll have to resort to fiddling the already loaded classes, a solution that isn't appropriate in production code.

To set up OCSP, you need to set the security property, by adding the following to your initialization code:

java.security.Security.setProperty("ocsp.enable", "true")

It is a small mercy that "ocsp.enable" is checked at runtime from PKIXCertPathValidator, so you can do that any time you feel like.

Currently, the configuration look like this:

ws.ssl.checkRevocation = true
ws.ssl.revocationLists = [ "http://example.com/crl" ]

When checkRevocation is true, it will set "ocsp.enable" to true, set up the static revocation lists and do the work of passing in settings to the trust manager.

Generating an individual CRL is done using a DataInputStream:

  def generateCRL(inputStream: InputStream): CRL = {
    val cf = CertificateFactory.getInstance("X509")
    cf.generateCRL(inputStream)
  }

  def generateCRLFromURL(url: URL): CRL = {
    val connection = url.openConnection()
    connection.setDoInput(true)
    connection.setUseCaches(false)
    val inStream = new DataInputStream(connection.getInputStream)
    try {
      generateCRL(inStream)
    } finally {
      inStream.close()
    }
  }

When you set up the trust manager, you have to set up an instance of PKIXBuilderParameters (see Fixing X.509 Certificates for where this fits in). Then, the CRLs as a CertStore with its own CollectionCertStoreParameters class.

  def buildTrustManagerParameters(trustStore: KeyStore,
    revocationEnabled: Boolean,
    revocationListOption: Option[Seq[CRL]],
    signatureConstraints: Set[AlgorithmConstraint],
    keyConstraints: Set[AlgorithmConstraint]): CertPathTrustManagerParameters = {
    import scala.collection.JavaConverters._

    val certSelect: X509CertSelector = new X509CertSelector
    val pkixParameters = new PKIXBuilderParameters(trustStore, certSelect)
    pkixParameters.setRevocationEnabled(revocationEnabled)

    // Set the static revocation list if it exists...
    revocationListOption.map {
      crlList =>
        import scala.collection.JavaConverters._
        pkixParameters.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(crlList.asJavaCollection)))
    }

    // Add the algorithm checker in here...
    val checkers: Seq[PKIXCertPathChecker] = Seq(
      new AlgorithmChecker(signatureConstraints, keyConstraints)
    )

    // Use the custom cert path checkers we defined...
    pkixParameters.setCertPathCheckers(checkers.asJava)
    new CertPathTrustManagerParameters(pkixParameters)
  }

And we're done.

Testing

Once you have everything configured, you can turn on debugging to check that OCSP is enabled:

java -Djava.security.debug="certpath ocsp"

And optionally use Wireshark to sniff OCSP responses.

There are a number of OSCP responders which are simply broken when you turn them on, but How's My SSL works well.

Finally, a note about testing. Because of the system properties problem, using configuration from inside a running JVM is difficult, which can snarl tests:

class HowsMySSLSpec extends PlaySpecification with CommonMethods {
  val timeout: Timeout = 20.seconds
  "WS" should {
    "connect to a remote server" in {
      val rawConfig = play.api.Configuration(ConfigFactory.parseString(
        """
          |ws.ssl.debug=["certpath", "ocsp"]
          |ws.ssl.checkRevocation=true  # doesn't set system properties before classes load!
        """.stripMargin))

      val client = createClient(rawConfig)
      val response = await(client.url("https://www.howsmyssl.com/a/check").get())(timeout)
      response.status must be_==(200)
    }
  }
}

Instead, you need to specify the system properties to SBT or set the system properties before the test runs:

javaOptions in Test ++= Seq("-Dcom.sun.security.enableCRLDP=true", "-Dcom.sun.net.ssl.checkRevocation=true")

You should see as output (under JDK 1.8):

certpath: -Using checker7 ... [sun.security.provider.certpath.RevocationChecker]
certpath: connecting to OCSP service at: http://gtssl2-ocsp.geotrust.com
certpath: OCSP response status: SUCCESSFUL
certpath: OCSP response type: basic
certpath: Responder's name: CN=GeoTrust SSL CA - G2 OCSP Responder, O=GeoTrust Inc., C=US
certpath: OCSP response produced at: Wed Mar 19 13:57:32 PDT 2014
certpath: OCSP number of SingleResponses: 1
certpath: OCSP response cert #1: CN=GeoTrust SSL CA - G2 OCSP Responder, O=GeoTrust Inc., C=US
certpath: Status of certificate (with serial number 159761413677206476752317239691621661939) is: GOOD
certpath: Responder's certificate includes the extension id-pkix-ocsp-nocheck.
certpath: OCSP response is signed by an Authorized Responder
certpath: Verified signature of OCSP Response
certpath: Response's validity interval is from Wed Mar 19 13:57:32 PDT 2014 until Wed Mar 26 13:57:32 PDT 2014
certpath: -checker7 validation succeeded

And that takes care of testing.

Next

Hostname Verification!

Comments