Chứng chỉ ứng dụng khách Java qua HTTPS / SSL


116

Tôi đang sử dụng Java 6 và đang cố gắng tạo một HttpsURLConnectionmáy chủ từ xa chống lại máy chủ từ xa, sử dụng chứng chỉ máy khách.
Máy chủ đang sử dụng chứng chỉ gốc tự ký và yêu cầu xuất trình chứng chỉ máy khách được bảo vệ bằng mật khẩu. Tôi đã thêm chứng chỉ gốc máy chủ và chứng chỉ máy khách vào kho khóa java mặc định mà tôi tìm thấy trong /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts(OSX 10.5). Tên của tệp kho khóa dường như gợi ý rằng chứng chỉ ứng dụng khách không được phép đi vào đó?

Dù sao, việc thêm chứng chỉ gốc vào cửa hàng này đã giải quyết được vấn đề khét tiếng javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.

Tuy nhiên, bây giờ tôi đang gặp khó khăn về cách sử dụng chứng chỉ ứng dụng khách. Tôi đã thử hai cách tiếp cận và không đưa tôi đến đâu.
Đầu tiên và ưu tiên, hãy thử:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
URL url = new URL("https://somehost.dk:3049");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslsocketfactory);
InputStream inputstream = conn.getInputStream();
// The last line fails, and gives:
// javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

Tôi đã thử bỏ qua lớp HttpsURLConnection (không lý tưởng vì tôi muốn nói chuyện HTTP với máy chủ) và thực hiện việc này thay thế:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("somehost.dk", 3049);
InputStream inputstream = sslsocket.getInputStream();
// do anything with the inputstream results in:
// java.net.SocketTimeoutException: Read timed out

Tôi thậm chí không chắc rằng chứng chỉ khách hàng là vấn đề ở đây.


Tôi đã cung cấp cho khách hàng hai chứng chỉ để xác định cái nào cần thêm vào kho khóa và cửa hàng tin cậy, bạn có thể vui lòng giúp xác định vấn đề này vì bạn đã trải qua loại vấn đề tương tự stackoverflow.com/questions/61374276/…
henrycharles

Câu trả lời:


100

Cuối cùng cũng giải quyết được rồi;). Có một gợi ý mạnh mẽ ở đây (Câu trả lời của Gandalfs cũng chạm vào nó một chút). Các liên kết bị thiếu (chủ yếu) là tham số đầu tiên trong số các tham số dưới đây và ở một mức độ nào đó tôi đã bỏ qua sự khác biệt giữa kho khóa và cửa hàng tin cậy.

Chứng chỉ máy chủ tự ký phải được nhập vào kho tin cậy:

keytool -import -alias gridserver -file Gridserver.crt -storepass $ PASS -keystore gridserver.keystore

Các thuộc tính này cần được đặt (trên dòng lệnh hoặc trong mã):

-Djavax.net.ssl.keyStoreType=pkcs12
-Djavax.net.ssl.trustStoreType=jks
-Djavax.net.ssl.keyStore=clientcertificate.p12
-Djavax.net.ssl.trustStore=gridserver.keystore
-Djavax.net.debug=ssl # very verbose debug
-Djavax.net.ssl.keyStorePassword=$PASS
-Djavax.net.ssl.trustStorePassword=$PASS

Mã ví dụ làm việc:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
URL url = new URL("https://gridserver:3049/cgi-bin/ls.py");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslsocketfactory);
InputStream inputstream = conn.getInputStream();
InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
BufferedReader bufferedreader = new BufferedReader(inputstreamreader);

String string = null;
while ((string = bufferedreader.readLine()) != null) {
    System.out.println("Received " + string);
}

Tôi đã sử dụng một url như: localhost: 8443 / Application_Name / getAttributes . Tôi có một phương pháp với ánh xạ url / getAttribute. Phương thức này trả về một danh sách các phần tử. Tôi đã sử dụng HttpsUrlConnection, mã phản hồi kết nối là 200, nhưng nó không cung cấp cho tôi danh sách thuộc tính khi tôi sử dụng inputStream, nó cung cấp cho tôi nội dung html của trang đăng nhập của tôi. Tôi đã thực hiện Xác thực và đặt loại nội dung là JSON. Vui lòng đề xuất
Deepak

83

Mặc dù không được khuyến nghị nhưng bạn cũng có thể tắt hoàn toàn xác thực chứng chỉ SSL:

import javax.net.ssl.*;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

public class SSLTool {

  public static void disableCertificateValidation() {
    // Create a trust manager that does not validate certificate chains
    TrustManager[] trustAllCerts = new TrustManager[] { 
      new X509TrustManager() {
        public X509Certificate[] getAcceptedIssuers() { 
          return new X509Certificate[0]; 
        }
        public void checkClientTrusted(X509Certificate[] certs, String authType) {}
        public void checkServerTrusted(X509Certificate[] certs, String authType) {}
    }};

    // Ignore differences between given hostname and certificate hostname
    HostnameVerifier hv = new HostnameVerifier() {
      public boolean verify(String hostname, SSLSession session) { return true; }
    };

    // Install the all-trusting trust manager
    try {
      SSLContext sc = SSLContext.getInstance("SSL");
      sc.init(null, trustAllCerts, new SecureRandom());
      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
      HttpsURLConnection.setDefaultHostnameVerifier(hv);
    } catch (Exception e) {}
  }
}

72
Cần lưu ý rằng việc tắt xác thực chứng chỉ như thế này sẽ mở ra kết nối với các cuộc tấn công MITM có thể xảy ra: không sử dụng trong sản xuất .
Bruno

3
Rất may, mã không biên dịch. 'Giải pháp' này hoàn toàn không an toàn.
Marquis of Lorne,

5
@ neu242, không, nó không thực sự phụ thuộc vào việc bạn sử dụng nó để làm gì. Nếu bạn muốn sử dụng SSL / TLS, bạn muốn bảo mật kết nối của mình trước các cuộc tấn công MITM, đó là toàn bộ vấn đề. Xác thực máy chủ là không cần thiết nếu bạn có thể đảm bảo rằng không ai có thể thay đổi lưu lượng truy cập, nhưng các tình huống mà bạn nghi ngờ có thể có kẻ nghe trộm cũng sẽ không có khả năng thay đổi lưu lượng mạng cũng khá hiếm.
Bruno

1
@ neu242 Cảm ơn vì đoạn mã. Tôi thực sự đang nghĩ đến việc sử dụng điều này trong sản xuất cho một mục đích rất cụ thể (thu thập thông tin web) và tôi đã đề cập đến việc triển khai của bạn trong một câu hỏi. ( Stackoverflow.com/questions/13076511/… ). Nếu bạn có thời gian, bạn có thể vui lòng xem nó và cho tôi biết nếu có bất kỳ rủi ro bảo mật nào mà tôi đã bỏ qua không?
Sal

1
@Bruno Nếu tôi chỉ ping máy chủ, điều đó có thực sự ảnh hưởng đến tôi bởi các cuộc tấn công MITM không?
PhoonOne

21

Bạn đã đặt thuộc tính KeyStore và / hoặc Hệ thống TrustStore chưa?

java -Djavax.net.ssl.keyStore=pathToKeystore -Djavax.net.ssl.keyStorePassword=123456

hoặc từ với mã

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);

Tương tự với javax.net.ssl.trustStore


13

Nếu bạn đang xử lý một cuộc gọi dịch vụ web bằng khung Axis, có một câu trả lời đơn giản hơn nhiều. Nếu tất cả mong muốn là khách hàng của bạn có thể gọi dịch vụ web SSL và bỏ qua lỗi chứng chỉ SSL, chỉ cần đặt câu lệnh này trước khi bạn gọi bất kỳ dịch vụ web nào:

System.setProperty("axis.socketSecureFactory", "org.apache.axis.components.net.SunFakeTrustSocketFactory");

Áp dụng các tuyên bố từ chối trách nhiệm thông thường về điều này là Điều Rất Xấu trong môi trường sản xuất.

Tôi tìm thấy điều này tại Axis wiki .


OP đang xử lý một HttpsURLConnection, không phải Axis
neu242

2
Tôi hiểu. Tôi không có ý định ám chỉ rằng câu trả lời của tôi tốt hơn trong trường hợp chung. Chỉ là nếu bạn đang sử dụng khung Axis, bạn có thể có câu hỏi của OP trong bối cảnh đó. (Đó là cách tôi tìm thấy câu hỏi này ngay từ đầu.) Trong trường hợp đó, cách tôi đã cung cấp đơn giản hơn.
Mark Meuer

5

Đối với tôi, đây là những gì hoạt động khi sử dụng Apache HttpComponents ~ HttpClient 4.x:

    KeyStore keyStore  = KeyStore.getInstance("PKCS12");
    FileInputStream instream = new FileInputStream(new File("client-p12-keystore.p12"));
    try {
        keyStore.load(instream, "helloworld".toCharArray());
    } finally {
        instream.close();
    }

    // Trust own CA and all self-signed certs
    SSLContext sslcontext = SSLContexts.custom()
        .loadKeyMaterial(keyStore, "helloworld".toCharArray())
        //.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) //custom trust store
        .build();
    // Allow TLSv1 protocol only
    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        sslcontext,
        new String[] { "TLSv1" },
        null,
        SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //TODO
    CloseableHttpClient httpclient = HttpClients.custom()
        .setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) //TODO
        .setSSLSocketFactory(sslsf)
        .build();
    try {

        HttpGet httpget = new HttpGet("https://localhost:8443/secure/index");

        System.out.println("executing request" + httpget.getRequestLine());

        CloseableHttpResponse response = httpclient.execute(httpget);
        try {
            HttpEntity entity = response.getEntity();

            System.out.println("----------------------------------------");
            System.out.println(response.getStatusLine());
            if (entity != null) {
                System.out.println("Response content length: " + entity.getContentLength());
            }
            EntityUtils.consume(entity);
        } finally {
            response.close();
        }
    } finally {
        httpclient.close();
    }

Tệp P12 chứa chứng chỉ ứng dụng khách và khóa riêng tư ứng dụng khách, được tạo bằng BouncyCastle:

public static byte[] convertPEMToPKCS12(final String keyFile, final String cerFile,
    final String password)
    throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException,
    NoSuchProviderException
{
    // Get the private key
    FileReader reader = new FileReader(keyFile);

    PEMParser pem = new PEMParser(reader);
    PEMKeyPair pemKeyPair = ((PEMKeyPair)pem.readObject());
    JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter().setProvider("BC");
    KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair);

    PrivateKey key = keyPair.getPrivate();

    pem.close();
    reader.close();

    // Get the certificate
    reader = new FileReader(cerFile);
    pem = new PEMParser(reader);

    X509CertificateHolder certHolder = (X509CertificateHolder) pem.readObject();
    java.security.cert.Certificate x509Certificate =
        new JcaX509CertificateConverter().setProvider("BC")
            .getCertificate(certHolder);

    pem.close();
    reader.close();

    // Put them into a PKCS12 keystore and write it to a byte[]
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
    ks.load(null);
    ks.setKeyEntry("key-alias", (Key) key, password.toCharArray(),
        new java.security.cert.Certificate[]{x509Certificate});
    ks.store(bos, password.toCharArray());
    bos.close();
    return bos.toByteArray();
}

Đây keyStorelà những gì chứa khóa cá nhân và chứng chỉ.
EpicPandaForce

1
Bạn cần bao gồm 2 phần phụ thuộc này để mã convertPEMtoP12 hoạt động: <dependency> <groupId> org.bouncycastle </groupId> <artifactId> bcprov-jdk15on </artifactId> <version> 1.53 </version> </dependency> < phụ thuộc> <groupId> org.bouncycastle </groupId> <artifactId> bcpkix-jdk15on </artifactId> <version> 1.53 </version> </dependency>
BirdOfPrey

@EpicPandaForce Tôi gặp lỗi: Caught: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Không thể truyền đối tượng với lớp 'org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject' sang lớp 'int' trong dòng ks. setKeyEntry - bất kỳ manh mối nào có thể sai
Vishal Biyani 27/12/16

