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

Certificate pinning is not working on android 2.3.X #2

Closed
delgurth opened this issue Jul 10, 2014 · 12 comments
Closed

Certificate pinning is not working on android 2.3.X #2

delgurth opened this issue Jul 10, 2014 · 12 comments

Comments

@delgurth
Copy link
Contributor

I'm trying to get this certificate pinning library to work on android 2.3.X but I'm running into an Exception:

javax.net.ssl.SSLPeerUnverifiedException: No peer certificate

The gradle file seems to suggest you intent to support android 2.2.X, that's why I'm raising this issue.

Do you have a clue what's wrong?

The exception and some debug output from the Retrofit client:

07-10 12:10:21.700    1540-1568/co.infinum.https D/Retrofit﹕ ---> HTTP GET https://api.github.com/users/ikust
07-10 12:10:21.700    1540-1568/co.infinum.https D/Retrofit﹕ User-Agent: hello-pinnedcerts
07-10 12:10:21.700    1540-1568/co.infinum.https D/Retrofit﹕ ---> END HTTP (0-byte body)
07-10 12:10:21.980    1540-1568/co.infinum.https D/Retrofit﹕ ---- ERROR https://api.github.com/users/ikust
07-10 12:10:21.980    1540-1568/co.infinum.https D/Retrofit﹕ javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
            at org.apache.harmony.xnet.provider.jsse.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:258)
            at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:93)
            at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:381)
            at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:164)
            at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164)
            at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119)
            at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:359)
            at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
            at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)
            at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465)
            at retrofit.client.ApacheClient.execute(ApacheClient.java:70)
            at retrofit.client.ApacheClient.execute(ApacheClient.java:64)
            at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:357)
            at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:262)
            at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:313)
            at retrofit.CallbackRunnable.run(CallbackRunnable.java:38)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
            at retrofit.Platform$Android$2$1.run(Platform.java:136)
            at java.lang.Thread.run(Thread.java:1019)
07-10 12:10:21.980    1540-1568/co.infinum.https D/Retrofit﹕ ---- END ERROR
@ikust
Copy link
Owner

ikust commented Jul 10, 2014

Yes, support from 2.2.x is intended.

I've checked the issue, the example stopped working on 4.4.x as well (didn't get the chance to test on other versions yet). The problem is that the certificate on https://api.github.com expired and was replaced with a new one.

This is the stack trace you get when trying to connect to a server that has a certificate different than the one pinned. I've fixed the issue by generating the new keystore (check out pull request #4)

Could you please confirm it's working on 2.3.x?

@delgurth
Copy link
Contributor Author

Sorry, it's not working on either a physical samsung i9000 with android 2.3.3, nor on an emulated samsung galaxy s2 with android 2.3.7.

After these tests I tried your development branch, but that's not working either. The exception from retrofit is a bit different then, but the cause is the same I guess. Somehow the keystore is not read properly.

For testing I used the VM's from Genymotion

07-11 09:52:19.750      129-134/? W/InputManagerService﹕ Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@40d7f6e0
07-11 09:52:19.785    3593-3606/co.infinum.https D/Retrofit﹕ ---> HTTP GET https://api.github.com/users/ikust
07-11 09:52:19.789    3593-3606/co.infinum.https D/Retrofit﹕ User-Agent: hello-pinnedcerts
07-11 09:52:19.789    3593-3606/co.infinum.https D/Retrofit﹕ ---> END HTTP (0-byte body)
07-11 09:52:20.285    3593-3606/co.infinum.https D/Retrofit﹕ ---- ERROR https://api.github.com/users/ikust
07-11 09:52:20.316    3593-3606/co.infinum.https D/Retrofit﹕ javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:477)
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:328)
            at com.squareup.okhttp.Connection.upgradeToTls(Connection.java:146)
            at com.squareup.okhttp.Connection.connect(Connection.java:107)
            at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:291)
            at com.squareup.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.java:252)
            at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:203)
            at com.squareup.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:344)
            at com.squareup.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:295)
            at com.squareup.okhttp.internal.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:489)
            at com.squareup.okhttp.internal.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:136)
            at retrofit.client.UrlConnectionClient.readResponse(UrlConnectionClient.java:94)
            at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:49)
            at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:357)
            at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:262)
            at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:313)
            at retrofit.CallbackRunnable.run(CallbackRunnable.java:38)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
            at retrofit.Platform$Android$2$1.run(Platform.java:136)
            at java.lang.Thread.run(Thread.java:1019)
     Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
            at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:161)
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:664)
            at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method)
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:474)
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:328)
            at com.squareup.okhttp.Connection.upgradeToTls(Connection.java:146)
            at com.squareup.okhttp.Connection.connect(Connection.java:107)
            at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:291)
            at com.squareup.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.java:252)
            at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:203)
            at com.squareup.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:344)
            at com.squareup.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:295)
            at com.squareup.okhttp.internal.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:489)
            at com.squareup.okhttp.internal.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:136)
            at retrofit.client.UrlConnectionClient.readResponse(UrlConnectionClient.java:94)
            at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:49)
            at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:357)
            at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:262)
            at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:313)
            at retrofit.CallbackRunnable.run(CallbackRunnable.java:38)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
            at retrofit.Platform$Android$2$1.run(Platform.java:136)
            at java.lang.Thread.run(Thread.java:1019)
     Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
            at org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi.engineValidate(PKIXCertPathValidatorSpi.java:110)
            at java.security.cert.CertPathValidator.validate(CertPathValidator.java:197)
            at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:156)
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:664)
            at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method)
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:474)
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:328)
            at com.squareup.okhttp.Connection.upgradeToTls(Connection.java:146)
            at com.squareup.okhttp.Connection.connect(Connection.java:107)
            at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:291)
            at com.squareup.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.java:252)
            at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:203)
            at com.squareup.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:344)
            at com.squareup.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:295)
            at com.squareup.okhttp.internal.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:489)
            at com.squareup.okhttp.internal.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:136)
            at retrofit.client.UrlConnectionClient.readResponse(UrlConnectionClient.java:94)
            at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:49)
            at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:357)
            at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:262)
            at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:313)
            at retrofit.CallbackRunnable.run(CallbackRunnable.java:38)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
            at retrofit.Platform$Android$2$1.run(Platform.java:136)
            at java.lang.Thread.run(Thread.java:1019)
