TooTallNate / Java-WebSocket

A barebones WebSocket client and server implementation written in 100% Java.
http://tootallnate.github.io/Java-WebSocket
MIT License
10.52k stars 2.58k forks source link

Is WSS with client certificate authentication supported in this library ? #420

Closed sristi closed 7 years ago

sristi commented 7 years ago

I tried to implement WSS connection using SSLContext created from client certificate and CA certificate just like we do in plain java/JEE. But after the connection attempt, the socket is never opened.

brobits commented 7 years ago

Do you receive a framing exception after the initial connect? Does your socket open if you trust all certificates? Not recommending to deploy a trust all solution to production, but it may help identifying the issue.

sristi commented 7 years ago

No Framing Exception. No, there is no possibility to connect to server without presenting client's certificate(Two-way SSL). However, Socket gets connected (and works OK) in Server Authentication (One-way SSL).

hellochenwang commented 7 years ago

I tried with Chrome, always getting "WebSocket connection to 'wss://localhost:8887/' failed: WebSocket opening handshake timed out" error

sristi commented 7 years ago

@hellochenwang, which type of authentication are you using -- server-authentication or client-authentication ? I have worked with both type of authentication and there is no problem.

BTW, the problem we are discussing is getting connected from Android WSS:// clients.

hellochenwang commented 7 years ago

@sristi I'm using server-authentication. Basically just like accessing https from chrome, in my case i'm trying to access wss from chrome. Did you modify the code to get it work?

So you are doing two way authentication(client verifies server's identity and server also verifies client's) with browser as the client? If this works, one way should work too.

In the example folder, there's a SSLClientExample.java and SSLServerExample.java. They work in paris, but SSLServerExample doesn't work with browsers.

sristi commented 7 years ago

@hellochenwang, No modification in this library code (if you had asked about it) has been done. For server-authentication, wss connection from Chrome client just works fine.

I've not used SSLServerExample from this library. For server-side sockets, I've used JEE7 libraries. I've been using this (Java-WebSocket) library only for connection from Android app to my JEE7 web application server.

For testing ws/wss websockets, I'm using chrome's extension called 'Simple WebSocket Client 0.1.3' (you might be already using it & also available for FireFox too).

martinalig commented 7 years ago

@sristi, have you been able to use client certificates with this library?

marci4 commented 7 years ago

Hello @sristi and others,

could you please try to build your jar directly from the sources? I merged some pull requests fixing bugs with wss!

Greetings marci4

sristi commented 7 years ago

Hello @marci4, I built the jar from source code with maven. The jar looks ok. I attempted to use it in android app for WSS connection with (Self-signed)client-certificate, and it was working Ok.

Cheers!

marci4 commented 7 years ago

Hello @sristi,

please don't use maven right now. We haven't pushed an update to this repository yet! So there are propably not all relevant changes included.

(If you are unable to build the jar directly, fell free to ask)

Greetings marci4

sristi commented 7 years ago

Hello @marci4, Actually I built jar from the source cloned from github (and built jar using mvn clean install from CLI) not from maven central. I think you got misled by my last comment. Sorry for that.

Greetings,

marci4 commented 7 years ago

