如果指定了 URL 路径,则握手失败

如果指定了 URL 路径,则握手失败

我正在尝试通过 HTTPS 使用 REST API,但仅当我指定 URL 路径时才会出现握手失败(不会仅出现域名错误)。

按照 API 所有者的指示,我收到了一个 .PEM 文件(未签名的证书?),我用它生成了一个 CSR 并将其发回,他们又给我一个私钥文件,然后我将该文件与证书和签名链一起封装到 PFX 文件中。

我将 PFX 导入 Internet Explorer 证书存储区,现在可以毫无问题地访问“根”URL(仅域名)和 API URL(包含 URL 路径)。它也可以在 Google Chrome 和 Postman 中运行。

现在,我正尝试让它与其他 3 个应用程序一起工作:

  • CA 技术 - API 管理
  • Sofware AG - 集成服务器
  • Java 8 通过 HttpsURLConnection

在每种情况下,我都可以访问“根”URL 并获得响应,但如果我在 URL 中添加路径,则会出现握手失败:

让我们看看我对每个应用程序做了什么...

1)CA Technologies - API 管理

我使用“管理证书”导入向导将 PFX 导入 CA API 网关。它添加了 3 个证书条目:实际证书、中间证书颁发机构和配置为信任锚的根证书颁发机构。证书已检查以用于“出站 SSL 连接”。

但是当我使用带有路径的断言“通过 HTTPS 路由”到我的 URL 时,我遇到了握手失败。我还尝试使用“管理证书”添加向导向商店添加证书,并为其提供带有下载证书路径的 URL。它在我的商店中添加了 3 个不同的证书,但仍然不起作用。

2)Software AG-集成服务器

由于此 ESB 无法读取 PFX 文件,我通过 Internet Explorer 证书导出将 3 个证书(实际证书 + 签名链)导出到 .CER 文件。在 WebMethods Integration Server Administrator 控制台中,我使用“配置客户端证书”菜单添加了证书。我还使用其默认值(DEFAULT_IS_KEYSTORE 和 DEFAULT_IS_TRUSTSTORE)配置了密钥库和信任库。

但是使用服务“pub.client:http”时出现了与之前相同的错误。然后我尝试将我的证书直接添加到已启动的 jvm 的密钥库中 (%SAG_PATH%/jvm/jvm/jre/lib/security),但问题并未得到解决。

3)Java 8 通过 HttpsURLConnection

作为最后的手段,我尝试让它直接在“纯” Java 中工作。我将证书添加到我的 cacerts 存储中,并创建了一个类来连接到我的 URL 并打印信息:

HttpsURLConnection con = (HttpsURLConnection) new URL(url).openConnection();

System.out.println("Response Code : " + con.getResponseCode());
System.out.println("Cipher Suite : " + con.getCipherSuite());
System.out.println("\n");

Certificate[] certs = con.getServerCertificates();
for (Certificate cert : certs) {
    System.out.println("Cert Type : " + cert.getType());
    System.out.println("Cert Hash Code : " + cert.hashCode());
    System.out.println("Cert Public Key Algorithm : " + cert.getPublicKey().getAlgorithm());
    System.out.println("Cert Public Key Format : " + cert.getPublicKey().getFormat());
    System.out.println("\n");
}

如果我尝试访问根 URL,我会收到 OK 响应:

Response Code : 200
Cipher Suite : TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

Cert Type : X.509
Cert Hash Code : -952876714
Cert Public Key Algorithm : RSA
Cert Public Key Format : X.509

Cert Type : X.509
Cert Hash Code : 272760578
Cert Public Key Algorithm : RSA
Cert Public Key Format : X.509

Cert Type : X.509
Cert Hash Code : -1335658159
Cert Public Key Algorithm : RSA
Cert Public Key Format : X.509

但是如果我将路径添加到实际 API,握手也会失败:

javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
    at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:2023)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1125)
    at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:930)
    at sun.security.ssl.AppInputStream.read(AppInputStream.java:105)
    at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
    at java.io.BufferedInputStream.read1(BufferedInputStream.java:286)
    at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
    at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:704)
    at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:647)
    at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:675)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1569)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1474)
    at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:338)
    at test.Test.print_https_cert(Test.java:60)
    at test.Test.main(Test.java:23)

问题

我是否需要另一个证书来获取 API 的实际 URL(包含完整路径)?
为什么我的浏览器可以使用证书,而其他应用程序却不行?我该如何纠正这个问题才能让它正常工作?

