3

I am trying to get a mutual authentication request to work on android. I am testing against my own server so I have a self signed CA and client certificate.

So I will have to allow for untrusted server cert.

Here is what I am doing:

KeyStore clientCertificate = KeyStore.getInstance("PKCS12");
InputStream client_inputStream = getResources().openRawResource(R.raw.client);
clientCertificate.load(client_inputStream, "password".toCharArray());
new SSLRequest(clientCertificate).execute();

Then the AsyncTask to perform the request:

class SSLRequest extends AsyncTask<Void, Void, Void> {

    private Exception exception;
    private KeyStore clientCertificate;

    public SSLRequest(KeyStore clientCertificate) {
        this.clientCertificate = clientCertificate;
    }

    protected Void doInBackground(Void... params) {
        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(null, null);
            SSLSocketFactory sf = new SSLSocketFactory(clientCertificate, null, trustStore);

            HttpParams httpParameters = new BasicHttpParams();
            SchemeRegistry schemeRegistry = new SchemeRegistry();
            schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
            schemeRegistry.register(new Scheme("https", sf, 443));
            ClientConnectionManager cm = new SingleClientConnManager(httpParameters, schemeRegistry);

            DefaultHttpClient httpClient = new DefaultHttpClient(cm, httpParameters);

            HttpGet request = new HttpGet("https://myserver.com/some.json");
            HttpResponse response = httpClient.execute(request);
            response.getEntity().consumeContent();
        } catch (Exception e) {
            this.exception = e;
        }

        return null;
    }

    protected void onPostExecute(Void params) {
        if (exception != null) {
            status.setText("Error making SSL handshake");
        } else {
            status.setText("Successful SSL handshake");
        }
    }
}

I have tested this request in a browser and in an iOS client but I cannot get it to work in Android.

I think this is the right way to allow untrusted server certificates:

KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
SSLSocketFactory sf = new SSLSocketFactory(clientCertificate, "password", trustStore);

Not sure why I am getting:

javax.net.ssl.SSLPeerUnverifiedException

EDIT:

I had to add my own SSLSocketFactory to trust the self signed server cert:

public class MySSLSocketFactory extends SSLSocketFactory {
    SSLContext sslContext = SSLContext.getInstance("TLS");

    public MySSLSocketFactory(KeyStore keystore, String keystorePassword, KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(keystore, keystorePassword, truststore);

        TrustManager trustManager = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };

        sslContext.init(null, new TrustManager[] {trustManager}, null);
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    @Override
    public Socket createSocket() throws IOException {
        return sslContext.getSocketFactory().createSocket();
    }
}

Once I use that I get the following:

javax.net.ssl.SSLHandshakeException: Handshake failed

EDIT 2:

I am on Lollipop and using apache as my web server. My apache web server config has:

#   SSL Protocol support:
# List the enable protocol levels with which clients will be able to
# connect.  Disable SSLv2 access by default:
SSLProtocol all -SSLv2 -SSLv3

#   SSL Cipher Suite:
# List the ciphers that the client is permitted to negotiate.
# See the mod_ssl documentation for a complete list.
SSLHonorCipherOrder on
SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS"

Could this be my problem: HttpClient fails with Handshake Failed in Android 5.0 Lollipop

Not really sure what I need to change. I have looked all over and this seems to be the recommend config (for now).

EDIT 3:

Here is the full stack trace:

