如何读取 Java 中的 SNI 扩展?

How to read SNI extension in Java?

我想从 TLS Client Hello Message 中查找主机名。我想在 java 为透明 ssl proxy 完成握手之前找到主机名。

有没有什么方法可以在不编写整个 ssl handshake 逻辑的情况下找到 SNI extension 值?

Java 是否支持 ssl handshake 初始内存缓冲区? 我的想法是:

  1. 读取 TLS 客户端问候消息,解析并找到 SNI 值
  2. 调用sslSocket.startHandshake(initialBuffer) 初始缓冲区将包含 TLS 客户端问候数据包数据。所以Java可以握手

第二个想法是使用SSLEngine class。但它似乎比要求更多的实施。我假设 SSLEngine 用于大多数异步,以防我不需要它。

第三个想法是实现完整的TLS protocol

哪个想法更好?

SSLSocket 和 SSLEngine 都缺少(非常令人费解的)对服务器连接的适当 SNI 支持。

我自己遇到了同样的问题并最终编写了一个库:TLS Channel。不仅如此,它实际上是对 SSLEngine 的一个完整抽象,暴露为一个 ByteChannel。关于 SNI,库在创建 SSLEngine 之前解析第一个字节。然后,用户可以向服务器通道提供功能,根据收到的域名 select SSLContexts。

这个问题的公认答案有点旧,并没有真正提供问题的答案。

您应该在初始化 SSLContext 时使用自定义 KeyManager,诀窍是使用 javax.net.ssl.X509ExtendedKeyManager(注意扩展项)。这将提供在密钥别名 selection 过程中公开 SSLEngine 而不是普通(无用)套接字的可能性。

方法 chooseEngineServerAlias(...) 将在 SSL 握手过程中被调用,以便 select 来自 KeyStore 的正确别名 (cert/key)。 SNI 信息 已经填充 并且在 SSLEngine 中可用。这里的技巧是将 SSLSession 转换为 ExtendedSSLSession(注意扩展项),然后转换为 getRequestedServerNames().

KeyManager[] km = new KeyManager[]
{
    new X509ExtendedKeyManager()
    {
        public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine)
        {
            if( engine.getHandshakeSession() instanceof ExtendedSSLSession )
            {
               List<SNIServerName> sni = ((ExtendedSSLSession)engine.getHandshakeSession()).getRequestedServerNames();

                // select the proper certificate alias based on SNI here
            }
        }
    }
}

SSLContext context = SSLContext.getInstance("TLS");
context.init(km, null, null);