谢谢。


编辑

下列的@EnricoBasseti建议,我尝试启用/禁用(默认情况下禁用)CA API Gateway 中的“遵循重定向”选项。它没有改变错误消息。

然后我尝试通过修改代码来强制 Java 发送 SNI:

SSLContext sslContext = SSLContext.getDefault();
SSLParameters sslParameters = new SSLParameters();
List<SNIServerName> sniHostNames = new ArrayList<>(1);
sniHostNames.add(new SNIHostName(url.getHost()));
sslParameters.setServerNames(sniHostNames);
SSLSocketFactory wrappedSSLSocketFactory = new SSLSocketFactoryWrapper(sslContext.getSocketFactory(), sslParameters);
HttpsURLConnection.setDefaultSSLSocketFactory(wrappedSSLSocketFactory);

HttpsURLConnection con = (HttpsURLConnection) url.openConnection();

但是即使有了 SNI,如果我使用完整的 URL 而不是仅使用域名,仍然会出现握手失败。


编辑2

通过-Djavax.net.debug.all,我获得了有关握手失败的更多信息:

verify_data:  { 216, 178, 49, 39, 2, 6, 100, 218, 54, 81, 137, 239 }
***
[write] MD5 and SHA1 hashes:  len = 16
0000: 14 00 00 0C D8 B2 31 27   02 06 64 DA 36 51 89 EF  ......1'..d.6Q..
Padded plaintext before ENCRYPTION:  len = 16
0000: 14 00 00 0C D8 B2 31 27   02 06 64 DA 36 51 89 EF  ......1'..d.6Q..
main, WRITE: TLSv1.2 Handshake, length = 40
[Raw write]: length = 45
0000: 16 03 03 00 28 00 00 00   00 00 00 00 00 44 C8 8F  ....(........D..
0010: A5 85 F7 18 F4 14 E3 9F   79 C6 4C 7B E3 50 61 59  ........y.L..PaY
0020: B0 83 38 DA 16 91 49 41   76 B3 E0 CD 17           ..8...IAv....
[Raw read]: length = 5
0000: 15 03 03 00 1A                                     .....
[Raw read]: length = 26
0000: 00 00 00 00 00 00 00 07   15 66 D8 8D F8 A5 88 D2  .........f......
0010: 44 24 BE 7B E4 1D 75 F7   58 C2                    D$....u.X.
main, READ: TLSv1.2 Alert, length = 26
Padded plaintext after DECRYPTION:  len = 2
0000: 02 28                                              .(
main, RECV TLSv1.2 ALERT:  fatal, handshake_failure
%% Invalidated:  [Session-1, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]
%% Invalidated:  [Session-2, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]
main, called closeSocket()
main, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
main, called close()
main, called closeInternal(true)
Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256
Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384
Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384
Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

不确定这是否意味着我缺少特定的密码。


编辑3

通过“根”URL,我得到以下日志:

verify_data:  { 40, 57, 242, 4, 89, 211, 6, 190, 109, 98, 13, 50 }
***
[write] MD5 and SHA1 hashes:  len = 16
0000: 14 00 00 0C 28 39 F2 04   59 D3 06 BE 6D 62 0D 32  ....(9..Y...mb.2
Padded plaintext before ENCRYPTION:  len = 16
0000: 14 00 00 0C 28 39 F2 04   59 D3 06 BE 6D 62 0D 32  ....(9..Y...mb.2
main, WRITE: TLSv1.2 Handshake, length = 40
[Raw write]: length = 45
0000: 16 03 03 00 28 00 00 00   00 00 00 00 00 5B 4A 01  ....(........[J.
0010: 6B 3B C3 43 29 8F EF CA   4B 85 85 93 BD 6C E3 9A  k;.C)...K....l..
0020: 2C D0 73 32 2A 33 1A 4A   5B 09 D1 A7 9C           ,.s2*3.J[....
[Raw read]: length = 5
0000: 14 03 03 00 01                                     .....
[Raw read]: length = 1
0000: 01                                                 .
main, READ: TLSv1.2 Change Cipher Spec, length = 1
[Raw read]: length = 5
0000: 16 03 03 00 28                                     ....(
[Raw read]: length = 40
0000: 00 00 00 00 00 00 00 00   72 1F F6 DD 23 77 96 2D  ........r...#w.-
0010: DB BC 1E 10 CC 8A 64 6E   8B C1 A9 04 8A 08 62 20  ......dn......b 
0020: 2A 13 E3 DD 56 57 C3 AB                            *...VW..
main, READ: TLSv1.2 Handshake, length = 40
Padded plaintext after DECRYPTION:  len = 16
0000: 14 00 00 0C 6E 14 F6 CB   16 CA D8 B2 68 96 70 19  ....n.......h.p.
*** Finished
verify_data:  { 110, 20, 246, 203, 22, 202, 216, 178, 104, 150, 112, 25 }
***
%% Didn't cache non-resumable client session: [Session-1, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]
[read] MD5 and SHA1 hashes:  len = 16
[...] OMMITED (actual content of the URL) [...]

编辑4

最初,我无法直接在 cacerts 中导入 PFX,因为出现错误Illegal Key Size。但由于我配置了“UnlimitedJCEPolicyJDK8”,因此我能够导入它。

然后,按照@dave_thompson_085 的建议,我指定了在我的 Java 代码中使用的 keyStore:

System.setProperty("javax.net.ssl.trustStore", "XXXXX/jdk1.8.0_71/jre/lib/security/cacerts");
System.setProperty("javax.net.ssl.trustStorePassword", "XXXXX");
System.setProperty("javax.net.ssl.keyStore", "XXXXX/jdk1.8.0_71/jre/lib/security/cacerts");
System.setProperty("javax.net.ssl.keyStorePassword", "XXXXX");

我第一次遇到错误是java.security.UnrecoverableKeyException: Cannot recover key因为我存储密钥的密码与密钥库的密码不同。但是当我对两个元素使用相同的密码时,它就可以正常工作。

如果我理解正确的话,我只是发送了证书,但没有发送我的私钥。我知道必须找到一种方法在 CA API 网关和 Software AG Integration Server 中执行相同的操作。

答案1

TLS 连接是在 HTTP 请求之前建立的,因此无法区分(在服务器端)您将请求哪个端点(仅使用 SNI 的主机名)。这就是证书不包含完整 URL 的原因。

因此,在不同的 URL 握手时崩溃是非常奇怪的 - 也许端点 URL 正在重定向302到另一台服务器?查看您使用的应用程序是否有禁用该选项的选项Follow HTTP redirects(或类似的东西)。

您也可以使用 Internet Explorer 来检查这一点:查看当您尝试访问时 URL 是否发生变化https://api.xxx.com/method/

答案2

谢谢@dave_thompson_085@EnricoBasseti,我设法让它与我的 3 个应用程序一起工作。

以下是我针对每个人所做的事情:

1)CA Technologies - API 管理

在“管理私钥”导入向导中,我从 PFX 文件添加了密钥。然后在“管理证书”添加向导中,我从私钥的证书链(第 4 个选项)导入了证书。然后,我使用“管理证书”添加向导从 API URL 导入证书(在我的情况下是不同的证书)。

然后在我的策略中,右键单击“路由到 HTTP(S) 断言”,然后单击“选择私钥”以强制 CA API 网关使用我先前导入的私钥而不是其默认私钥。

2)Software AG-集成服务器

在 myWebmethod Web 控制台的“安全”和“密钥库”中,我单击了“创建密钥库别名”。我配置了一个新的 PCKS12 密钥库,并为其提供了我的 PFX 文件(必须分别指定密钥库密码和密钥密码)。

然后在“安全”和“证书”中,我单击“配置客户端证书”,并从我的认证链中添加根证书(我之前已使用 IE 导出)。我还从 URL 认证链中添加了根证书(我的情况有所不同)。

最后在我的 Flow Service 中,在使用“pub.client:http”组件之前,我添加了一个“pub.security.keystore:setKeyAndChain”组件,配置为使用我的新 KeyStore 别名及其 Key 别名。

3)Java 8 通过 HttpsURLConnection

我必须用 Oracle 提供的安全策略文件替换我的 JDK 安全策略文件:无限制JCEPolicyJDK8(仅当使用 JDK8 < 161 时)。然后,我导入了 cacerts 文件 (%JAVA_HOME%/jre/lib/security/cacerts) 中的密钥对。我还导入了 API URL 的根证书(与我的 PFX 文件中包含的证书不同)。

然后在我的代码中,我强制 Java 使用我的 cacerts 作为 KeyStore(参见我原始帖子中的编辑 4)。

相关内容