2009-11-03 43 views
170

Tôi khá mới đối với HTTPS/SSL/TLS và tôi hơi bối rối về những gì chính xác mà khách hàng phải xuất trình khi xác thực bằng chứng chỉ.Xác thực chứng chỉ ứng dụng khách Java HTTPS

Tôi đang viết một ứng dụng khách Java cần thực hiện một POST dữ liệu đơn giản cho một URL cụ thể. Phần đó hoạt động tốt, vấn đề duy nhất là nó phải được thực hiện qua HTTPS. Phần HTTPS khá dễ xử lý (với HTTPclient hoặc sử dụng hỗ trợ HTTPS tích hợp sẵn của Java), nhưng tôi bị kẹt khi xác thực bằng chứng chỉ ứng dụng khách. Tôi đã nhận thấy đã có một câu hỏi rất giống nhau ở đây, mà tôi đã không thử với mã của tôi được nêu ra (sẽ làm như vậy đủ sớm). Vấn đề hiện tại của tôi là - bất cứ điều gì tôi làm - máy khách Java không bao giờ gửi cùng chứng chỉ (tôi có thể kiểm tra điều này với các bãi PCAP).

Tôi muốn biết chính xác khách hàng phải trình bày gì với máy chủ khi xác thực bằng chứng chỉ (đặc biệt cho Java - nếu điều đó quan trọng)? Đây có phải là tệp JKS hay PKCS # 12 không? Những gì được cho là trong họ; chỉ là chứng chỉ ứng dụng khách hoặc một khóa? Nếu có, chìa khóa nào? Có khá nhiều sự nhầm lẫn về tất cả các loại tệp, loại chứng chỉ khác nhau và các loại tệp khác nhau.

Như tôi đã nói trước đây tôi mới sử dụng HTTPS/SSL/TLS vì vậy tôi sẽ đánh giá cao một số thông tin cơ bản (không phải là một bài luận; Tôi sẽ giải quyết các liên kết đến bài viết hay).

Trả lời

186

Cuối cùng quản lý để giải quyết tất cả các vấn đề, vì vậy tôi sẽ trả lời câu hỏi của riêng tôi.Đây là các thiết lập/tập tin mà tôi đã sử dụng để quản lý để giải quyết các vấn đề cụ thể của tôi;

Các khách hàng của keystore là một PKCS # 12 định dạng tập tin chứa

  1. công giấy chứng nhận của khách hàng (trong trường hợp này có chữ ký của CA tự ký)
  2. của khách hàng tin khóa

Để tạo nó, tôi đã sử dụng OpenSSL pkcs12 lệnh, ví dụ;

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

Mẹo: chắc chắn bạn sẽ có được OpenSSL mới nhất, không phiên bản 0.9.8h vì điều đó dường như bị một lỗi mà không cho phép bạn tạo ra đúng PKCS # 12 tập tin.

Tệp PKCS # 12 này sẽ được trình khách Java sử dụng để trình bày chứng chỉ ứng dụng khách đến máy chủ khi máy chủ yêu cầu khách hàng xác thực rõ ràng. Xem Wikipedia article on TLS để biết tổng quan về cách giao thức để xác thực chứng chỉ ứng dụng thực sự hoạt động (cũng giải thích lý do tại sao chúng tôi cần khóa riêng của khách hàng tại đây). truststore

Các khách hàng là một thẳng về phía trước định dạng JKS tập tin có chứa các gốc hoặc chứng chỉ CA trung gian. Các chứng chỉ CA này sẽ xác định điểm cuối nào bạn sẽ được phép giao tiếp, trong trường hợp này nó sẽ cho phép máy khách của bạn kết nối với bất kỳ máy chủ nào trình bày chứng chỉ được ký bởi một trong các CA của truststore.

Để tạo nó, bạn có thể sử dụng công cụ khóa Java tiêu chuẩn, ví dụ;

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 

Sử dụng truststore này, khách hàng của bạn sẽ cố gắng làm một cái bắt tay SSL hoàn chỉnh với tất cả các máy chủ người trình bày một giấy chứng nhận có chữ ký của CA xác định bởi myca.crt.

