Ssl

如果指定了 URL 路徑,則握手失敗

  • May 3, 2018

我正在嘗試通過 HTTPS 使用 REST API,但只有在我指定 URL 路徑時才會出現握手失敗(僅在域名方面沒有收到錯誤)。

按照 API 所有者的指示,我收到了一個 .PEM 文件(未簽名的證書?),我使用該文件生成了一個 CSR,然後我將其發回,後者又給了我一個私鑰文件,然後我將其與證書和簽名鏈一起封裝到 PFX 文件中。

我將 PFX 導入到 Internet Explorer 證書儲存中,現在我可以毫無問題地訪問“根”URL(僅限域名)和 API URL(包括 URL 路徑)。它也適用於 Google Chrome 和 Postman。

現在,我正在嘗試使其與其他 3 個應用程序一起使用:

  • CA Technologies - API 管理
  • Software AG - 集成伺服器
  • Java 8 通過 HttpsURLConnection

在每種情況下,我都可以訪問“根”URL 並獲得響應,但如果我在 URL 中添加路徑,我會遇到握手失敗:

讓我們看看我為每個應用程序做了什麼……

1) CA Technologies - API 管理

我使用“管理證書”導入嚮導在 CA API Gateway 中導入了 PFX。它添加了 3 個證書條目:實際證書、中間證書頒發機構和配置為信任錨的根證書頒發機構。檢查證書以與“出站 SSL 連接”一起使用。

但是,當我對帶有路徑的 URL 使用斷言“通過 HTTPS 路由”時,握手失敗。我還嘗試使用“管理證書”添加嚮導向商店添加證書,並為其提供帶有下載證書路徑的 URL。它在我的商店中添加了 3 個不同的證書,但它仍然不起作用。

2) Software AG - 集成伺服器

由於此 ESB 無法讀取 PFX 文件,因此我通過 Internet Explorer 證書導出將 3 個證書(實際 + 簽名鏈)導出到 .CER 文件。在 WebMethods Integration Server 管理員控制台中,我使用“配置客戶端證書”菜單添加了證書。我還使用預設值(DEFAULT_IS_KEYSTORE 和 DEFAULT_IS_TRUSTSTORE)配置了 Keystore 和 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 Gateway 和 Software AG Integration Server 中找到一種方法來做同樣的事情。

感謝@dave_thompson_085@EnricoBasseti,我設法讓它與我的3 個應用程序一起工作。

這是我為每個人所做的:

1) CA Technologies - API 管理

在“管理私鑰”導入嚮導中,我從 PFX 文件中添加了密鑰。然後在“管理證書”添加嚮導中,我從私鑰的證書鏈中導入了證書(第 4 個選項)。然後我使用“管理證書”添加嚮導從 API URL 導入證書(在我的例子中是不同的證書)。

然後在我的策略中,我右鍵點擊“路由到 HTTP(S) 斷言”並點擊“選擇私鑰”以強制 CA API Gateway 使用我之前導入的私鑰,而不是其預設密鑰。

2) Software AG - 集成伺服器

在 myWebmethod Web 控制台中,在“安全”和“密鑰庫”中,點擊“創建密鑰庫別名”。我配置了一個新的 PCKS12 密鑰庫並將我的 PFX 文件提供給它(必須分別指定密鑰庫密碼和密鑰密碼)。

然後在“安全”和“證書”中,點擊“配置客戶端證書”並從我的證書鏈中添加根證書(我之前使用 IE 導出)。我還從 URL 認證鏈中添加了根證書(在我的情況下不同)。

最後,在我的流服務中,在使用“pub.client:http”組件之前,我添加了一個“pub.security.keystore:setKeyAndChain”組件,配置為使用我的新 KeyStore 別名及其 Key 別名。

3) Java 8 通過 HttpsURLConnection

我不得不用 oracle 提供的文件替換我的 JDK 安全策略文件:UnlimitedJCEPolicyJDK8(僅當使用 JDK8 < 161 時)。然後我在我的 cacerts 文件 (%JAVA_HOME%/jre/lib/security/cacerts) 中導入了密鑰對。我還導入了 API URL 的根證書(與我的 PFX 文件中包含的證書不同)。

然後在我的程式碼中,我強制 Java 使用我的 cacerts 作為 KeyStore(請參閱我原始文章中的 EDIT 4)。

TLS 連接是在 HTTP 請求之前建立的,因此無法區分(在伺服器端)您將請求哪個端點(僅使用 SNI 的主機名)。這就是證書不包含完整 URL 的原因。

因此,在不同 URL 的握手時崩潰是非常奇怪的——也許端點 URL 正在302重定向到另一台伺服器?查看您正在使用的應用程序是否具有禁用該選項的選項Follow HTTP redirects(或類似的選項)。

您也可以使用 Internet Explorer 進行檢查:查看當您嘗試訪問時 URL 是否更改https://api.xxx.com/method/

引用自:https://serverfault.com/questions/909974