Netty SSL 中的字段长度溢出

rua*_*hao 1 java ssl openssl netty tls1.2

我使用 Netty 实现带有安全套接字的服务器。我的 sslHandler 代码是:

SslHandler sslHandler = SslContextBuilder
                            .forServer(certFile, keyFile)
                            .trustManager(trustFile)
                            .clientAuth(ClientAuth.REQUIRE)
                            .build()
                            .newHandler(channel.alloc());
Run Code Online (Sandbox Code Playgroud)

trustFile是一个 File 对象,其中包含大约 700 条证书文本,例如:

-----开始证书----- MIIEHDCCAwSgAwiBAgIJAOR6+3G8C6f7MA0GCSqGSIb3DQEBCwUAMIGNMQswCQYD VQQGEwJVUzESMBAGA1UECAwJQ2FsaWZvbWlhMRwwGgYDVQQKDBNDaXNjbyBTeXN0 ................................ ............................ igHdyc519KbYSMfhuM9gXw35LPmFWStBGYikBcMZJ1WmWxb/eZOK1SMjVQ/L/JVg -----结束证书-----

当我连接服务器时

curl -k -v -E client.pem --key client.key.pem --cacert rootCA.pem https://10.140.28.33:31069

弹出异常:

11:00:18.636 [nioEventLoopGroup-3-2] WARN io.netty.channel.DefaultChannelPipeline - An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.handler.codec.DecoderException: java.lang.RuntimeException: Field length overflow, the field length (106142) should be less than 65536
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:459)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1334)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:926)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:134)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:644)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:579)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:496)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:458)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
    at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:138)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.RuntimeException: Field length overflow, the field length (106142) should be less than 65536
    at sun.security.ssl.Handshaker.checkThrown(Handshaker.java:1476)
    at sun.security.ssl.SSLEngineImpl.checkTaskThrown(SSLEngineImpl.java:535)
    at sun.security.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:813)
    at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:781)
    at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:624)
    at io.netty.handler.ssl.SslHandler$SslEngineType$3.unwrap(SslHandler.java:255)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1162)
    at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1084)
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428)
    ... 16 common frames omitted
Caused by: java.lang.RuntimeException: Field length overflow, the field length (106142) should be less than 65536
    at sun.security.ssl.HandshakeOutStream.checkOverflow(HandshakeOutStream.java:231)
    at sun.security.ssl.HandshakeOutStream.putInt16(HandshakeOutStream.java:163)
    at sun.security.ssl.HandshakeMessage$CertificateRequest.send(HandshakeMessage.java:1442)
    at sun.security.ssl.HandshakeMessage.write(HandshakeMessage.java:143)
    at sun.security.ssl.ServerHandshaker.clientHello(ServerHandshaker.java:971)
    at sun.security.ssl.ServerHandshaker.processMessage(ServerHandshaker.java:224)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1026)
    at sun.security.ssl.Handshaker$1.run(Handshaker.java:966)
    at sun.security.ssl.Handshaker$1.run(Handshaker.java:963)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.security.ssl.Handshaker$DelegatedTask.run(Handshaker.java:1416)
    at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1301)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1214)
    ... 19 common frames omitted
Run Code Online (Sandbox Code Playgroud)

但是,如果我修剪信任文件,只留下几个证书,则不会发生错误。

这是 JDK 的错误吗?我怎样才能避免它?

dav*_*085 5

JSSE没有给出更清晰的警报可能是一个bug,但有一个根本性的问题。

当 SSL/TLS 服务器请求客户端身份验证时,它通常会发送客户端应在证书请求消息中使用的证书颁发机构列表,请参阅rfc5246 7.4.4或更早版本。由于您信任大量自签名证书,其中每个自签名者实际上充当自己的 CA,这意味着您的服务器需要发送大量 CA 列表- 但此列表总共限制为 65535 字节。您的异常表明您正在尝试发送 106142 字节,但不适合65535 字节;这意味着您的证书名称(主题)平均约为 150 个字节,在我看来,如果这些名称完全在您的企业内使用,那么这似乎有点偏高,因此可能不需要像公共网络这样的全球唯一名称(尤其是 EV 及其增强的身份要求)。

如果所有客户端在没有提示的情况下都知道要使用哪个证书,一种可能的解决方法是让服务器将 CA 列表发送为空,这是允许的,但不鼓励这样做。JSSE 只是从 trustmanager 的getAcceptedIssuers()方法中填充 CertReq.CAlist,而 TrustManager API 是为自定义而设计的,因此您可以将真正的 X509TrustManager 包装为正常验证接收到的证书链,但getAcceptedIssuers()作为空数组返回。对于实际的 Java 类(SSLContextet amici)来说,这相当容易,但我不确定在 Netty 的“改进”中到底该在哪里寻找。

但是,正如 EJP 的评论中所指出的,更好的解决方案不是单独信任大量自签名证书,而是让 CA 颁发客户端证书,然后服务器只需要信任该 CA(以及传递性地颁发它颁发的证书)并且CertReq 仅自动指定该 CA。如果您还没有合适的已建立的 CA 可供使用,则有很多选项可以创建您自己的 CA,在此处的其他 Q 和其他堆栈中进行了讨论(IME 主要是 security.SX unix.SX 和 serverfault),但考虑到您正在使用Java记住,因为j7keytool -gencert做了一个最小但可用的CA功能。(除了密钥对和 CSR 生成之外,这还keytool可以追溯到黑暗时代。)