Lack of mutual authorization on Android w / javax.net.ssl.SSLHandshakeException: Failed to complete the handshake
I am trying to get a mutual authentication request to work in android. I am testing my server so I have my own CA and client certificate.
Thus, I will have to allow the uncertified server certificate.
That's what I'm 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 AsyncTask will execute 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've tested this request in the browser and in the iOS client, but I can't seem to get it to work on Android.
I think this is the correct way to resolve 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 myself with the server's server certificate:
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 this, I get this:
javax.net.ssl.SSLHandshakeException: Handshake error
EDIT 2:
I am on Lollipop and use apache as my web server. My Apache webserver 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 failed with Handshake handshake in Android 5.0 Lollipop
Not sure what I need to change. I've looked all over the place and it looks like the recommended config (for now).
EDIT 3:
Here is the complete 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)
source to share
I am trying to get a mutual authentication request to work on android ...
(comment) CA server is not installed on the device. It's not obligatory. There must be only a client certificate to complete the handshake. This is why I have uploaded a zero trust to allow an uncertified server certificate.
OK, so the easiest solution is to install the CA on the device. I understand that you do not want to do this, and I really do not blame you. This is not the best solution because it throws your CA into a mix with others in the CA Zoo.
Since you are doing this all programmatically, I believe this is your solution: Use PEM Encoded CA Cert on the filesystem directly for HTTPS request? ... The client will verify the server certificate with your certification authority and then continue with the client certificate. CA does not need to be installed on the device.
Another alternative is custom TrustManager
. But I prefer the system to perform checks rather than override behavior and checks. There are a lot of checks that you must perform and they are error prone. First, you need to know what RFCs are going to go in order to figure out what those checks are.
I am using custom TrustManager
for the public key. There are many examples of this; see, for example, Certificate and Public Key on the OWASP website. You can use pinning and betray checks because you don't need to trust trust. You either talk to the expected server - with the public key X; or not - and it doesn't matter what the third party says (like CA).
Also keep in mind that Java and Android SSLFactory
have some issues, for example getInstance("TLS")
will also return SSLv3; and TLS 1.1 and 1.2 will be disabled. To fix this, 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 give you all that is strong (around 112 bits of security and up).
!aNULL
removes anonymous protocols, but !kRSA
removes key transfer (but not RSA signing), so you are left with Integer and Elliptic Curve Diffie-Hellman. Below is an example of RSA signing with Au=RSA
.
HIGH
will also give you a weak / injured dash for example MD5
and RC4
so you will explicitly remove them. You also remove unnecessary cipher suites like SRP
and PSK
.
You can check the ciphers below the string 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 line. Overall, I think it looks good. RC4 will give you Legacy cryptographic warnings from some browsers. Just discard it as the uppercase 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
source to share