# 背景介绍

在一个项目中,我们使用了其他同事提供的 websocket 代码。项目接近测试结束阶段时,我对代码进行了审查,发现 IDE 提示当前使用的 websocket 版本存在 CVE 漏洞。起初认为这可能只是一个小问题,只需要简单地升级版本就能解决。然而,事实证明这个过程比预想的要复杂得多。

# 使用的开源组件:org.java-websocket

原始依赖版本是 1.3.0:

<dependency>
    <groupId>org.java-websocket</groupId>
    <artifactId>Java-WebSocket</artifactId>
    <version>1.3.0</version>
</dependency>

# 升级过程中遇到的主要问题

当我尝试将版本直接升级到最新的 1.5.6 时,遇到了两个主要问题:

  1. API 发生变化
  2. 信任所有证书的方法报错

# 1. API 变化

升级前后的代码对比:

// 升级前
MyWebsocketClient client = new MyWebsocketClient(new URI(uri), new Draft_76(), new HashMap<>(), 2000);
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
client.setWebSocketFactory(new DefaultSSLWebSocketServerFactory(sc));
// 升级后
MyWebsocketClient client = new MyWebsocketClient(new URI(uri), new Draft_6455(), new HashMap<>(), 2000);
SSLContext sc = SSLContext.getInstance("TLS");  
sc.init(null, trustAllCerts, new java.security.SecureRandom());  
client.setSocketFactory(sc.getSocketFactory());

主要变化:

  • setWebSocketFactory() 方法变为 setSocketFactory()
  • Draft_76 更新为 Draft_6455

通过查看 GitHub 上的官方示例和 git 记录,我发现 API 经历了以下变化:

  1. chatclient.setWebSocketFactory(new SSLWebSocketClientFactory(sslContext));
  2. chatclient.setSocket(factory.createSocket());
  3. chatclient.setSocketFactory(factory);

# 2. 信任所有证书报错

升级后,日志中出现了证书校验的报错。通过源码分析,发现问题出在 chooseTrustManager 方法:

private X509TrustManager chooseTrustManager(TrustManager[] tm) {
    // 代码省略...
    return new AbstractTrustManagerWrapper((X509TrustManager)tm[i]);
}

这个方法返回的 AbstractTrustManagerWrapper 包含了证书校验逻辑,而不是使用我们自定义的 X509TrustManager

# 解决方案

为了解决这个问题,我们需要实现 X509ExtendedTrustManager 接口。以下是修改后的代码:

public Optional<MyWebsocketClient> generateWebsocketClient(String uri) throws URISyntaxException {
    MyWebsocketClient client = new MyWebsocketClient(new URI(uri), new Draft_6455(), new HashMap<>(), 2000);
    TrustManager[] trustAllCerts = new TrustManager[]{
            new X509ExtendedTrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { }
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { }
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { }
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { }
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { }
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { }
            }
    };
    try {
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        client.setSocketFactory(sc.getSocketFactory());
    } catch (NoSuchAlgorithmException | KeyManagementException e) {
        log.error(e.getMessage(), e);
    }
    boolean connected = false;
    try {
        connected = client.connectBlocking();
    } catch (InterruptedException e) {
        log.error("connectBlocking fail", e);
    }
    if (!connected) {
        client.close();
        return Optional.empty();
    }
    return Optional.of(client);
}

# 总结

  1. 升级第三方依赖时,要仔细查看更新日志和 API 变化。
  2. 对于安全相关的更改,如 SSL/TLS 配置,需要特别注意。
  3. 在升级过程中,逐步测试每个变更是很重要的。
  4. 保持与开源社区的最新动态同步,可以帮助我们更好地理解和应对 API 变化。