什么是“javax.net.ssl.SSLHandshakeException:在重新协商期间限制服务器证书更改”,如何避免它?

43

我们使用的是Oracle jdk 1.7.0_71和Tomcat 7.0.55。不幸的是,在服务器之间进行SSL连接期间,我们开始收到以下异常:

javax.net.ssl.SSLHandshakeException: server certificate change is restrictedduring renegotiation

什么意思?如何预防?

在Tomcat重新启动后,该异常将消失。

完整的堆栈信息:

Caused by: javax.net.ssl.SSLHandshakeException: server certificate change is restrictedduring renegotiation
        at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) ~[?:1.7.0_71]
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1884) ~[?:1.7.0_71]
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276) ~[?:1.7.0_71]
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:266) ~[?:1.7.0_71]
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1402) ~[?:1.7.0_71]
        at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:209) ~[?:1.7.0_71]
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:878) ~[?:1.7.0_71]
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:814) ~[?:1.7.0_71]
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1016) ~[?:1.7.0_71]
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312) ~[?:1.7.0_71]
        at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:702) ~[?:1.7.0_71]
        at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:122) ~[?:1.7.0_71]
        at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82) ~[?:1.7.0_71]
        at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140) ~[?:1.7.0_71]
        at org.apache.commons.httpclient.methods.EntityEnclosingMethod.writeRequestBody(EntityEnclosingMethod.java:506) ~[commons-httpclient-3.1.jar:?]
        at org.apache.commons.httpclient.HttpMethodBase.writeRequest(HttpMethodBase.java:2114) ~[commons-httpclient-3.1.jar:?]
        at org.apache.commons.httpclient.HttpMethodBase.execute(HttpMethodBase.java:1096) ~[commons-httpclient-3.1.jar:?]
        at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:398) ~[commons-httpclient-3.1.jar:?]
        at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:171) ~[commons-httpclient-3.1.jar:?]
        at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397) ~[commons-httpclient-3.1.jar:?]
        at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:323) ~[commons-httpclient-3.1.jar:?]
        at org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor.executePostMethod(CommonsHttpInvokerRequestExecutor.java:205) ~[spring-web-3.2.9.RELEASE.jar:3.2.9.RELEASE]
        at org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor.doExecuteRequest(CommonsHttpInvokerRequestExecutor.java:140) ~[spring-web-3.2.9.RELEASE.jar:3.2.9.RELEASE]
        at org.springframework.remoting.httpinvoker.AbstractHttpInvokerRequestExecutor.executeRequest(AbstractHttpInvokerRequestExecutor.java:136) ~[spring-web-3.2.9.RELEASE.jar:3.2.9.RELEASE]
        at org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor.executeRequest(HttpInvokerClientInterceptor.java:192) ~[spring-web-3.2.9.RELEASE.jar:3.2.9.RELEASE]
        at org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor.executeRequest(HttpInvokerClientInterceptor.java:174) ~[spring-web-3.2.9.RELEASE.jar:3.2.9.RELEASE]
        at org.springframework.remoting.httpinvoker.HttpInvokerClientInterceptor.invoke(HttpInvokerClientInterceptor.java:142) ~[spring-web-3.2.9.RELEASE.jar:3.2.9.RELEASE]
        ... 160 more

1
看起来在重新协商期间,您的服务器发送了一个与最初不同的证书。您是在运行过程中更改了证书,还是配置了多个证书? - Steffen Ullrich
不,只有一个证书可以执行SSL握手。 - Michael
"X" 是什么意思? - Buffalo
如果这是一个错误,它似乎并没有在所有情况下得到修复。我们也在JDK 1.8.0_40上看到了这个问题。 - lreeder
5个回答

50

在最近的Java更新中,“SSL V3.0 Poodle Vulnerability - CVE-2014-3566”之后,客户端层代码中出现这个错误消息是由于代码加固导致的。 这是一个漏洞 - 如果您无法立即更新JRE,则以下是解决方法:

第一种选择是在建立HTTPS连接时强制使用TLS协议:

如果您可以将HttpClient更新为比4.3.6更高版本,则SSLv3将被默认禁用,您的代码不应再报告此类异常。

