I am using SSLSocket to connect to a server. I want to use ssl SNI extension. I can use SSLParameter.setServerNames in java. But it appears in android it is not available till sdk 24. What are my options here?
I had the same problem, solved by using reflection.
Connect the socket using:
SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket socket = (SSLSocket) sf.createSocket(uri.getHost(), 443);
setSNIHost(sf, socket, uri.getHost());
SSLSession s = socket.getSession();
where setSNIHost is declared as:
public void setSNIHost(final SSLSocketFactory factory, final SSLSocket socket, final String hostname) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
Log.i(TAG, "Setting SNI via SSLParameters");
SNIHostName sniHostName = new SNIHostName(hostname);
SSLParameters sslParameters = socket.getSSLParameters();
List<SNIServerName> sniHostNameList = new ArrayList<>(1);
sniHostNameList.add(sniHostName);
sslParameters.setServerNames(sniHostNameList);
socket.setSSLParameters(sslParameters);
} else if (factory instanceof android.net.SSLCertificateSocketFactory) {
Log.i(TAG, "Setting SNI via SSLCertificateSocketFactory");
((android.net.SSLCertificateSocketFactory)factory).setHostname(socket, hostname);
} else {
Log.i(TAG, "Setting SNI via reflection");
try {
socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname);
} catch (Throwable e) {
Log.e(TAG, "Could not call SSLSocket#setHostname(String) method ", e);
}
}
}
I don't really remember where I copied the code for the reflection, I simply added the support for API 24.
Related
I found out that android 4 doesn't play well with ssl , when trying to contact an api with https it causes a crash
javax.net.ssl.SSLException: SSL handshake aborted: ssl=0xb8dbad20: I/O error during system call, Connection reset by peer
Here's what i tried from other similar questions:
if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22) {
try {
Logger.e("under lolipop");
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, new TrustManager[] { new MyTrustManager() }, new SecureRandom());
client.sslSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
Logger.e("HTTPS"+ e.getMessage() );
}
}
Which didn't effect the outcome
And
if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22) {
try {
client.sslSocketFactory(new TLSSocketFactory(), (X509TrustManager)trustAllCerts[0])
.build();
Logger.e("SETUP TRUST SSL");
return client.build();
} catch (KeyManagementException e) {
Logger.e("SETUP TRUST SSL Failed "+e.getMessage());
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
Logger.e("SETUP TRUST SSL Failed "+e.getMessage());
e.printStackTrace();
}
}
return client.build();
}
final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
#Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] chain,
String authType) throws CertificateException {
}
#Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] chain,
String authType) throws CertificateException {
}
#Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[0];
}
} };
This code gives a different error :
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
Is there anyway to fix this , I must support android 4 and also use https ,
Any help will do !
I ran into a similar issue on Android 4.4 some time ago when our backend dropped support for TLS 1.0 and 1.1. I solved this by installing a new security provider with Google Play Services ProviderInstaller.
In your apps gradle.build file add
implementation "com.google.android.gms:play-services-auth:16.0.1"
In your startup Activity call ProviderInstaller.installIfNeeded() as early as possible. Here is an example method that tries to install the provider:
private static void installGooglePlayServicesProvider(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { //Devices with Android 5.1+ should support TLS 1.x out of the box
try {
ProviderInstaller.installIfNeeded(context);
} catch (GooglePlayServicesRepairableException e) {
Log.e("ProviderInstaller", "Google Play Services is out of date!", e);
GoogleApiAvailability.getInstance().showErrorNotification(context, e.getConnectionStatusCode());
} catch (GooglePlayServicesNotAvailableException e) {
Log.e("ProviderInstaller", "Google Play Services is unavailable!", e);
}
}
}
For more information on ProviderInstaller see Goolge developer page:
Patch the security provider with ProviderInstaller
When using TLS 1.2 you might have to force enable support on some devices.
Take a look at the following acticle and their Tls12SocketFactory implementation: Working with TLS 1.2 on Android 4.4 and Lower
Okhttp 3.13.x has dropped support for TLS1.2 and below and only support for Android API 21+. You will have to use Okhttp 3.12.x version branch for using it with Android 4.x devices.
More here
Attempting to establish an SSLSession using a self-signed certificate and the new-ish Network Security Configuration method as well as cwac-netsecurity library for backward compatibility works on all other versions of android (that I have tried) except Android 7.0.
I have an android app that connects to a device to provide a UI for that device. The connection uses a TLSv1 SSLSocket and a self-signed server certificate on the device side. Prior to Nougat, I had the server certs embedded as a BKS key store and loaded at run-time to create a custom TrustManager in order to intialize the SSL context and create the socket. This no longer works under Android 7+. Following some other questions on SO (Cannot connect via SSL using self signed certificate on Android 7 and above), I was able to use the Network Security Configuration method to establish the SSLSocket connection on an Android 8 device (https://developer.android.com/training/articles/security-config#ConfigCustom). For backward compatibility, I am using the cwac-netsecurity library from CommonsWare (https://github.com/commonsguy/cwac-netsecurity). I built a small test application and am testing against openssl s_server. What I have written establishes an SSLSession successfully from android devices running 4.4, 6.0, and 8.0. For some reason however, it does not do so on an android device running 7.0. Here are code snippets that show the test. Any help would be appreciated.
Test code in android app
{
// createSocketFactory
SSLSocketFactory factory = (SSLSocketFactory)SSLSocketFactory.getDefault(); //null;
TrustManagerBuilder tmb = new TrustManagerBuilder();
tmb.withManifestConfig(MainActivity.this);
tmb.withCertChainListener(
new CertChainListener()
{
#Override
public void onChain(X509Certificate[] chain, String domain)
{
if (domain == null)
{
Log.d(TAG, "onChain: Certificate chain for request to unknown domain");
}
else
{
Log.d(TAG, "onChain: Certificate chain for request to: " + domain);
}
for (X509Certificate cert : chain)
{
Log.d(TAG, "onChain: Subject: " + cert.getSubjectX500Principal().getName());
Log.d(TAG, "onChain: Issuer: " + cert.getIssuerX500Principal().getName());
}
}
});
CompositeTrustManager ctm = tmb.build();
try
{
SSLContext sc = SSLContext.getInstance("TLSv1");
sc.init(null, new TrustManager[] { ctm }, new SecureRandom());
factory = sc.getSocketFactory();
}
catch (NoSuchAlgorithmException e)
{
Log.e(TAG, "createSocketFactory: failed to get SSLContext", e);
}
catch (KeyManagementException e)
{
Log.e(TAG, "createSocketFactory: failed to init SSLContext from CompositeTrustManager");
}
// createSslSocket
SSLSocket socket = null;
String addr = "172.31.106.60";
int port = 50001;
if (factory != null)
{
Log.d(TAG, "createSocketFactory - SUCCESS");
try
{
socket = (SSLSocket)factory.createSocket(addr, port);
Log.d(TAG, "createAltSocket - SUCCESS");
socket.setEnabledProtocols(new String[] { "TLSv1" });
socket.setEnabledCipherSuites(new String[] { "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" });
socket.setTcpNoDelay(true);
socket.setKeepAlive(true);
}
catch (IOException e)
{
Log.e(TAG, "createSslSocket - Couldn't create SSLSocket", e);
try
{
socket.close();
}
catch (IOException e1)
{
e1.printStackTrace();
}
finally
{
socket = null;
}
}
}
else
{
Log.d(TAG, "createSocketFactory - FAILED");
}
// testSslSocket
if (socket != null && socket.isConnected())
{
SSLSession session = socket.getSession();
if (session != null && session.isValid())
Log.d(TAG, "testSslSocket: SESSION SUCCESS");
else
Log.d(TAG, "testSslSocket: SESSION FAILED");
try
{
socket.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
network_securiity_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="#raw/test_cert_chain"/>
<certificates src="system"/>
</trust-anchors>
</base-config>
</network-security-config>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.ssltesting">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:networkSecurityConfig="#xml/network_security_config">
<meta-data
android:name="android.security.net.config"
android:resource="#xml/network_security_config" />
test server
openssl s_server -tls1 -WWW -accept 50001 -cert test.crt -key mcv.key -state
In all working cases, the SSLSession is valid at the end of the test routine, and I get logging from the CertChainListener. However, when running this on my Android 7.0 device, all is quiet. I get no logging from the CertChainListener, the SSLSession is not valid, and I get the following on the server side:
Using default temp DH parameters
ACCEPT
SSL_accept:before/accept initialization
SSL3 alert write:fatal:handshake failure
SSL_accept:error in error
140386525526784:error:1408A0C1:SSL routines:ssl3_get_client_hello:no shared cipher:s3_srvr.c:1417:
ACCEPT
I am trying to do certificate pinning. The network library my company used doesn't support pinning. So I have to do it manually.
This is the code I use
protected Void doInBackground(Void... params) {
String actualKey = "OpenSSLRSAPublicKey{modulus=ccf0883ebc511bb86f7f6e360385cf3a" +
"8720fa0d9f3367278baf2fd43d29c21b4384f09ae14207beeb429563639d4388aca65a3" +
"a5f5d2c902bf33e6df904598e6a5a1c037add731bdce606c664368cbc4bb7e269bbda82" +
"ff20bd9ca484f5bd660d5628bca4a8f376acf1cab07f0d9476df283ef44d3bf52d4b730" +
"3187cf587cbb2ce981e01b6cb32ba4f9b197b60013ff19215abb7d2ca9608007df82641" +
"b05127ec9557927e8bd68ff183f8b72720f93152f207f89b446e38fc7aa3db4928f5fb7" +
"92f33898381e7bc5ddb612d2e3a3191854797add8e0d47ed9f7da709e55a89aa7369620" +
"2d90275ada9d43fb462a16839787b6ea3c83df66a1d6e528a38d0d,publicExponent=1" +
"0001}";
try {
SSLSocketFactory factory = HttpsURLConnection.getDefaultSSLSocketFactory();
SSLSocket socket = (SSLSocket) factory.createSocket("prisonvoicemail.com", 443);
socket.startHandshake();
Certificate[] certs = socket.getSession().getPeerCertificates();
Certificate cert = certs[0];
String key = cert.getPublicKey().toString();
Log.d(LOG_TAG, key);
if(key.equals(actualKey)){
Log.d(LOG_TAG, "Success");
} else {
Log.d(LOG_TAG, "Failure");
}
} catch (IOException e){
e.printStackTrace();
}
return null;
But for some reason it doesn't work. When I connect normally it get success, when I connect through a proxy (mitmproxy) to test a different certificate simulating a man in the middle attack, I also get success. It's like its completely bypassing the proxy and going straight to the normal certificiate. I don't know why this is.
Firstly,
I want to use session ticket in android, My code as follows:
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
SSLContext cpmContext = SSLContext.getInstance("TLSv1.2");
cpmContext.init(null, null, null);
SSLSocket socket = (SSLSocket) cpmContext.getSocketFactory().createSocket(ip, port);
socket.setEnabledProtocols(socket.getEnabledProtocols());
socket.setEnabledCipherSuites(socket.getEnabledCipherSuites());
Class c = socket.getClass();
try {
Method m = c.getMethod("setUseSessionTickets",boolean.class);
m.invoke(socket,true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
SSLSession session = socket.getSession();
I capture the data blcok by tcpdump, the code can get "
TLSv1.2 224 New Session Ticket, Change Cipher Spec, Hello Request, Hello Request"
,so I think I get the session ticket, but when I reconnect to server, "session ticket "content of client hello is as follow:
"Extension:sessionTicket TLS
Type: SessionTicket TLS(0x0023)
length:0
Data:(0 bytes)"
it did not execute resume.
then I use SSLCertificateSocketFactory to create SSLSocket:
private Socket createSocketOnLine(final String ip, final int port) throws UnknownHostException, IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException, KeyManagementException {
SSLCertificateSocketFactory sf = (SSLCertificateSocketFactory) SSLCertificateSocketFactory
.getDefault(30 * 1000);
SSLSocket socket = (SSLSocket) sf.createSocket(ip, port);
socket.setEnabledProtocols(socket.getEnabledProtocols());
socket.setEnabledCipherSuites(socket.getEnabledCipherSuites());
enableSessionTicket(sf, socket);
SSLSession session = socket.getSession();
return socket;
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public void enableSessionTicket(SSLCertificateSocketFactory sf, Socket socket) {
if (VERSION.SDK_INT > 17) {
sf.setUseSessionTickets(socket, true);
}
}
this code donot even enable session and the version of tls is always TLSv1.0,who can tell me how to enable it and set version of tls to be tlsv1.2?
PS:I test it on android 4.4 and L
Android's SSLCertificateSocketFactory is broken for you on some/many devices. It does not provide an interface/method for you to specify the enabled protocols. Instead, it simply uses the system default for the enabledProtocols. On earlier Android devices, that would be TLS and SSL3 and not TLSv1.2.
Because of the way it is implemented, you can't subclass it and attempt to override its behavior. There is no way for you to set the TLS protocol you want, let alone to set the ciphersuites that you may need to control.
Unfortunately, some of the other features of SSLCertificateSocketFactory are lost as well. For example, you can't enable SNI or set alpns protocols "officially" without it.
Thus, your solution will need to subclass SSLSocketFactory to provide your own socketfactory to control the settings you need as you do in your first code example. You can use your reflection trick above to enable session tickets in your own createSocket() method implementations.
I'm using gottox socket.io java client for an Android chat application. I could connect to both web-socket and Xhr transport in HTTP mode. But when i switch to HTTPS only Xhr mode is working. i used the default SSL Context as below
SocketIO.setDefaultSSLSocketFactory(SSLContext.getInstance("Default"));
This works fine in Xhr mode. But in websocket transport there are no responses or errors.
Update
It might be that with new versions IO.setDefaultSSLContext and IO. setDefaultHostnameVerifier methods are not available. Instead now we can create our own OkHttpClient and set the hostname verifier and ssl socket factory etc on it as mentioned on socket.io-client-java usage. Here is the sniplet from there:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.hostnameVerifier(myHostnameVerifier)
.sslSocketFactory(mySSLContext.getSocketFactory(), myX509TrustManager)
.build(); // default settings for all sockets
IO.setDefaultOkHttpWebSocketFactory(okHttpClient);
IO.setDefaultOkHttpCallFactory(okHttpClient);
Initial Answer:
I had the same issue with io.socket:socket.io-client:0.7.0 version of socket.io library on Android for long. It used to work fine for http protocol, however for https protocol it had trouble establishing connection giving xhr poll errors.
Following solution works for me without modifying the library itself:
// Socket connection
private Socket mSocket;
// Configure options
IO.Options options = new IO.Options();
// ... add more options
// End point https
String yourEndpoint = "https://whatever.yoururl.com"
String yourHostName = "yoururl.com"
// If https, explicitly tell set the sslContext.
if (yourEndpoint.startsWith("https://")) {
try {
// Default settings for all sockets
// Set default ssl context
IO.setDefaultSSLContext(SSLContext.getDefault());
// Set default hostname
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
return hv.verify(yourHostName, session);
}
};
IO.setDefaultHostnameVerifier(hostnameVerifier);
// set as an option
options.sslContext = SSLContext.getDefault();
options.hostnameVerifier = hostnameVerifier;
options.secure = true;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
// Instantiate the socket
mSocket = IO.socket(mEndpoint, options);
Hope this helps.
It works but you have to do some modifications on io.socket library.
Instead of using the socketio.jar, import into src folder the io.socket library (You'll find inside socket.io-java-client package). There, you have to edit the WebsocketTransport class.
Here you have the solution
https://github.com/Gottox/socket.io-java-client/issues/60
public WebsocketTransport(URI uri, IOConnection connection) {
super(uri);
this.connection = connection;
SSLContext context = null;
try {
context = SSLContext.getInstance("TLS", "HarmonyJSSE");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
}
try {
context.init(null, null, null);
} catch (KeyManagementException e) {
e.printStackTrace();
}
if("wss".equals(uri.getScheme()) && context != null) {
this.setWebSocketFactory(new DefaultSSLWebSocketClientFactory(context));
}
}
And remember to call the setDefaultSSLSocketFactory like this:
socket = new SocketIO();
socket.setDefaultSSLSocketFactory(SSLContext.getDefault());
socket.connect("https://www.myHttpsServer.com:443/");
Hope it helps someone ;)
Websocket with SSL working in AndroidAsync. Using that for now.