Связь через веб-сервис Android Https (SSL / TLS 1.2)
В моем приложении для Android мне нужно связаться с веб-сервисом https и прочитать ответ.
Я сообщил серверу, настроил SSL с TLS 1.2.
Я использую следующий пример кода для подключения к службе (запрос получения https), но только устройства, работающие под управлением Android 5.0 или более поздней версии, могут успешно общаться и читать ответ....
Все остальные устройства ниже этой версии (Android 5.0) не могут общаться и выдают IOException во время попытки установить соединение...
HttpResponse response = null;
try
{
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet();
request.setURI(new URI("https://domain.co.uk/services/pay.aspx?param1=val1¶m2=val2"));
response = client.execute(request);
HttpEntity entity = response.getEntity();
String responseString = EntityUtils.toString(entity);
String decodedResStr = URLDecoder.decode(responseString, "UTF-8");
Log.v("AppState", "Response: " + decodedResStr);
}
catch (Exception e)
{
e.printStackTrace();
Log.v("AppState", "Exception: " + e.getMessage() )
}
ИЛИ ЖЕ
// HttpURLConnection urlConnection = null;
HttpsURLConnection urlConnection = null;
try
{
URL url = new URL("https://domain.co.uk/services/pay.aspx?param1=val1¶m2=val2");
//urlConnection = (HttpURLConnection) url.openConnection();
urlConnection = (HttpsURLConnection) url.openConnection();
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
}
catch (Exception e)
{
e.printStackTrace();
Log.v("AppState", "Exception: " + e.getMessage() )
}
finally
{
urlConnection.disconnect();
}
Вопрос 1
Хотелось бы узнать, делаю ли я здесь что-то не так (если в моем коде отсутствует какой-либо дополнительный параметр, который будет поддерживать более старые версии Android, чем 5.0, для поддержки связи через веб-сервис TLS 1.2)?
вопрос 2
Я просто погуглил и нашел этот ДОКУМЕНТ.
Там говорится, что минимальный поддерживающий браузер в ANDROID для связи с TLS 1.2 - это "Google Android 5.0 OS Browser". Так это же ограничение применяется при попытке подключения по коду (приложения)?
Если да, то какую минимальную версию Android я должен поддерживать, если хотите общаться с этим веб-сервисом (минимальная версия Android, поддерживающая веб-сервисы TLS 1.2)?
Примерные трассировки стека исключений:
Android 2.2 Симулятор
11-06 12:51:01.885: W/System.err(352): java.io.IOException: SSL handshake failure: I/O error during system call, Unknown error: 0
11-06 12:51:01.895: W/System.err(352): at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.nativeconnect(Native Method)
11-06 12:51:01.895: W/System.err(352): at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:316)
11-06 12:51:01.895: W/System.err(352): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection.getSecureSocket(HttpConnection.java:168)
11-06 12:51:01.905: W/System.err(352): at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:399)
11-06 12:51:01.915: W/System.err(352): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:1152)
11-06 12:51:01.915: W/System.err(352): at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:253)
11-06 12:51:01.915: W/System.err(352): at com.serviceapp.WSHelperHttpURLConnection.executeAndroid(WSHelperHttpURLConnection.java:93)
11-06 12:51:01.915: W/System.err(352): at com.serviceapp.HttpPage$1$1.run(HttpPage.java:69)
11-06 12:51:01.915: W/System.err(352): at java.lang.Thread.run(Thread.java:1096)
Android 3.0 Симулятор
11-06 12:56:22.917: W/System.err(447): javax.net.ssl.SSLException: Connection closed by peer
11-06 12:56:22.927: W/System.err(447): at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method)
11-06 12:56:22.927: W/System.err(447): at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:485)
11-06 12:56:22.927: W/System.err(447): at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:323)
11-06 12:56:22.927: W/System.err(447): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection.setupSecureSocket(HttpConnection.java:167)
11-06 12:56:22.937: W/System.err(447): at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:479)
11-06 12:56:22.937: W/System.err(447): at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl$HttpsEngine.makeConnection(HttpsURLConnectionImpl.java:428)
11-06 12:56:22.937: W/System.err(447): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.retrieveResponse(HttpURLConnectionImpl.java:1038)
11-06 12:56:22.937: W/System.err(447): at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:523)
11-06 12:56:22.937: W/System.err(447): at org.apache.harmony.luni.internal.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:283)
11-06 12:56:22.947: W/System.err(447): at com.serviceapp.WSHelperHttpURLConnection.executeAndroid(WSHelperHttpURLConnection.java:93)
11-06 12:56:22.947: W/System.err(447): at com.serviceapp.HttpPage$1$1.run(HttpPage.java:69)
11-06 12:56:22.947: W/System.err(447): at java.lang.Thread.run(Thread.java:1020)
редактирует
Это полная трассировка стека при использовании реализации класса MySSLSocketFactory Роберта на устройстве Android 4.4.2 И Android 5.1.1.
11-06 14:26:46.962: W/System.err(14700): java.lang.IllegalArgumentException: protocol TLS1.2 is not supported
11-06 14:26:46.985: W/System.err(14700): at com.android.org.conscrypt.NativeCrypto.checkEnabledProtocols(NativeCrypto.java:879)
11-06 14:26:46.985: W/System.err(14700): at com.android.org.conscrypt.OpenSSLSocketImpl.setEnabledProtocols(OpenSSLSocketImpl.java:807)
11-06 14:26:46.985: W/System.err(14700): at com.serviceapp.MySSLSocketFactory.createSocket(WSURlCon.java:99)
11-06 14:26:46.986: W/System.err(14700): at com.serviceapp.MySSLSocketFactory.createSocket(WSURlCon.java:1)
11-06 14:26:46.986: W/System.err(14700): at com.android.okhttp.Connection.upgradeToTls(Connection.java:131)
11-06 14:26:46.986: W/System.err(14700): at com.android.okhttp.Connection.connect(Connection.java:107)
11-06 14:26:46.986: W/System.err(14700): at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:294)
11-06 14:26:46.987: W/System.err(14700): at com.android.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.java:255)
11-06 14:26:46.988: W/System.err(14700): at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:206)
11-06 14:26:46.988: W/System.err(14700): at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:345)
11-06 14:26:46.990: W/System.err(14700): at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:296)
11-06 14:26:46.990: W/System.err(14700): at com.android.okhttp.internal.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:179)
11-06 14:26:46.991: W/System.err(14700): at com.android.okhttp.internal.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:246)
11-06 14:26:46.991: W/System.err(14700): at com.serviceapp.WSURlCon.executeAndroid(WSURlCon.java:33)
11-06 14:26:46.992: W/System.err(14700): at com.serviceapp.HttpPage$1$1.run(HttpPage.java:74)
11-06 14:26:46.992: W/System.err(14700): at java.lang.Thread.run(Thread.java:848)
1 ответ
Согласно документации разработчика Android, TLS 1.2 доступен и активирован на устройствах с уровнем API 20+ (Android 4.4 Wearable):
http://developer.android.com/reference/javax/net/ssl/SSLEngine.html
Я предполагаю, что ни одно из ваших тестовых устройств не использует этот уровень API, поэтому вы получили результат, что только 5.0 устройств могут подключаться.
Мой личный опыт показывает, что некоторые устройства 4.4 поддерживают TLS 1.2, но он не включен. Вы можете попробовать включить его, позвонив setEnabledProtocols(new String[]{"TLSv1.2"})
на используемом SSLSocket.
Для этого элегантным решением является реализация собственного SSLSocketFactory с использованием шаблона Proxy:
public class MySSLSocketFactory extends SSLSocketFactory {
SSLSocketFactory sslSocketFactory;
public MySSLSocketFactory(SSLSocketFactory sslSocketFactory) {
super();
this.sslSocketFactory = sslSocketFactory;
}
@Override
public String[] getDefaultCipherSuites() {
return sslSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return sslSocketFactory.getSupportedCipherSuites();
}
@Override
public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(s, host, port, autoClose);
socket.setEnabledProtocols(new String[] { "TLSv1.2" });
return socket;
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(host, port);
socket.setEnabledProtocols(new String[] { "TLSv1.2" });
return socket;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException,
UnknownHostException {
SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(host, port, localHost, localPort);
socket.setEnabledProtocols(new String[] { "TLSv1.2" });
return socket;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(host, port);
socket.setEnabledProtocols(new String[] { "TLSv1.2" });
return socket;
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
throws IOException {
SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(address, port, localAddress, localPort);
socket.setEnabledProtocols(new String[] { "TLSv1.2" });
return socket;
}
Вы можете использовать это так:
...
urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setSSLSocketFactory(new MySSLSocketFactory(urlConnection.getSSLSocketFactory()));
...
Вот котлинская версия ответа Роберта, которая устранила проблему в моем случае:
import java.net.InetAddress
import java.net.Socket
import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
class MySSLSocketFactory(socket_factory :SSLSocketFactory):
SSLSocketFactory()
{
val sslSocketFactory :SSLSocketFactory
init
{
this.sslSocketFactory = socket_factory
}
override fun getDefaultCipherSuites(): Array<String>
{
return this.sslSocketFactory.getDefaultCipherSuites()
}
override fun createSocket(socket: Socket?, host: String?, port: Int, autoclose: Boolean): Socket
{
val socket: SSLSocket = this.sslSocketFactory.createSocket(socket, host, port, autoclose) as SSLSocket
socket.setEnabledProtocols(arrayOf("TLSv1.2"))
return socket
}
override fun createSocket(host: String?, port: Int): Socket
{
val socket: SSLSocket = this.sslSocketFactory.createSocket(host, port) as SSLSocket
socket.setEnabledProtocols(arrayOf("TLSv1.2"))
return socket
}
override fun createSocket(host: String?, port: Int, local_host: InetAddress?, local_port: Int): Socket {
val socket = sslSocketFactory.createSocket(host, port, local_host, local_port) as SSLSocket
socket.enabledProtocols = arrayOf("TLSv1.2")
return socket
}
override fun createSocket(host: InetAddress?, port: Int): Socket
{
val socket = sslSocketFactory.createSocket(host, port) as SSLSocket
socket.enabledProtocols = arrayOf("TLSv1.2")
return socket
}
override fun createSocket(address: InetAddress?, port: Int, local_address: InetAddress?, local_port: Int): Socket
{
val socket = sslSocketFactory.createSocket(address, port, local_address, local_port) as SSLSocket
socket.enabledProtocols = arrayOf("TLSv1.2")
return socket
}
override fun getSupportedCipherSuites(): Array<String>
{
return this.sslSocketFactory.getSupportedCipherSuites()
}
}