如果您无法升级HttpClient版本,则必须使用此答案的代码来限制协议到TLS:https://dev59.com/LF8d5IYBdhLWcg3wzEz4#26439487

对于Java 7运行时的其他http访问,必须设置以下系统属性:

-Dhttps.protocols="TLSv1"

详细信息请参见此处:Java http客户端和POODLE


第二个选项是放宽客户端检查,仍然允许使用以下属性重新协商:

-Djdk.tls.allowUnsafeServerCertChange=true 
-Dsun.security.ssl.allowUnsafeRenegotiation=true


第三个选项是根据Burp论坛中的这篇文章,“改进”您的服务器证书,以在“主题备用名称”中包括集群成员的所有IP地址。


第四个选项是在添加此证书/重新协商检查之前,将Java版本降级到7u41之前(待确认)。

更新:此错误行为现在已在JDK更新7u85和8u60中修复。感谢Pada发现了JDK-8072385参考。


您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - sorin
5
据我所知,导致这个问题的代码是:http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/rev/eabde5c42157,现在看来他们已经用https://bugs.openjdk.java.net/browse/JDK-8072385修复了它。因此,修复程序将包括在7u85和8u60中。 - Pada
2
我现在正在运行Java 8u60,并且使用https.protocols="TLSv1",但是一个已部署的产品仍然遇到这个错误。显然还没有修复。 - Yves Martin
https协议不应包含引号,因此正确的值是:-Dhttps.protocols=TLSv1 - Piotr Idzikowski
为什么不呢?我同意引号在这里是无用的,但不应该阻止选项的工作。你有没有注意到使用和不使用引号会导致失败? - Yves Martin
显示剩余2条评论

5
以下代码片段在以下条件下适用于我们企业环境:
  • seamless (run-time) certificate update is a critical requirement
  • it is too costly to update the HTTPClient used in the application
  • restricting https protocol to "TLSv1" doesn't have effect
  • the application is a JNLP served java client and neither the "allowUnsafeServerCertChange" and "allowUnsafeRenegotiation" are not allowed to be passed to the client application via JNLP arguments (i'm guessing JWS is blocking them due to security reasons)
  • setting the "allowUnsafeServerCertChange" and "allowUnsafeRenegotiation" via System.setProperty() calls during the bootstrap of the application doesn't have effect.

    if (e.getCause() instanceof SSLHandshakeException) {
        logger.debug("server https certificate has been altered");
        try {
            Class<?> c = Class.forName("sun.security.ssl.ClientHandshaker");
            Field allowUnsafeServerCertChangeField = c.getDeclaredField("allowUnsafeServerCertChange");
            allowUnsafeServerCertChangeField.setAccessible(true);
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(allowUnsafeServerCertChangeField, allowUnsafeServerCertChangeField.getModifiers() & ~Modifier.FINAL);
            allowUnsafeServerCertChangeField.set(null, true);
            logger.debug("client has been updated in order to support SSL certificate change (re-negotiation) on runtime.");
        }
        catch (Exception ex) {
            logger.debug("client cannot be updated to support SSL certificate change (re-negotiation) on runtime. Please restart the application.", ex);
        }
    }
    
请注意,这应该被视为一种黑客行为(引入漏洞),只应在可信的环境中使用。在采取此方法之前,应尝试 Yves 的答案中的所有选项。

2

这也可能是由于连接配置错误引起的,例如 haproxy 中有一个或多个负载平衡目标指向错误的 IP 地址,导致 X% 的请求获取不同的证书。


我同意。但在我的情况下,它与集群中的LDAP/SSL ActiveDirectory端点有关,管理员似乎不愿手动在两个端点上部署相同的证书,因为这会破坏自动更新机制。但你是对的,一个带有正确备用名称的10年证书是最好的选择。 - Yves Martin

2

我遇到了这个问题,是因为服务器端更新了它们的证书。在此之前我们的客户端一直正常工作。我们只需重新启动程序,就可以恢复正常。


0
我遇到了这个问题,结果发现是客户端在负载均衡器后面使用了不同的证书调用服务。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接