" javax.net.ssl.SSLHandshakeException: Handshake failed" Even after adding custom TrustManager and Certificate Pinning

1

I am planning to use Jamendo API to download music but upon connection to the API the following error was thrown

javax.net.ssl.SSLHandshakeException: Handshake failed
        at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:286)
        at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.kt:351)
        at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.kt:310)
        at okhttp3.internal.connection.RealConnection.connect(RealConnection.kt:178)
        at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:236)
        at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:109)
        at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:77)
        at okhttp3.internal.connection.Transmitter.newExchange$okhttp(Transmitter.kt:162)
        at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:35)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
        at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:82)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
        at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:84)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
        at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:71)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
        at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.kt:184)
        at okhttp3.RealCall.execute(RealCall.kt:66)
        at com.example.musicplayer.utils.CertificatePinningKt.certificatePinning(CertificatePinning.kt:26)
        at com.example.musicplayer.fragments.HomeFragment$onActivityCreated$1$1.invokeSuspend(HomeFragment.kt:42)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:740)
     Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xedb6ee48: Failure in SSL library, usually a protocol error
    error:100000f0:SSL routines:OPENSSL_internal:UNSUPPORTED_PROTOCOL (external/boringssl/src/ssl/handshake_client.cc:576 0xe5faba43:0x00000000)
        at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
        at com.android.org.conscrypt.NativeSsl.doHandshake(NativeSsl.java:375)
        at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:224)
            ... 27 more

Then I came to know that certain CA certificates are not known to Android and the way out is either enable TrustManager to not verify any certificate on an https request like the accepted answer of this SO post or to add a custom TrustManager to accept the CA certificate of the server to which I need to communicate according to this google doc , and I took the latter approach.

!) First, I checked the Jamendo's server info using the following command

$ openssl s_client -connect jamendo.com:443 | openssl x509 -noout -subject -issuer

which results in

depth=3 C = US, O = "The Go Daddy Group, Inc.", OU = Go Daddy Class 2 Certification Authority verify return:1
depth=2 C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", CN = Go Daddy Root Certificate Authority - G2 verify return:1
depth=1 C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", OU = http://certs.godaddy.com/repository/, CN = Go Daddy Secure Certificate Authority - G2 verify return:1
depth=0 C = LU, L = Luxembourg, O = Jamendo SA, CN = .jamendo.com verify return:1
subject= /C=LU/L=Luxembourg/O=Jamendo SA/CN=
.jamendo.com issuer= /C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc./OU=http://certs.godaddy.com/repository//CN=Go Daddy Secure Certificate Authority - G2

It looks like that Jamendo is using GoDaddy's hosting service so I went to their certification page and as in the above output, the certificates used are from G2 group so I downloaded root and intermediate certificates(first two) as they are mentioned in the output.

2) And then I tried to create custom SSLSocketFactory and TrustManager which results into the same error. The approach is shown in the following code

fun getCACertificateAndroid(resources: Resources): Pair<SSLSocketFactory, X509TrustManager> {
    val cf: CertificateFactory = CertificateFactory.getInstance("X.509")

    //  certificate 1
    var caInput: InputStream = resources.openRawResource(R.raw.gdig2)
    val ca: X509Certificate = caInput.use {
        cf.generateCertificate(it) as X509Certificate
    }
    Log.d("GetCACertificateAndroid", "getCACertificateAndroid: ca= + ${ca.subjectDN}")

    //  certificate 2
    caInput = resources.openRawResource(R.raw.gdroot_g2)
    val ca1: X509Certificate = caInput.use {
        cf.generateCertificate(it) as X509Certificate
    }
    Log.d("GetCACertificateAndroid", "getCACertificateAndroid: ca1= + ${ca1.subjectDN}")

    // Create a KeyStore containing our trusted CAs
    val keyStoreType = KeyStore.getDefaultType()
    val keyStore = KeyStore.getInstance(keyStoreType).apply {
        load(null, null)
        setCertificateEntry("ca", ca)
        setCertificateEntry("ca1", ca1)
    }

    // Create a TrustManager that trusts the CAs inputStream our KeyStore
    val tmfAlgorithm: String = TrustManagerFactory.getDefaultAlgorithm()
    val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
        init(keyStore)
    }

    // Create an SSLContext that uses our TrustManager
    val context: SSLContext = SSLContext.getInstance("TLS").apply {
        init(null, tmf.trustManagers, null)
    }


    return Pair(context.socketFactory, tmf.trustManagers[0] as X509TrustManager)
}

