java - 使用java中的自签名证书连接到websocket

标签 java websocket jetty

我需要使用 Java 连接到使用自签名证书的 WebSocket 服务器。我正在尝试使用 Jetty 库,并且对 Java 还很陌生,但我发现很难弄清楚需要做什么。我可以非常简单地使用 NodeJS 进行连接:

const WebSocket = require('ws');
const ws = new WebSocket('wss://192.168.100.220:9000/', ['ws-valence'], {
  rejectUnauthorized: false,
});

但是,修改我在 Jetty docs 上找到的示例并没有让我走得太远。

我实现了一个与回显测试服务器配合良好的基本客户端,如上面链接的示例所示。然后我继续用我自己的协议(protocol)和IP地址配置它:

  private static void connectToBasestation() {

//    String destUri = "ws://echo.websocket.org";
    String basestationUri = "wss://192.168.100.220:9000/";
    SslContextFactory ssl = new SslContextFactory(); // ssl config
    ssl.setTrustAll(true); // trust all certificates
    WebSocketClient client = new WebSocketClient(ssl); // give ssl config to client
    BasestationSocket socket = new BasestationSocket();
    ArrayList<String> protocols = new ArrayList<String>();
    protocols.add("ws-valence");

    try
    {
        client.start();
        URI bsUri = new URI(basestationUri);
        ClientUpgradeRequest request = new ClientUpgradeRequest();
        request.setSubProtocols(protocols);
        client.connect(socket, bsUri, request);
        System.out.printf("Connecting to : %s%n", bsUri);

        // wait for closed socket connection.
        socket.awaitClose(5,TimeUnit.SECONDS);
    }
    catch (Throwable t)
    {
        t.printStackTrace();
    }
    finally
    {
        try
        {
            client.stop();
        }
        catch (Exception e)
        {
            e.printStackTrace();

        }
    }
  }

但是,我收到一个以 0 null 作为值的 UpgradeException,并且我的 onConnect 方法永远不会被调用。我猜这是一个安全问题,但我不能确定,因为服务器是一台旧机器——有点像黑匣子。但我在想我的方法可能有问题吗?有人可以在这里提供任何建议吗?

编辑 1:按照建议包括可信 SSL 工厂。它没有改变任何东西,包括下面的堆栈跟踪。

编辑 3:上面列出了一个类似的问题,但这是不同的,因为 1) 我收到了不同的错误代码,2) 添加可信的 SSL 工厂并不能解决问题。

编辑 2:这是我从下面的 OnError 中获得的堆栈跟踪:

Caused by: javax.net.ssl.SSLException: Received fatal alert: handshake_failure
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:208)
    at sun.security.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1666)
    at sun.security.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1634)
    at sun.security.ssl.SSLEngineImpl.recvAlert(SSLEngineImpl.java:1800)
    at sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:1083)
    at sun.security.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:907)
    at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:781)
    at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:624)
    at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.fill(SslConnection.java:681)
    at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.process(HttpReceiverOverHTTP.java:128)
    at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.receive(HttpReceiverOverHTTP.java:73)
    at org.eclipse.jetty.client.http.HttpChannelOverHTTP.receive(HttpChannelOverHTTP.java:133)
    at org.eclipse.jetty.client.http.HttpConnectionOverHTTP.onFillable(HttpConnectionOverHTTP.java:155)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:281)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:102)
    at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:291)
    at org.eclipse.jetty.io.ssl.SslConnection$3.succeeded(SslConnection.java:151)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:102)
    at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118)
    ... 3 more
org.eclipse.jetty.websocket.api.UpgradeException: 0 null
    at org.eclipse.jetty.websocket.client.WebSocketUpgradeRequest.onComplete(WebSocketUpgradeRequest.java:522)
    at org.eclipse.jetty.client.ResponseNotifier.notifyComplete(ResponseNotifier.java:216)
    at org.eclipse.jetty.client.ResponseNotifier.notifyComplete(ResponseNotifier.java:208)
    at org.eclipse.jetty.client.HttpReceiver.terminateResponse(HttpReceiver.java:470)
    at org.eclipse.jetty.client.HttpReceiver.abort(HttpReceiver.java:552)
    at org.eclipse.jetty.client.HttpChannel.abortResponse(HttpChannel.java:156)
    at org.eclipse.jetty.client.HttpSender.terminateRequest(HttpSender.java:381)
    at org.eclipse.jetty.client.HttpSender.abort(HttpSender.java:566)
    at org.eclipse.jetty.client.HttpSender.anyToFailure(HttpSender.java:350)
    at org.eclipse.jetty.client.HttpSender$CommitCallback.failed(HttpSender.java:717)
    at org.eclipse.jetty.client.http.HttpSenderOverHTTP$HeadersCallback.failed(HttpSenderOverHTTP.java:310)
    at org.eclipse.jetty.io.WriteFlusher$PendingState.fail(WriteFlusher.java:263)
    at org.eclipse.jetty.io.WriteFlusher.onFail(WriteFlusher.java:516)
    at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint$FailWrite.run(SslConnection.java:1251)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:762)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:680)
    at java.lang.Thread.run(Thread.java:748)

