[spring-projects/spring-boot]WebTestClient 协议/密码套件明确指定不起作用

2024-05-14 199 views
9
问题

我一直在使用 Java 17 和 TLSv1.3。在为 TLSv1.3 指定正确的 TLS_CHACHA20_POLY1305_SHA256 并为 TLSv1.2 指定正确的 TLS_AES_256_GCM_SHA384 时,密码套件/协议不可用WebTestClient。这是相关的代码部分和日志。

应用程序.yml
server:
  port: 8443
  ssl:
    enabled: true
    key-store-type: PKCS12
    key-store: classpath:keystore/localhost.p12
    key-store-password: ${LOCALHOST_KEYSTORE_PASSWORD}
    key-alias: localhost
    ciphers:
      - TLS_CHACHA20_POLY1305_SHA256
      - TLS_AES_256_GCM_SHA384
      - TLS_AES_128_GCM_SHA256
      - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
      - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
      - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
      - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    enabled-protocols:
      - TLSv1.3
      - TLSv1.2
WebTest客户端构建
  protected def getHttpClient(String protocol, List<String> cipherSuites) {
    def httpClient = HttpClient
      .create().secure({
        it.sslContext(SslContextBuilder
          .forClient()
          .trustManager(InsecureTrustManagerFactory.INSTANCE)
          .protocols(protocol)
          .ciphers(cipherSuites)
          .build())
      })
    WebTestClient
      .bindToServer(new ReactorClientHttpConnector(httpClient))
      .baseUrl(getBaseUrl())
      .build()
  }
测试(在 Groovy/Spock 中,抱歉)
  def "when TLSv1.2 allowed cipher suite used then accepted"() {
        given:
        def client = getHttpClient(TestConstants.TLSV1_2, List.of("TLS_AES_256_GCM_SHA384"))
        expect:
        client
                .get()
                .uri("/posts/${TestConstants.TEST_UUID}")
                .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
                .exchange()
                .expectStatus().isOk()
  }

  def "when TLSv1.3 allowed cipher suite used then accepted"() {
        given:
        def client = getHttpClient(TestConstants.TLSV1_3, List.of("TLS_CHACHA20_POLY1305_SHA256"))
        expect:
        client
                .get()
                .uri("/posts/${TestConstants.TEST_UUID}")
                .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
                .exchange()
                .expectStatus().isOk()
  }
日志

tls12.log tls13.log

回答

5

感谢您的报告,@optimisticninja。这两个日志文件似乎具有相同的内容。

不幸的是,目前有太多未知因素,我们没有理由花一些时间调查您所报告的行为。例如,您没有提到您正在使用的 Spring Boot 版本、您正在使用哪个嵌入式 Web 服务器,或者问题是否特定于WebTestClient.为了消除(大部分)这些未知因素并帮助我们诊断问题,您能否将代码片段转换为我们可以自己运行的最小示例?您可以通过将其压缩并将其附加到此问题或将其推送到 GitHub 上的单独存储库来与我们共享此类示例。

3

绝对地。当我有几分钟的时间时,我会回复。临时:

  • 春季启动:2.5.6
  • 嵌入式服务器:Netty
1

@wilkinsona 这是启用 SSL 日志记录请求的演示。

它似乎仅在 TLSv1.2 上失败。在这里看来TLSv1.2 应该使用TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,而不是TLS_AES_256_GCM_SHA384。这似乎是来自错误部分的复制面食问题。抱歉浪费了您的时间。

演示1.zip

1

完全没问题。感谢您让我们知道。

7

完全没问题。感谢您让我们知道。

稍微更新一下,有问题。TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA256并且TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256不可操作,我必须使用 RSA 变体。

演示2.zip

3

关闭问题以重新打开一个问题,而不会出现特定于密码套件无法正常工作的问题。

5

@optimisticninjaWebClient一般不直接处理密码套件或 TLS 设置,因此,如果您遇到此类问题,您很可能可以使用普通版本重现相同问题,HttpClient并将问题直接报告给reactor-netty。考虑此类问题的另一种方法是尝试使用另一个连接器(例如 Jetty 客户端)重现该问题。

