PKIX path building failed while making SSL connection
Asked Answered
C

2

17

I'm integrating with a Merchant Account called CommWeb and I'm sending an SSL post to their URL (https://migs.mastercard.com.au/vpcdps). When I try to send the post, I get the following exception:

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

The code (which I didn't write, and that already exists in our codebase) that performs the post is:

public static HttpResponse sendHttpPostSSL(String url, Map<String, String> params) throws IOException {
    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 documentation for the Merchant Account integration says nothing about certificates. They did provide some sample JSP code that seems to blindly accept certificates:

<%! // Define Static Constants
    // ***********************
public static X509TrustManager s_x509TrustManager = null;
public static SSLSocketFactory s_sslSocketFactory = null;

static {
        s_x509TrustManager = new X509TrustManager() {
        public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[] {}; } 
        public boolean isClientTrusted(X509Certificate[] chain) { return true; } 
        public boolean isServerTrusted(X509Certificate[] chain) { return true; } 
    };

    java.security.Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
    try {
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, new X509TrustManager[] { s_x509TrustManager }, null);
        s_sslSocketFactory = context.getSocketFactory();
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e.getMessage());
    }
}

...
...
           // write output to VPC
            SSLSocket ssl = (SSLSocket)s_sslSocketFactory.createSocket(s, vpc_Host, vpc_Port, true);
            ssl.startHandshake();
            os = ssl.getOutputStream();
            // get response data from VPC
            is = ssl.getInputStream();
...
...
%>

Our webapp has a keystore, and I tried adding the certificate (which I exported from firefox) using the keytool command, but that didn't work and I got the same error. I've tried solutions on the web (importing the key and using System.setProperty) but that seems kind of clunky and it didn't work (gave me a NoSuchAlgorithmError). Any help is appreciated!

Clambake answered 18/2, 2010 at 17:7 Comment(1)
#21076679Protactinium
L
14

Evidently the valicert class 3 CA certificate is not in your default truststore (which is probably the cacerts file in your JRE lib/security directory, but see the JSSE documentation for the full story).

You could add this certificate to the cacerts file, but I don't recommend this. Instead, I think you should create your own truststore file (which can be a copy of the cacerts file) and add the valicert root ca to this. Then point to this file with the javax.net.ssl.trustStore system property.

Laurin answered 19/2, 2010 at 1:23 Comment(3)
I will try this out tomorrow. For now, I got it to work by creating a new socket factory that implements SecureProtocolSocketFactory from commons.httpclient. It blindly accepts the certificate. However, I want to change this and make it work the right way. I'll let you know what happens. Thanks!Clambake
I'm just going to go ahead and accept your solution and add mine as a comment. I was able to figure it out only after looking at the documentation you pointed me to!Clambake
Greg, can you explain to me "add the valicert root ca to this". What does it mean, and how it should be done?Shorter
C
7

I figure I should update this answer with what I actually did. Using the documentation that GregS provided, I created a trust manager for valicert. In the trust manager, I load the 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();
    }
}

Now I just register 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.

Clambake answered 17/1, 2011 at 18:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.