Question

I'm working on a Java program that will send POST requests to a website for my company to use. We do not own this website, they are separate from us. I've been fighting with various ways to actually pass it the very picky parameters it wants in order for me to do work on it from a program (as opposed to doing it manually).

I've found that the Apache HttpClient 4.3 seems to be my best route for actually trying to access it, anything results in a angry response from the website telling me my username and password and not valid/authorized.

But then I got an error because the site certificate doesn't match, I contacted their support and they reportedly share an infrastructure with another site so the certificate mismatch is expected.

So I went commandline and generated a keystore, passed that to the program and then got the error "java.security.cert.CertificateException: No subject alternative DNS name matching".

Some hunting lead me to utilize a verifier, which removed errors.

Then I realized that I can't make URLConnection/HttpsURLConnection and HttpClient/HttpPost work together. That's where I'm stuck. I'm not sure how to make the code that handles my keystore, TrustManager, SSLSocketFactory, etc connect to the part where I actually have to connect and POST.

Code that handles the certificates and verification:

  InputStream in = new FileInputStream(new File("C:\\Program Files (x86)\\Java\\jre7\\bin\\my.keystore")); 
   KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); 
    ks.load(in, "blahblah".toCharArray()); 
in.close(); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 
    tmf.init(ks);
     X509TrustManager defaultTrustManager = (X509TrustManager)tmf.getTrustManagers()[0]; 
     SSLContext context = SSLContext.getInstance("TLS"); 
     context.init(null, new TrustManager[] {defaultTrustManager}, null); 
     javax.net.ssl.SSLSocketFactory sslSocketFactory = context.getSocketFactory();

URL url = new URL("https://emailer.driveclick.com/dbadmin/xml_post.pl"); 

     URLConnection con = url.openConnection(); 
    ((HttpsURLConnection) con).setSSLSocketFactory(sslSocketFactory); 
   ((HttpsURLConnection) con).setHostnameVerifier(new Verifier());
     con.connect(); 
    in = con.getInputStream();

Code that should be connecting me to the website:

 try {
        //log into the website

        String url2 = "https://emailer.driveclick.com/dbadmin/xml_post.pl";
        HttpClient client = HttpClientBuilder.create().build();



        HttpPost post = new HttpPost(url2);


        post.setHeader("User-Agent", USER_AGENT);

        List<BasicNameValuePair> urlParameters = new ArrayList<>();
        urlParameters.add(new BasicNameValuePair("username", "namefoo"));
        urlParameters.add(new BasicNameValuePair("api_password", "passfoo"));

        post.setEntity(new UrlEncodedFormEntity(urlParameters));

        org.apache.http.HttpResponse response = client.execute(post);

        System.out.println("\nSending 'POST' request to URL : " + url2);
        System.out.println("Post parameters : " + post.getEntity());
        System.out.println("Response Code : " + response.getStatusLine().getStatusCode());

        BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
        StringBuffer result = new StringBuffer();
        String line = "";
        while ((line = rd.readLine()) != null)
        {
            result.append(line);
        }
        System.out.println(result.toString());
    } catch (UnsupportedEncodingException ex) {
        Logger.getLogger(LastFileMove.class.getName()).log(Level.SEVERE, null, ex);
    } catch (IOException ex) {
        Logger.getLogger(LastFileMove.class.getName()).log(Level.SEVERE, null, ex);
    }

EDIT: I forgot to include the little class I made for the Verifier that I referenced.

public class Verifier implements HostnameVerifier 
{

public boolean verify(String arg0, SSLSession arg1) {
        return true;   // mark everything as verified
}
}

Update 5/8/2014 SSLConext and Verifier are now set up like this:

SSLContext sslContext = SSLContexts.custom()
    .useTLS()
    .loadTrustMaterial(ks)
    .build(); 
X509HostnameVerifier verifier = new AbstractVerifier() 
{
@Override
public void verify(final String host, final String[] 
cns, final String[] subjectAlts) throws SSLException 
{
    verify(host, cns, subjectAlts, true);
}
};

And I've gone ahead and changed my HttpClient to a closeable one here:

CloseableHttpClient client = HttpClients.custom()
sslSocketFactory)
.setHostnameVerifier(verifier)
.setSslcontext(sslContext)
.build();

And I'm back to having "javax.net.ssl.SSLException: hostname in certificate didn't match" errors. Suggestions?

Was it helpful?

Solution

I have no idea how Verifier is implemented but this code snippet demonstrates how one can create a custom hostname verifier none of those shipped with HttpClient fits their needs

KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream in = new FileInputStream(new File("C:\\Program Files (x86)\\Java\\jre7\\bin\\my.keystore"));
try {
    ks.load(in, "blahblah".toCharArray());
} finally {
    in.close();
}
SSLContext sslContext = SSLContexts.custom()
        .useTLS()
        .loadTrustMaterial(ks)
        .build();
X509HostnameVerifier verifier = new AbstractVerifier() {

    @Override
    public void verify(final String host, final String[] cns, final String[] subjectAlts) throws SSLException {
        verify(host, cns, subjectAlts, true);
    }

};

CloseableHttpClient hc = HttpClients.custom()
        .setSslcontext(sslContext)
        .setHostnameVerifier(verifier)
    .build();
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top