07-11 09:52:20.316    3593-3606/co.infinum.https D/Retrofit﹕ ---- END ERROR

@ikust
Copy link
Owner

ikust commented Jul 11, 2014

Ok, thanks for the info.

I'll investigate the issue and let you know when I have more info why could this be happening.

@ikust
Copy link
Owner

ikust commented Jul 12, 2014

I think I've figured it out.

The certificates from the certificate chain must be imported in proper order for things to work on 2.3.x. Check #5 for more details. Long story short, certificates must be imported from the bottom of the chain up, and every one under different alias in the keystore. Root certificate should have "ca" alias.

Keystore in master has been updated with the last pull request. I've tested on Samsung GT-S5600 and it worked. Let me know if it works for you now.

It seems that things work on 4.x.x regardless of the order in which certificates are stored in the keystore (additionaly, they can all be imported under the same alias at once). This is what I've concluded from my tests.

@delgurth
Copy link
Contributor Author

Unfortunately still no success.

It succeeds more then it fails now, but it still fails from time to time, about 1 out of 4 requests (both on the physical device as on the emulator). I also tried the OKHttpClient, and that perhaps gives a hint in what's going wrong. Perhaps the certificate chain is not returned in the proper order from time to time.

07-14 09:30:56.940    1652-1695/co.infinum.https D/Retrofit﹕ ---> HTTP GET https://api.github.com/users/ikust
07-14 09:30:56.940    1104-1111/system_process W/InputManagerService﹕ Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@b6653588
07-14 09:30:56.940    1652-1695/co.infinum.https D/Retrofit﹕ User-Agent: hello-pinnedcerts
07-14 09:30:56.940    1652-1695/co.infinum.https D/Retrofit﹕ ---> END HTTP (0-byte body)
07-14 09:30:57.170    1652-1695/co.infinum.https D/Retrofit﹕ ---- ERROR https://api.github.com/users/ikust
07-14 09:30:57.180    1652-1695/co.infinum.https D/Retrofit﹕ javax.net.ssl.SSLHandshakeException: org.bouncycastle.jce.exception.ExtCertPathValidatorException: IssuerName(CN=DigiCert High Assurance EV Root CA, OU=www.digicert.com, O=DigiCert Inc, C=US) does not match SubjectName(CN=DigiCert SHA2 High Assurance Server CA, OU=www.digicert.com, O=DigiCert Inc, C=US) of signing certificate.
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:477)
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:328)
            at com.squareup.okhttp.Connection.upgradeToTls(Connection.java:146)
            at com.squareup.okhttp.Connection.connect(Connection.java:107)

@delgurth
Copy link
Contributor Author

Never mind that... That it worked was because I didn't have any certificate pinning. It needs some more rework.

Not sure why, but using a custom trust manager helps. Even though the exception which should work around the certificate order problem is never thrown, it fixes my problem on both the physical as emulated device. I'm still getting the problems with the apache client, but no longer with the retrofit & okhttp client.

I used this as an example:

https://github.com/rfreedman/android-ssl/blob/master/src/main/java/com/chariotsolutions/example/http/SSLContextFactory.java
https://github.com/rfreedman/android-ssl/blob/master/src/main/java/com/chariotsolutions/example/http/CustomTrustManager.java

