20

[edit: If you are here for Let's Encrypt expiry event from January 2021, read this first https://letsencrypt.org/2020/12/21/extending-android-compatibility.html]

Connecting to https://valid-isrgrootx1.letsencrypt.org/ via OkHttp on Android M or earlier fails, while the connection works on N or later.

    OkHttpClient client = new OkHttpClient();

    try {
      Request request = new Request.Builder()
              .url("https://valid-isrgrootx1.letsencrypt.org/robots.txt")
              .build();
      try (Response response = client.newCall(request).execute()) {
        assertTrue(response.code() == 200 || response.code() == 404);
        assertEquals(Protocol.HTTP_2, response.protocol());
      }
    } catch (SSLHandshakeException sslhe) {
      sslhe.printStackTrace();
    }
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:322)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:320)
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.java:284)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:169)
at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:258)
at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:135)
at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:114)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:127)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:257)
at okhttp3.RealCall.execute(RealCall.java:93)
at okhttp.regression.LetsEncryptTest.sendRequest(LetsEncryptTest.java:133)
at okhttp.regression.LetsEncryptTest.getFailsWithoutAdditionalCert(LetsEncryptTest.java:52)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at androidx.test.ext.junit.runners.AndroidJUnit4.run(AndroidJUnit4.java:154)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:395)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1853)
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:318)
at com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:219)
at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:114)
at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:550)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:318)
... 50 more
Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
... 56 more
Community
  • 1
  • 1
Yuri Schimke
  • 12,435
  • 3
  • 35
  • 69

5 Answers5

20

[edit: If you are here for Let's Encrypt expiry event from January 2021, read this first https://letsencrypt.org/2020/12/21/extending-android-compatibility.html]

As generic advice, if you need to add an root CA to older Android devices, this example shows how this can work.

The issue relates to the known expiry of the ISRG root certificate for Let's encrypt in 2021. This (test) server is using the replacement certificate which is only supported on versions of Android N (7.1.1) and later.

The following code will work against the root certificate used by lets encrypt in future. It builds on okhttp-tls.

Note: none of this advice applies in combination with CertificatePinner, if you choose to also pin certificates, then please discuss your strategy with your in-house security team.

    boolean androidNorEarlier = Build.VERSION.SDK_INT <= 25;

    OkHttpClient.Builder builder = new OkHttpClient.Builder();

    if (androidNorEarlier) {
      // TODO: download fresh from https://letsencrypt.org/certs/isrgrootx1.pem
      String isgCert =
              "-----BEGIN CERTIFICATE-----\n" +
              "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" +
              "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" +
              "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" +
              "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" +
              "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" +
              "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" +
              "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" +
              "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" +
              "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" +
              "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" +
              "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" +
              "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" +
              "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" +
              "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" +
              "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" +
              "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" +
              "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" +
              "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" +
              "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" +
              "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" +
              "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" +
              "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" +
              "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" +
              "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" +
              "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" +
              "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" +
              "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" +
              "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" +
              "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" +
              "-----END CERTIFICATE-----";

      CertificateFactory cf = CertificateFactory.getInstance("X.509");
      Certificate isgCertificate = cf.generateCertificate(new ByteArrayInputStream(isgCert.getBytes("UTF-8")));

      HandshakeCertificates certificates = new HandshakeCertificates.Builder()
              .addTrustedCertificate((X509Certificate) isgCertificate)
              // Uncomment to allow connection to any site generally, but could possibly cause
              // noticeable memory pressure in Android apps.
//              .addPlatformTrustedCertificates()
              .build();

      builder.sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager());
    }

    OkHttpClient client = builder.build();

    Request request = new Request.Builder()
            .url("https://valid-isrgrootx1.letsencrypt.org/robots.txt")
            .build();
    try (Response response = client.newCall(request).execute()) {
      assertTrue(response.code() == 200 || response.code() == 404);
      assertEquals(Protocol.HTTP_2, response.protocol());
    }

