2009-05-13 21 views
147

Mô-đun tôi đang thêm vào ứng dụng Java lớn của chúng tôi phải trò chuyện với trang web được bảo mật SSL của một công ty khác. Vấn đề là trang web sử dụng chứng chỉ tự ký. Tôi có một bản sao của chứng chỉ để xác minh rằng tôi không gặp phải một cuộc tấn công trung gian, và tôi cần kết hợp chứng chỉ này vào mã của chúng tôi sao cho kết nối với máy chủ sẽ thành công.Tôi làm cách nào để sử dụng các chứng chỉ khác nhau trên các kết nối cụ thể?

Dưới đây là các mã cơ bản:

void sendRequest(String dataPacket) { 
    String urlStr = "https://host.example.com/"; 
    URL url = new URL(urlStr); 
    HttpURLConnection conn = (HttpURLConnection)url.openConnection(); 
    conn.setMethod("POST"); 
    conn.setRequestProperty("Content-Length", data.length()); 
    conn.setDoOutput(true); 
    OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream()); 
    o.write(data); 
    o.flush(); 
} 

Nếu không có bất kỳ xử lý bổ sung tại chỗ cho các chứng chỉ tự ký, điều này qua đời ở tuổi conn.getOutputStream() với ngoại lệ sau:

Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 
.... 
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 
.... 
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 

Lý tưởng nhất , mã của tôi cần dạy Java chấp nhận chứng chỉ tự ký này, cho một điểm này trong ứng dụng, và không nơi nào khác.

Tôi biết rằng tôi có thể nhập chứng chỉ vào kho lưu trữ chứng chỉ của JRE và điều đó sẽ cho phép Java chấp nhận nó. Đó không phải là cách tiếp cận tôi muốn thực hiện nếu tôi có thể giúp; nó có vẻ rất xâm lấn để làm trên tất cả các máy khách hàng của chúng tôi cho một mô-đun mà họ có thể không sử dụng; nó sẽ ảnh hưởng đến tất cả các ứng dụng Java khác bằng cách sử dụng cùng một JRE và tôi không thích điều đó mặc dù tỷ lệ cược của bất kỳ ứng dụng Java nào khác từng truy cập vào trang web này là không. Nó cũng không phải là một hoạt động tầm thường: trên UNIX tôi phải có quyền truy cập để sửa đổi JRE theo cách này.

Tôi cũng thấy rằng tôi có thể tạo một cá thể TrustManager thực hiện một số kiểm tra tùy chỉnh. Có vẻ như tôi thậm chí có thể tạo ra một TrustManager ủy nhiệm cho TrustManager thực trong tất cả các trường hợp ngoại trừ một chứng chỉ này. Nhưng có vẻ như TrustManager được cài đặt trên toàn cầu, và tôi cho là sẽ ảnh hưởng đến tất cả các kết nối khác từ ứng dụng của chúng tôi, và điều đó không có mùi hoàn toàn đúng với tôi.

Cách ưa thích, tiêu chuẩn hoặc tốt nhất để thiết lập ứng dụng Java để chấp nhận chứng chỉ tự ký là gì? Tôi có thể hoàn thành tất cả các mục tiêu mà tôi có trong đầu, hay tôi sẽ phải thỏa hiệp? Có tùy chọn nào liên quan đến tệp và thư mục cũng như cài đặt cấu hình và mã không có không?

+0

sửa lỗi nhỏ, đang hoạt động: http://www.rgagnon.com/javadetails/java-fix-certificate-problem-in-HTTPS.html –

+16

@Hasenpriester: vui lòng không đề xuất trang này. Nó vô hiệu hóa tất cả xác minh tin cậy. Bạn không chỉ chấp nhận chứng chỉ tự ký mà bạn muốn, bạn cũng chấp nhận bất kỳ chứng chỉ nào mà kẻ tấn công MITM sẽ giới thiệu cho bạn. – Bruno

Trả lời

150

Tự mình tạo SSLSocket nhà máy và đặt nó trên HttpsURLConnection trước khi kết nối.