Vâng, bạn đang sử dụng Groovy thay vì một ngôn ngữ được đánh máy nghiêm ngặt. (về mặt kỹ thuật mà phương pháp có một ID và một giấy chứng nhận, không chỉ là một chứng chỉ)
EpicPandaForce

4

Tôi sử dụng gói Apache commons HTTP Client để thực hiện việc này trong dự án hiện tại của tôi và nó hoạt động tốt với SSL và chứng chỉ tự ký (sau khi cài đặt nó vào cacerts như bạn đã đề cập). Hãy xem nó ở đây:

http://hc.apache.org/httpclient-3.x/tutorial.html

http://hc.apache.org/httpclient-3.x/sslguide.html


1
Điều này có vẻ giống như một gói khá gọn gàng, nhưng lớp sẽ làm cho tất cả hoạt động 'AuthSSLProtocolSocketFactory' được bảo đảm không phải là một phần của bản phân phối chính thức, không phải trong 4.0beta (mặc dù ghi chú phát hành nói rằng nó là như vậy) hoặc trong 3.1. Tôi đã hack xung quanh với nó một chút và bây giờ có vẻ như bị mắc kẹt vĩnh viễn với 5 phút treo trước khi nó chỉ rớt kết nối. Nó thực sự kỳ lạ - nếu tôi tải CA và chứng chỉ khách hàng vào bất kỳ trình duyệt nào, nó sẽ bay.
Ngày

1
Apache HTTP Client 4 có thể lấy SSLContexttrực tiếp, vì vậy bạn có thể cấu hình tất cả theo cách này, thay vì sử dụng AuthSSLProtocolSocketFactory.
Bruno

1
Có cách nào để thực hiện tất cả các nội dung chứng chỉ máy khách trong bộ nhớ thay vì thông qua kho khóa bên ngoài không?
Sridhar Sarnobat

4

Tôi nghĩ rằng bạn gặp sự cố với chứng chỉ máy chủ của mình, không phải là chứng chỉ hợp lệ (tôi nghĩ đây là ý nghĩa của "handshake_failure" trong trường hợp này):

Nhập chứng chỉ máy chủ của bạn vào kho khóa trustcacerts của bạn trên JRE của ứng dụng khách. Điều này có thể dễ dàng thực hiện với keytool :

keytool
    -import
    -alias <provide_an_alias>
    -file <certificate_file>
    -keystore <your_path_to_jre>/lib/security/cacerts

Tôi đã cố gắng dọn dẹp và bắt đầu lại, và thất bại bắt tay đã biến mất. Bây giờ tôi chỉ nhận được 5 phút im lặng chết người trước khi kết nối bị ngắt: o
Ngày

1

Sử dụng mã bên dưới

-Djavax.net.ssl.keyStoreType=pkcs12

hoặc là

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);

không bắt buộc. Ngoài ra, không cần tạo nhà máy SSL tùy chỉnh của riêng bạn.

Tôi cũng gặp phải vấn đề tương tự, trong trường hợp của tôi, đã xảy ra sự cố khiến chuỗi chứng chỉ hoàn chỉnh không được nhập vào cửa hàng tin cậy. Nhập chứng chỉ bằng tiện ích keytool ngay chứng chỉ gốc fom, bạn cũng có thể mở tệp cacerts trong notepad và xem chuỗi chứng chỉ hoàn chỉnh có được nhập hay không. Kiểm tra tên bí danh bạn đã cung cấp trong khi nhập chứng chỉ, mở chứng chỉ và xem nó chứa bao nhiêu chứng chỉ, cùng một số chứng chỉ nên có trong tệp cacerts.

Ngoài ra, tệp cacerts nên được cấu hình trong máy chủ mà bạn đang chạy ứng dụng của mình, hai máy chủ sẽ xác thực lẫn nhau bằng khóa công khai / riêng tư.


Tạo nhà máy SSL tùy chỉnh của riêng bạn phức tạp và dễ xảy ra lỗi hơn nhiều so với việc thiết lập hai thuộc tính hệ thống.
Marquis of Lorne
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.