The new host certificates are root signed by the ISRG Root X1 CA.

 ./cft --host valid-isrgrootx1.letsencrypt.org           
CN:     valid-isrgrootx1.letsencrypt.org
Pin:    sha256/489aa1610850a89c720217b9d9dbdc7f80918119f32b88c2dd3bcfaf1de29079
SAN:    valid-isrgrootx1.letsencrypt.org
Key Usage: DigitalSignature, KeyEncipherment
Ext Key Usage: serverAuth, clientAuth
Authority Info Access:
    ocsp: http://ocsp.int-x3.letsencrypt.org
    caIssuers: http://cert.int-x3.letsencrypt.org/
Valid:  2020-10-14T15:00:50Z..2021-01-12T15:00:50Z (1 months)
CA: false

CN:     Let's Encrypt Authority X3
Pin:    sha256/60b87575447dcba2a36b7d11ac09fb24a9db406fee12d2cc90180517616e8a18
SAN:    <N/A>
Key Usage: DigitalSignature, KeyCertSign, CRLSign
Authority Info Access:
    ocsp: http://ocsp.root-x1.letsencrypt.org/
    caIssuers: http://cert.root-x1.letsencrypt.org/
Valid:  2016-10-06T15:43:55Z..2021-10-06T15:43:55Z (10 months)
CA: true Max Intermediate: 0

CN:     ISRG Root X1 (signed by locally-trusted root)
Pin:    sha256/0b9fa5a59eed715c26c1020c711b4f6ec42d58b0015e14337a39dad301c5afc3
SAN:    <N/A>
Key Usage: KeyCertSign, CRLSign
Valid:  2015-06-04T11:04:38Z..2035-06-04T11:04:38Z (14 years)
CA: true

Strict Transport Security: max-age=604800

OCSP status: GOOD

Existing certificates are signed by DST Root CA X3 which expires in September 2021.

$  ./cft --host letsencrypt.org 
CN:     lencr.org
Pin:    sha256/b93116ebda5e22efe089e7710b221557eb80a2e13c60a58687c0ce0369afd68a
SAN:    lencr.org, letsencrypt.org, www.lencr.org, www.letsencrypt.org
Key Usage: DigitalSignature, KeyEncipherment
Ext Key Usage: serverAuth, clientAuth
Authority Info Access:
    ocsp: http://ocsp.int-x3.letsencrypt.org
    caIssuers: http://cert.int-x3.letsencrypt.org/
Valid:  2020-11-03T21:00:55Z..2021-02-01T21:00:55Z (2 months)
CA: false

CN:     Let's Encrypt Authority X3
Pin:    sha256/60b87575447dcba2a36b7d11ac09fb24a9db406fee12d2cc90180517616e8a18
SAN:    <N/A>
Key Usage: DigitalSignature, KeyCertSign, CRLSign
Authority Info Access:
    ocsp: http://isrg.trustid.ocsp.identrust.com
    caIssuers: http://apps.identrust.com/roots/dstrootcax3.p7c
Valid:  2016-03-17T16:40:46Z..2021-03-17T16:40:46Z (4 months)
CA: true Max Intermediate: 0

CN:     DST Root CA X3 (signed by locally-trusted root)
Pin:    sha256/563b3caf8cfef34c2335caf560a7a95906e8488462eb75ac59784830df9e5b2b
SAN:    <N/A>
Key Usage: KeyCertSign, CRLSign
Valid:  2000-09-30T21:12:19Z..2021-09-30T14:01:15Z (10 months)
CA: true

Strict Transport Security: max-age=31536000

OCSP status: GOOD

Available Fixes

Name Versions API level ISRG Root X1 network
security
config
OkHttp 3.12
+ Fix
No official codename 1 1
1.1 2
Cupcake 1.5 3
Donut 1.6 4
Eclair 2.0 – 2.1 5 – 7
Froyo 2.2 – 2.2.3 8
Gingerbread 2.3 – 2.3.7 9 – 10 X
Honeycomb 3.0 – 3.2.6 11 – 13 X
Ice Cream Sandwich 4.0 – 4.0.4 14 – 15 X
Jelly Bean 4.1 – 4.3.1 16 – 18 X
KitKat 4.4 – 4.4.4 19 – 20 X
Lollipop 5.0 – 5.1.1 21 – 22 X
Marshmallow 6.0 – 6.0.1 23 X
Nougat 7.0 – 7.1.2 24 – 25 7.1.1+ X X
Oreo 8.0 – 8.1 26 – 27 X X X
Pie 9 28 X X X
Android 10 10 29 X X X
Android 11 11 30 X X X
Yuri Schimke
  • 12,435
  • 3
  • 35
  • 69
  • Can you link for more info on memory pressure issue? Is it because okhttp will copy all system root CAs into memory? – Sergii Pechenizkyi Nov 15 '20 at 13:52
  • 2
    Nothing specific, but whatever optimisations a built in provider might do to avoid loading all certificates into memory, we will undo. If every Android app you run does this, it potentially have a large impact. On Android 22 emulator I get 163 certificates. So I guess small number of megabytes at most per client instance? – Yuri Schimke Nov 15 '20 at 14:16
  • What is the workaround for say, a browser using `WebView` running on Lollipop or Mashmallow? – Craig Russell Nov 20 '20 at 09:27
  • @CraigRussell not sure, you should ask on the LE forum. I suspect if you test it won't work and you may want to use an external browser for showing that content. But I don't know. – Yuri Schimke Nov 20 '20 at 10:16
  • The AndroidKeyStore is available from Android 4.3, maybe an app can just install the certs info that? – ballzak Nov 25 '20 at 01:42
  • Where's that `HandshakeCertificates` class from? – Phil Nov 26 '20 at 11:43
  • 1
    If there are any doubts about the PEM certificate, you can check it [here](https://www.sslshopper.com/certificate-decoder.html) – David Rawson Dec 08 '20 at 23:07
  • 1
    @DavidRawson I put a comment to download fresh from https://letsencrypt.org/certs/isrgrootx1.pem – Yuri Schimke Dec 09 '20 at 06:39
  • 1
    @Phil `HandshakeCertificates` comes from OkHttp 4.X version. If you are stuck on 3.1.2 because you still support Android 4.4 or earlier, you need to code it yourself. See my answer. – Fradow Dec 15 '20 at 10:00
  • For how to handle this we android webviews see https://stackoverflow.com/questions/65304273/handling-lets-encrypt-new-certificates-in-webview-for-android-m-and-older/65304274#65304274 f – Aitor Viana Dec 15 '20 at 10:54
6

On Android Nougat (7) you can add/update android:networkSecurityConfig in AndroidManifest.xml to point to a local certificate.

https://www.danieldent.com/blog/android-apps-lets-encrypt-dst-root-expiry/

<network-security-config>
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="@raw/isrg_root_x2" />
            <certificates src="@raw/isrg_root_x1" />
            <certificates src="system" />
        </trust-anchors>
    </base-config>
</network-security-config>
Yuri Schimke
  • 12,435
  • 3
  • 35
  • 69
4

For Let's Encrypt expiry event from January 2021, this is no longer required.

Read https://letsencrypt.org/2020/12/21/extending-android-compatibility.html before applying.

Yuri Schimke
  • 12,435
  • 3
  • 35
  • 69
3

Alternative answer based on code from @Yuri Schimke that works with OkHttp 3.1.2, Volley and HttpsUrlConnection.

Since those don't have HandshakeCertificates class that does the heavy-lifting, you need to do it yourself:

public TrustManagerFactory getTrustManagerFactory() throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException {
    //Note: hardcode it, because the device might not even have the certificate to download it over https
    String isgCert =
            "-----BEGIN CERTIFICATE-----\n" +
                    "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" +
                    "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" +
                    "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" +
                    "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" +
                    "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" +
                    "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" +
                    "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" +
                    "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" +
                    "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" +
                    "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" +
                    "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" +
                    "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" +
                    "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" +
                    "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" +
                    "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" +
                    "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" +
                    "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" +
                    "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" +
                    "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" +
                    "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" +
                    "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" +
                    "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" +
                    "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" +
                    "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" +
                    "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" +
                    "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" +
                    "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" +
                    "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" +
                    "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" +
                    "-----END CERTIFICATE-----";

    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    Certificate isgCertificate = cf.generateCertificate(new ByteArrayInputStream(isgCert.getBytes(StandardCharsets.UTF_8)));

    // Create a KeyStore containing our trusted CAs
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(null, null);
    keyStore.setCertificateEntry("isrg_root", isgCertificate);

    //Default TrustManager to get device trusted CA
    TrustManagerFactory defaultTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    defaultTmf.init((KeyStore) null);

    X509TrustManager trustManager = (X509TrustManager) defaultTmf.getTrustManagers()[0];
    int number = 0;
    for(Certificate cert : trustManager.getAcceptedIssuers()) {
        keyStore.setCertificateEntry(Integer.toString(number), cert);
        number++;
    }

    // Create a TrustManager that trusts the CAs in our KeyStore
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(keyStore);
    return tmf;
}

Then you can configure it, for OkHttp:

public static OkHttpClient getHttpClient() throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
    OkHttpClient.Builder builder = new OkHttpClient.Builder();

    if (Build.VERSION.SDK_INT <= 25) {
        TrustManagerFactory tmf = getTrustManagerFactory();
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, tmf.getTrustManagers(), null);
        builder.sslSocketFactory(context.getSocketFactory(), (X509TrustManager) tmf.getTrustManagers()[0]);
    }
    return builder.build();
}