... 
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); 
conn.setSSLSocketFactory(sslFactory); 
conn.setMethod("POST"); 
... 

Bạn sẽ muốn tạo một SSLSocketFactory và giữ nó xung quanh.Dưới đây là bản phác thảo cách khởi tạo:

/* Load the keyStore that includes self-signed cert as a "trusted" entry. */ 
KeyStore keyStore = ... 
TrustManagerFactory tmf = 
    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 
tmf.init(keyStore); 
SSLContext ctx = SSLContext.getInstance("TLS"); 
ctx.init(null, tmf.getTrustManagers(), null); 
sslFactory = ctx.getSocketFactory(); 

Nếu bạn cần trợ giúp tạo lưu trữ khóa, vui lòng nhận xét.


Dưới đây là một ví dụ về nạp các cửa hàng chính:

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 
keyStore.load(trustStore, trustStorePassword); 
trustStore.close(); 

Để tạo các cửa hàng trọng điểm với một giấy chứng nhận dạng PEM, bạn có thể viết mã của riêng bạn sử dụng CertificateFactory, hoặc chỉ nhập nó với keytool từ JDK (keytool sẽ không hoạt động cho "mục nhập khóa", nhưng chỉ tốt cho "mục nhập đáng tin cậy").

keytool -import -file selfsigned.pem -alias server -keystore server.jks 
+3

Cảm ơn bạn rất nhiều! Những kẻ khác đã giúp tôi đi đúng hướng, nhưng cuối cùng thì đó là cách tiếp cận tôi đã làm. Tôi đã có một nhiệm vụ mệt mỏi khi chuyển đổi tệp chứng chỉ PEM thành tệp kho khóa Java JKS và tôi đã tìm thấy trợ giúp cho điều đó tại đây: http://stackoverflow.com/questions/722931/ssl-socket-php-code-needs-to- be-converting-to-java – skiphoppy

+1

Tôi rất vui vì nó đã hoạt động. Tôi xin lỗi bạn đã đấu tranh với cửa hàng chủ chốt; Tôi nên có chỉ cần bao gồm nó trong câu trả lời của tôi. Nó không quá khó khăn với CertificateFactory. Trong thực tế, tôi nghĩ rằng tôi sẽ làm một bản cập nhật cho bất cứ ai đến sau này. – erickson

+0

Rất tiếc. Điều đó có nghĩa là chuyển đổi PEM thành JKS dễ dàng hơn nhiều so với những gì tôi đã làm! Cảm ơn! :) – skiphoppy

12

Chúng tôi sao chép kho tin cậy của JRE và thêm chứng chỉ tùy chỉnh của chúng tôi vào kho ủy thác đó, sau đó yêu cầu ứng dụng sử dụng truststore tùy chỉnh với thuộc tính hệ thống. Bằng cách này, chúng tôi chỉ để lại kho tin cậy JRE mặc định.

Nhược điểm là khi bạn cập nhật JRE, bạn sẽ không nhận được kho tin cậy mới được tự động hợp nhất với tùy chỉnh của bạn.

Bạn có thể xử lý trường hợp này bằng cách cài đặt hoặc khởi động thường trình xác minh truststore/jdk và kiểm tra sự không khớp hoặc tự động cập nhật truststore. Tôi không biết điều gì sẽ xảy ra nếu bạn cập nhật truststore khi ứng dụng đang chạy.

Giải pháp này không phải là 100% thanh lịch hoặc dễ sử dụng nhưng nó đơn giản, hoạt động và không yêu cầu mã.

12

Tôi đã phải làm điều gì đó như thế này khi sử dụng commons-httpclient để truy cập vào máy chủ https nội bộ có chứng chỉ tự ký. Có, giải pháp của chúng tôi là tạo ra một TrustManager tùy chỉnh mà chỉ đơn giản là thông qua tất cả mọi thứ (đăng nhập một thông báo gỡ lỗi).

Điều này xảy ra khi có SSLSocketFactory riêng của chúng tôi tạo ổ cắm SSL từ SSLContext cục bộ của chúng tôi, được thiết lập để chỉ TrustManager cục bộ được liên kết với nó. Bạn không cần phải đi gần một kho khóa/certstore ở tất cả.