06-01 13:23:14.369  24486-24571/mil.nga.giat.handshake W/System.err﹕ javax.net.ssl.SSLHandshakeException: Handshake failed
06-01 13:23:14.369  24486-24571/mil.nga.giat.handshake W/System.err﹕ at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:390)
06-01 13:23:14.369  24486-24571/mil.nga.giat.handshake W/System.err﹕ at com.android.org.conscrypt.OpenSSLSocketImpl.waitForHandshake(OpenSSLSocketImpl.java:623)
06-01 13:23:14.369  24486-24571/mil.nga.giat.handshake W/System.err﹕ at com.android.org.conscrypt.OpenSSLSocketImpl.getInputStream(OpenSSLSocketImpl.java:585)
06-01 13:23:14.369  24486-24571/mil.nga.giat.handshake W/System.err﹕ at org.apache.http.impl.io.SocketInputBuffer.<init>(SocketInputBuffer.java:75)
06-01 13:23:14.369  24486-24571/mil.nga.giat.handshake W/System.err﹕ at org.apache.http.impl.SocketHttpClientConnection.createSessionInputBuffer(SocketHttpClientConnection.java:88)
06-01 13:23:14.369  24486-24571/mil.nga.giat.handshake W/System.err﹕ at org.apache.http.impl.conn.DefaultClientConnection.createSessionInputBuffer(DefaultClientConnection.java:175)
06-01 13:23:14.369  24486-24571/mil.nga.giat.handshake W/System.err﹕ at org.apache.http.impl.SocketHttpClientConnection.bind(SocketHttpClientConnection.java:111)
06-01 13:23:14.371  24486-24571/mil.nga.giat.handshake W/System.err﹕ at org.apache.http.impl.conn.DefaultClientConnection.openCompleted(DefaultClientConnection.java:134)
06-01 13:23:14.371  24486-24571/mil.nga.giat.handshake W/System.err﹕ at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:177)
06-01 13:23:14.371  24486-24571/mil.nga.giat.handshake W/System.err﹕ at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:169)
06-01 13:23:14.371  24486-24571/mil.nga.giat.handshake W/System.err﹕ at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:124)
06-01 13:23:14.372  24486-24571/mil.nga.giat.handshake W/System.err﹕ at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:365)
06-01 13:23:14.372  24486-24571/mil.nga.giat.handshake W/System.err﹕ at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:560)
06-01 13:23:14.372  24486-24571/mil.nga.giat.handshake W/System.err﹕ at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:492)
06-01 13:23:14.372  24486-24571/mil.nga.giat.handshake W/System.err﹕ at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:470)
06-01 13:23:14.372  24486-24571/mil.nga.giat.handshake W/System.err﹕ at mil.nga.giat.handshake.HandshakeActivity$SSLRequest.doInBackground(HandshakeActivity.java:115)
06-01 13:23:14.372  24486-24571/mil.nga.giat.handshake W/System.err﹕ at mil.nga.giat.handshake.HandshakeActivity$SSLRequest.doInBackground(HandshakeActivity.java:85)
06-01 13:23:14.372  24486-24571/mil.nga.giat.handshake W/System.err﹕ at android.os.AsyncTask$2.call(AsyncTask.java:292)
06-01 13:23:14.373  24486-24571/mil.nga.giat.handshake W/System.err﹕ at java.util.concurrent.FutureTask.run(FutureTask.java:237)
06-01 13:23:14.373  24486-24571/mil.nga.giat.handshake W/System.err﹕ at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
06-01 13:23:14.373  24486-24571/mil.nga.giat.handshake W/System.err﹕ at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
06-01 13:23:14.373  24486-24571/mil.nga.giat.handshake W/System.err﹕ at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
06-01 13:23:14.374  24486-24571/mil.nga.giat.handshake W/System.err﹕ at java.lang.Thread.run(Thread.java:818)
06-01 13:23:14.374  24486-24571/mil.nga.giat.handshake W/System.err﹕ Caused by: javax.net.ssl.SSLProtocolException: SSL handshake terminated: ssl=0xa21b8800: Failure in SSL library, usually a protocol error
06-01 13:23:14.374  24486-24571/mil.nga.giat.handshake W/System.err﹕ error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure (external/openssl/ssl/s3_pkt.c:1303 0xb4b57be0:0x00000003)
06-01 13:23:14.375  24486-24571/mil.nga.giat.handshake W/System.err﹕ at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
06-01 13:23:14.375  24486-24571/mil.nga.giat.handshake W/System.err﹕ at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:318)
Community
  • 1
  • 1