Ahhh ok. Sorry for that then. (English is not my first language :( )

Could you provide some code for future developers? If it is fine for you I would add it to the wiki.

Greetings Marci4

sristi commented 7 years ago

Hello @marci4, Here I'm posting my dummy activity code that uses the java_websocket library to make WSS connection with client certificate authentication. Though the source is very rough, but the code is functional, at least it fulfills the required functionality for demo. I have tested the functionality on android API level 10 to 25, and it works OK. So, here we go.

This is the layout code:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.syntech.websockettester.SecureWebsocketActivity" 
    android:orientation="vertical">
<LinearLayout android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <Button android:id="@+id/btnTestWss"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="WSS"
        android:layout_weight="4"/>

</LinearLayout>
<LinearLayout android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <TextView
        android:id="@+id/websocketResponse"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="kkk"
        android:scrollbars="vertical"/>
    </LinearLayout>
</LinearLayout>

And this is the Activity code:

package com.sristi.websockettester;

import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyStore;
import java.util.List;

import org.apache.http.conn.ssl.SSLSocketFactory;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_17;
import org.java_websocket.handshake.ServerHandshake;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

/**
 * @author sristi
 * 
 * This is dummy activity to implement java_websocket library [ref. https://github.com/TooTallNate/Java-WebSocket.git]
 * in android platforms. 
 * 
 * Secure websocket connection with client certificate has been implemented in this activity.
 * Functionality of this activity has been tested from android platforms 2.3.3 [API level 10] to 7.1.1[API level 25]
 * and works OK in all API levels. 
 * NOTE: R.raw.ca is my application's truststore in BKS format [I've placed the file in raw folder]
 * R.raw.keystore is my application's client certificate in P12 format [I've placed the file in raw folder]
 * webServiceUri is my server's websocket end point URL 
 */
public class SecureWebsocketActivity extends Activity {

    private static final String tag = "WEBSOCKET_ACTIVITY";
    private static WebSocketClient webSocketClient;

    private String webServiceUri = "wss://107.233.47.33:8443/mywebservice/services/";

    TextView websocketResponse;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final Button btnOpenSecureWebSocket = (Button)findViewById(R.id.btnTestWss);
        websocketResponse = (TextView)findViewById(R.id.websocketResponse);
        websocketResponse.setMovementMethod(new ScrollingMovementMethod());

        btnOpenSecureWebSocket.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                connectWebSocket();
            }
        });

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @SuppressLint("TrulyRandom")
    private void connectWebSocket() {
        try {
            URI uri = new URI(webServiceUri);
            webSocketClient = new WebSocketClient(uri, new Draft_17()) {
                @Override
                public void onOpen(ServerHandshake serverHandshake) {
                    Log.i(tag, "SOCKET OPENED");
                }

                @SuppressLint("NewApi")
                @Override
                public void onMessage(final String message) {
                    Log.i(tag, "Message from server is:" + message);
                    runOnUiThread(new Runnable() {

                        @Override
                        public void run() {
                            updateUi(message);

                        }
                    });

                }
                @Override
                public void onClose(int i, final String s, boolean b) {
                    if (webSocketClient == null) {
                        connectWebSocket();
                    }
                    logEvent("Websocket", "Closed " + s);
                }

                @Override
                public void onError(Exception e) {
                    logEvent("Websocket", "Error " + e.getMessage());
                }
            };

            //Connect the secure websoket now
            if(null!=webSocketClient && !webSocketClient.isOpen())
            {   
                try {
                    if (webServiceUri.startsWith("wss")){
                        logEvent("SELF-Signed Conn",
                                "Trying wss conn with Self-Signed Certificate...");

                        webSocketClient.setSocket(getSslSocketFactory().createSocket());

                        webSocketClient.connect();
                    }
                } catch (Exception e2) {
                    logEvent("WebSocket Unreachable", e2.getMessage());
                }
            }
        } catch (URISyntaxException e) {
            logEvent("WebSocket got some exception:", e.getMessage());
            return;
        }
    }

    private void logEvent(String title, String eventMessage) {
        Log.i(title, eventMessage);
    }

    /**
     * @param myPackage
     * @return
     */
    public boolean isApplicationGuiActive(String myPackage) {
        ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> runningTaskInfo = manager
                .getRunningTasks(1);
        ComponentName componentInfo = runningTaskInfo.get(0).topActivity;
        return componentInfo.getPackageName().equals(myPackage);
    }

    private SSLSocketFactory getSslSocketFactory() {
        try {
            // Get an instance of the Bouncy Castle KeyStore format
            KeyStore truststore = KeyStore.getInstance("BKS");
            KeyStore keystore = KeyStore.getInstance("PKCS12");
            String keystorePass = "MY_CLIENT_CERTIFICATE_PASS";
            String truststorePass = "MY_CLIENT_TRUSTSTORE_PASS";
            // Get the raw resource, which contains the keystore with
            // your trusted certificates (root and any intermediate certs)
            InputStream truststoreBksStream = getResources().openRawResource(R.raw.ca);
            InputStream keystoreStream = getResources().openRawResource(R.raw.keystore);
            try {
                // Initialize the keystore with the provided trusted certificates
                truststore.load(truststoreBksStream, truststorePass.toCharArray());
                keystore.load(keystoreStream, keystorePass.toCharArray());
            } finally {
                truststoreBksStream.close();
                keystoreStream.close();
            }
            SSLSocketFactory sf = new SSLSocketFactory(keystore, keystorePass, truststore);
            sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            return sf;
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }
    private void updateUi(String currentMessage)
    {
        String oldMessage = websocketResponse.getText().toString();
        websocketResponse.setText(oldMessage+"\n"+currentMessage);
    }
}

And this one is Manifest file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.sristi.websockettester"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="19" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".SecureWebsocketActivity"
            android:configChanges="orientation|screenSize|keyboardHidden"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>

cheers!

sristi commented 7 years ago

OMG! the automatic formats here resulted awful to my codes!

marci4 commented 7 years ago

Hello @sristi

thx for your example!

(btw fixed the formatting for you :) )

Greetings marci4