Java client certificates over HTTPS/SSL
Asked Answered
B

9

124

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.

Bragi answered 17/5, 2009 at 20:51 Comment(2)
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 #61374776Merman
Does this answer your question? Resolving javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed Error?Entice
B
107

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);
}
Bragi answered 19/5, 2009 at 12:34 Comment(1)
I have use a url like: 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 suggestCran
O
85

While not recommended, you can also disable SSL cert validation altogether, using the following code that came from The Java Developers Almanac:

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) {}   } }
Overstuffed answered 18/5, 2009 at 8:58 Comment(16)
You can do this same thing in an even simpler way if you're using the Axis framework. See my answer below.Pheasant
It should be noted that disabling certificate validation like this opens the connection to possible MITM attacks: do not use in production.Ipomoea
Code doesn't compile, thankfully. This 'solution' is radically insecure.Conrad
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.Overstuffed
@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.Ipomoea
@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.Conrad
@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.(#13077011). If you have time, could you please look at it, and let me know if there are any security risks I missed?Pecten
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?Starchy
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.Broads
@Ipomoea If i am only pinging the server, would that really affect me at all, by MITM attacks?Litalitany
@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 HttpsURLConnections.Ipomoea
@Ipomoea I asked a similar question to this, this is what i am doing by "pinging". #29422037Litalitany
it doesn't work for some sites, ex. vsopen.ru : gist.github.com/yetanothercoder/331989cf5aee2b833ee8Electrobiology
Does not work for sites using CloudFlare Flexible SSL service, such as kitematic.com (Caused by: javax.net.ssl.SSLException: Received fatal alert: internal_error)Cavefish
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.Dimenhydrinate
Question is about client certificate... this cannot be skipped...Darnel
H
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

Hales answered 17/5, 2009 at 22:42 Comment(0)
P
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.

Pheasant answered 7/7, 2010 at 21:42 Comment(2)
The OP is dealing with a HttpsURLConnection, not AxisOverstuffed
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.Pheasant
C
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();
}
Clamworm answered 6/11, 2014 at 12:29 Comment(4)
The keyStore is what contains the private key and certificate.Clamworm
You need to include these 2 dependencies for the convertPEMtoP12 code to work: <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.53</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk15on</artifactId> <version>1.53</version> </dependency>Truly
@Clamworm 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 wrongMarinamarinade
Yeah, you're using Groovy instead of a strictly typed language. (technically that method takes an ID and a certificate, not just a certificate)Clamworm
F
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

Forefoot answered 17/5, 2009 at 21:23 Comment(3)
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.Bragi
Apache HTTP Client 4 can take an SSLContext directly, so you can configure all this this way, instead of using AuthSSLProtocolSocketFactory.Ipomoea
Is there a way to do all the client certificate stuff in-memory instead of through an external keystore?Stephenson
S
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
Supination answered 18/5, 2009 at 8:55 Comment(1)
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 :oBragi
D
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.

Disaccustom answered 31/3, 2014 at 15:54 Comment(1)
Creating your own custom SSL factory is a lot more complicated and error-prone than setting two system properties.Conrad
O
0

Although this question is more than 12 years old and has a-lot of good answers I want to provide an alternative. Here is a small snippet of loading the keystore and truststore and getting the sslsocketfactory or sslcontext:

SSLFactory sslFactory = SSLFactory.builder()
        .withIdentityMaterial("clientcertificate.p12", "password".toCharArray(), "PKCS12")
        .withTrustMaterial("gridserver.keystore", "password".toCharArray(), "PKCS12")
        .build();

SSLSocketFactory sslSocketFactory = sslFactory.getSslSocketFactory();
SSLContext sslContext = sslFactory.getSslContext();

This example code snippet is from the library: GitHub - SSLContext Kickstart You can add it with the following snippet:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart</artifactId>
    <version>7.0.2</version>
</dependency>
Oriental answered 24/10, 2021 at 21:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.