Các tệp ở trên chỉ dành cho khách hàng. Khi bạn muốn thiết lập một máy chủ, máy chủ cần các tệp khóa và tin cậy của riêng nó. Một bước đi tuyệt vời để thiết lập một ví dụ hoàn toàn làm việc cho cả máy khách và máy chủ Java (sử dụng Tomcat) có thể được tìm thấy trên this website.

vấn đề/chú/Mẹo

  1. khách hàng thẩm định chứng chỉ chỉ có thể được thực thi bởi máy chủ.
  2. (Quan trọng!) Khi máy chủ yêu cầu chứng chỉ ứng dụng khách (như là một phần của bắt tay TLS), nó cũng sẽ cung cấp danh sách các CA đáng tin cậy như một phần của yêu cầu chứng chỉ. Khi chứng chỉ ứng dụng khách mà bạn muốn hiển thị để xác thực là không phải được ký bởi một trong các CA này, nó sẽ không được trình bày (theo ý kiến ​​của tôi, đây là hành vi kỳ lạ, nhưng tôi chắc chắn có lý do) .Đây là nguyên nhân chính của vấn đề của tôi, vì bên kia đã không định cấu hình máy chủ của họ đúng cách để chấp nhận chứng chỉ ứng dụng khách tự ký của tôi và chúng tôi cho rằng vấn đề ở cuối của tôi không cung cấp đúng chứng chỉ ứng dụng khách trong yêu cầu.
  3. Nhận Wireshark. Nó có phân tích gói SSL/HTTPS tuyệt vời và sẽ là một sự giúp đỡ rất lớn trong việc gỡ lỗi và tìm ra vấn đề. Nó tương tự như -Djavax.net.debug=ssl nhưng có cấu trúc hơn và (được cho là) ​​dễ hiểu hơn nếu bạn không thoải mái với đầu ra gỡ lỗi SSL của Java.
  4. Hoàn toàn có thể sử dụng thư viện Apache httpclient. Nếu bạn muốn sử dụng httpclient, chỉ cần thay thế URL đích bằng HTTPS tương đương và thêm các đối số JVM sau (giống với bất kỳ ứng dụng khách nào khác, bất kể thư viện bạn muốn sử dụng để gửi/nhận dữ liệu qua 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
+3

"Khi chứng chỉ ứng dụng khách muốn hiển thị để xác thực không được ký bởi một trong các CA này, nó sẽ không được hiển thị ở tất cả". Các chứng chỉ không được trình bày vì máy khách biết rằng chúng sẽ không được máy chủ chấp nhận. Ngoài ra, chứng chỉ của bạn có thể được ký bởi CA trung gian "ICA" và máy chủ có thể giới thiệu máy khách của bạn với CA gốc "RCA" và trình duyệt web của bạn sẽ vẫn cho phép bạn chọn chứng chỉ mặc dù ICA không phải là RCA. – KyleM

+2

Như một ví dụ của chú thích ở trên, hãy xem xét một tình huống mà bạn có một CA gốc (RCA1) và hai CA trung gian (ICA1 và ICA2). Trên Apache Tomcat nếu bạn nhập RCA1 vào kho lưu trữ tin cậy, trình duyệt web của bạn sẽ hiển thị TẤT CẢ các chứng chỉ được ký bởi ICA1 và ICA2, mặc dù chúng không nằm trong kho lưu trữ tin cậy của bạn. Điều này là bởi vì đó là chuỗi quan trọng không phải là chứng chỉ cá nhân. – KyleM

+2

"theo ý kiến ​​của tôi, đây là hành vi kỳ lạ, nhưng tôi chắc chắn có một lý do cho nó". Lý do cho nó là đó là những gì nó nói trong RFC 2246. Không có gì lạ về nó. Cho phép khách hàng trình bày các chứng chỉ không được máy chủ chấp nhận là điều kỳ quặc và lãng phí thời gian và không gian. – EJP

24

Tệp JKS của họ chỉ là vùng chứa cho chứng chỉ và cặp khóa. Trong một kịch bản chứng thực client-side, các bộ phận khác nhau của các phím sẽ được đặt ở đây: cửa hàng

  • Các client 's sẽ chứa tư nhân và công cộng cặp khóa của khách hàng. Nó được gọi là một kho khóa .
  • Máy chủ máy chủ sẽ chứa khóa công khai của khách hàng chính. Nó được gọi là truststore.

Việc tách cửa hàng tin cậy và kho khóa không bắt buộc nhưng được khuyến nghị. Chúng có thể là cùng một tệp vật lý.

Để xác định vùng hệ thống tập tin của hai cửa hàng, sử dụng các thuộc tính hệ thống sau:

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

và trên máy chủ:

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

Để xuất giấy chứng nhận của khách hàng (khóa công khai) để một để bạn có thể sao chép nó vào máy chủ, sử dụng

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

Để nhập publi của khách hàng c chìa khóa vào keystore của máy chủ, sử dụng (như các poster đề cập, đây đã được thực hiện bởi các quản trị viên máy chủ)

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

Tôi có lẽ nên đề cập đến rằng tôi không có quyền kiểm soát máy chủ. Máy chủ đã nhập chứng chỉ công khai của chúng tôi. Tôi đã được các quản trị viên của hệ thống nói rằng tôi cần cung cấp một cách rõ ràng chứng chỉ để nó có thể được gửi cùng trong quá trình bắt tay (máy chủ của họ yêu cầu một cách rõ ràng điều này). – tmbrggmn

+0

Bạn sẽ cần các khóa công khai và riêng tư cho chứng chỉ công khai của bạn (chứng chỉ được biết đến với máy chủ) dưới dạng tệp JKS. – sfussenegger

+0

Cảm ơn mã ví dụ. Trong đoạn mã trên, "mykey-public.cer" chính xác là gì? Đây có phải là chứng chỉ công khai của khách hàng (chúng tôi sử dụng chứng chỉ tự ký) không? – tmbrggmn

8

Maven pom.xml: mã

<?xml version="1.0" encoding="UTF-8"?> 
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 
    <modelVersion>4.0.0</modelVersion> 
    <groupId>some.examples</groupId> 
    <artifactId>sslcliauth</artifactId> 
    <version>1.0-SNAPSHOT</version> 
    <packaging>jar</packaging> 
    <name>sslcliauth</name> 
    <dependencies> 
     <dependency> 
      <groupId>org.apache.httpcomponents</groupId> 
      <artifactId>httpclient</artifactId> 
      <version>4.4</version> 
     </dependency> 
    </dependencies> 
</project> 

Java:

package some.examples; 

import java.io.FileInputStream; 
import java.io.IOException; 
import java.security.KeyManagementException; 
import java.security.KeyStore; 
import java.security.KeyStoreException; 
import java.security.NoSuchAlgorithmException; 
import java.security.UnrecoverableKeyException; 
import java.security.cert.CertificateException; 
import java.util.logging.Level; 
import java.util.logging.Logger; 
import javax.net.ssl.SSLContext; 
import org.apache.http.HttpEntity; 
import org.apache.http.HttpHost; 
import org.apache.http.client.config.RequestConfig; 
import org.apache.http.client.methods.CloseableHttpResponse; 
import org.apache.http.client.methods.HttpPost; 
import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 
import org.apache.http.ssl.SSLContexts; 
import org.apache.http.impl.client.CloseableHttpClient; 
import org.apache.http.impl.client.HttpClients; 
import org.apache.http.util.EntityUtils; 
import org.apache.http.entity.InputStreamEntity; 

public class SSLCliAuthExample { 

private static final Logger LOG = Logger.getLogger(SSLCliAuthExample.class.getName()); 

private static final String CA_KEYSTORE_TYPE = KeyStore.getDefaultType(); //"JKS"; 
private static final String CA_KEYSTORE_PATH = "./cacert.jks"; 
private static final String CA_KEYSTORE_PASS = "changeit"; 

private static final String CLIENT_KEYSTORE_TYPE = "PKCS12"; 
private static final String CLIENT_KEYSTORE_PATH = "./client.p12"; 
private static final String CLIENT_KEYSTORE_PASS = "changeit"; 

public static void main(String[] args) throws Exception { 
    requestTimestamp(); 
} 

public final static void requestTimestamp() throws Exception { 
    SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(
      createSslCustomContext(), 
      new String[]{"TLSv1"}, // Allow TLSv1 protocol only 
      null, 
      SSLConnectionSocketFactory.getDefaultHostnameVerifier()); 
    try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(csf).build()) { 
     HttpPost req = new HttpPost("https://changeit.com/changeit"); 
     req.setConfig(configureRequest()); 
     HttpEntity ent = new InputStreamEntity(new FileInputStream("./bytes.bin")); 
     req.setEntity(ent); 
     try (CloseableHttpResponse response = httpclient.execute(req)) { 
      HttpEntity entity = response.getEntity(); 
      LOG.log(Level.INFO, "*** Reponse status: {0}", response.getStatusLine()); 
      EntityUtils.consume(entity); 
      LOG.log(Level.INFO, "*** Response entity: {0}", entity.toString()); 
     } 
    } 
} 

public static RequestConfig configureRequest() { 
    HttpHost proxy = new HttpHost("changeit.local", 8080, "http"); 
    RequestConfig config = RequestConfig.custom() 
      .setProxy(proxy) 
      .build(); 
    return config; 
} 

public static SSLContext createSslCustomContext() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException { 
    // Trusted CA keystore 
    KeyStore tks = KeyStore.getInstance(CA_KEYSTORE_TYPE); 
    tks.load(new FileInputStream(CA_KEYSTORE_PATH), CA_KEYSTORE_PASS.toCharArray()); 

    // Client keystore 
    KeyStore cks = KeyStore.getInstance(CLIENT_KEYSTORE_TYPE); 
    cks.load(new FileInputStream(CLIENT_KEYSTORE_PATH), CLIENT_KEYSTORE_PASS.toCharArray()); 

    SSLContext sslcontext = SSLContexts.custom() 
      //.loadTrustMaterial(tks, new TrustSelfSignedStrategy()) // use it to customize 
      .loadKeyMaterial(cks, CLIENT_KEYSTORE_PASS.toCharArray()) // load client certificate 
      .build(); 
    return sslcontext; 
} 

} 
+0