lostintranslation
  • 23,756
  • 50
  • 159
  • 262
  • *"..I am trying to get a mutual authentication request to work on android..."* - probably dumb questions, but is your CA installed on the device? And is the server certificate signed by it? – jww Jun 01 '15 at 16:03
  • *"...I think this is the right way to allow untrusted server certificates..."* - nope, that's not it either. [The most dangerous code in the world: validating SSL certificates in non-browser software](https://crypto.stanford.edu/~dabo/pubs/abstracts/ssl-client-bugs.html). – jww Jun 01 '15 at 16:03
  • Its a test server!!! I know a self signed cert is not the way to go for production. I am asking how to mutual handshake with an untrusted server cert. Please re-read the question. – lostintranslation Jun 01 '15 at 16:06
  • Server CA is not installed on the device. It doesn't have to be. Only the client cert needs to be there to complete the handshake. That is why I am loaded a null truststore to allow the untrusted server cert. – lostintranslation Jun 01 '15 at 16:10
  • Thinking this might have something to do with it, http://stackoverflow.com/questions/27112082/httpclient-fails-with-handshake-failed-in-android-5-0-lollipop – lostintranslation Jun 01 '15 at 16:48
  • *"Please re-read the question"* - you said you have a CA: *"I have a self signed CA and client certificate."* - Forgive my ignorance, but what's the point of running a CA if you don't certify anything with it? Or why only certify the client certificate, and not the server certificate? – jww Jun 01 '15 at 17:41
  • *"I know a self signed cert is not the way to go for production..."* - I did not say that. I implied you should not use untrusted server certificates, meaning they have to be validated. Once you validate them, they can be used. It does not matter if you use your private CA or a public CA. – jww Jun 01 '15 at 17:44
  • I am trying to test mutual auth request with a client cert. I could care less about trusting the server right now. – lostintranslation Jun 01 '15 at 19:15
  • `sslContext.init(null, new TrustManager[] {trustManager}, null);` the key managers are missing if you are using mutual auth – EpicPandaForce Jun 01 '15 at 20:57

2 Answers2

3

I never put the client cert in the KeyManager:

    KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
    kmf.init(keystore, "password".toCharArray());
    sslContext.init(kmf.getKeyManagers(), new TrustManager[]{tm}, null);
lostintranslation
  • 23,756
  • 50
  • 159
  • 262
2

I am trying to get a mutual authentication request to work on android...

(comment) Server CA is not installed on the device. It doesn't have to be. Only the client cert needs to be there to complete the handshake. That is why I am loaded a null truststore to allow the untrusted server cert.

OK, so the easiest solution is to install the CA on the device. I understand you don't want to do that, and I don't really blame you. Its not the best solution because it tosses your CA into the mix with others in the CA Zoo.

Since you are doing this all programmatically, then I believe this is your solution: Use PEM Encoded CA Cert on filesystem directly for HTTPS request?. The client will validate the server certificate using your CA, and then continue with the client cert. The CA does not need to be installed on the device.

Another alternative is a custom TrustManager. But I prefer to let the system perform the checks rather than overriding behaviors and checks. There's a lot of checks you have to perform, and they are error prone. For starters, you have to know what RFCs to go to figure out what those checks are.

I will use a custom TrustManager for Public Key Pinning. There are lots of example of that; see, for example, Certificate and Public Key Pinning on OWASP's site. You can use pinning and forgo the checks because you don't need to confer trust. You either talk to the expected server - with public key X; or you don't - and it does not matter what a third party says (like a CA).


Also be aware that Java and Android's SSLFactory has some issues, like getInstance("TLS") will also return SSLv3; and TLS 1.1 and 1.2 will be disabled. To fix that, see Which Cipher Suites to enable for SSL Socket?.


Related to your edit:

SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256
    EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL
    !LOW !3DES !MD5 !EXP !PSK !SRP !DSS"

This usually works fine:

HIGH:!aNULL:!kRSA:!MD5:!RC4:!SRP:!PSK

HIGH will get you everything that's strong (about 112-bits of security and above).

!aNULL removes anonymous protocols, and !kRSA removes key transport (but not RSA signing) so you are left with Integer and Elliptic Curve Diffie-Hellman. You can see an example of RSA signing below with Au=RSA.

HIGH will also get you some weak/wounded cruft, like MD5 and RC4, so you explicitly remove them. You also remove unneeded cipher suites, like SRP and PSK.

You can check the ciphers under the string with openssl ciphers -v 'HIGH:!aNULL:!kRSA:!MD5:!RC4:!SRP:!PSK':

$ openssl ciphers -v 'HIGH:!aNULL:!kRSA:!MD5:!RC4:!SRP:!PSK'
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AES(256)  Mac=SHA384
ECDHE-ECDSA-AES256-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AES(256)  Mac=SHA384
ECDHE-RSA-AES256-SHA    SSLv3 Kx=ECDH     Au=RSA  Enc=AES(256)  Mac=SHA1
ECDHE-ECDSA-AES256-SHA  SSLv3 Kx=ECDH     Au=ECDSA Enc=AES(256)  Mac=SHA1
...

