Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSLSocketFactory with Client Certificate works fine with okhttp2 but not with okhttp3 #2786

Closed
mboukadir opened this issue Aug 11, 2016 · 16 comments
Labels
bug Bug in existing code needs info More information needed from reporter

Comments

@mboukadir
Copy link

mboukadir commented Aug 11, 2016

Here's how I created the SSLSocketFactory that works fine with okhttp2.x but not with okhttp3:

public SSLSocketFactory createSslSocketFactory() {
    try {
      String base64PKCS12 = "****';
      String certPassword = "****";

      InputStream is = new ByteArrayInputStream(base64PKCS12.getBytes());
      Base64InputStream b64is = new Base64InputStream(is, Base64.DEFAULT);

      char[] tableauCertPassword = certPassword.toCharArray();

      // Import PKCS12 in KeyStore
      KeyStore appKeyStore = KeyStore.getInstance("PKCS12");
      appKeyStore.load(b64is, tableauCertPassword);

      is.close();
      b64is.close();

      // Building SSL context for future use
      KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
      keyManagerFactory.init(appKeyStore, tableauCertPassword);
      KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

      // Add Trustmanager
      KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
      InputStream instream = app.getResources().openRawResource(R.raw.truststore);
      trustStore.load(instream, "*****".toCharArray());
      instream.close();

      TrustManagerFactory tmf =
          TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
      tmf.init(trustStore);
      TrustManager[] managers = tmf.getTrustManagers();

      SSLContext context = SSLContext.getInstance("TLS");
      context.init(keyManagers, managers, null);

      return context.getSocketFactory();
    } catch (Exception e) {
      Timber.e(e, "SSLSocket creation exception %s", e.getMessage());
      throw new AssertionError(e);
    }
  }

With okhttp3 i have tried this, as @swankjesse did in : exemple CutomTust [https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java], but in my case I have to use TrustStore :


private KeyManager[] keyManagers;

  OkHttpClient provideOkHttpClient(Application app) {
    X509TrustManager trustManager = trustManagerForCertificates(app);
    SSLContext sslContext = null;
    try {
      sslContext = SSLContext.getInstance("TLS");
      sslContext.init(keyManagers, new TrustManager[] { trustManager }, null);
    } catch (GeneralSecurityException e) {
      throw new RuntimeException(e);
    }

    return new OkHttpClient.Builder()//
        .sslSocketFactory(sslContext.getSocketFactory(),trustManager)//
        .addInterceptor(new HostSelectionInterceptor()).build();
  }

  public  X509TrustManager trustManagerForCertificates(Application app) {

    try {
      String base64PKCS12 = "**";
      String certPassword = "**";

      InputStream is = new ByteArrayInputStream(base64PKCS12.getBytes());
      Base64InputStream b64is = new Base64InputStream(is, Base64.DEFAULT);

      char[] tableauCertPassword = certPassword.toCharArray();

      // Import PKCS12 in KeyStore
      KeyStore appKeyStore = KeyStore.getInstance("PKCS12");
      appKeyStore.load(b64is, tableauCertPassword);

      is.close();
      b64is.close();

Here is where I manage the TRUSTSTORE

  // Add Trustmanager
      KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
      InputStream instream = app.getResources().openRawResource(R.raw.truststore);
      trustStore.load(instream, "****".toCharArray());
      instream.close();

// Building SSL context for future use
      KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
      keyManagerFactory.init(appKeyStore, tableauCertPassword);
      keyManagers = keyManagerFactory.getKeyManagers();



     TrustManagerFactory tmf =
          TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
      tmf.init(trustStore );

      TrustManager[] trustManagers = tmf.getTrustManagers();

      if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
        throw new IllegalStateException(
            "Unexpected default trust managers:" + Arrays.toString(trustManagers));
      }
      return (X509TrustManager) trustManagers[0];
    } catch (Exception e) {
      Timber.e(e, "SSLSocket creation exception %s", e.getMessage());
      throw new AssertionError(e);
    }
  }

Thanks in advance

@swankjesse
Copy link
Member

How does it fail? It's working for other developers!

@mboukadir
Copy link
Author

