TakahikoKawasaki / nv-websocket-client

High-quality WebSocket client implementation in Java.
Apache License 2.0
2.03k stars 292 forks source link

Fixed SSL handshake failure on Android versions below Nougat (Version 7/SDK 24) when connecting to WSS endpoints #225

Closed vlasky closed 3 years ago

vlasky commented 3 years ago

When connecting to a WSS endpoint, Android versions below Nougat (Version 7/SDK 24) require setHostname() to be invoked on the socket object, before the connection is established, in order to properly enable SNI.

This used to work but was broken in nv-websocket-client release 2.9. That version introduced RFC 6555 support (the Dual Stack / Happy Eyeballs algorithm enhancement). A side effect of this change was that the getSocket() method was replaced with getConnectedSocket(). This removed the ability for nv-websocket-client users to directly access the socket object before connection establishment and invoke setHostname() on it.

Consequently, attempts to connect to a WSS endpoint on the affected Android versions would unavoidably result in a javax.net.ssl.SSLProtocolException. That is now fixed.

I have modified setServerNames() in SNIHelper.java to check the Android SDK version and invoke setHostname() on the socket object when necessary. setServerNames() is only ever called before connection establishment.

The limitation of this technique is that only one single SNI server name can be passed to setHostname(), so only the first server name passed to the setServerNames() function will be used when running under these old Android versions.

Relevant links:

Issue #187 "Handshake failed (Android 5 and 6)" Issue #112 "RFC 6555 support" PR #183 "Add RFC 6555 support (Dual Stack / Happy Eyeballs)"

TakahikoKawasaki commented 3 years ago

@vlasky Thank you very much! I've released nv-websocket-client 2.13 which includes this PR.

polivmi1 commented 2 years ago

@TakahikoKawasaki this doesn't seem to fix the issue as `private static void initialize() throws Exception { // Constructor which represents javax.net.ssl.SNIHostName(String). // The class is available since Java 1.8 / Android API Level 24 (Android 7.0) sSNIHostNameConstructor = Misc.getConstructor( "javax.net.ssl.SNIHostName", new Class<?>[] { String.class });

    // Method which represents javax.net.ssl.SSLParameters.setServerNames(List<SNIServerName>).
    // The method is available since Java 1.8 / Android API Level 24 (Android 7.0)
    sSetServerNamesMethod = Misc.getMethod(
            "javax.net.ssl.SSLParameters", "setServerNames", new Class<?>[] { List.class });
}`

is still called. This causes a crash on the newest AGP used with Android 33 - 7.3.x

java.lang.NoClassDefFoundError at javax.net.ssl.SNIServerName.<clinit>(Unknown Source) at java.lang.Class.classForName(Native Method) at java.lang.Class.forName(Class.java:308) at java.lang.Class.forName(Class.java:272)

and NoClassDefFoundError is an Error and not an exception

https://developer.android.com/reference/javax/net/ssl/SNIServerName was added with API 24

A fix is to either catch the error, or better prevent this code to run on API < 24