3

I have an application that I want to use on the emulator for testing purposes. I'm using the emulator/platform/system image in API level 19 with x86 ABI and Google APIs.

The app needs to POST some data to an HTTPS enabled server. The certificate is provided by Let's Encrypt and is signed by the X3 CA.

I have the following error when trying to make the request:

Couldn't execute API call retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall@b0fdfbd8 (Request{method=POST, url=https://hostname.net/api/session, tag=null})
javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb80589b0: Failure in SSL library, usually a protocol error
error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version (external/openssl/ssl/s23_clnt.c:741 0xa90ed990:0x00000000)
  at: com.android.org.conscrypt.OpenSSLSocketImpl startHandshake (OpenSSLSocketImpl.java:448)
  at: okhttp3.internal.connection.RealConnection connectTls (RealConnection.java:299)
  at: okhttp3.internal.connection.RealConnection establishProtocol (RealConnection.java:268)
  at: okhttp3.internal.connection.RealConnection connect (RealConnection.java:160)
  at: okhttp3.internal.connection.StreamAllocation findConnection (StreamAllocation.java:256)
  at: okhttp3.internal.connection.StreamAllocation findHealthyConnection (StreamAllocation.java:134)
  at: okhttp3.internal.connection.StreamAllocation newStream (StreamAllocation.java:113)
  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:125)
  at: okhttp3.internal.http.RealInterceptorChain proceed (RealInterceptorChain.java:147)
  at: okhttp3.internal.http.RealInterceptorChain proceed (RealInterceptorChain.java:121)
  at: com.redacted.backend.SessionInterceptor intercept (SessionInterceptor.java:61)
  at: okhttp3.internal.http.RealInterceptorChain proceed (RealInterceptorChain.java:147)
  at: okhttp3.internal.http.RealInterceptorChain proceed (RealInterceptorChain.java:121)
  at: okhttp3.RealCall getResponseWithInterceptorChain (RealCall.java:200)
  at: okhttp3.RealCall execute (RealCall.java:77)
  at: retrofit2.OkHttpCall execute (OkHttpCall.java:180)
  at: retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall execute (ExecutorCallAdapterFactory.java:91)
  at: com.redacted.backend.Backend executeApiCall (Backend.java:139)
  at: com.redacted.backend.Backend createSession (Backend.java:62)
  at: com.redacted.viewmodel.LoginActivityViewModel$1 run (LoginActivityViewModel.java:50)
  at: java.util.concurrent.ThreadPoolExecutor runWorker (ThreadPoolExecutor.java:1112)
  at: java.util.concurrent.ThreadPoolExecutor$Worker run (ThreadPoolExecutor.java:587)
  at: java.lang.Thread run (Thread.java:841)

So I figured that the emulator had an outdated CA bundle, so I uploaded a new one generated following these instructions:

This is a custom script but it should be straightforward. $device contains the emulator name, such as emulator-5554.

echo "Download lets encrypt X3 CA cert"
[ -f X3.pem ] && rm X3.pem
wget -O X3.pem https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt || fail "Unable to download LE X3 CA cert"

echo "Encode CA cert to emulator format"
filename="$(openssl x509 -in X3.pem -hash -noout).0"
openssl x509 -in X3.pem >$filename
openssl x509 -in X3.pem -text -fingerprint -noout >>$filename

echo "Push encoded CA cert to $device"
$adb -s $device root || fail "Unable to access emulator '$device' as root"
$adb -s $device remount || fail "Unable to remount RW the emulator '$device'"
$adb -s $device push $filename /system/etc/security/cacerts/ || fail "Unable to push encoded CA cert to '$device'"

The file was indeed pushed, but I still have the same error message in the logs.

Is there a way to update the CA certs on the emulator?

More information:

  • server uses TLS v1.2
  • I even added the server certificate (not its CA) in the cacerts folder of the emulator, same result
Benoit Duffez
  • 11,839
  • 12
  • 77
  • 125
  • What version(s) of TLS does the server support? – clownba0t Jan 11 '18 at 10:32
  • 1
    have look [this](https://stackoverflow.com/a/30302235/5110595) hope it helps you. – Hemant Parmar Jan 11 '18 at 10:32
  • @clownba0t: great question. It uses TLSv1.2 with `ECDHE-RSA-AES256-GCM-SHA384`. I also noticed that the certificate is issued for `test.domain.net` but I'm using `demo.domain.net` (same IP). I will try to use `test.domain.net` and see if that was the issue. – Benoit Duffez Jan 11 '18 at 13:35
  • @HemantParmar: I'm using KitKat 4.4, the link mentions <4.4. I had already seen this and assumed that a strict <4.4 meant that my emulator didn't have this bug. – Benoit Duffez Jan 11 '18 at 13:35
  • 1
    Actually, TLS versions 1.1 and 1.2 are supported from API 16, but they're not enabled by default until API 20 (see https://developer.android.com/reference/javax/net/ssl/SSLSocket.html). That could be a cause of the issue. If the server wants TLS v1.2 and it's not enabled on the client, the client could very well be falling back to SSLv3 and failing. – clownba0t Jan 11 '18 at 14:05
  • @clownba0t: wow, nice. Indeed the server offers TLS v1.1 and v1.2 but not SSL v3. I will try to force the use of TLS and see if this solves the issue. – Benoit Duffez Jan 11 '18 at 14:33
  • @clownba0t: I have implemented what's described [here](https://github.com/square/okhttp/issues/2372#issuecomment-244807676) and it indeed fixed the issue. I will let you post this as an answer to get all the credit :) – Benoit Duffez Jan 11 '18 at 15:00

1 Answers1

4

Support for certain versions of TLS on earlier versions of Android is a bit complicated. TLS v1.0 is supported from API 1, and TLS v1.1 and v1.2 from API 16. However, despite being supported, TLS v1.1 and v1.2 are not enabled by default for client sockets until API 20. See the SSLSocket documentation for details.

What this means is that by default SSL connections from Android devices running APIs 16, 17, 18 and 19 to servers which only support TLS v1.1 or v1.2 will probably fail. The link that Hemant Parmar posted in the comments seems to explain why - the client appears to fall back to SSLv3 in these cases, which the server doesn't support and therefore the handshake fails.

The fix is to specifically enable TLS v1.1 and/or TLS v1.2 support in your application code. There are quite a few articles around which describe various ways of doing this - for example, How to enable TLS 1.2 support in an Android application (running on Android 4.1 JB), and the issue on the okhttp repository that you kindly posted in the comments.

clownba0t
  • 1,103
  • 10
  • 12