JDK-8059009 : LDAPCertStore fails to retrieve CRL after LDAP server closes idle connection
  • Type: Bug
  • Component: security-libs
  • Sub-Component: java.security
  • Affected Version: 7,8,9
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2014-09-23
  • Updated: 2020-11-30
  • Resolved: 2015-01-15
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 9
9 b47Fixed
Related Reports
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :


A DESCRIPTION OF THE PROBLEM :
sun.security.provider.certpath.PKIXCertPathValidator fails certificate chain validation on CRL check using sun.security.provider.certpath.ldap.LDAPCertStore if LDAP server closes idle connection. 

Please see: 
http://stackoverflow.com/questions/25991400/how-to-amend-ldap-connection-properties-in-ldapcertstore-for-x509-cert-chain-val
https://stackoverflow.com/questions/8787577/how-to-reconnect-when-the-ldap-server-is-restarted

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Initialize PKIXCertPathValidator with LDAPCertStore
2. Wait server to close connection (sleep required time)
3. Try to validate valid certificate chain

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
no exception
ACTUAL -
exception

ERROR MESSAGES/STACK TRACES THAT OCCUR :
java.security.cert.CertPathValidatorException: java.security.cert.CertStoreException: javax.naming.CommunicationException: connection closed [Root exception is java.io.IOException: connection closed]; remaining name ...

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
@Test
public void testRevocationListValidation() throws Exception {
    String trustStoreFile = "trustStoreFilePath";
    KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    InputStream is = getClass().getResourceAsStream(trustStoreFile);
    if (is == null) {
        throw new FileNotFoundException(String.format("KeyStore file '%s' is not found on classpath", trustStoreFile));
    }
    trustStore.load(is, "password".toCharArray());
    Set<TrustAnchor> trustedAnchors = new HashSet<TrustAnchor>();
    for (String caCertificateAlias : new String[]{"ca"}) {
        X509Certificate certificate = (X509Certificate) trustStore.getCertificate(caCertificateAlias);
        trustedAnchors.add(new TrustAnchor(certificate, null));
    }
    PKIXParameters parameters = new PKIXParameters(trustedAnchors);
    CertStore certStore = CertStore.getInstance("LDAP", new LDAPCertStoreParameters("ldapHost", 389));
    parameters.setCertStores(Collections.singletonList(certStore));

    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(getClass().getResourceAsStream("keystore.jks"), "password".toCharArray());

    String keyStoreAlias = "dev-test";
    Certificate[] userCertificateChain = keyStore.getCertificateChain(keyStoreAlias);

    for (int i = 0; i < 3; i++) {
        System.out.println("Starting validation " + i);
        CertPath userCertificatePath = CertificateFactory.getInstance("X.509").generateCertPath(Arrays.asList(userCertificateChain));
        CertPathValidator.getInstance("PKIX").validate(userCertificatePath, parameters);
        System.out.println("Validation " + i + " succeeded");
        if (i == 1) {
            System.out.println("Sleeping after second validation");
            TimeUnit.SECONDS.sleep(90); // Server connection timeout ~ 60 sec
        }
    }

}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
1. Init fresh LDAPCertStore for each validation (or catch exception and search for connection closed message and than reinit)
2. Make dummy LDAP requests with period less then idle connection timeout (but it won't help if server restarts) 


Comments
Review: http://mail.openjdk.java.net/pipermail/security-dev/2014-December/011459.html
16-01-2015

This is not a regression in JDK 7u. The problem is reproducible on JDK 7 b147 fcs. This affects JDK 8 and 9 as well.
24-09-2014

I think it is not only about the situation when LDAP server closes connection by timeout. CertpathValidator can't retrieve any information from LDAPCertStore if connection closed for some reason. I reproduced the problem with the following code: import java.io.FileInputStream; import java.io.IOException; import java.net.URI; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.security.cert.*; import java.util.*; import java.security.cert.PKIXRevocationChecker.Option; import java.util.concurrent.TimeUnit; public class LDAPTest { private List<X509Certificate> certs = new ArrayList<>(); private X509Certificate trustedCert; private String ldapHost; private int ldapPort; /** * @param args the command line arguments */ public static void main(String[] args) throws Exception { LDAPTest test = new LDAPTest(); test.runTest(args); } private void runTest(String[] args) throws Exception { parseArgs(args); startApplication(); } private void parseArgs(String[] args) throws Exception { if (args.length < 4) { throw new IllegalArgumentException("Wrong parameters"); } certs.add(getCertFromFile(args[0])); trustedCert = getCertFromFile(args[1]); ldapHost = args[2]; ldapPort = Integer.parseInt(args[3]); if (certs.isEmpty()) { throw new IllegalArgumentException("Certificate is not set"); } if (trustedCert == null) { throw new IllegalArgumentException("Trusted certificate is not set"); } } private void startApplication() throws CertificateException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, CertPathValidatorException, InterruptedException { CertificateFactory cf = CertificateFactory.getInstance("X509"); CertPath cp = (CertPath) cf.generateCertPath(certs); Set<TrustAnchor> trustedCertsSet = new HashSet<>(); trustedCertsSet.add(new TrustAnchor(trustedCert, null)); CertPathValidator cpv = CertPathValidator.getInstance("PKIX"); PKIXRevocationChecker revocationChecker = (PKIXRevocationChecker) cpv. getRevocationChecker(); revocationChecker.setOptions(EnumSet.of(Option.PREFER_CRLS)); PKIXParameters params = new PKIXParameters(trustedCertsSet); params.addCertPathChecker(revocationChecker); CertStore certStore = CertStore.getInstance("LDAP", new LDAPCertStoreParameters(ldapHost, ldapPort)); params.addCertStore(certStore); for (int i=0; i<100; i++) { try { cpv.validate(cp, params); System.out.println("Validate certpath (" + i + "): success"); } catch(Exception e) { System.out.println("Unexpected exception:"); e.printStackTrace(); } TimeUnit.SECONDS.sleep(10); } } /** * Load a X509 certificate from file. */ protected static X509Certificate getCertFromFile(String path) throws IOException, CertificateException { FileInputStream fis = new FileInputStream(path); CertificateFactory cf = CertificateFactory.getInstance("X509"); return (X509Certificate) cf.generateCertificate(fis); } } 1. Run this code with command like the following; java ${JAVA_OPTS} LDAPTest ee.crt root.ctr ldap.server.host 389 2. Then force socket that is used for LDAP connection to close. What I did on Linux: 2.1. Get PID for java process: netstat -np | grep java | grep ESTABLISHED tcp6 0 0 10.162.83.43:41175 10.128.49.76:1389 ESTABLISHED 27613/java 2.2. Get a file descriptor of the socket lsof -np 27613 ... java 27613 artem 15u IPv6 1495805 0t0 TCP 10.162.83.43:41175->10.128.49.76:1389 (ESTABLISHED) The file descriptor is 15u 2.3. Close the file descriptor by gdb: sudo gdb -p 27613 Run in gdb terminal: call close (15) quit Then, the java application starts to throw the following exceptions: java.security.cert.CertPathValidatorException: Unable to determine revocation status due to network error at sun.security.provider.certpath.PKIXMasterCertPathValidator.validate(PKIXMasterCertPathValidator.java:129) at sun.security.provider.certpath.PKIXCertPathValidator.validate(PKIXCertPathValidator.java:212) at sun.security.provider.certpath.PKIXCertPathValidator.validate(PKIXCertPathValidator.java:140) at sun.security.provider.certpath.PKIXCertPathValidator.engineValidate(PKIXCertPathValidator.java:79) at java.security.cert.CertPathValidator.validate(CertPathValidator.java:292) at LDAPTest.startApplication(LDAPTest.java:77) at LDAPTest.runTest(LDAPTest.java:33) at LDAPTest.main(LDAPTest.java:28) Caused by: java.security.cert.CertStoreException: javax.naming.CommunicationException: Bad file descriptor [Root exception is java.net.SocketException: Bad file descriptor]; remaining name 'CN=yassir,OU=bcn,OU=testing,O=sun,C=us' at sun.security.provider.certpath.ldap.LDAPCertStore.getCRLs(LDAPCertStore.java:721) at sun.security.provider.certpath.ldap.LDAPCertStore.engineGetCRLs(LDAPCertStore.java:848) at java.security.cert.CertStore.getCRLs(CertStore.java:181) at sun.security.provider.certpath.RevocationChecker.checkCRLs(RevocationChecker.java:498) at sun.security.provider.certpath.RevocationChecker.checkCRLs(RevocationChecker.java:459) at sun.security.provider.certpath.RevocationChecker.check(RevocationChecker.java:361) at sun.security.provider.certpath.RevocationChecker.check(RevocationChecker.java:337) at sun.security.provider.certpath.PKIXMasterCertPathValidator.validate(PKIXMasterCertPathValidator.java:119) ... 7 more Caused by: javax.naming.CommunicationException: Bad file descriptor [Root exception is java.net.SocketException: Bad file descriptor]; remaining name 'CN=yassir,OU=bcn,OU=testing,O=sun,C=us' at com.sun.jndi.ldap.LdapCtx.doSearch(LdapCtx.java:2002) at com.sun.jndi.ldap.LdapCtx.doSearchOnce(LdapCtx.java:1933) at com.sun.jndi.ldap.LdapCtx.c_getAttributes(LdapCtx.java:1325) at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_getAttributes(ComponentDirContext.java:235) at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.getAttributes(PartialCompositeDirContext.java:141) at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.getAttributes(PartialCompositeDirContext.java:129) at javax.naming.directory.InitialDirContext.getAttributes(InitialDirContext.java:142) at sun.security.provider.certpath.ldap.LDAPCertStore$LDAPRequest.getValueMap(LDAPCertStore.java:373) at sun.security.provider.certpath.ldap.LDAPCertStore$LDAPRequest.getValues(LDAPCertStore.java:339) at sun.security.provider.certpath.ldap.LDAPCertStore.getCRLs(LDAPCertStore.java:719) ... 14 more Caused by: java.net.SocketException: Bad file descriptor at java.net.SocketOutputStream.socketWrite0(Native Method) at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:109) at java.net.SocketOutputStream.write(SocketOutputStream.java:153) at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82) at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140) at com.sun.jndi.ldap.Connection.writeRequest(Connection.java:426) at com.sun.jndi.ldap.LdapClient.search(LdapClient.java:557) at com.sun.jndi.ldap.LdapCtx.doSearch(LdapCtx.java:1985) at com.sun.jndi.ldap.LdapCtx.doSearchOnce(LdapCtx.java:1933) at com.sun.jndi.ldap.LdapCtx.c_getAttributes(LdapCtx.java:1325) at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_getAttributes(ComponentDirContext.java:235) at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.getAttributes(PartialCompositeDirContext.java:141) at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.getAttributes(PartialCompositeDirContext.java:129) at javax.naming.directory.InitialDirContext.getAttributes(InitialDirContext.java:142) at sun.security.provider.certpath.ldap.LDAPCertStore$LDAPRequest.getValueMap(LDAPCertStore.java:373) at sun.security.provider.certpath.ldap.LDAPCertStore$LDAPRequest.getValues(LDAPCertStore.java:339) at sun.security.provider.certpath.ldap.LDAPCertStore.getCRLs(LDAPCertStore.java:719) at sun.security.provider.certpath.ldap.LDAPCertStore.engineGetCRLs(LDAPCertStore.java:826) ... 13 more LDAP server is alive, and actually the information can still be retrieved from the server. But CertpathValidation don't try to restore the connection.
24-09-2014

The most likely cause for this is because the implementation maintains a cache of CertStore providers per URI. It is probably fetching the LDAPCertStore from the cache but not restoring the connection. Should not be too hard to fix.
24-09-2014