Thursday, November 9. 2017
Customizing OCSP/CRL revocation check in java
Long time ago I wrote some entries about a bug in the OCSP revocation check implementation in javase. It was when java 6 and 7 were being used and version 8 was in development. In that time I realized that all the OCSP behavior was just configured using system properties (ocsp.enable, ocsp.responderURL,...). For me it was strange that there was no programmatic way (only for this specific check) to configure the revocation. Some options could be configured using PKIXParameters or PKIXBuilderParameters but the OCSP ones were not among them.
From time to time I re-checked if that situation was improved but I did not find anything new. Nevertheless the other day I saw in this stackoverflow thread that there is a solution, and it is in place since java 8! (so it is a very old solution although I had no idea about it). The idea is based in the use of the class PKIXRevocationChecker. In those old entries I used an old java program that checked the certificates using the system properties, this is the new CertificateChecker.java using the revocation checker:
public static void main(String[] args) throws Exception {
if (args.length != 4 && args.length != 5) {
throw new IllegalArgumentException("Usage: java CertificateChecker {responder_cert_alias}" );
}
String certFile = args[0];
String cacertsFile = args[1];
String cacertsPassword = args[2];
String responderUrl = args[3];
/* old style removed
Security.setProperty("ocsp.enable", "true");
System.setProperty("com.sun.security.enableCRLDP", "false");
Security.setProperty("ocsp.responderURL", responderUrl);
if (args.length == 5) {
Security.setProperty("ocsp.responderCertSubjectName", args[4]);
}*/
// read the certificate from the file
System.out.println("Loading certificate...");
FileInputStream is = new FileInputStream(certFile);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
// read the cacerts keystore to check signature
System.out.println("Loading cacerts...");
KeyStore cacerts = KeyStore.getInstance(KeyStore.getDefaultType());
cacerts.load(new FileInputStream(cacertsFile), cacertsPassword.toCharArray());
// set security options to ocsp validation (only ocsp)
CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX");
PKIXRevocationChecker rc = (PKIXRevocationChecker) cpb.getRevocationChecker();
rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.NO_FALLBACK));
rc.setOcspResponder(new URI(responderUrl));
if (args.length == 5) {
rc.setOcspResponderCert((X509Certificate) cacerts.getCertificate(args[4]));
}
// check the certpath with PKIX
List certs = new ArrayList();
certs.add(cert);
CertPath certPath = cf.generateCertPath(certs);
CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
PKIXParameters params = new PKIXParameters(cacerts);
params.addCertPathChecker(rc);
//params.setRevocationEnabled(false);
System.out.println("Performing PKIX validation...");
PKIXCertPathValidatorResult cpvResult =
(PKIXCertPathValidatorResult) cpv.validate(certPath, params);
System.out.println("Result: OK");
}
I have commented out the old properties, just to compare. Now the PKIXRevocationChecker is obtained from the PKIX CertPathBuilder implementation and it is configured with the OCSP URL, the trusted certificate (now a complete X509Certificate is passed, I decided to use the alias as the fifth argument of the program to locate the trusted certificate in the cacerts file) and some other general options you can tune (in this case I disabled CRLs, only OCSP is used, with the option no fallback). And it works, with no general configuration I can check the revocation of a certificate just using local/programmatic settings (only setup for this instance of the path validator).
java -cp . CertificateChecker client1.pem cacerts.demo changeit http://localhost:3456 "ocsp-trusted"
Loading certificate...
Loading cacerts...
Performing PKIX validation...
Result: OK
This overlooked feature means that you can establish an OCSP validation for a SSLContext independently of any general configuration. So a special revocation configuration can be used when connecting to a server or when accepting a client certificate (and particularly for this socket). For example this is a method to create a client socket with OCSP validation of the connecting server certificate.
public static SSLSocket createSocketWithPKIXValidator(String hostname, int port) throws Exception {
// create a PKIXRevocationChecker with the options we want: URL, Options, certificate,...
// think about what you want to be configurable
CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX");
PKIXRevocationChecker rc = (PKIXRevocationChecker)cpb.getRevocationChecker();
rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.NO_FALLBACK));
rc.setOcspResponder(new URI("http://localhost:3456"));
// now create the trustore and so on to create the PKIX parameters
KeyStore ts = KeyStore.getInstance("JKS");
FileInputStream tfis = new FileInputStream("cacerts");
ts.load(tfis, "changeit".toCharArray());
// create the PKIX paramaters and add the PKIXRevocationChecker
PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(ts, new X509CertSelector());
pkixParams.addCertPathChecker(rc);
// now you can create the trust managers and so on
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(new CertPathTrustManagerParameters(pkixParams));
kmf.init(ts, "changeit".toCharArray());
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
// get the socket factory and create the connection => SSLPoke example
SSLSocketFactory sslsocketfactory = (SSLSocketFactory) ctx.getSocketFactory();
return (SSLSocket) sslsocketfactory.createSocket(hostname, port);
}
In this case the PKIXRevocationChecker is added to PKIXBuilderParameters, which are used to initialize a TrustManager, that finally is added to the SSLContext. With this context the socket is created, and that socket will use OCSP to validate the server certificate. I tested the connection in a slightly modified OcspSSLPoke.java which connects two times to a server (first one using this specific socket with OCSP configured and the second one with default java settings).
java OcspSSLPoke www.google.es 443
Successfully connected
======================================================================
Successfully connected
======================================================================
Remember that the certpath validation can be debugged using the property -Djava.security.debug=certpath. This will dump detailed log of the certpath validation (this way you can ensure that the OCSP responder is really called). In the previous program the first connection will show a call to the google OCSP responder (in my test the ocsp used was http://clients1.google.com/ocsp), and in the second one no ocsp connection will be executed (by default java does not execute any revocation check).
So the summary of this entry is that now the revocation check can be configured using the PKIXRevocationChecker class in a programmatic way, besides this configuration is specific and not general to the whole JVM. This way a specific checker can be associated (ocsp and crl configuration options) to a SSLContext or a CertPathValidator. This is a great surprise to me and it is incredible that I overlooked it for such a long time (and I indeed was searching this information actively). At least now it is explained here in the blog. Hope it helps to someone else.
Best regards.
Comments