7

Thinking I'd hit the same issue as other folks, I've been going through the numerous similar problems and potential solutions, but with no luck.

The trust store I'm using is cacerts, located in lib/security of a Java 1.6.0 JRE (build 1.6.0_20-b02... could this be the root of the problem?). I've also tried with jssecacerts.

Using InstallCert (per other similar issues posted), I can see my certificate is in fact installed and valid (and I've removed it, re-imported it, etc to make sure I'm seeing the right data):

java InstallCert <my host name>
Loading KeyStore jssecacerts...
Opening connection to <my host name>:443...
Starting SSL handshake...
No errors, certificate is already trusted

Checking in keytool and Portecle, re-importing the cert (I've tried generating from openssl with -showcert, exporting from browsers and scp'ing it over, etc) gives me "That already exists under this other alias over here" type of message. So there doesn't appear to be any issue with the way the cert is getting into the tool(s).

Forcing explicit trustStore paths in the code doesn't make any difference, and in all cases what I end up seeing when I turn on debugging (via a setProperty of javax.net.debug to "all") is:

main, SEND TLSv1 ALERT:  fatal, description = certificate_unknown
main, WRITE: TLSv1 Alert, length = 2 [Raw write]: length = 7 0000: 15
03 01 00 02 02 2E                               ....... main, called
closeSocket() main, handling exception:
javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to
find valid certification path to requested target

Unfortunately I can't allow overriding the check by implementing my own TrustManager - it has to actually check.

The certificate I get from the host has a number of extensions (9, to be exact), which makes me wonder if they're somehow part of this issue.

What else can I check/try? Change over to a different JRE version?

Bill
  • 211
  • 1
  • 2
  • 7
  • Why can't you implement your own `TrustManager`? You can still use the certificates in your trust manager and perform the check. – Vivin Paliath Jun 18 '12 at 23:02
  • @Vivin - The core problem I'm trying to solve is within an application layer I don't directly control (in this case a Grails application calling some web services which have recently become HTTPS-only), so while I could use it in my own local scenario I can't enforce its use upstream. Which is a pain, but I'll deal with it later - for now if I can make it work at the most granular level, it at least tells me where to go next in the chain of resolution. – Bill Jun 19 '12 at 11:26
  • Are you running this under an app server like GlassFish? You might check to see if the process is running the Java binary in /usr/lib/jvm/java-1.6.0-sun-1.6.0.20.x86_64 and not some other Java binary that doesn't like your trustStore (OpenJDK, etc). The debug output you posted is helpful, can you post more? – Brad Oct 02 '12 at 22:44

3 Answers3

0

My guess is either of these things happened:

a) You run your code on a web server. They often use their own trust store - so are you really sure that it's cacerts that's being used when your code is executed?

b) By default, Java will try to check the validity of the certificates by downloading and interpreting CRLs. If you are behind a proxy, the download fails, and as a consequence the whole PKIX check would fail.

emboss
  • 38,880
  • 7
  • 101
  • 108
  • 1
    As he's installing the server certificate directly, (b) doesn't apply. Good answer otherwise. – user207421 Jun 18 '12 at 22:50
  • @EJP Oops, you're right, I mixed it up with importing the root certificate instead. Will fix that. – emboss Jun 18 '12 at 23:07
  • At first I thought A was it, then when I boiled it down to just the tiny sample code with full debugs and it still failed I was bummed. Appreciate the suggestions though. – Bill Jun 19 '12 at 11:13
0

The SSL debug trace will show which cacerts file you are using, as long as you don't manually load it yourself. Clearly you aren't using the one you think you are.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • Good thought to check, but not the case: "trustStore is: /usr/lib/jvm/java-1.6.0-sun-1.6.0.20.x86_64/jre/lib/security/jssecacerts". As I mentioned, I've tried it with both jssecacerts and cacerts, which includes enforcing the path through trustStore settings. – Bill Jun 19 '12 at 11:05
0

You can still check the certificate by implementing your own trust manager. I ran into a similar issue here. I also tried adding the certificate to cacerts but to no avail.

In your trust manager, you need to explicitly load up the certificates. Essentially what I had to do was something like this:

First I create a trust manager that uses the actual certificate files:

public class ValicertX509TrustManager implements X509TrustManager {

    X509TrustManager pkixTrustManager;

    ValicertX509TrustManager() throws Exception {

        String valicertFile = "/certificates/ValicertRSAPublicRootCAv1.cer";
        String commwebDRFile = "/certificates/DR_10570.migs.mastercard.com.au.crt";
        String commwebPRODFile = "/certificates/PROD_10549.migs.mastercard.com.au.new.crt";

        Certificate valicert = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(valicertFile));
        Certificate commwebDR = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(commwebDRFile));
        Certificate commwebPROD = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(commwebPRODFile));

        KeyStore keyStore = KeyStore.getInstance("JKS");
        keyStore.load(null, "".toCharArray());
        keyStore.setCertificateEntry("valicert", valicert);
        keyStore.setCertificateEntry("commwebDR", commwebDR);
        keyStore.setCertificateEntry("commwebPROD", commwebPROD);

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
        trustManagerFactory.init(keyStore);

        TrustManager trustManagers[] = trustManagerFactory.getTrustManagers();

        for(TrustManager trustManager : trustManagers) {
            if(trustManager instanceof X509TrustManager) {
                pkixTrustManager = (X509TrustManager) trustManager;
                return;
            }
        }

        throw new Exception("Couldn't initialize");
    }

    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        pkixTrustManager.checkServerTrusted(chain, authType);
    }

    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        pkixTrustManager.checkServerTrusted(chain, authType);
    }

    public X509Certificate[] getAcceptedIssuers() {
        return pkixTrustManager.getAcceptedIssuers();
    }
}

