我正在尝试通过 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)。