For Volley:

public RequestQueue configureRequestQueue(Context context) {
    if (Build.VERSION.SDK_INT <= 25) {
        try {
            TrustManagerFactory tmf = NetworkUtility.getTrustManagerFactory();
            // Create an SSLContext that uses our TrustManager
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, tmf.getTrustManagers(), null);

            HurlStack httpStack = new HurlStack(null, sslContext.getSocketFactory());
            return Volley.newRequestQueue(context, httpStack);
        } catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
            e.printStackTrace();
        }
    }
    return Volley.newRequestQueue(context);
}

For HttpsUrlConnection:

public void configureHttpsUrlConnection() {
    if (Build.VERSION.SDK_INT <= 25) {
        try {
            TrustManagerFactory tmf = getTrustManagerFactory();
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, tmf.getTrustManagers(), null);
            HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | IOException | CertificateException e) {
            e.printStackTrace();
        }
    }
}
Fradow
  • 213
  • 1
  • 6
2

For Glide just register an OkHttp ModelLoaderFactory for GlideUrl model class.

@GlideModule
class GlobalGlideModule : AppGlideModule() {
    override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
            try {

                val isrgRootX1 = ... // X509Certificate

                val handshakeCertificates = HandshakeCertificates.Builder()
                    .addTrustedCertificate(isrgRootX1)
                    .addPlatformTrustedCertificates()
                    .build()

                val okHttpClient = OkHttpClient.Builder()
                    .sslSocketFactory(handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager())
                    .build()

                // use our custom okHttp instead of default HTTPUrlConnection
                registry.replace(
                    GlideUrl::class.java,
                    InputStream::class.java,
                    OkHttpUrlLoader.Factory(okHttpClient)
                )
            } catch (t: Throwable) {
                super.registerComponents(context, glide, registry)
            }
        } else {
            super.registerComponents(context, glide, registry)
        }
    }
}

See example in this PR.

Aitor Viana
  • 933
  • 6
  • 15