From 1f9b8e24aee5a15136af9241dae3569131a25234 Mon Sep 17 00:00:00 2001 From: Kacper Kluka Date: Tue, 21 Jun 2022 15:14:41 +0200 Subject: [PATCH 1/5] Fix Java-WebSocket problem on Android below 24 --- .../lib/transport/WebSocketTransport.java | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java b/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java index d6d73b9a7..b795cdeba 100644 --- a/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java +++ b/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java @@ -13,7 +13,10 @@ import java.util.Timer; import java.util.TimerTask; +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; import org.java_websocket.client.WebSocketClient; import org.java_websocket.exceptions.WebsocketNotConnectedException; @@ -147,8 +150,29 @@ class WsClient extends WebSocketClient { @Override public void onOpen(ServerHandshake handshakedata) { Log.d(TAG, "onOpen()"); - connectListener.onTransportAvailable(WebSocketTransport.this); - flagActivity(); + if (isHostnameVerified(params.host)) { + connectListener.onTransportAvailable(WebSocketTransport.this); + flagActivity(); + } else { + connectListener.onTransportUnavailable(WebSocketTransport.this, ConnectionManager.REASON_REFUSED); + close(); + } + } + + /** + * Added because we had to override the onSetSSLParameters() that usually performs this verification. + * When the minSdkVersion will be updated to 24 we should remove this method and its usages. + * https://github.com/TooTallNate/Java-WebSocket/wiki/No-such-method-error-setEndpointIdentificationAlgorithm#workaround + */ + private boolean isHostnameVerified(String hostname) { + SSLSession session = getSSLSession(); + if (HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session)) { + Log.i(TAG, "Successfully verified hostname"); + return true; + } else { + Log.e(TAG, "Hostname verification failed, expected " + hostname + ", found " + session.getPeerHost()); + return false; + } } @Override @@ -234,6 +258,13 @@ public void onError(final Exception e) { connectListener.onTransportUnavailable(WebSocketTransport.this, new ErrorInfo(e.getMessage(), 503, 80000)); } + @Override + protected void onSetSSLParameters(SSLParameters sslParameters) { + // Overriding without calling the setEndpointIdentificationAlgorithm() to solve an issue on Android below 24. + // When the minSdkVersion will be updated to 24 we should remove this empty method. + // https://github.com/TooTallNate/Java-WebSocket/wiki/No-such-method-error-setEndpointIdentificationAlgorithm#workaround + } + private synchronized void dispose() { /* dispose timer */ try { From 2999d2a48e31b9fe2fe1175281c887419c5c7f66 Mon Sep 17 00:00:00 2001 From: Kacper Kluka Date: Wed, 22 Jun 2022 07:58:09 +0200 Subject: [PATCH 2/5] Do not explicitly call connectListener when hostname verification fails as close() will do it --- lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java b/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java index b795cdeba..62cd18a46 100644 --- a/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java +++ b/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java @@ -154,7 +154,6 @@ public void onOpen(ServerHandshake handshakedata) { connectListener.onTransportAvailable(WebSocketTransport.this); flagActivity(); } else { - connectListener.onTransportUnavailable(WebSocketTransport.this, ConnectionManager.REASON_REFUSED); close(); } } From 99927b0877ab57010520358da73625a6ba2b7a47 Mon Sep 17 00:00:00 2001 From: Kacper Kluka Date: Wed, 22 Jun 2022 07:58:37 +0200 Subject: [PATCH 3/5] Change SSLSession variable to a final one --- lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java b/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java index 62cd18a46..ab826f40e 100644 --- a/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java +++ b/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java @@ -164,7 +164,7 @@ public void onOpen(ServerHandshake handshakedata) { * https://github.com/TooTallNate/Java-WebSocket/wiki/No-such-method-error-setEndpointIdentificationAlgorithm#workaround */ private boolean isHostnameVerified(String hostname) { - SSLSession session = getSSLSession(); + final SSLSession session = getSSLSession(); if (HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session)) { Log.i(TAG, "Successfully verified hostname"); return true; From 8831ae3d391b000e48ae961701a8069a3fb6816f Mon Sep 17 00:00:00 2001 From: Kacper Kluka Date: Wed, 22 Jun 2022 07:58:49 +0200 Subject: [PATCH 4/5] Change hostname verification log level to verbose --- lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java b/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java index ab826f40e..576430de2 100644 --- a/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java +++ b/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java @@ -166,7 +166,7 @@ public void onOpen(ServerHandshake handshakedata) { private boolean isHostnameVerified(String hostname) { final SSLSession session = getSSLSession(); if (HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session)) { - Log.i(TAG, "Successfully verified hostname"); + Log.v(TAG, "Successfully verified hostname"); return true; } else { Log.e(TAG, "Hostname verification failed, expected " + hostname + ", found " + session.getPeerHost()); From f35c5b6a11956637a4ed0a9eeebc554693ff8778 Mon Sep 17 00:00:00 2001 From: Kacper Kluka Date: Wed, 22 Jun 2022 09:48:47 +0200 Subject: [PATCH 5/5] Do manual hostname verification only if the automatic one fails --- .../lib/transport/WebSocketTransport.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java b/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java index 576430de2..b49d0db15 100644 --- a/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java +++ b/lib/src/main/java/io/ably/lib/transport/WebSocketTransport.java @@ -150,11 +150,11 @@ class WsClient extends WebSocketClient { @Override public void onOpen(ServerHandshake handshakedata) { Log.d(TAG, "onOpen()"); - if (isHostnameVerified(params.host)) { + if (shouldExplicitlyVerifyHostname && !isHostnameVerified(params.host)) { + close(); + } else { connectListener.onTransportAvailable(WebSocketTransport.this); flagActivity(); - } else { - close(); } } @@ -259,9 +259,16 @@ public void onError(final Exception e) { @Override protected void onSetSSLParameters(SSLParameters sslParameters) { - // Overriding without calling the setEndpointIdentificationAlgorithm() to solve an issue on Android below 24. - // When the minSdkVersion will be updated to 24 we should remove this empty method. - // https://github.com/TooTallNate/Java-WebSocket/wiki/No-such-method-error-setEndpointIdentificationAlgorithm#workaround + try { + super.onSetSSLParameters(sslParameters); + shouldExplicitlyVerifyHostname = false; + } catch (NoSuchMethodError exception) { + // This error will be thrown on Android below level 24. + // When the minSdkVersion will be updated to 24 we should remove this overridden method. + // https://github.com/TooTallNate/Java-WebSocket/wiki/No-such-method-error-setEndpointIdentificationAlgorithm#workaround + Log.w(TAG, "Error when trying to set SSL parameters, most likely due to an old Java API version", exception); + shouldExplicitlyVerifyHostname = true; + } } private synchronized void dispose() { @@ -337,6 +344,7 @@ private synchronized void schedule(TimerTask task, long delay) { private Timer timer = new Timer(); private TimerTask activityTimerTask = null; private long lastActivityTime; + private boolean shouldExplicitlyVerifyHostname = true; } public String toString() {