Okhttp android Websocket and Jetty Server connecti

2019-01-20 19:00发布

问题:

I have my okhttpclient android websocket and jetty stand-alone server given below. I have to enable strong security for the communication between the android client and jetty server. I am facing issue on client side which is given below.

Server Side

package com.wss.okhttp;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;

import javax.servlet.ServletException;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;

public class JettySSLServer {

    public static void main(String[] args) throws IOException {

        JettyEndpoint endpoint = new JettyEndpoint();

        Server webServer = new Server();

        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.setContextPath("/");
        webServer.setHandler(context);

        // --------------------SSL-Connection Start---------------------------//
        KeyStore keyStore = null;
        Certificate mPinnedCertificate = null;

        try {
            keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null, new char[] {});
            mPinnedCertificate = readPinnedCertificate();
            keyStore.setCertificateEntry("ca", mPinnedCertificate);
        } catch (KeyStoreException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (CertificateException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        SslContextFactory contextFactory = new SslContextFactory();

        contextFactory.setIncludeProtocols("TLSv1.2");
        contextFactory.setKeyStore(keyStore);
        contextFactory.setKeyStorePassword("MIIEpAIBAAKCAQEAtzc9IK4U2YkfgASQ51v3IdjZUKABXw1RzUd+SxS8phI6O7Rb\r\n"
                + "RL+/KDQGPAtdrML2qDzaANIYa8rZ9jAyTnFAHpuykD8ByHf7RhogjPhJEvQDZkiX\r\n"
                + "r0hFS9A0ypqWn3fRWVXTREZTNGKgs0TQMNCY4Lm2H/lrgxNubaROn0KffLt+c5rK\r\n"
                + "7e3NXOcUUTP/tlkeC2JyHVIT8Cv2acaYJDD3PfHY5MSbvIORelVTp67eJkCSM+xF\r\n"
                + "spEi1SRuvRoBT+LMALNiIpi8nYBtNKlyDwmK2w38n11216g5DP3ipfZRHypk6048\r\n"
                + "vCO0qbgfwGfaep54twh94QJ4rjNi9X7f0F0qzCex7vmpJMpJ4gRl02mzni1DanOy\r\n"
                + "ExJB8ImpS3Il2jh2kVSbfLSg66UW33yAMKyCRCXypTSLgMGHetVDS+gHwcyFcE/M\r\n"
                + "nAY/k60CgYEA3ccY7AYSz10czJC0Y2ZPnw6NzESBNlWBgFIODQyKE5J2FKezJsR8\r\n"
                + "+LPRtEn+JeYI5+Q/jZZBR5qMXGaI+tprOlZKTSVcH4PQKOr7Ogd7v9leyH6zrfAe\r\n"
                + "k37acLaLtQE54tIyQVRLZW0dxzCiJ/tobJy+1f4TfWnpuRd4Y9xCnvMCgYEA03zT\r\n"
                + "aQLxW0ZBNbcz9ivDdbjy5kK2m1vA7Rq9LzawR0K9W05WOKUH7T1Ybp/idNTZfjKo\r\n"
                + "k+G2DV9ts/vQEL//3PthWo/FWZ8hsA5P1J+cT0RrwKKgWjCPNArp2l/T4vEdkGdM\r\n"
                + "GBbB6KZe5Wsn+HKPBszU35A8K2pD5PpebV0RGNcCgYBSEMmFFR5Cw2bTv7wwh/xw\r\n"
                + "lBcefj7+FxfrnvF6HKi/Y1P0grXFY7IG6atwtmyoI34qKQjnYLFZSLQlwP9xK/+/\r\n"
                + "v4yRDYEQXFtbuNAsAfbl4A61zES62X7G/4rfaH08Bm8gIr3b9NBNgNojCjkG6H4U\r\n"
                + "qs/nKbSWlOmaxzeSZD/2xwKBgQCJOlz/rc4ouLyFe1v3J0yMLbdHHBDbXD0iXRBW\r\n"
                + "+3iEtNSj03/0/3jWQtEH7y0FPDvoPDzQwEvd/4bym7nVtI/0txTjq5iV38D/OTop\r\n"
                + "sGu/r5jvhVbhTtMNJOu7LCUUA/p4Ad8JXnLyYEoBOXfVKZiPBAg5DKFOVoS5po/x\r\n"
                + "DMuUPwKBgQCa4cym/jJnK6r7h2xzE5bHLcniuud0F1DgCMkW/x026z4owpGtSCyK\r\n"
                + "BEQn/PY0rnSioRkcNjm5leGb1oOaFcT/QBgGhVpm09TyA/v8tj96pP631fYayzZh\r\n"
                + "lBEvszx6LOLEBbIioiXFtp1JhmWzkxvbuB114S3ChK+IKVrgZYTjvQ==");

        contextFactory.setIncludeCipherSuites("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256");

        contextFactory.setTrustAll(true);

        SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(contextFactory,
                org.eclipse.jetty.http.HttpVersion.HTTP_1_1.toString());

        ServerConnector sslConnector = new ServerConnector(webServer, sslConnectionFactory);
        sslConnector.setPort(8443);
        webServer.addConnector(sslConnector);

        ServerConnector wsConnector = new ServerConnector(webServer);
        wsConnector.setPort(50055);
        webServer.addConnector(wsConnector);
        // --------------------SSL-Connection End---------------------------//

        ServerContainer container;
        try {
            container = WebSocketServerContainerInitializer.configureContext(context);
            container.addEndpoint(endpoint.getClass());

            WebSocketServerContainerInitializer.configureContext(context);

            webServer.start();

        } catch (ServletException servEx) {
            System.out.println(servEx.getMessage());
        } catch (DeploymentException depEx) {
            System.out.println(depEx.getMessage());
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }

    }

    private static Certificate readPinnedCertificate() throws CertificateException, IOException {

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream caInput = new FileInputStream(System.getProperty("user.dir") + "/assets/va_cert.pem");
        Certificate ca;
        try {
            ca = cf.generateCertificate(caInput);
        } finally {
            caInput.close();
        }

        return ca;
    }

}

Server Side Log

2018-04-26 18:33:51.049:INFO::main: Logging initialized @190ms
2018-04-26 18:33:51.229:INFO:oejs.Server:main: jetty-9.3.z-SNAPSHOT
2018-04-26 18:33:51.596:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@4e7dc304{/,null,AVAILABLE}
2018-04-26 18:33:51.606:INFO:oejus.SslContextFactory:main: x509=X509@396f6598(ca,h=[],w=[]) for SslContextFactory@394e1a0f(null,null)
2018-04-26 18:33:51.630:INFO:oejs.ServerConnector:main: Started ServerConnector@458c1321{SSL,[ssl]}{0.0.0.0:8443}
2018-04-26 18:33:51.635:INFO:oejs.ServerConnector:main: Started ServerConnector@11438d26{HTTP/1.1,[http/1.1]}{0.0.0.0:50055}
2018-04-26 18:33:51.636:INFO:oejs.Server:main: Started @777ms

Client Side

package com.example.myapplication;

import android.content.Intent;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import okhttp3.CipherSuite;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.TlsVersion;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;

public class MainActivity extends AppCompatActivity {