Now, using this trust manager, I had to create a socket factory:

public class ValicertSSLProtocolSocketFactory implements ProtocolSocketFactory {

    private SSLContext sslContext = null;

    public ValicertSSLProtocolSocketFactory() {
        super();
    }

    private static SSLContext createValicertSSLContext() {
        try {
            ValicertX509TrustManager valicertX509TrustManager = new ValicertX509TrustManager();
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, new ValicertX509TrustManager[] { valicertX509TrustManager}, null);
            return context;
        }

        catch(Exception e) {
            Log.error(Log.Context.Net, e);
            return null;
        }
    }

    private SSLContext getSSLContext() {
        if(this.sslContext == null) {
            this.sslContext = createValicertSSLContext();
        }

        return this.sslContext;
    }

    public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException {
        return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
    }

    public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort, final HttpConnectionParams params) throws IOException {
        if(params == null) {
            throw new IllegalArgumentException("Parameters may not be null");
        }

        int timeout = params.getConnectionTimeout();
        SocketFactory socketFactory = getSSLContext().getSocketFactory();

        if(timeout == 0) {
            return socketFactory.createSocket(host, port, localAddress, localPort);
        }

        else {
            Socket socket = socketFactory.createSocket();
            SocketAddress localAddr = new InetSocketAddress(localAddress, localPort);
            SocketAddress remoteAddr = new InetSocketAddress(host, port);
            socket.bind(localAddr);
            socket.connect(remoteAddr, timeout);
            return socket;
        }
    }

    public Socket createSocket(String host, int port) throws IOException {
        return getSSLContext().getSocketFactory().createSocket(host, port);
    }

    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
        return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    public boolean equals(Object obj) {
        return ((obj != null) && obj.getClass().equals(ValicertSSLProtocolSocketFactory.class));
    }

    public int hashCode() {
        return ValicertSSLProtocolSocketFactory.class.hashCode();
    }
}

Then I just registered a new protocol:

Protocol.registerProtocol("vhttps", new Protocol("vhttps", new ValicertSSLProtocolSocketFactory(), 443));
PostMethod postMethod = new PostMethod(url);
for (Map.Entry<String, String> entry : params.entrySet()) {
    postMethod.addParameter(entry.getKey(), StringUtils.Nz(entry.getValue()));
}

HttpClient client = new HttpClient();
int status = client.executeMethod(postMethod);
if (status == 200) {
    StringBuilder resultBuffer = new StringBuilder();
    resultBuffer.append(postMethod.getResponseBodyAsString());
    return new HttpResponse(resultBuffer.toString(), "");
} else {
    throw new IOException("Invalid response code: " + status);
}

The only disadvantage is that I had to create a specific protocol (vhttps) for this particular certificate.

Community
  • 1
  • 1
Vivin Paliath
  • 94,126
  • 40
  • 223
  • 295
  • Hard to believe all that was really necessary. There's a distinct flavour of not understanding the underlying problem about all this. – user207421 Jun 18 '12 at 23:13
  • @EJP No need to be so patronizing. I'm painfully aware that this solution is not optimal. If you have a better solution, I'm happy to hear it. I tried numerous things to get this to work, including adding the certificate (I even made sure I could trace the trust chain all the way) to the server's cacerts file. Nothing seemed to work. I researched this problem a lot before finally using this solution. It wasn't my first choice. – Vivin Paliath Jun 18 '12 at 23:19
  • @Vivin - Appreciate the suggestion, though I'm really hoping I don't have to go this route. :) Lots of other code that I don't directly control would have to be changed, so it's one of those scenarios where I keep hoping there's a mistake somewhere on my side or the machine config side. – Bill Jun 19 '12 at 11:18
  • @Bill No problem; glad to help! I agree, this problem is intensely frustrating! I hope you're able to find a solution. – Vivin Paliath Jun 19 '12 at 14:37
  • @Vivin I finally found a solution - I don't completely understand it yet, so I'm digging further. What's happening is when I create or update the cacerts file on this specific machine via keytool or Portecle I see the failure, but when I create cacerts on another machine and I replace my cacerts file on this host it works! So wierd. This seems to indicate I've got a lib or version issue on that host, but I'm still confused as to why every other indication from debugs/etc shows up as "Yup, all is well!". Once I have a more conclusive reason I'll update in case it helps other people. – Bill Jun 20 '12 at 14:32
  • @Bill That is pretty odd! When I had this issue I tried numerous things, but nothing seemed to work. We even added the certificates on the production machine, to no avail. – Vivin Paliath Jun 20 '12 at 15:40
  • @EJP Like how you're addressing the issue head-on by providing an alternate solution? Thought so. – Vivin Paliath Jul 06 '12 at 01:48