Vì vậy, đây là trong LocalSSLSocketFactory của chúng tôi:

static { 
    try { 
     SSL_CONTEXT = SSLContext.getInstance("SSL"); 
     SSL_CONTEXT.init(null, new TrustManager[] { new LocalSSLTrustManager() }, null); 
    } catch (NoSuchAlgorithmException e) { 
     throw new RuntimeException("Unable to initialise SSL context", e); 
    } catch (KeyManagementException e) { 
     throw new RuntimeException("Unable to initialise SSL context", e); 
    } 
} 

public Socket createSocket(String host, int port) throws IOException, UnknownHostException { 
    LOG.trace("createSocket(host => {}, port => {})", new Object[] { host, new Integer(port) }); 

    return SSL_CONTEXT.getSocketFactory().createSocket(host, port); 
} 

Cùng với các phương pháp khác thực hiện SecureProtocolSocketFactory. LocalSSLTrustManager là việc triển khai trình quản lý tin cậy giả đã nói ở trên.

+7

Nếu bạn vô hiệu hóa tất cả xác minh tin cậy, có ít điểm sử dụng SSL/TLS ở địa điểm đầu tiên. Bạn có thể thử nghiệm cục bộ, nhưng không phải nếu bạn muốn kết nối bên ngoài. – Bruno

+0

Tôi nhận được ngoại lệ này khi chạy nó trên Java 7. javax.net.ssl.SSLHandshakeException: không có bộ mã hóa nào trong chung Bạn có thể hỗ trợ không? – Uri

14

Nếu tạo ra một SSLSocketFactory không phải là một lựa chọn, chỉ cần nhập key vào JVM

  1. Lấy khóa công khai: $openssl s_client -connect dev-server:443, sau đó tạo ra một tập tin dev-máy chủ server.pem trông giống như

    -----BEGIN CERTIFICATE----- 
    lklkkkllklklklklllkllklkl 
    lklkkkllklklklklllkllklkl 
    lklkkkllklk.... 
    -----END CERTIFICATE----- 
    
  2. Nhập khóa: #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem. Mật khẩu: changeit

  3. Restart JVM

Nguồn: How to solve javax.net.ssl.SSLHandshakeException?

+1

Tôi không nghĩ rằng điều này giải quyết được câu hỏi ban đầu, nhưng nó đã giải quyết được vấn đề * của tôi, vì vậy cảm ơn! –

11

tôi đã đi qua rất nhiều nơi trong SO và các trang web để giải quyết điều này. Đây là mã mà làm việc cho tôi:

   ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes()); 
       CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); 
       X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream); 
       String alias = "alias";//cert.getSubjectX500Principal().getName(); 

       KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); 
       trustStore.load(null); 
       trustStore.setCertificateEntry(alias, cert); 
       KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); 
       kmf.init(trustStore, null); 
       KeyManager[] keyManagers = kmf.getKeyManagers(); 

       TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); 
       tmf.init(trustStore); 
       TrustManager[] trustManagers = tmf.getTrustManagers(); 

       SSLContext sslContext = SSLContext.getInstance("TLS"); 
       sslContext.init(keyManagers, trustManagers, null); 
       URL url = new URL(someURL); 
       conn = (HttpsURLConnection) url.openConnection(); 
       conn.setSSLSocketFactory(sslContext.getSocketFactory()); 

app.certificateString là một String chứa Giấy chứng nhận, ví dụ:

  static public String certificateString= 
      "-----BEGIN CERTIFICATE-----\n" + 
      "MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" + 
      "BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" + 
      ... a bunch of characters... 
      "5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" + 
      "-----END CERTIFICATE-----"; 

Tôi đã thử nghiệm mà bạn có thể đặt bất kỳ ký tự trong chuỗi chứng , nếu nó là tự ký, miễn là bạn giữ cấu trúc chính xác ở trên. Tôi đã nhận được chuỗi chứng chỉ với dòng lệnh Terminal của máy tính xách tay của mình.

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