By the way, here is your string. All in all, I think it looks good. RC4 will give you the Obsolete Cryptography warnings from some browsers. Just ditch it since the padding oracles in block ciphers are fixed (again...).

$ openssl ciphers -v 'EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384: \
EECDH+aRSA+SHA256:EECDH+aRSA+RC4:EECDH:EDH+aRSA:RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS'
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(128) Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(128) Mac=AEAD
ECDHE-ECDSA-AES256-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AES(256)  Mac=SHA384
ECDHE-ECDSA-AES128-SHA256 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AES(128)  Mac=SHA256
ECDHE-RSA-AES256-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AES(256)  Mac=SHA384
ECDHE-RSA-AES128-SHA256 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AES(128)  Mac=SHA256
ECDHE-RSA-RC4-SHA       SSLv3 Kx=ECDH     Au=RSA  Enc=RC4(128)  Mac=SHA1
ECDHE-RSA-AES256-SHA    SSLv3 Kx=ECDH     Au=RSA  Enc=AES(256)  Mac=SHA1
ECDHE-ECDSA-AES256-SHA  SSLv3 Kx=ECDH     Au=ECDSA Enc=AES(256)  Mac=SHA1
ECDHE-RSA-AES128-SHA    SSLv3 Kx=ECDH     Au=RSA  Enc=AES(128)  Mac=SHA1
ECDHE-ECDSA-AES128-SHA  SSLv3 Kx=ECDH     Au=ECDSA Enc=AES(128)  Mac=SHA1
ECDHE-ECDSA-RC4-SHA     SSLv3 Kx=ECDH     Au=ECDSA Enc=RC4(128)  Mac=SHA1
DHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=DH       Au=RSA  Enc=AESGCM(256) Mac=AEAD
DHE-RSA-AES256-SHA256   TLSv1.2 Kx=DH       Au=RSA  Enc=AES(256)  Mac=SHA256
DHE-RSA-AES256-SHA      SSLv3 Kx=DH       Au=RSA  Enc=AES(256)  Mac=SHA1
DHE-RSA-CAMELLIA256-SHA SSLv3 Kx=DH       Au=RSA  Enc=Camellia(256) Mac=SHA1
DHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=DH       Au=RSA  Enc=AESGCM(128) Mac=AEAD
DHE-RSA-AES128-SHA256   TLSv1.2 Kx=DH       Au=RSA  Enc=AES(128)  Mac=SHA256
DHE-RSA-AES128-SHA      SSLv3 Kx=DH       Au=RSA  Enc=AES(128)  Mac=SHA1
DHE-RSA-SEED-SHA        SSLv3 Kx=DH       Au=RSA  Enc=SEED(128) Mac=SHA1
DHE-RSA-CAMELLIA128-SHA SSLv3 Kx=DH       Au=RSA  Enc=Camellia(128) Mac=SHA1
ECDH-RSA-RC4-SHA        SSLv3 Kx=ECDH/RSA Au=ECDH Enc=RC4(128)  Mac=SHA1
ECDH-ECDSA-RC4-SHA      SSLv3 Kx=ECDH/ECDSA Au=ECDH Enc=RC4(128)  Mac=SHA1
RC4-SHA                 SSLv3 Kx=RSA      Au=RSA  Enc=RC4(128)  Mac=SHA1
Community
  • 1
  • 1
jww
  • 97,681
  • 90
  • 411
  • 885
  • I am using a custom TrustManager to trust the server cert (trusting all certs). So I still don't see why I need to embed the server CA. – lostintranslation Jun 01 '15 at 19:22
  • Could very well be getInstance("TLS"). I posted the full stack trace. I had SSL3 disabled, but even after enabling it just to see if it worked I got the same error. – lostintranslation Jun 01 '15 at 19:23
  • @lostintranslation - maybe I focused on the wrong thing. I think I focused on *"I will have to allow for untrusted server cert"*. That's sacred ground for me, and its not necessarily true. Sorry about that. – jww Jun 01 '15 at 21:22