mboukadir commented Aug 11, 2016

Thanks for response,

The problem I am actually facing is: I have two keystores to manage, the first resembles to yours I refere to it in my code with : TrustStore it has a Certificates Authority . the second is a appKeyStore has a client certificate and a password.

But in your example you have one and only one keystore (TrustKeystore).

With okhttp2 I initialezed keyManagerFactory with appKeyStore as follow:

KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(appKeyStore, tableauCertPassword);
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

and for the trustStore I used :

  TrustManagerFactory tmf =
          TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
   tmf.init(trustStore);
   TrustManager[] managers = tmf.getTrustManagers();

But this does not work with okhttp3:
Here is the exception I am getting
javax.net.ssl.SSLException: Read error: ssl=0x9dd07200: I/O error during system call, Connection reset by peer

While following your solution I did not use the trustStore, I initilized the keyManagerFactory and the TrustManagerFactory with the appKeyStore. As result I get :

 Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:324)
at com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:225)
at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:115)
at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:556)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
 at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:324)
 : ... 47 more
Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.  

I adapted your solution to my situation, I initialised the TrustManagerFactory with TrustStore and as result I get :

Caused by: javax.net.ssl.SSLException: Read error: ssl=0xab163140: I/O error during system call, Connection reset by peer
 at com.android.org.conscrypt.NativeCrypto.SSL_read(Native Method)
 at com.android.org.conscrypt.OpenSSLSocketImpl$SSLInputStream.read(OpenSSLSocketImpl.java:705)

how can I use the TrustStore with okhttp3?

Thanks in advance

@swankjesse
Copy link
Member

The trust store you pass to OkHttp should be the one you're using to validate the server's certificates. Typically these are from a certificate authority.

@mboukadir
Copy link
Author

mboukadir commented Aug 11, 2016

Yes, the trust store is from the CA and that work fine with okhttp2 and HttpURLConnection,
here's how I created the SSLSocketFactory for them:

public SSLSocketFactory createSslSocketFactory() {
    try {

     //Client Certificate
      String base64PKCS12 = "****';
      String certPassword = "****";

      InputStream is = new ByteArrayInputStream(base64PKCS12.getBytes());
      Base64InputStream b64is = new Base64InputStream(is, Base64.DEFAULT);

      char[] tableauCertPassword = certPassword.toCharArray();

      // Import PKCS12 in KeyStore
      KeyStore appKeyStore = KeyStore.getInstance("PKCS12");
      appKeyStore.load(b64is, tableauCertPassword);

      is.close();
      b64is.close();

      // Building SSL context for future use
      KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
      keyManagerFactory.init(appKeyStore, tableauCertPassword);
      KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

      // Add Trustmanager
      KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
      // CA certificate
      InputStream instream = app.getResources().openRawResource(R.raw.truststore); 
      trustStore.load(instream, "*****".toCharArray());
      instream.close();

      TrustManagerFactory tmf =
          TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
      tmf.init(trustStore);
      TrustManager[] managers = tmf.getTrustManagers();

      SSLContext context = SSLContext.getInstance("TLS");
      context.init(keyManagers, managers, null);

      return context.getSocketFactory();
    } catch (Exception e) {
      Timber.e(e, "SSLSocket creation exception %s", e.getMessage());
      throw new AssertionError(e);
    }
  }

Do you have any idea of how i can create sslSolcketFactory and x509TrustManager for okhttp3 as from createSslSocketFactory( ) method ?

Thanks in advance

@yschimke
Copy link
Collaborator

If I understand correctly, its an issue that you have two keystore files and the API doesn't make that possible.

sslSocketFactory(
        SSLSocketFactory sslSocketFactory, X509TrustManager trustManager)

If this is the case, I've done something similar with multiple trust stores. See

https://github.com/yschimke/oksocial/blob/master/src/main/java/com/baulsupp/oksocial/security/CertificateUtils.java#L42

But it is very possible I'm misunderstanding.

@mboukadir
Copy link
Author

mboukadir commented Aug 12, 2016

@yschimke, In fact I have two kestores :
The first with my client certificate is in PKCS 12 base64 format I load it into an appropriate KeyStore and I init the keyManager with it:

 //Client Certificate
      String base64PKCS12 = "****';
      String certPassword = "****";

      InputStream is = new ByteArrayInputStream(base64PKCS12.getBytes());
      Base64InputStream b64is = new Base64InputStream(is, Base64.DEFAULT);

      char[] tableauCertPassword = certPassword.toCharArray();

      // Import PKCS12 in KeyStore
      KeyStore appKeyStore = KeyStore.getInstance("PKCS12");
      appKeyStore.load(b64is, tableauCertPassword);
      is.close();
      b64is.close();

      // lnit keyManagerFactory with Client Certificate's keyStore 
      KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
      keyManagerFactory.init(appKeyStore, tableauCertPassword);
      KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

The second keyStore is the trustStore that contains Certificates Authourity, and I initialize my TrustManagerFactory with it:

      // Add Trustmanager
      KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
      // CA certificate
      InputStream instream = app.getResources().openRawResource(R.raw.truststore); 
      trustStore.load(instream, "*****".toCharArray());
      instream.close();

      TrustManagerFactory tmf =
          TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
      tmf.init(trustStore);
      TrustManager[] managers = tmf.getTrustManagers();

To adapt my code to okhttp3 I used @swankjesse sample, but the diffrence remains in the fact that it doesn't handle a keyStore, for KeyManagerFactory, with Client Certificate. It uses only Certificates Authority in a trustKeyStore.

As a reminder my public SSLSocketFactory createSslSocketFactory() {..} works very fine with okhttp2 and HttpURLConnection.

@mboukadir mboukadir changed the title SSLSocketFactory works fine with okhttp2 but not with okhttp3 SSLSocketFactory with Client Certificate works fine with okhttp2 but not with okhttp3 Aug 15, 2016
@swankjesse
Copy link
Member

I’m not quite sure what we could be doing differently on this. The big change in 3.x is that we accept a separate X509TrustManager but it’s only used for certificate pinning.

At the moment we don’t have any tests that do HTTPS with client certificates. Would you be interested in contributing one?

@swankjesse swankjesse added bug Bug in existing code needs info More information needed from reporter labels Aug 19, 2016
@swankjesse swankjesse added this to the Icebox milestone Aug 19, 2016
@yschimke
Copy link
Collaborator

My reading is that BasicCertificateChainCleaner also filters certificates if it can't find a trusted root CA in the trust manager. i.e. it effects things even without certificate pinning.

@mboukadir
Copy link
Author

I would love to contribute in solving the problem.
However, I cannot share the client certificates, due to confidentiality issues.
In addition the company I am working does not allow running tests on their servers with mocked certificates.
Keep in mind that in order to fix this bug, I am ready to help out.

@yschimke
Copy link
Collaborator

I created a test case for the non-android case showing client auth working #2804

You could use this to make a similar test for Android.

@yschimke
Copy link
Collaborator

yschimke commented Aug 22, 2016

Just for shits and giggles, does your existing code still work with 2.7.4?

@mboukadir
Copy link
Author

@yschimke yes it does work fine with 2.7.4.
But I didn’t try yet to adapt your pool request to my situation.
I will post the results as soon as I do. 

@n4167
Copy link

n4167 commented Sep 8, 2016

https://github.com/square/okhttp/wiki/HTTPS#customizing-trusted-certificates
this method works fine for me ,but i want to know is there a method to access any https website with a default certificate or ignore ssl error.
ps .sslSocketFactory(sslSocketFactory) this method does not work with okhttp 3.4.1

@swankjesse
Copy link
Member

No action to take on this.

@bjornson
Copy link

bjornson commented May 3, 2018

@mboukadir Did you ever find a proper solution to this? I am facing the same problem with a client certificate. If I use the client certificate for the trusManager, the app complains about "missing trust anchor", and if I use the server certificate for the trusManager, the "connection is reset by the peer".

@vasiledoe
Copy link

@bjornson any solutions?

@swankjesse swankjesse removed this from the Icebox milestone Nov 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Bug in existing code needs info More information needed from reporter
Projects
None yet
Development

No branches or pull requests

6 participants