Nếu bạn muốn có chứng chỉ khả dụng cho tất cả các ứng dụng sử dụng cài đặt JVM cụ thể, hãy làm theo [câu trả lời này] (http: //stackoverflow.com/a/16397662/1134080) thay thế. – ADTC

+0

là phương thức 'configureRequest()' để thiết lập proxy của dự án khách hàng phải không? – shareef

+0

có, đó là cấu hình máy khách http và cấu hình proxy trong trường hợp này là – wildloop

26

Câu trả lời khác cho thấy cách cấu hình toàn cầu chứng chỉ ứng dụng khách. Tuy nhiên nếu bạn muốn lập trình xác định các khách hàng trọng điểm cho một kết nối đặc biệt, chứ không phải là toàn cầu định nghĩa nó trên mọi ứng dụng chạy trên JVM của bạn, sau đó bạn có thể cấu hình SSLContext riêng của bạn như vậy:

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")); 
+0

Tôi đã phải sử dụng 'sslContext = SSLContexts.custom(). LoadTrustMaterial (keyFile, PASSWORD) .build();'. Tôi không thể làm cho nó hoạt động với 'loadKeyMaterial (...)'. –

+1

@ConorSvensson Tài liệu tin cậy dành cho khách hàng tin tưởng chứng chỉ máy chủ từ xa, tài liệu chính dành cho máy chủ tin tưởng khách hàng. – Magnus

+1

Tôi thực sự thích câu trả lời ngắn gọn và trực tuyến này. Trong trường hợp mọi người quan tâm, tôi cung cấp một ví dụ làm việc ở đây với hướng dẫn xây dựng. https://stackoverflow.com/a/46821977/154527 –

0

Tôi nghĩ việc sửa chữa đây là loại kho khóa, pkcs12 (pfx) luôn có khóa riêng và loại JKS có thể tồn tại mà không có khóa cá nhân. Trừ khi bạn chỉ định trong mã của bạn hoặc chọn một chứng chỉ thông qua trình duyệt, máy chủ không có cách nào để biết nó là đại diện cho một khách hàng ở đầu bên kia.

Các vấn đề liên quan