1
votes

I have

  1. A self-signed server certificate (from a third-party organization I need to communicate with)
  2. My client certificate, containing the secret key, signed by this server certificate.

Now I need to send a POST request via HTTPS using these certificates. I managed to test the connection over https in Internet Explorer after I installed them in browser:

  1. server cert - into the trusted CA
  2. client cert - into the personal certs.

In java until now I used the code, given in SO: Java client certificates over HTTPS/SSL in the answer by neu242, i.e. accepted any certificate. But now the server side does accept this, i.e. I get SSL-handshake failure.

Thanks to SO: X509TrustManager Override without allowing ALL certs? I tried to return the server certificate in getAcceptedIssuers, but in vain. It throws

javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

right after getAcceptedIssuers returns.

public X509Certificate[] getAcceptedIssuers() {
    try {
        X509Certificate scert;
        try (InputStream inStream = new FileInputStream("..\\server.crt")) {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            scert = (X509Certificate) cf.generateCertificate(inStream);
        }                        
        return new X509Certificate[]{scert};
    } catch (Exception ex) {
        writeLogFile(ex.getMessage());
        return new X509Certificate[]{};
    }
}

I guess I should specify the client certificate somehow, but cannot find any way to. I may be wrong of course.

Hope someone can lead me the right direction.

1
Is the handshake failing at client end or at server end? Can you run both your server and client with additional jvm argument -Djavax.net.debug=all and share the console output?thiyaga

1 Answers

0
votes

In the end I managed to make it work.

As the server certificate is self-signed, I had to place it into a truststore. To avoid adding it to common JRE cacerts, I placed it into a truststore in a separate file with the following command (thanks to Common SSL issues in Java:

keytool -import -v -trustcacerts 
        -file servercert.crt -keystore server.jks 
        -keypass mypwd -storepass mypwd

Then I used the obtained truststore and the client certificate containing the secret key to initialize key stores and specify them to the SSL context (thanks to sql.ru: Received fatal alert: handshake_failure):

String pwd = "mypwd";
InputStream keyStoreUrl = new FileInputStream("client.p12");
InputStream trustStoreUrl = new FileInputStream("server.jks");

KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(keyStoreUrl, pwd.toCharArray());
KeyManagerFactory keyManagerFactory = 
    KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, pwd.toCharArray());

KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(trustStoreUrl, pwd.toCharArray());
TrustManagerFactory trustManagerFactory = 
    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);

final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(keyManagerFactory.getKeyManagers(), 
                trustManagerFactory.getTrustManagers(), 
                new SecureRandom());
SSLContext.setDefault(sslContext);

Also I had to specify HostnameVerifier, as there were some inconsistency with the server certificate and this server's url:

HostnameVerifier allHostsValid = new HostnameVerifier() {
    public boolean verify(String hostname, SSLSession session) {
        //...
    }
};

HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);

And that is it. Further on it was as simple as:

url = new URL(targetURL);

connection = (HttpsURLConnection) url.openConnection();            
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", 
                              "application/x-www-form-urlencoded;charset=utf-8");
connection.setRequestProperty("Accept", "application/x-www-form-urlencoded");
connection.setRequestProperty("Accept-Charset", "UTF-8");
connection.setRequestProperty("Content-Length", 
                              Integer.toString(Data.getBytes("utf8").length));
connection.setDoInput(true);
connection.setDoOutput(true);

connection.getOutputStream().write(Data.getBytes("utf8"));
// read response...