【问题标题】:jdk7 call https url handshake_failurejdk7 调用 https url handshake_failure
【发布时间】:2021-04-07 11:18:35
【问题描述】:

jdk1.7.0_79 和 okhttp 3.8 调用 https url 失败

main, WRITE: TLSv1 Handshake, length = 148
main, READ: TLSv1 Alert, length = 2
main, RECV TLSv1 ALERT:  fatal, handshake_failure
main, called closeSocket()
main, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
main, called close()
main, called closeInternal(true)

写和读都TLSv1为什么还是handshake_failure,如果用jdk8也没关系,握手信息如下

main, WRITE: TLSv1.2 Handshake, length = 170
main, READ: TLSv1.2 Handshake, length = 93
main, READ: TLSv1.2 Handshake, length = 5516

客户端和服务器都使用 TLSv1.2

所以尝试将 jdk7 默认 tls 更改为 TLSv1.2 在此 doc 中遵循以下方法

// Enable TLS 1.0, 1.1 and 1.2 in an SSLSocket object.
sslSocket.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});

但这次还是handshake_failure,调试信息是

main, WRITE: TLSv1.2 Handshake, length = 178
main, READ: TLSv1.2 Alert, length = 2
main, RECV TLSv1 ALERT:  fatal, handshake_failure
main, called closeSocket()
main, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
main, called close()
main, called closeInternal(true)

这次 write 使用 TLSv1.2 但 recv 仍然使用 TLSv1。 为什么会这样?如何让客户端和服务器都使用TLSV1.2?

【问题讨论】:

  • 实际上它并没有“使用 TLSv1”。 WRITE 和 READ 日志条目使用记录中的版本在记录级别完成,但 recvAlert(和 sendAlert)记录存储在 SSLSocketImpl 类中的“已同意”版本,该版本尚未设置。查看实际消息——无论是在 javax.net.debug 输出中还是在外部使用 wireshark 或类似的——你会看到 ClientHello 提供 TLSv1.2 和警报 is TLSv1.2。见stackoverflow.com/questions/49748108/…

标签: https java-7 okhttp tls1.2


【解决方案1】:

OkHttp 3.12.12(JDK 7 的 3.x 维护版本)应该已经在 J​​DK 7 上激活了 TLSv1.2。不知道为什么你自己有这个代码。

https://github.com/square/okhttp/blob/parent-3.12.12/okhttp/src/main/java/okhttp3/internal/platform/Platform.java#L298

  public SSLContext getSSLContext() {
    String jvmVersion = System.getProperty("java.specification.version");
    if ("1.7".equals(jvmVersion)) {
      try {
        // JDK 1.7 (public version) only support > TLSv1 with named protocols
        return SSLContext.getInstance("TLSv1.2");
      } catch (NoSuchAlgorithmException e) {
        // fallback to TLS
      }
    }

    try {
      return SSLContext.getInstance("TLS");
    } catch (NoSuchAlgorithmException e) {
      throw new IllegalStateException("No TLS provider", e);
    }
  }

您可以使用类似于https://github.com/square/okhttp/blob/master/okhttp-testing-support/src/main/kotlin/okhttp3/OkHttpDebugLogging.kt 的代码启用帧日志记录

【讨论】:

  • 谢谢!但是 okhttp3.12.12 有同样的问题:javax.net.ssl.SSLHandshakeException: 收到致命警报:handshake_failure 虽然写入使用 TLSv1.2 main, WRITE: TLSv1.2 Handshake, length = 176 但 recv 仍然使用 TLSv1(主要,RECV TLSv1 ALERT:致命,handshake_failure)
  • 是否有您正在测试的公共服务器?
  • 似乎是服务器端限制导致 jdk7 + okhhtp3.12.12 可以成功调用另一个使用 TLSv1.2 的主机
  • 这里有同样的问题。我更新到 3.12.12 并且它工作了一段时间。在对服务器进行一些更新后,它不再适用于 JDK1.7。另一方面,它适用于 JDK8 或 JDK1.7,直接使用 SSLContext / HttpsUrlConnection
【解决方案2】:

正如另一个答案指出的那样,如果使用Java 7,您至少需要 OkHttp 3.12.12 才能支持 TLSv1.2。

但您还必须考虑在服务器中启用的密码套件必须与 OkHttp 兼容。

默认情况下,OkHttp 使用 17 个“已批准”密码套件的列表,您可以在此处查看 https://github.com/square/okhttp/blob/okhttp_3.12.x/okhttp/src/main/java/okhttp3/ConnectionSpec.java(请参阅 APPROVED_CIPHER_SUITES

但在这 17 个中,只有 5 个受 de JDK 1.7 支持:

TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
TLS_RSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
TLS_RSA_WITH_AES_128_CBC_SHA
SSL_RSA_WITH_3DES_EDE_CBC_SHA

因此,如果服务器不接受任何此类密码套件,则会收到握手失败错误。

如果服务器接受 Java 7 支持的一些密码套件,但不在 OkHttp 中的 APPROVED_CIPHER_SUITES 列表中,您可以创建自己的 ConnectionSpec 并在 OkHttpClient.Builder 中使用它。例如,假设服务器接受 Java 7 支持的以下密码套件:

TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256

您可以执行以下操作将它们添加到 OkHttp 支持的密码套件列表中:

// Create a list of cipher suites based on the default approved list by OkHttp
List<CipherSuite> cipherSuites = new ArrayList<CipherSuite>(ConnectionSpec.MODERN_TLS.cipherSuites());

// Add another cipher suites suported by Java 7
cipherSuites.add(CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384);
cipherSuites.add(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256);

// Build a connection spec based on the default overridding the cipher suites.
ConnectionSpec COMPATIBLE_SPEC = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
            .cipherSuites(cipherSuites.toArray(new CipherSuite[0]))
            .build();


// Build a client specifying our own connection spec
OkHttpClient client = new OkHttpClient.Builder()
            .connectionSpecs(Util.immutableList(COMPATIBLE_SPEC))
            .build();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-10
    • 1970-01-01
    • 2015-06-25
    • 2014-08-28
    • 2017-06-25
    • 2011-10-25
    • 2013-12-20
    相关资源
    最近更新 更多