最佳答案

TLS/SSL 握手错误相当普遍。

您不知道问题发生在 TLS/SSL 握手的哪一部分。

You can use -Djavax.net.debug=all command line option on Java to see the raw details of the TLS/SSL handshake, and this might be a good place to start troubleshooting your issues with.

一些选项...

<小时/>

对于证书名称问题

如果您连接到服务器并且提供的证书没有 与您在 URI 中用于连接的主机名匹配,这是违反的 Java 本身存在的端点识别算法。

示例场景:

  1. 您连接到 wss://192.168.1.0:8443/chat
  2. 证书将自身报告为 chatserver.acme.com

这是违规行为,因为 URI 192.168.1.0 中的主机名与证书 chatserver.acme.com 不匹配

在使用 wss://localhostwss://127.0.0.1 进行测试时,这种情况尤其常见

您可以告诉 Java 不要像这样执行端点标识检查...

SslContextFactory.Client ssl = new SslContextFactory.Client(); // ssl config
ssl.setEndpointIdentificationAlgorithm(null); // disable endpoint identification algorithm.
WebSocketClient client = new WebSocketClient(ssl); // give ssl config to client

⚠️警告:不建议这样做,并且很容易导致中间人攻击!

<小时/>

对于证书信任问题

尝试为所有证书启用信任。

  1. 为 WebSocket 客户端启用 SSL/TLS
  2. 信任 SSL/TLS 配置上的所有证书

示例(假设 Jetty 9.4.19.v20190610 或更高版本):

SslContextFactory.Client ssl = new SslContextFactory.Client(); // ssl config
ssl.setTrustAll(true); // trust all certificates
WebSocketClient client = new WebSocketClient(ssl); // give ssl config to client

⚠️警告:不建议这样做,并且很容易导致中间人攻击!

<小时/>

对于证书算法问题

用于创建证书的算法将限制 TLS/SSL 握手期间可用的密码套件。

例如,如果服务器只有 DSA 证书(已知易受攻击),则 RSA 或 ECDSA 证书都不可用。

用于创建证书的位数也相关,如果服务器证书的位数太少,那么 Java 本身会拒绝它。

如果您可以控制服务器证书,请确保您生成的证书包含 RSA 和 ECDSA 证书,其中 RSA 至少为 2048 位(或更多),ECDSA 至少为 256 位。

<小时/>

对于密码套件问题

在 Jetty 端尝试使用空的密码套件排除列表。

⚠️警告:这可以让您使用已知的易受攻击的密码套件!

  1. 为 WebSocket 客户端启用 SSL/TLS
  2. 删除密码套件排除列表

示例(假设 Jetty 9.4.19.v20190610 或更高版本):

SslContextFactory.Client ssl = new SslContextFactory.Client(); // ssl config
ssl.setExcludeCipherSuites(); // blank out the default excluded cipher suites
WebSocketClient client = new WebSocketClient(ssl); // give ssl config to client

⚠️警告:不建议这样做,任何现代计算机(甚至手机)都可以轻松读取您的加密流量

关于java - 使用java中的自签名证书连接到websocket,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51129012/

相关文章:

java - 阻止对 Jetty 服务器的请求

tomcat - 如何在没有内置 jetty 的情况下运行 cxf jax-ws(改为使用 tomcat)

java - 嵌入式 Jetty 9 HTML 表单向应用程序发送数据

java - 我在 xml 文件中传递参数,但系统仍将其值设为 param-val-not-found

html - 基于源的安全模型是什么意思?

javascript - 第二个消费者未收到 ActiveMQ 消息

php - Ratchet IO 服务器突然停止工作

java - 如何制作一个JAVA小程序自动运行?

java - Java 的条件 xpath

java - 如何在 Spring XML 文件中声明 Java 8 方法引用?