See: https://github.com/delgurth/hello-pinnedcerts/commit/cfe90939a675adcc9d788a177cbc6c7a8d636979

@delgurth
Copy link
Contributor Author

Figured out why the CustomTrustManager worked. It didn't do anything with the certificates in the key store, so no certificate pinning was happening at all, so no problem.

The problem I'm encountering also doesn't seem to be related to the order in which the certificate chain is send by the server. It's something weird within the android specific version of the BouncyCastle library. If you try it a few times in a row, it will succeed in the end. But I don't like such magic, so what I'm doing now is:

  1. check if the normal flow is working
  2. check if it's perhaps a certificate chain order problem, if so, try it again with fixed ordering
  3. if it's not the certificate chain order, see if we have an alias for the certificate in our keystore. If it's found, we trust it and it's ok.

With 3, you only need to provide the certificate at the level you want to verify (verification of the server certificate itself is generally a bad idea, since those certificates expire pretty quickly).

See: delgurth@a8f9642

Not sure if you want to pull all that, since the fix is only in the retrofit client and not in the apache one.

Other changes:

  • I added an oauth token to the Retrofit requests so you can try more then 60 times a hour. You need to put that in the strings.xml file.
  • I changed the not working URL to use the Retrofit client, as I only fixed it in the retrofit client and not in the Apache client.
  • I added some more output, so you can see the difference between the requests. Guess I should add a request counter as well, so you see that it's being updated each request...

@ikust
Copy link
Owner

ikust commented Jul 15, 2014

Sorry, didn't get the chance to reply earlier.

Will check it. You can make a pull request. If the retrofit OkHttp client is working I'll pull and update the readme with a disclaimer that Apache client doesn't work properly on 2.3.x.

It is actually recommended to use HttpUrlConnection / HttpsUrlConnection on Android > 2.2:
http://android-developers.blogspot.com/2011/09/androids-http-clients.html

I just wasn't aware that Apache had such serious bugs on higher Android versions.

OkHttp Client is based on HttpUrlConnection if I recall right, so it is the right choice for 2.3.x and above.

@ikust
Copy link
Owner

ikust commented Jul 16, 2014

Merged your changes to master, and updated the readme file with a note that pinning Apache client won't work on Android 2.3.x.

Checked the OkHttpClient on 2.3.7 Genymotion image and it is working as you described.

Feel free to check if I've missed something.

@delgurth delgurth closed this as completed Dec 3, 2014
@opnchaudhary
Copy link

I am using this library with Retrofit-1.7.1. But I am getting the following exception. This doesn't seem to because of hello-pinnedcert library, but can you help me what I might be doing wrong? The app is for Android API >14

 D/Retrofit﹕ javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xcdb008: Failure in SSL library, usually a protocol error
    error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol (external/openssl/ssl/s23_clnt.c:683 0x40298cf5:0x00000000)
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:460)
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:257)
            at com.squareup.okhttp.Connection.upgradeToTls(Connection.java:183)
            at com.squareup.okhttp.Connection.connect(Connection.java:151)
            at com.squareup.okhttp.OkHttpClient$1.connect(OkHttpClient.java:84)
            at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:321)
            at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:241)
            at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:420)
            at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:371)
            at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:466)
            at com.squareup.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:105)
            at com.squareup.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:25)
            at retrofit.client.UrlConnectionClient.readResponse(UrlConnectionClient.java:73)
            at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.java:38)
            at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.java:321)
            at retrofit.RestAdapter$RestHandler.access$100(RestAdapter.java:220)
            at retrofit.RestAdapter$RestHandler$2.obtainResponse(RestAdapter.java:278)
            at retrofit.CallbackRunnable.run(CallbackRunnable.java:42)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
            at retrofit.Platform$Android$2$1.run(Platform.java:142)
            at java.lang.Thread.run(Thread.java:856)
     Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xcdb008: Failure in SSL library, usually a protocol error
    error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol (external/openssl/ssl/s23_clnt.c:683 0x40298cf5:0x00000000)
            at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method)
            at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:410)
            ... 21 more

@delgurth
Copy link
Contributor Author

@opnchaudhary First of all, it's bad practice to comment on an issue with a question that's unrelated to the issue being addressed, certainly a closed one. None the less I'll try to help you.

But since you do not provide details about the host that you are experiencing problems with it's hard to help you. It might be that the URL you try to connect with has SSL support disabled. Otherwise it's probably using a SSL dialect that your client does not support.
See if this answer is helping you: http://stackoverflow.com/a/15168180

@opnchaudhary
Copy link

Sorry for the bad practice. Anyways the problem is fixed. It was because of SSLv3 enabled in the server along with other versions. Disabled v3 and its working fine. Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants