views:

1160

answers:

1

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!

+1  A: 

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.

GregS
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!
Vivin Paliath
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!
Vivin Paliath