6

@optimisticninjaWebClient一般不直接处理密码套件或 TLS 设置,因此,如果您遇到此类问题,您很可能可以使用普通版本重现相同问题,HttpClient并将问题直接报告给reactor-netty。考虑此类问题的另一种方法是尝试使用另一个连接器(例如 Jetty 客户端)重现该问题。

在查看测试堆栈跟踪后才意识到这一点。谢谢你,爬行他们的问题寻找骗子。

6

有什么变化?通过上面的代码片段,您已经通过HttpClient向连接器提供实例来完全控制。如果您指的是服务器端,我们已经在 #25913 中解决了这个问题(作为您指出的问题的后续)。我们是不是错过了什么?

3

我从通过 application.yml/properties 覆盖服务器端的角度思考。如果我误解了这里的预期用途,请随时解释。不想打扰,只是想理解。

当在 application.yml/properties 中显式定义时,属性似乎仍然被忽略,并被默认套件覆盖。

    ciphers:
      - TLS_CHACHA20_POLY1305_SHA256
      - TLS_AES_256_GCM_SHA384
      - TLS_AES_128_GCM_SHA256
      - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
      - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
      - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
      - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

服务器日志

2021-11-17 07:20:09.241 DEBUG 561240 --- [    Test worker] io.netty.handler.ssl.JdkSslContext       : Default cipher suites (JDK): [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384]
7

我已经在 Netty 中设置了一个调试点JdkSslContext,并且似乎考虑了使用应用程序属性配置的密码。您在日志中看到的行实际上是Netty 记录的默认日志

9

对于所有的困惑和我自己的超前感到抱歉。感谢您的耐心和时间。

0

完全不用担心 - 我没有进行集成测试并真正复制您正在尝试的内容。所以某处可能还有另一个问题。

3

完全不用担心 - 我没有进行集成测试并真正复制您正在尝试的内容。所以某处可能还有另一个问题。

我不知道这到底是什么。以下是 TLSv1.2 上 ECDSA 密码失败的最小示例。我不知所措,这里指出这些应该是最高优先级,我看到他们在 netty 中的支持。看看 DemoControllerTest。

tls-cipher-suite-demo.zip

6

感谢您提供样品。密码本身匹配,但据我所知,您的密钥不兼容。我更新application.yml为仅启用TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,然后尝试使用curl发出请求:

$ curl -v --insecure https://localhost:8443/demo
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS alert, handshake failure (552):
* error:14004410:SSL routines:CONNECT_CR_SRVR_HELLO:sslv3 alert handshake failure
* Closing connection 0
curl: (35) error:14004410:SSL routines:CONNECT_CR_SRVR_HELLO:sslv3 alert handshake failure

运行应用程序会-Djavax.net.debug=all显示握手期间服务器端发生的情况。

客户端握手表明它支持两种启用的密码:

