Sertifikat klien Java melalui HTTPS / SSL

116

Saya menggunakan Java 6 dan mencoba membuat HttpsURLConnectionserver jauh melawan, menggunakan sertifikat klien.
Server menggunakan sertifikat akar yang ditandatangani sendiri, dan mengharuskan sertifikat klien yang dilindungi sandi disajikan. Saya telah menambahkan sertifikat root server dan sertifikat klien ke keystore java default yang saya temukan di /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts(OSX 10.5). Nama file keystore sepertinya menyarankan bahwa sertifikat klien tidak seharusnya masuk ke sana?

Bagaimanapun, menambahkan sertifikat root ke toko ini menyelesaikan masalah yang terkenal itu javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.

Namun, saya sekarang terjebak pada cara menggunakan sertifikat klien. Saya sudah mencoba dua pendekatan dan tidak membawa saya kemana-mana.
Pertama, dan lebih disukai, coba:

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

Saya telah mencoba melewatkan kelas HttpsURLConnection (tidak ideal karena saya ingin berbicara tentang HTTP dengan server), dan melakukan ini sebagai gantinya:

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

Saya bahkan tidak yakin bahwa sertifikat klien adalah masalahnya di sini.

Jan
sumber
Saya telah memberikan dua sertifikat dari klien bagaimana mengidentifikasi mana yang perlu ditambahkan ke keystore dan truststore. Bisakah Anda membantu mengidentifikasi masalah ini karena Anda telah mengalami masalah serupa stackoverflow.com/questions/61374276/…
henrycharles

Jawaban:

100

Akhirnya dipecahkan;). Punya petunjuk kuat di sini (jawaban Gandalf sedikit menyinggung soal itu juga). Tautan yang hilang (kebanyakan) adalah yang pertama dari parameter di bawah ini, dan sampai batas tertentu saya mengabaikan perbedaan antara keystore dan truststore.

Sertifikat server yang ditandatangani sendiri harus diimpor ke truststore:

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

Properti ini perlu disetel (baik di baris perintah, atau dalam kode):

-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

Kode contoh kerja:

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);
}
Jan
sumber
Saya telah menggunakan url seperti: localhost: 8443 / Application_Name / getAttributes . Saya memiliki metode dengan pemetaan url / getAttribute. Metode ini mengembalikan daftar elemen. Saya telah menggunakan HttpsUrlConnection, kode tanggapan koneksi adalah 200, tetapi tidak memberi saya daftar atribut ketika saya menggunakan inputStream, itu memberi saya konten html dari halaman login saya. Saya telah melakukan Autentikasi dan menyetel jenis konten sebagai JSON. Mohon saran
Deepak
83

Meskipun tidak disarankan, Anda juga dapat menonaktifkan validasi sertifikat SSL sekaligus:

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) {}
  }
}
neu242
sumber
72
Perlu dicatat bahwa menonaktifkan validasi sertifikat seperti ini membuka koneksi ke kemungkinan serangan MITM: jangan gunakan dalam produksi .
Bruno
3
Untungnya, kode tidak dapat dikompilasi. 'Solusi' ini sangat tidak aman.
Marquis dari Lorne
5
@ neu242, tidak, ini tidak benar-benar bergantung pada tujuan Anda menggunakannya. Jika Anda ingin menggunakan SSL / TLS, Anda ingin mengamankan koneksi Anda dari serangan MITM, itulah intinya. Otentikasi server tidak diperlukan jika Anda dapat menjamin bahwa tidak ada seorang pun yang dapat mengubah lalu lintas, tetapi situasi di mana Anda menduga mungkin ada penyadap yang juga tidak dalam posisi untuk mengubah lalu lintas jaringan juga cukup jarang.
Bruno
1
@ neu242 Terima kasih atas cuplikan kodenya. Saya sebenarnya berpikir untuk menggunakan ini dalam produksi untuk tujuan yang sangat spesifik (perayapan web), dan saya menyebutkan implementasi Anda dalam sebuah pertanyaan. ( Stackoverflow.com/questions/13076511/… ). Jika Anda punya waktu, dapatkah Anda melihatnya, dan beri tahu saya jika ada risiko keamanan yang saya lewatkan?
Sal
1
@Bruno Jika saya hanya melakukan ping ke server, apakah itu benar-benar mempengaruhi saya sama sekali, oleh serangan MITM?
PhoonOne
21

Sudahkah Anda menyetel properti KeyStore dan / atau TrustStore System?

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

atau dari dengan kode

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

Sama dengan javax.net.ssl.trustStore

Gandalf
sumber
13

Jika Anda berurusan dengan panggilan layanan web menggunakan kerangka kerja Axis, ada jawaban yang jauh lebih sederhana. Jika semua yang diinginkan adalah klien Anda dapat memanggil layanan web SSL dan mengabaikan kesalahan sertifikat SSL, cukup letakkan pernyataan ini sebelum Anda menjalankan layanan web apa pun:

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

Penafian biasa tentang hal ini sebagai Hal yang Sangat Buruk yang harus dilakukan di lingkungan produksi berlaku.

