ssl client - Java HTTPS客戶端證書認證




connection (6)

我對HTTPS / SSL / TLS相當陌生,我對使用證書進行身份驗證時客戶端應該呈現的內容感到困惑。

我正在編寫一個Java客戶端,需要對特定的URL執行簡單的POST數據POST。 這部分工作正常,唯一的問題是它應該通過HTTPS完成。 HTTPS部分相當容易處理(使用HTTPclient或使用Java的內置HTTPS支持),但我堅持使用客戶端證書進行身份驗證。 我注意到在這裡已經有一個非常類似的問題,我還沒有用我的代碼嘗試過(將盡快完成)。 我目前的問題是 - 無論我做什麼 - Java客戶端永遠不會發送證書(我可以使用PCAP轉儲來檢查此問題)。

我想知道當使用證書進行身份驗證時,客戶端應該向服務器提供什麼(專門用於Java - 如果這很重要)? 這是一個JKS文件,還是PKCS#12? 他們應該有什麼; 只是客戶端證書或密鑰? 如果是這樣,哪個鍵? 關於所有不同類型的文件,證書類型等都存在相當多的混淆。

正如我之前所說的,我是HTTPS / SSL / TLS的新手,所以我希望能夠獲得一些背景信息(不一定是散文;我會解決鏈接到好文章的問題)。


Answers

最後設法解決所有問題,所以我會回答我自己的問題。 這些是我用來解決特定問題的設置/文件;

客戶端的密鑰庫是一個包含PKCS#12格式的文件

  1. 客戶的公共證書(在這種情況下由自簽名的CA簽署)
  2. 客戶的私鑰

為了生成它,我使用了OpenSSL的pkcs12命令;

openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "Whatever"

提示:確保您獲得最新的OpenSSL, 而不是版本0.9.8h,因為這似乎受到一個不允許您正確生成PKCS#12文件的錯誤的影響。

當服務器明確請求客戶端進行身份驗證時,Java客戶端將使用此PKCS#12文件將客戶端證書呈現給服務器。 請參閱TLS上維基百科文章,了解客戶端證書身份驗證協議實際如何工作的概述(也解釋了為什麼我們需要此處的客戶端私鑰)。

客戶端的信任庫是一個簡單的JKS格式文件,包含 證書中間CA證書 。 這些CA證書將確定您將被允許與哪些端點通信,在這種情況下,它將允許您的客戶端連接到任何一個服務器提供由其中一個信任庫的CA簽名的證書。

為了生成它,你可以使用標準的Java keytool,例如;

keytool -genkey -dname "cn=CLIENT" -alias truststorekey -keyalg RSA -keystore ./client-truststore.jks -keypass whatever -storepass whatever
keytool -import -keystore ./client-truststore.jks -file myca.crt -alias myca

使用此信任庫,您的客戶端將嘗試與提供由myca.crt標識的CA簽名的證書的所有服務器進行完整的SSL握手。

以上文件僅限客戶使用。 當你想要設置服務器時,服務器也需要自己的密鑰和信任庫文件。 在這個網站上可以找到為Java客戶端和服務器(使用Tomcat)設置完整工作示例的很好的演練。

問題/備註/提示

  1. 客戶端證書認證只能由服務器執行。
  2. 重要! )當服務器請求客戶端證書(作為TLS握手的一部分)時,它還會提供一個可信CA的列表作為證書請求的一部分。 當你希望出示身份驗證的客戶端證書沒有被這些CA之一簽名時,它根本不會被呈現(在我看來,這是奇怪的行為,但我相信這是有原因的)。 這是我的問題的主要原因,因為另一方沒有正確配置他們的服務器來接受我的自簽名客戶端證書,並且我們認為問題在於我沒有在請求中正確提供客戶端證書。
  3. 獲取Wireshark。 它具有很好的SSL / HTTPS數據包分析功能,對於調試和發現問題將有很大的幫助。 它類似於-Djavax.net.debug=ssl但是如果您對Java SSL調試輸出不舒服,則更具結構化和(可以說)更易於解釋。
  4. 完全可以使用Apache httpclient庫。 如果您想使用httpclient,只需將目標URL替換為HTTPS等效項並添加以下JVM參數(對於任何其他客戶端而言都是相同的,無論您想要通過HTTP / HTTPS發送/接收數據的庫是什麼) :

    -Djavax.net.debug=ssl
    -Djavax.net.ssl.keyStoreType=pkcs12
    -Djavax.net.ssl.keyStore=client.p12
    -Djavax.net.ssl.keyStorePassword=whatever
    -Djavax.net.ssl.trustStoreType=jks
    -Djavax.net.ssl.trustStore=client-truststore.jks
    -Djavax.net.ssl.trustStorePassword=whatever

