118

I am using Java 6 and am trying to create an HttpsURLConnection against a remote server, using a client certificate.
The server is using an selfsigned root certificate, and requires that a password-protected client certificate is presented. I've added the server root certificate and the client certificate to a default java keystore which I found in /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts (OSX 10.5). The name of the keystore file seems to suggest that the client certificate is not supposed to go in there?

Anyway, adding the root certificate to this store solved the infamous javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.

However, I'm now stuck on how to use the client certificate. I've tried two approaches and neither gets me anywhere.
First, and preferred, try:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
URL url = new URL("https://somehost.dk:3049");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslsocketfactory);
InputStream inputstream = conn.getInputStream();
// The last line fails, and gives:
// javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

I've tried skipping the HttpsURLConnection class (not ideal since I want to talk HTTP with the server), and do this instead:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("somehost.dk", 3049);
InputStream inputstream = sslsocket.getInputStream();
// do anything with the inputstream results in:
// java.net.SocketTimeoutException: Read timed out

I am not even sure that the client certificate is the problem here.

javanna
  • 53,926
  • 12
  • 135
  • 121
Jan
  • 3,906
  • 6
  • 20
  • 21
  • i have given two certificate from client how to identify which one needs to add in keystore and truststore could you please help identify this issue as you have already gone through similar kind of issue https://stackoverflow.com/questions/61374276/access-https-restful-service-using-web-client-in-spring-boot-2-0-throwing-except – henrycharles Apr 23 '20 at 07:07
  • Does this answer your question? [Resolving javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed Error?](https://stackoverflow.com/questions/9619030/resolving-javax-net-ssl-sslhandshakeexception-sun-security-validator-validatore) – rogerdpack Feb 22 '21 at 16:27

8 Answers8

103

Finally solved it ;). Got a strong hint here (Gandalfs answer touched a bit on it as well). The missing links was (mostly) the first of the parameters below, and to some extent that I overlooked the difference between keystores and truststores.

The self-signed server certificate must be imported into a truststore:

keytool -import -alias gridserver -file gridserver.crt -storepass $PASS -keystore gridserver.keystore

These properties need to be set (either on the commandline, or in code):

-Djavax.net.ssl.keyStoreType=pkcs12
-Djavax.net.ssl.trustStoreType=jks
-Djavax.net.ssl.keyStore=clientcertificate.p12
-Djavax.net.ssl.trustStore=gridserver.keystore
-Djavax.net.debug=ssl # very verbose debug
-Djavax.net.ssl.keyStorePassword=$PASS
-Djavax.net.ssl.trustStorePassword=$PASS

Working example code:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
URL url = new URL("https://gridserver:3049/cgi-bin/ls.py");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslsocketfactory);
InputStream inputstream = conn.getInputStream();
InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
BufferedReader bufferedreader = new BufferedReader(inputstreamreader);

String string = null;
while ((string = bufferedreader.readLine()) != null) {
    System.out.println("Received " + string);
}
javanna
  • 53,926
  • 12
  • 135
  • 121
Jan
  • 3,906
  • 6
  • 20
  • 21
  • I have use a url like: https://localhost:8443/Application_Name/getAttributes. I have a method with /getAttribute url mapping. This method returns a list of elements. I have used HttpsUrlConnection , connection reponse code is 200, but its not giving me the attribute list when i use inputStream, it gives me html content of my login page. I have done Authentication and set the content type as JSON. Please suggest – Deepak May 11 '17 at 17:40
84

While not recommended, you can also disable SSL cert validation alltogether:

import javax.net.ssl.*;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

public class SSLTool {

  public static void disableCertificateValidation() {
    // Create a trust manager that does not validate certificate chains
    TrustManager[] trustAllCerts = new TrustManager[] { 
      new X509TrustManager() {
        public X509Certificate[] getAcceptedIssuers() { 
          return new X509Certificate[0]; 
        }
        public void checkClientTrusted(X509Certificate[] certs, String authType) {}
        public void checkServerTrusted(X509Certificate[] certs, String authType) {}
    }};

    // Ignore differences between given hostname and certificate hostname
    HostnameVerifier hv = new HostnameVerifier() {
      public boolean verify(String hostname, SSLSession session) { return true; }
    };

    // Install the all-trusting trust manager
    try {
      SSLContext sc = SSLContext.getInstance("SSL");
      sc.init(null, trustAllCerts, new SecureRandom());
      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
      HttpsURLConnection.setDefaultHostnameVerifier(hv);
    } catch (Exception e) {}
  }
}
neu242
  • 14,861
  • 13
  • 69
  • 107
  • You can do this same thing in an even simpler way if you're using the Axis framework. See my answer below. – Mark Meuer Mar 13 '12 at 19:14
  • 74
    It should be noted that disabling certificate validation like this opens the connection to possible MITM attacks: **do not use in production**. – Bruno Apr 30 '12 at 09:13
  • 3
    Code doesn't compile, thankfully. This 'solution' is radically insecure. – user207421 Apr 30 '12 at 10:16
  • Thanks @EJP, a "return null;" managed to disappear in a previous edit. As to "do not use in production", that really depends on what you use it for. – neu242 Apr 30 '12 at 11:28
  • 6
    @neu242, no, it doesn't really depend on what you use it for. If you want to use SSL/TLS, you want to secure your connection against MITM attacks, that's the whole point. Server authentication is not necessary if you can guarantee that no one will be able to alter the traffic, but situations where you suspect there may be eavesdroppers that wouldn't also be in a position to alter the network traffic too are quite rare. – Bruno Apr 30 '12 at 11:32
  • @neu242 `return null;` is also wrong. See the contract for `X509TrustManager`. As for the rest, Bruno is correct: this stuff should not be deployed in production. SSL & TLS are not secure unless at least one peer is authenticated. See the discussion in RFC 2246. – user207421 Apr 30 '12 at 11:40
  • 1
    @neu242 Thanks for the code snippet. I'm actually thinking of using this in production for a very specific purpose (web crawling), and I mentioned your implementation in a question.(http://stackoverflow.com/questions/13076511/security-risks-in-disabling-ssl-certification-for-java-program). If you have time, could you please look at it, and let me know if there are any security risks I missed? – Sal Oct 25 '12 at 20:25
  • 1
    Wasn't the question about problems with using client certificate? That snippet "solves" problems with server certificates. How it can be the highest voted question? – Piotr Sobczyk Dec 01 '14 at 15:49
  • *Don't do this!* The big risk here is that once it "works" and this security hole is in your code base, it's *super* easy to forget to fix this. There will be even less time to get it working properly when you're close to release and the deadline is approaching. Then you end up in a position where you've drilled the lock out of your front door, because you had trouble locking the door. – MW. Feb 18 '15 at 09:55
  • 1
    @Bruno If i am only pinging the server, would that really affect me at all, by MITM attacks? – PhoonOne Apr 03 '15 at 12:58
  • @JennyC It depends what you mean by "pinging". This trust manager simply offers you no more guarantees than plain HTTP. This SSLContext is also installed on all `HttpsURLConnection`s. – Bruno Apr 03 '15 at 13:27
  • @Bruno I asked a similar question to this, this is what i am doing by "pinging". http://stackoverflow.com/questions/29421537/connect-to-a-https-secure-website-and-ping-the-latency?noredirect=1#comment47015158_29421537 – PhoonOne Apr 03 '15 at 13:44
  • it doesn't work for some sites, ex. https://www.vsopen.ru/ : https://gist.github.com/yetanothercoder/331989cf5aee2b833ee8 – yetanothercoder Oct 27 '15 at 17:06
  • Does not work for sites using CloudFlare Flexible SSL service, such as https://kitematic.com/ (`Caused by: javax.net.ssl.SSLException: Received fatal alert: internal_error`) – user1480019 Dec 19 '15 at 14:53
  • How to use this code while opening connection. I'm trying to hit https url from main method. when url.openConnection(); it is displaying the error. Sorry if its noob. Just want to learn. – Krishna May 05 '17 at 13:05
  • Question is about client certificate... this cannot be skipped... – Diego Ramos Oct 09 '20 at 20:28
21

Have you set the KeyStore and/or TrustStore System properties?

java -Djavax.net.ssl.keyStore=pathToKeystore -Djavax.net.ssl.keyStorePassword=123456

or from with the code

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);

Same with javax.net.ssl.trustStore

Gandalf
  • 8,800
  • 7
  • 44
  • 81
13

If you are dealing with a web service call using the Axis framework, there is a much simpler answer. If all want is for your client to be able to call the SSL web service and ignore SSL certificate errors, just put this statement before you invoke any web services:

System.setProperty("axis.socketSecureFactory", "org.apache.axis.components.net.SunFakeTrustSocketFactory");

The usual disclaimers about this being a Very Bad Thing to do in a production environment apply.

I found this at the Axis wiki.

Mark Meuer
  • 6,462
  • 6
  • 37
  • 60
  • The OP is dealing with a HttpsURLConnection, not Axis – neu242 Mar 14 '12 at 07:24
  • 2
    I understand. I did not intend to imply that my answer was better in the general case. It is just that if you *are* using the Axis framework, you may have the OP's question in that context. (That's how I found this question in the first place.) In that case, the way I have provided is simpler. – Mark Meuer Mar 19 '12 at 22:19
6

For me, this is what worked using Apache HttpComponents ~ HttpClient 4.x:

    KeyStore keyStore  = KeyStore.getInstance("PKCS12");
    FileInputStream instream = new FileInputStream(new File("client-p12-keystore.p12"));
    try {
        keyStore.load(instream, "helloworld".toCharArray());
    } finally {
        instream.close();
    }

    // Trust own CA and all self-signed certs
    SSLContext sslcontext = SSLContexts.custom()
        .loadKeyMaterial(keyStore, "helloworld".toCharArray())
        //.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) //custom trust store
        .build();
    // Allow TLSv1 protocol only
    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        sslcontext,
        new String[] { "TLSv1" },
        null,
        SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //TODO
    CloseableHttpClient httpclient = HttpClients.custom()
        .setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) //TODO
        .setSSLSocketFactory(sslsf)
        .build();
    try {

        HttpGet httpget = new HttpGet("https://localhost:8443/secure/index");

        System.out.println("executing request" + httpget.getRequestLine());

        CloseableHttpResponse response = httpclient.execute(httpget);
        try {
            HttpEntity entity = response.getEntity();

            System.out.println("----------------------------------------");
            System.out.println(response.getStatusLine());
            if (entity != null) {
                System.out.println("Response content length: " + entity.getContentLength());
            }
            EntityUtils.consume(entity);
        } finally {
            response.close();
        }
    } finally {
        httpclient.close();
    }

The P12 file contains the client certificate and client private key, created with BouncyCastle:

public static byte[] convertPEMToPKCS12(final String keyFile, final String cerFile,
    final String password)
    throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException,
    NoSuchProviderException
{
    // Get the private key
    FileReader reader = new FileReader(keyFile);

    PEMParser pem = new PEMParser(reader);
    PEMKeyPair pemKeyPair = ((PEMKeyPair)pem.readObject());
    JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter().setProvider("BC");
    KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair);

    PrivateKey key = keyPair.getPrivate();

    pem.close();
    reader.close();

    // Get the certificate
    reader = new FileReader(cerFile);
    pem = new PEMParser(reader);

    X509CertificateHolder certHolder = (X509CertificateHolder) pem.readObject();
    java.security.cert.Certificate x509Certificate =
        new JcaX509CertificateConverter().setProvider("BC")
            .getCertificate(certHolder);

    pem.close();
    reader.close();

    // Put them into a PKCS12 keystore and write it to a byte[]
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
    ks.load(null);
    ks.setKeyEntry("key-alias", (Key) key, password.toCharArray(),
        new java.security.cert.Certificate[]{x509Certificate});
    ks.store(bos, password.toCharArray());
    bos.close();
    return bos.toByteArray();
}
EpicPandaForce
  • 71,034
  • 25
  • 221
  • 371
  • The `keyStore` is what contains the private key and certificate. – EpicPandaForce Nov 06 '14 at 12:29
  • 1
    You need to include these 2 dependencies for the convertPEMtoP12 code to work: org.bouncycastle bcprov-jdk15on 1.53 org.bouncycastle bcpkix-jdk15on 1.53 – BirdOfPrey Dec 11 '15 at 13:49
  • @EpicPandaForce I get an error : Caught: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object with class 'org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject' to class 'int' in line ks.setKeyEntry - any clues what might be wrong – Vishal Biyani Dec 27 '16 at 12:03
  • Yeah, you're using Groovy instead of a strictly typed language. (technically that method takes an ID and a certificate, not just a certificate) – EpicPandaForce Dec 27 '16 at 12:25
4

I use the Apache commons HTTP Client package to do this in my current project and it works fine with SSL and a self-signed cert (after installing it into cacerts like you mentioned). Please take a look at it here:

http://hc.apache.org/httpclient-3.x/tutorial.html

http://hc.apache.org/httpclient-3.x/sslguide.html

Taylor Leese
  • 46,410
  • 27
  • 106
  • 138
  • 1
    This seems like a pretty neat package, but the class that should make it all work 'AuthSSLProtocolSocketFactory' is apparantly not part of the official distribution, neither in 4.0beta (despite the release notes stating that it is), or in 3.1. I've hacked around with it a bit and now seem to be permanently stuck with a 5 minute hang before it just drops the connection. It's really odd - if I load the CA and the client cert into any browser, it just flies. – Jan May 19 '09 at 11:58
  • 1
    Apache HTTP Client 4 can take an `SSLContext` directly, so you can configure all this this way, instead of using `AuthSSLProtocolSocketFactory`. – Bruno Sep 14 '10 at 22:00
  • 1
    Is there a way to do all the client certificate stuff in-memory instead of through an external keystore? – Sridhar Sarnobat Jun 19 '18 at 21:17
4

I think you have an issue with your server certificate, is not a valid certificate (I think this is what "handshake_failure" means in this case):

Import your server certificate into your trustcacerts keystore on client's JRE. This is easily done with keytool:

keytool
    -import
    -alias <provide_an_alias>
    -file <certificate_file>
    -keystore <your_path_to_jre>/lib/security/cacerts
sourcerebels
  • 4,982
  • 1
  • 29
  • 51
  • I tried cleaning up and starting over, and the handshake failure went away. Now I just get 5 minutes of dead silence before the connection is terminated :o – Jan May 19 '09 at 12:00
1

Using below code

-Djavax.net.ssl.keyStoreType=pkcs12

or

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);

is not at all required. Also there is no need to create your own custom SSL factory.

I also encountered the same issue, in my case there was a issue that complete certificate chain was not imported into truststores. Import certificates using keytool utility right fom root certificate, also you can open cacerts file in notepad and see if the complete certificate chain is imported or not. Check against the alias name you have provided while importing certificates, open the certificates and see how many does it contains, same number of certificates should be there in cacerts file.

Also cacerts file should be configured in the server you are running your application, the two servers will authenticate each other with public/private keys.

Amr Kamel
  • 402
  • 5
  • 16
Ankur Singhal
  • 23,626
  • 10
  • 70
  • 108
  • 1
    Creating your own custom SSL factory is a lot more complicated and error-prone than setting two system properties. – user207421 May 01 '17 at 00:19