Saya menemukan ini di wiki Axis .

Mark Meuer
sumber
OP berurusan dengan HttpsURLConnection, bukan Axis
neu242
2
Saya mengerti. Saya tidak bermaksud menyiratkan bahwa jawaban saya lebih baik dalam kasus umum. Hanya saja jika Anda sedang menggunakan kerangka Axis, Anda mungkin memiliki pertanyaan OP dalam konteks itu. (Begitulah cara saya menemukan pertanyaan ini sejak awal.) Dalam hal ini, cara yang saya berikan lebih sederhana.
Mark Meuer
5

Bagi saya, inilah yang berhasil menggunakan 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();
    }

File P12 berisi sertifikat klien dan kunci pribadi klien, yang dibuat dengan 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();
}
EpicPandaForce
sumber
Ini keyStoreyang berisi kunci pribadi dan sertifikat.
EpicPandaForce
1
Anda perlu menyertakan 2 dependensi ini agar kode convertPEMtoP12 berfungsi: <dependency> <groupId> org.bouncycastle </groupId> <artifactId> bcprov-jdk15on </artifactId> <version> 1.53 </version> </dependency> < dependency> <groupId> org.bouncycastle </groupId> <artifactId> bcpkix-jdk15on </artifactId> <version> 1,53 </version> </dependency>
BirdOfPrey
@EpicPandaForce Saya mendapatkan kesalahan: Tertangkap: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Tidak dapat mentransmisikan objek dengan kelas 'org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject' ke kelas 'int' di baris ks. setKeyEntry - petunjuk apa pun yang mungkin salah
Vishal Biyani
Ya, Anda menggunakan Groovy, bukan bahasa yang diketik dengan ketat. (secara teknis metode itu mengambil ID dan sertifikat, bukan hanya sertifikat)
EpicPandaForce
4

Saya menggunakan paket Klien HTTP Apache commons untuk melakukan ini dalam proyek saya saat ini dan berfungsi dengan baik dengan SSL dan sertifikat yang ditandatangani sendiri (setelah menginstalnya ke cacert seperti yang Anda sebutkan). Silakan lihat di sini:

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

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

Taylor Leese
sumber
1
Ini sepertinya paket yang cukup rapi, tetapi kelas yang seharusnya membuat semuanya berfungsi 'AuthSSLProtocolSocketFactory' tampaknya bukan bagian dari distribusi resmi, baik di 4.0beta (meskipun catatan rilis menyatakan bahwa itu), atau di 3.1. Saya telah meretasnya sedikit dan sekarang tampaknya macet secara permanen dengan 5 menit hang sebelum hanya memutuskan koneksi. Benar-benar aneh - jika saya memuat CA dan sertifikat klien ke browser mana pun, ia akan terbang begitu saja.
Jan
1
Apache HTTP Client 4 dapat menerima secara SSLContextlangsung, sehingga Anda dapat mengkonfigurasi semua ini dengan cara ini, daripada menggunakan AuthSSLProtocolSocketFactory.
Bruno
1
Adakah cara untuk melakukan semua hal sertifikat klien dalam memori alih-alih melalui keystore eksternal?
Sridhar Sarnobat
4

Saya rasa Anda memiliki masalah dengan sertifikat server Anda, bukan sertifikat yang valid (menurut saya inilah yang dimaksud dengan "handshake_failure" dalam kasus ini):

Impor sertifikat server Anda ke keystore trustcacerts Anda pada JRE klien. Ini mudah dilakukan dengan keytool :

keytool
    -import
    -alias <provide_an_alias>
    -file <certificate_file>
    -keystore <your_path_to_jre>/lib/security/cacerts
sumber
sumber
Saya mencoba membersihkan dan memulai lagi, dan kegagalan jabat tangan hilang. Sekarang saya hanya mendapatkan 5 menit keheningan sebelum koneksi diputus: o
Jan
1

Menggunakan kode di bawah ini

-Djavax.net.ssl.keyStoreType=pkcs12

atau

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

sama sekali tidak diperlukan. Juga tidak perlu membuat pabrik SSL kustom Anda sendiri.

Saya juga mengalami masalah yang sama, dalam kasus saya ada masalah bahwa rantai sertifikat lengkap tidak diimpor ke truststore. Impor sertifikat menggunakan utilitas keytool tepat dari sertifikat root, Anda juga dapat membuka file cacerts di notepad dan melihat apakah rantai sertifikat lengkap diimpor atau tidak. Periksa dengan nama alias yang Anda berikan saat mengimpor sertifikat, buka sertifikat dan lihat berapa isinya, jumlah sertifikat yang sama harus ada di file cacerts.

File cacerts juga harus dikonfigurasi di server tempat Anda menjalankan aplikasi, kedua server akan mengautentikasi satu sama lain dengan kunci publik / pribadi.

Ankur Singhal
sumber
Membuat pabrik SSL kustom Anda sendiri jauh lebih rumit dan rawan kesalahan daripada menyetel dua properti sistem.
Marquis dari Lorne