他們的JKS文件只是一個證書和密鑰對的容器。 在客戶端身份驗證方案中,密鑰的各個部分將位於此處:

  • 客戶的商店將包含客戶的私鑰和公鑰對。 它被稱為密鑰庫
  • 服務器的商店將包含客戶端的公鑰。 它被稱為信任庫

信任庫和密鑰庫的分離不是強制性的,但建議使用。 它們可以是相同的物理文件。

要設置兩個商店的文件系統位置,請使用以下系統屬性:

-Djavax.net.ssl.keyStore=clientsidestore.jks

並在服務器上:

-Djavax.net.ssl.trustStore=serversidestore.jks

要將客戶端的證書(公鑰)導出到文件中,以便將其複製到服務器中,請使用

keytool -export -alias MYKEY -file publicclientkey.cer -store clientsidestore.jks

要將客戶端的公鑰導入到服務器的密鑰庫中,請使用(如上所述,這已由服務器管理員完成)

keytool -import -file publicclientkey.cer -store serversidestore.jks

對於那些只想設置雙向身份驗證(服務器和客戶端證書)的用戶,這兩個鏈接的組合可以幫助您:

雙向認證設置:

https://linuxconfig.org/apache-web-server-ssl-authentication

你不需要使用他們提到的openssl配置文件; 只是使用

  • $ openssl genrsa -des3 -out ca.key 4096

  • $ openssl req -new -x509 -days 365 -key ca.key -out ca.crt

生成您自己的CA證書,然後通過以下方式生成並簽署服務器和客戶端密鑰:

  • $ openssl genrsa -des3 -out server.key 4096

  • $ openssl req -new -key client.key -out server.csr

  • $ openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 100 -out server.crt

  • $ openssl genrsa -des3 -out client.key 4096

  • $ openssl req -new -key client.key -out client.csr

  • $ openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 101 -out client.crt

其餘的請按照鏈接中的步驟操作。 管理Chrome證書的工作方式與上述提到的firefox示例相同。

接下來,通過以下方式設置服務

https://www.digitalocean.com/community/tutorials/how-to-create-a-ssl-certificate-on-apache-for-ubuntu-14-04

請注意,您已經創建了服務器.crt和.key,因此您不必再執行該步驟。


我認為這裡的修復是keystore類型,pkcs12(pfx)總是有私鑰,JKS類型可以不存在私鑰存在。 除非您在代碼中指定或通過瀏覽器選擇證書,否則服務器無法知道它代表另一端的客戶端。


其他答案顯示瞭如何全局配置客戶端證書。 但是,如果要以編程方式為特定連接定義客戶端密鑰,而不是跨JVM上運行的每個應用程序全局定義它,則可以像這樣配置自己的SSLContext:

String keyPassphrase = "";

KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("cert-key-pair.pfx"), keyPassphrase.toCharArray());

SSLContext sslContext = SSLContexts.custom()
        .loadKeyMaterial(keyStore, null)
        .build();

HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
HttpResponse response = httpClient.execute(new HttpGet("https://example.com"));

請原諒我的挑剔,但大多數建議的解決方案,即min + rng.nextInt(max - min + 1)) ,由於以下事實似乎很危險:

  • rng.nextInt(n)無法達到Integer.MAX_VALUE
  • min為負時, (max - min)可能導致溢出。

一個萬無一失的解決方案將為[ Integer.MIN_VALUEInteger.MAX_VALUE ]中的任何min <= max返回正確的結果。 考慮以下天真的實現:

int nextIntInRange(int min, int max, Random rng) {
   if (min > max) {
      throw new IllegalArgumentException("Cannot draw random int from invalid range [" + min + ", " + max + "].");
   }
   int diff = max - min;
   if (diff >= 0 && diff != Integer.MAX_VALUE) {
      return (min + rng.nextInt(diff + 1));
   }
   int i;
   do {
      i = rng.nextInt();
   } while (i < min || i > max);
   return i;
}

雖然效率低下,但請注意while循環中成功的概率始終為50%或更高。







java ssl https client-certificates