javax.net.ssl|DEBUG|52|reactor-http-nio-2|2021-11-19 10:37:38.272 GMT|ClientHello.java:808|Consuming ClientHello handshake message (
"ClientHello": {
  "client version"      : "TLSv1.2",
  "random"              : "F64DE80C16050352377440335C91AF40EA209A5BC39B5DCB8D4F89B280656529",
  "session id"          : "",
  "cipher suites"       : "[TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xC030), TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xC02C), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384(0xC028), TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384(0xC024), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(0xC014), TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(0xC00A), TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(0x009F), TLS_DHE_RSA_WITH_AES_256_CBC_SHA256(0x006B), TLS_DHE_RSA_WITH_AES_256_CBC_SHA(0x0039), TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(0xCCA9), TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256(0xCCA8), TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256(0xCCAA), UNKNOWN-CIPHER-SUITE(0xFF85)(0xFF85), TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256(0x00C4), TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA(0x0088), UNKNOWN-CIPHER-SUITE(0x0081)(0x0081), TLS_RSA_WITH_AES_256_GCM_SHA384(0x009D), TLS_RSA_WITH_AES_256_CBC_SHA256(0x003D), TLS_RSA_WITH_AES_256_CBC_SHA(0x0035), TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256(0x00C0), TLS_RSA_WITH_CAMELLIA_256_CBC_SHA(0x0084), TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xC02F), TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xC02B), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256(0xC027), TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256(0xC023), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xC013), TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(0xC009), TLS_DHE_RSA_WITH_AES_128_GCM_SHA256(0x009E), TLS_DHE_RSA_WITH_AES_128_CBC_SHA256(0x0067), TLS_DHE_RSA_WITH_AES_128_CBC_SHA(0x0033), TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256(0x00BE), TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA(0x0045), TLS_RSA_WITH_AES_128_GCM_SHA256(0x009C), TLS_RSA_WITH_AES_128_CBC_SHA256(0x003C), TLS_RSA_WITH_AES_128_CBC_SHA(0x002F), TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256(0x00BA), TLS_RSA_WITH_CAMELLIA_128_CBC_SHA(0x0041), TLS_ECDHE_RSA_WITH_RC4_128_SHA(0xC011), TLS_ECDHE_ECDSA_WITH_RC4_128_SHA(0xC007), SSL_RSA_WITH_RC4_128_SHA(0x0005), SSL_RSA_WITH_RC4_128_MD5(0x0004), TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA(0xC012), TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA(0xC008), SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA(0x0016), SSL_RSA_WITH_3DES_EDE_CBC_SHA(0x000A), TLS_EMPTY_RENEGOTIATION_INFO_SCSV(0x00FF)]",
  "compression methods" : "00",
  "extensions"          : [
    "server_name (0)": {
      type=host_name (0), value=localhost
    },
    "ec_point_formats (11)": {
      "formats": [uncompressed]
    },
    "supported_groups (10)": {
      "versions": [x25519, secp256r1, secp384r1]
    },
    "signature_algorithms (13)": {
      "signature schemes": [rsa_pkcs1_sha512, ecdsa_secp521r1_sha512, UNDEFINED-SIGNATURE(239)_UNDEFINED-HASH(239), rsa_pkcs1_sha384, ecdsa_secp384r1_sha384, rsa_pkcs1_sha256, ecdsa_secp256r1_sha256, UNDEFINED-SIGNATURE(238)_UNDEFINED-HASH(238), UNDEFINED-SIGNATURE(237)_UNDEFINED-HASH(237), rsa_sha224, ecdsa_sha224, rsa_pkcs1_sha1, ecdsa_sha1]
    },
    "application_layer_protocol_negotiation (16)": {
      [h2, http/1.1]
    }
  ]
}
)

找到匹配的密码后,ServerHello调用sun.security.ssl.SSLKeyExchange.createPossessions(HandshakeContext),但这会返回一个空数组,因为密钥不属于任何受支持的算法。这会报告两次,每个密码一次:

javax.net.ssl|DEBUG|52|reactor-http-nio-2|2021-11-19 10:37:38.278 GMT|X509Authentication.java:331|demo private or public key is not of EC algorithm
javax.net.ssl|DEBUG|52|reactor-http-nio-2|2021-11-19 10:37:38.279 GMT|X509Authentication.java:331|demo private or public key is not of EdDSA algorithm
javax.net.ssl|DEBUG|52|reactor-http-nio-2|2021-11-19 10:37:38.279 GMT|X509Authentication.java:331|demo private or public key is not of EC algorithm
javax.net.ssl|DEBUG|52|reactor-http-nio-2|2021-11-19 10:37:38.279 GMT|X509Authentication.java:331|demo private or public key is not of EdDSA algorithm

结果,握手失败。

我远非 SSL 专家,因此对上述内容持保留态度,但在我看来,您需要更改密钥。

6

@wilkinsona 非常感谢你!我正在拔头发。你是绝对正确的。