    private Button start;
    private TextView output;
    private OkHttpClient client;
    private Certificate mPinnedCertificate;

    private final class EchoWebSocketListener extends WebSocketListener {
        private static final int NORMAL_CLOSURE_STATUS = 1000;

        @Override
        public void onOpen(WebSocket webSocket, Response response) {
            output("Sending----------");
            webSocket.send("Hello, Friend");
            webSocket.send("USA");
            webSocket.send(ByteString.decodeHex("Hi"));
            webSocket.close(NORMAL_CLOSURE_STATUS, "Goodbye !");
        }

        @Override
        public void onMessage(WebSocket webSocket, String text) {
            output("Receiving : " + text);
        }

        @Override
        public void onMessage(WebSocket webSocket, ByteString bytes) {
            output("Receiving bytes : " + bytes.hex());
        }

        @Override
        public void onClosing(WebSocket webSocket, int code, String reason) {
            webSocket.close(NORMAL_CLOSURE_STATUS, null);
            output("Closing : " + code + " / " + reason);
        }

        @Override
        public void onFailure(WebSocket webSocket, Throwable t, Response response) {
            output("Error : " + t.getMessage());
            Log.i("Connection Error ",t.getMessage());
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start = (Button) findViewById(R.id.start);
        output = (TextView) findViewById(R.id.output);
        prepareOkHttpClient();
//        client = new OkHttpClient();

        start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                start();
            }
        });

    }

    private void start() {
        Request request = new Request.Builder().url("wss://localhost:50055/sample").build();
        EchoWebSocketListener listener = new EchoWebSocketListener();
        WebSocket ws = client.newWebSocket(request, listener);

        client.dispatcher().executorService().shutdown();
    }

    private void output(final String txt) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                output.setText(output.getText().toString() + "\n\n" + txt);
            }
        });
    }

    private void prepareOkHttpClient() {
        try {

            ConnectionSpec wssSpecs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
                    .tlsVersions(TlsVersion.TLS_1_2)
                    .cipherSuites(
                            CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
                    .build();

            mPinnedCertificate = readPinnedCertificate("va_cert.der");
            // Create a KeyStore containing our trusted CAs
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null, new char[]{});
            keyStore.setCertificateEntry("ca", mPinnedCertificate);

            // Create a TrustManager that trusts the CAs in our KeyStore
            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = null;

            tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);

            // Create an SSLContext that uses our TrustManager
            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
            sslContext.init(null, tmf.getTrustManagers(), null);

            Log.i("Protocol : ",sslContext.getProvider()+" **** " + sslContext.getProtocol());

            TrustManager[] trustManagers = tmf.getTrustManagers();

            client = new OkHttpClient.Builder()
                    .connectionSpecs(Collections.singletonList(wssSpecs))
                    .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagers[0])
                    .connectTimeout(15000, TimeUnit.MILLISECONDS)
                    .build();

        } catch (NoSuchAlgorithmException | CertificateException
                | KeyStoreException | KeyManagementException | IOException e) {

            Log.i("SSL Exception ",e.getMessage());
        }

    }

    /**
     * Reads SSL certificate from App Assets folder.
     *
     * @param certAssetName File name of the SSL certificate.
     * @return Certificate object.
     * @throws CertificateException Certificate is invalid exception.
     * @throws IOException File does not exist.
     */
    private Certificate readPinnedCertificate(final String certAssetName)
            throws CertificateException, IOException {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        AssetManager assManager = this.getApplicationContext().getAssets();
        InputStream caInput = assManager.open(certAssetName);
        Certificate ca;
        try {
            ca = cf.generateCertificate(caInput);
        } finally {
            caInput.close();
        }

        return ca;
    }

}

Client Side Error

Connection Error: Unable to find acceptable protocols. isFallback=false, modes=[ConnectionSpec(cipherSuites=[TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256], tlsVersions=[TLS_1_2], supportsTlsExtensions=true)], supported protocols=[TLSv1, TLSv1.1, TLSv1.2]

Dont know how to fix this issue. Breaking my head for the past one week. Any help

回答1:

new SslConnectionFactory(contextFactory,  
        org.eclipse.jetty.http.HttpVersion.HTTP_2.toString());

WebSocket over HTTP/2 doesn't exist (yet).

https://daniel.haxx.se/blog/2016/06/15/no-websockets-over-http2/

The concept of WebSocket over HTTP/2 is brand new, the draft specs have only been talked about in the past few months.

https://tools.ietf.org/html/draft-ietf-httpbis-h2-websockets-01