ptrd / kwik

A QUIC client, client library and server implementation in Java. Supports HTTP3 with "Flupke" add-on.
GNU General Public License v3.0
383 stars 55 forks source link

Handshake error in Android #27

Closed DanielViniciusAlves closed 1 year ago

DanielViniciusAlves commented 1 year ago

Hello, I am new to both Java and QUIC and I am currently working on an app that connects to a local server using the QUIC protocol. While my code works perfectly in my Java project, I am encountering a handshake failure error when I try to implement it in Android. Is there a missing configuration that I need to set up? Please note that this is just a test code and I apologize for the presence of this much try-catch blocks.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        StrictMode.ThreadPolicy gfgPolicy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(gfgPolicy);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        SysOutLogger log = new SysOutLogger();
        QuicClientConnectionImpl.Builder builder = QuicClientConnectionImpl.newBuilder();
        try {
            builder.uri(new URI("//" + "192.168.0.113" + ":" + "4000"));
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
        builder.noServerCertificateCheck();
        builder.logger(log);
        try {
            builder.build();
        } catch (SocketException e) {
            throw new RuntimeException(e);
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
        QuicClientConnection connection = null;
        try {
            connection = builder.build();
        } catch (SocketException e) {
            throw new RuntimeException(e);
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }

        try {
            connection.connect(5000, "sample");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        try {
            send_package("hello mate!", connection);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        try {
            send_package("look, a second request on a separate stream!", connection);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        connection.closeAndWait();
    }

    private static void send_package(String payload, QuicClientConnection connection) throws IOException {
        QuicStream quicStream = connection.createStream(true);
        byte[] requestData = payload.getBytes(StandardCharsets.US_ASCII);
        quicStream.getOutputStream().write(requestData);
        quicStream.getOutputStream().close();
    }
}

And this is the error message:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.myapplication, PID: 21818
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.myapplication/com.example.myapplication.MainActivity}: java.lang.RuntimeException: java.net.ConnectException: Handshake error
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2957)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3032)
        at android.app.ActivityThread.-wrap11(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1696)
        at android.os.Handler.dispatchMessage(Handler.java:105)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6944)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
     Caused by: java.lang.RuntimeException: java.net.ConnectException: Handshake error
        at com.example.myapplication.MainActivity.onCreate(MainActivity.java:54)
        at android.app.Activity.performCreate(Activity.java:7183)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1220)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2910)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3032) 
        at android.app.ActivityThread.-wrap11(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1696) 
        at android.os.Handler.dispatchMessage(Handler.java:105) 
        at android.os.Looper.loop(Looper.java:164) 
        at android.app.ActivityThread.main(ActivityThread.java:6944) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374) 
     Caused by: java.net.ConnectException: Handshake error
        at net.luminis.quic.QuicClientConnectionImpl.connect(QuicClientConnectionImpl.java:234)
        at net.luminis.quic.QuicClientConnectionImpl.connect(QuicClientConnectionImpl.java:169)
        at com.example.myapplication.MainActivity.onCreate(MainActivity.java:52)
        at android.app.Activity.performCreate(Activity.java:7183) 
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1220) 
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2910) 
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3032) 
        at android.app.ActivityThread.-wrap11(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1696) 
        at android.os.Handler.dispatchMessage(Handler.java:105) 
        at android.os.Looper.loop(Looper.java:164) 
        at android.app.ActivityThread.main(ActivityThread.java:6944) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374) 
ptrd commented 1 year ago

Code looks good to me. If an error is logged to stdout (as you are using the SysOutLogger), is there a way you can see it? I guess the underlying error is logged.... Did you see https://github.com/ptrd/kwik/issues/23?

DanielViniciusAlves commented 1 year ago

I saw this issue, but It didn't clarify to me what is this error and doesn't seam to be related to the error that I'm getting, again I'm new to android development so I might be wrong. This is the stdout show in the logcat:

2023-02-14 10:29:53.859  5676-5676  adbd                    adbd                                 E  service_to_fd: shell:am force-stop com.example.myapplication
2023-02-14 10:29:54.217  5676-5676  adbd                    adbd                                 E  service_to_fd: exec:am force-stop com.example.myapplication
2023-02-14 10:29:54.286  5676-5676  adbd                    adbd                                 E  service_to_fd: shell:am start -n "com.example.myapplication/com.example.myapplication.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
---------------------------- PROCESS STARTED (20596) for package com.example.myapplication ----------------------------
2023-02-14 10:29:54.527 20596-20596 AppCompatDelegate       com.example.myapplication            D  Checking for metadata for AppLocalesMetadataHolderService : Service not found
2023-02-14 10:29:54.777 20596-20630 System.out              com.example.myapplication            I  29:54.691 Error: Connection closed by peer with transport error 2: 
2023-02-14 10:29:57.785 20596-20596 AndroidRuntime          com.example.myapplication            D  Shutting down VM
2023-02-14 10:29:57.797 20596-20596 AndroidRuntime          com.example.myapplication            E  FATAL EXCEPTION: main
                                                                                                    Process: com.example.myapplication, PID: 20596
                                                                                                    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.myapplication/com.example.myapplication.MainActivity}: java.lang.RuntimeException: java.net.ConnectException: Handshake error
                                                                                                        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2957)
                                                                                                        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3032)
                                                                                                        at android.app.ActivityThread.-wrap11(Unknown Source:0)
                                                                                                        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1696)
                                                                                                        at android.os.Handler.dispatchMessage(Handler.java:105)
                                                                                                        at android.os.Looper.loop(Looper.java:164)
                                                                                                        at android.app.ActivityThread.main(ActivityThread.java:6944)
                                                                                                        at java.lang.reflect.Method.invoke(Native Method)
                                                                                                        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
                                                                                                        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
                                                                                                    Caused by: java.lang.RuntimeException: java.net.ConnectException: Handshake error
                                                                                                        at com.example.myapplication.MainActivity.onCreate(MainActivity.java:54)
                                                                                                        at android.app.Activity.performCreate(Activity.java:7183)
                                                                                                        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1220)
                                                                                                        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2910)
                                                                                                        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3032) 
                                                                                                        at android.app.ActivityThread.-wrap11(Unknown Source:0) 
                                                                                                        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1696) 
                                                                                                        at android.os.Handler.dispatchMessage(Handler.java:105) 
                                                                                                        at android.os.Looper.loop(Looper.java:164) 
                                                                                                        at android.app.ActivityThread.main(ActivityThread.java:6944) 
                                                                                                        at java.lang.reflect.Method.invoke(Native Method) 
                                                                                                        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327) 
                                                                                                        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374) 
                                                                                                    Caused by: java.net.ConnectException: Handshake error
                                                                                                        at net.luminis.quic.QuicClientConnectionImpl.connect(QuicClientConnectionImpl.java:234)
                                                                                                        at net.luminis.quic.QuicClientConnectionImpl.connect(QuicClientConnectionImpl.java:169)
                                                                                                        at com.example.myapplication.MainActivity.onCreate(MainActivity.java:52)
                                                                                                        at android.app.Activity.performCreate(Activity.java:7183) 
                                                                                                        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1220) 
                                                                                                        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2910) 
                                                                                                        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3032) 
                                                                                                        at android.app.ActivityThread.-wrap11(Unknown Source:0) 
                                                                                                        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1696) 
                                                                                                        at android.os.Handler.dispatchMessage(Handler.java:105) 
                                                                                                        at android.os.Looper.loop(Looper.java:164) 
                                                                                                        at android.app.ActivityThread.main(ActivityThread.java:6944) 
                                                                                                        at java.lang.reflect.Method.invoke(Native Method) 
                                                                                                        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327) 
                                                                                                        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374) 
---------------------------- PROCESS ENDED (20596) for package com.example.myapplication ----------------------------

I got one warning about the Missing RSASSA-PSS support, but I wasn't able to replicate so I can show to you.

ptrd commented 1 year ago

If you use latest commit https://github.com/ptrd/kwik/commit/7dc340d576e9adcd55c98b74b5b3e6f357d37a89, the handshake error should contain the root cause. Missing RSASSA-PSS support could be the probleem indeed, as this was also reported in issue #23.

DanielViniciusAlves commented 1 year ago

Okay, I will try to use BouncyCastle as you suggested to solve this issue. Just out of curiosity, is RSASSA-PSS used to encrypt all of the messages sent during the handshake, and if so, is this the only encryption used in this process?

ptrd commented 1 year ago

1) See getSignatureAlgorithm() in https://github.com/ptrd/agent15/blob/master/src/net/luminis/tls/handshake/TlsEngine.java. It is used for certificate validation (so only in the handshake). Whether it is actually used depends on the selected signature scheme. When using ECDSA you won't need RSASSA-PSS; unfortunately, the server determines which signature schema is used ;-(.

2) For encryption, TLS (and QUIC) use AES or ChaCha20, see https://www.davidwong.fr/tls13/#appendix-B.4 and aeadEncrypt() in https://github.com/ptrd/kwik/blob/master/src/main/java/net/luminis/quic/crypto/Keys.java and https://github.com/ptrd/kwik/blob/master/src/main/java/net/luminis/quic/crypto/Chacha20Keys.java.

ptrd commented 1 year ago

Good news, I have a working sample on Android and it was much simpler than I thought ;-). Here are the steps i took:

  1. In TlsEngine class, method getSignatureAlgorithm, replace string constant "RSASSA-PSS" by "SHA256withRSA/PSS"
  2. In TlsClientEngine class, method checkCertificateValidity, replace trustMgr.checkServerTrusted(certificates.toArray(X509Certificate[]::new), "UNKNOWN"); by
    X509Certificate[] x509CertificatesArray = new X509Certificate[certificates.size()];
    for (int i = 0; i < certificates.size(); i++) {
        x509CertificatesArray[i] = certificates.get(i);
    }
    trustMgr.checkServerTrusted(x509CertificatesArray, "UNKNOWN");

In Android project, make sure to request persmission ACCESS_NETWORK_STATE (and INTERNET of course). In the Android build, I also set coreLibraryDesugaringEnabled to true and added dependency for desugaring (coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.2")), but i'm not even sure that is required.

When exploring further, we'll probably encounter some more List.toArray() calls that must be replaced and maybe other API calls that are not support by Android nor by the desugaring library, let's see how far we can get.

DanielViniciusAlves commented 1 year ago

Awesome! Thank you for the explanation. I'm also making a library in Java for my college final project(obviously much smaller), so your explanation is really helping me understand the Quic protocol better. Regarding the problem with RSASSA-PSS, I'll try it out and see what happens. If it works, perhaps you can add these alterations to the main branch. It would surely facilitate the use of the library on Android a lot!

ptrd commented 1 year ago

Yes, i will incorporate both changes in the main branch somehow.

ptrd commented 1 year ago

Done. Use PlatformMapping.usePlatformMapping(PlatformMapping.Platform.Android); before starting a connection.

glush commented 1 week ago

Do I Need use PlatformMapping.usePlatformMapping(PlatformMapping.Platform.Android) on Android client side? Can not find import.

ptrd commented 1 week ago

It is in package net.luminis.tls.env provided by Agent15. You need to use it when running on Android.