Some other file

...

val (_sslSocketFactory, x509tTrustManager) = getCACertificateAndroid(resources)

    val client = OkHttpClient.Builder()
        .sslSocketFactory(_sslSocketFactory, x509tTrustManager)
        .build()

    val gson = GsonBuilder()
        .setLenient()
        .create()

    val retrofit = Retrofit.Builder()
        .baseUrl("https://api.jamendo.com/v3.0/playlists/?client_id=c7668145&format=jsonpretty&namesearch=cool") //   "https://storage.googleapis.com/"
        .addConverterFactory(GsonConverterFactory.create(gson))
        .client(client)
        .build()

...

3) Then I came to know about Certificate pinning in OKHttp and thought to give it a try by getting the base64 encoding of the downloaded certificates by running the following command

openssl x509 -in cert.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

based on the accepted answer of this post and then tried to run the following code but still, the error persists.

fun certificatePinning() {
    val hostname = "api.jamendo.com/v3.0/playlists/?client_id=c7668145&format=jsonpretty&namesearch=cool"
    val sha256base64_hash1 = "sha256/Ko8tivDrEjiY90yGasP6ZpBU4jwXvHqVvQI0GS3GNdA="
    val sha256base64_hash2 = "sha256/8Rw90Ej3Ttt8RRkrg+WYDS9n7IS03bk5bjP/UXPtaY8="


    val certificatePinner = CertificatePinner.Builder()
        .add(hostname, sha256base64_hash1)
        .add(hostname, sha256base64_hash2)
        .build()
    val client = OkHttpClient.Builder()
        .certificatePinner(certificatePinner)
        .build()

    val request = Request.Builder()
        .url("https://$hostname")
        .build()
    client.newCall(request).execute()
}

If someone could point me in the right direction then it would be a great help.

android
kotlin
retrofit2
sslhandshakeexception
x509trustmanager
asked on Stack Overflow Sep 10, 2019 by Neeraj Sewani

2 Answers

1

The handshake issue is due to Jamendo API using an old deprecated TLS protocol version (1.0) and not support newer protocol versions:
* https://github.com/square/okhttp/issues/4670 * https://medium.com/square-corner-blog/okhttp-3-13-requires-android-5-818bb78d07ce

Side-notes: I would definitely opt against a custom TrustManager implementation, this would only make sense e.g. if your endpoint is using a self-signed certificate. As a basic check i would verify that your Android System TrustStore is working by trying to open the Jamendo URL directly on the phone/emulator browser to see if you get any issues? Pinning provides additional protection but does not resolve basic handshake issue you are seeing.

answered on Stack Overflow Sep 11, 2019 by nysos • edited Sep 12, 2019 by nysos
1

Check your 'TLS' support. I've faced with this problem. In my app I use retrofit lib. So try add 'COMPATIBLE_TLS' config to your OkHttpClient like:

OkHttpClient client = new OkHttpClient();
List<ConnectionSpec> connectionSpecs = new ArrayList<>();
connectionSpecs.add(ConnectionSpec.COMPATIBLE_TLS);
client.setConnectionSpecs(connectionSpecs);
...

And update your library in gradle at least to v2.7.5 like:

implementation 'com.squareup.okhttp:okhttp:2.7.5'
answered on Stack Overflow Apr 1, 2020 by Djek-Grif

User contributions licensed under CC BY-SA 3.0