Netty SSL 中的字段长度溢出
Field length overflow in Netty SSL
我使用 Netty 来实现带有安全套接字的服务器。我的 sslHandler 代码是:
SslHandler sslHandler = SslContextBuilder
.forServer(certFile, keyFile)
.trustManager(trustFile)
.clientAuth(ClientAuth.REQUIRE)
.build()
.newHandler(channel.alloc());
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.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.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.run(Handshaker.java:966)
at sun.security.ssl.Handshaker.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
但是如果我trim只剩下几个证书的信任文件,不会发生错误。
这是 JDK 错误吗?我怎样才能避免它?
可能是JSSE没有给出更明确的告警的bug,但是有一个根本性的问题。
当 SSL/TLS 服务器请求客户端身份验证时,它通常会发送客户端应在证书请求消息中使用的证书颁发机构列表,请参阅 rfc5246 7.4.4 或更早版本。由于您信任大量的自签名证书,其中每个自签名者都有效地充当自己的 CA,这意味着您的服务器需要发送大量 CA——但这个列表是有限的总计 65535 字节。您的异常显示您正在尝试发送 106142 字节 这不适合 65535 字节;这意味着您的证书名称(主题)平均约为 150 个字节,如果这些名称完全在您的企业内使用,这在我看来有点偏高,因此可能不需要像 public 网络这样的全球唯一名称(特别是具有增强身份要求的 EV。
如果您的所有客户端在没有提示的情况下都知道要使用哪个证书,则一种可能的解决方法是让服务器将 CA 列表发送为空,这是允许的,但不鼓励这样做。 JSSE 简单地从 trustmanager 的 getAcceptedIssuers()
方法填充 CertReq.CAlist,而 TrustManager API 是为定制而设计的,因此您可以将真正的 X509TrustManager 与一个正常验证接收到的证书链的包装在一起,但是returns getAcceptedIssuers()
作为空数组。这对于实际的 Java 类 (SSLContext
et amici) 来说相当容易,但我不确定在 Netty 的 'improvements'.
中到底在哪里看
但是,正如 EJP 在评论中指出的那样,更好的解决方案不是单独信任大量自签名证书,而是让 CA 颁发客户端证书,然后服务器只需要信任该 CA(并传递它颁发的证书)并且 CertReq 自动仅指定该 CA。如果您还没有合适的已建立的 CA 可以使用,那么有很多选择可以创建您自己的 CA,在此处的其他 Q 和其他 Stacks(IME 主要是 security.SX unix.SX 和 serverfault)中进行了讨论,但是假设您正在使用 Java 请记住,因为 j7 keytool -gencert
执行最小但可用的 CA 功能。 (除了 keytool
已经回到黑暗时代的密钥对和 CSR 生成。)
我使用 Netty 来实现带有安全套接字的服务器。我的 sslHandler 代码是:
SslHandler sslHandler = SslContextBuilder
.forServer(certFile, keyFile)
.trustManager(trustFile)
.clientAuth(ClientAuth.REQUIRE)
.build()
.newHandler(channel.alloc());
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.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.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.run(Handshaker.java:966)
at sun.security.ssl.Handshaker.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
但是如果我trim只剩下几个证书的信任文件,不会发生错误。
这是 JDK 错误吗?我怎样才能避免它?
可能是JSSE没有给出更明确的告警的bug,但是有一个根本性的问题。
当 SSL/TLS 服务器请求客户端身份验证时,它通常会发送客户端应在证书请求消息中使用的证书颁发机构列表,请参阅 rfc5246 7.4.4 或更早版本。由于您信任大量的自签名证书,其中每个自签名者都有效地充当自己的 CA,这意味着您的服务器需要发送大量 CA——但这个列表是有限的总计 65535 字节。您的异常显示您正在尝试发送 106142 字节 这不适合 65535 字节;这意味着您的证书名称(主题)平均约为 150 个字节,如果这些名称完全在您的企业内使用,这在我看来有点偏高,因此可能不需要像 public 网络这样的全球唯一名称(特别是具有增强身份要求的 EV。
如果您的所有客户端在没有提示的情况下都知道要使用哪个证书,则一种可能的解决方法是让服务器将 CA 列表发送为空,这是允许的,但不鼓励这样做。 JSSE 简单地从 trustmanager 的 getAcceptedIssuers()
方法填充 CertReq.CAlist,而 TrustManager API 是为定制而设计的,因此您可以将真正的 X509TrustManager 与一个正常验证接收到的证书链的包装在一起,但是returns getAcceptedIssuers()
作为空数组。这对于实际的 Java 类 (SSLContext
et amici) 来说相当容易,但我不确定在 Netty 的 'improvements'.
但是,正如 EJP 在评论中指出的那样,更好的解决方案不是单独信任大量自签名证书,而是让 CA 颁发客户端证书,然后服务器只需要信任该 CA(并传递它颁发的证书)并且 CertReq 自动仅指定该 CA。如果您还没有合适的已建立的 CA 可以使用,那么有很多选择可以创建您自己的 CA,在此处的其他 Q 和其他 Stacks(IME 主要是 security.SX unix.SX 和 serverfault)中进行了讨论,但是假设您正在使用 Java 请记住,因为 j7 keytool -gencert
执行最小但可用的 CA 功能。 (除了 keytool
已经回到黑暗时代的密钥对和 CSR 生成。)