Terima ssl sertifikat yang ditandatangani sendiri oleh server pada klien Java


215

Sepertinya pertanyaan standar, tapi saya tidak bisa menemukan arah yang jelas di mana pun.

Saya memiliki kode java yang mencoba terhubung ke server dengan sertifikat yang ditandatangani sendiri (atau kedaluwarsa). Kode melaporkan kesalahan berikut:

[HttpMethodDirector] I/O exception (javax.net.ssl.SSLHandshakeException) caught 
when processing request: sun.security.validator.ValidatorException: PKIX path 
building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target

Seperti yang saya pahami, saya harus menggunakan keytool dan memberi tahu java bahwa boleh saja mengizinkan koneksi ini.

Semua instruksi untuk memperbaiki masalah ini menganggap saya sepenuhnya mahir dengan keytool, seperti

buat kunci pribadi untuk server dan impor ke keystore

Adakah orang yang dapat memposting instruksi terperinci?

Saya menjalankan unix, jadi skrip bash akan menjadi yang terbaik.

Tidak yakin apakah ini penting, tetapi kode dieksekusi di jboss.


2
Lihat Bagaimana cara saya menerima sertifikat yang ditandatangani sendiri dengan Java HttpsURLConnection? . Jelas, akan lebih baik jika Anda bisa mendapatkan situs untuk menggunakan sertifikat yang valid.
Matthew Flaschen

2
Terima kasih atas tautannya, saya tidak melihatnya saat mencari. Tetapi kedua solusi di sana melibatkan kode khusus untuk mengirim permintaan dan saya menggunakan kode yang ada (amazon ws client untuk java). Masing-masing, itu adalah situs mereka yang saya hubungkan dan saya tidak dapat memperbaiki masalah sertifikatnya.
Nikita Rybak

2
@MatthewFlaschen - "Jelas, akan lebih baik jika Anda bisa mendapatkan situs untuk menggunakan sertifikat yang valid ..." - Sertifikat yang ditandatangani sendiri adalah sertifikat yang valid jika klien mempercayainya. Banyak yang berpikir memberikan kepercayaan kepada kartel CA / Browser adalah cacat keamanan.
jww

3
Terkait, lihat Kode paling berbahaya di dunia: memvalidasi sertifikat SSL dalam perangkat lunak non-browser . (Tautan ini diberikan karena Anda sepertinya mendapatkan jawaban spam yang menonaktifkan validasi).
jww

Jawaban:


307

Pada dasarnya Anda memiliki dua opsi di sini: tambahkan sertifikat yang ditandatangani sendiri ke toko kepercayaan JVM Anda atau konfigurasikan klien Anda

Pilihan 1

Ekspor sertifikat dari browser Anda dan impor di truststore JVM Anda (untuk membangun rantai kepercayaan):

<JAVA_HOME>\bin\keytool -import -v -trustcacerts
-alias server-alias -file server.cer
-keystore cacerts.jks -keypass changeit
-storepass changeit 

pilihan 2

Nonaktifkan Validasi Sertifikat:

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

// Install the all-trusting trust manager
try {
    SSLContext sc = SSLContext.getInstance("SSL"); 
    sc.init(null, trustAllCerts, new java.security.SecureRandom()); 
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (GeneralSecurityException e) {
} 
// Now you can access an https URL without having the certificate in the truststore
try { 
    URL url = new URL("https://hostname/index.html"); 
} catch (MalformedURLException e) {
} 

Perhatikan bahwa saya tidak merekomendasikan Opsi # 2 sama sekali . Menonaktifkan manajer kepercayaan mengalahkan beberapa bagian SSL dan membuat Anda rentan terhadap serangan tengah. Lebih suka Opsi # 1 atau, bahkan lebih baik, memiliki server menggunakan sertifikat "nyata" yang ditandatangani oleh CA terkenal.


7
Bukan hanya serangan MIM. Ini membuat Anda rentan terhubung ke situs yang salah. Benar-benar tidak aman. Lihat RFC 2246. Saya menentang memposting TrustManager ini setiap saat. Itu bahkan tidak benar wrt spesifikasinya sendiri.
Marquis of Lorne

10
@ EJP Saya benar-benar tidak merekomendasikan opsi kedua (Saya telah memperbarui jawaban saya untuk memperjelas). Namun, tidak mempostingnya tidak akan menyelesaikan apa-apa (ini adalah informasi publik) dan tidak IMHO layak untuk downvote.
Pascal Thivent

135
@ EJP Ini dengan mengajar orang bahwa Anda mendidik mereka, bukan dengan menyembunyikan sesuatu. Jadi menjaga rahasia atau ketidakjelasan bukanlah solusi sama sekali. Kode ini bersifat publik, API Java bersifat publik, lebih baik membicarakannya daripada mengabaikannya. Tapi aku bisa hidup denganmu tanpa persetujuan.
Pascal Thivent

10
Opsi lain - yang tidak Anda sebutkan - adalah memperbaiki sertifikat server dengan memperbaikinya sendiri atau dengan memanggil orang-orang dukungan yang relevan. Sertifikat host tunggal benar-benar sangat murah; menyia-nyiakan hal-hal yang ditandatangani sendiri adalah sen dolar-bodoh-bodoh (yaitu, bagi mereka yang tidak terbiasa dengan idiom bahasa Inggris, seperangkat prioritas yang benar-benar bodoh yang biaya banyak untuk menyelamatkan hampir tidak ada).
Donal Fellows

2
@ Kaya, terlambat 6 tahun, tetapi Anda juga bisa mendapatkan sertifikat server di firefox dengan mengklik ikon kunci -> informasi lebih lanjut -> Lihat Sertifikat -> tab Detail -> Ekspor ... Tersembunyi dengan baik.
Siddhartha

9

Saya mengejar masalah ini ke penyedia sertifikat yang bukan bagian dari host tepercaya JVM default JDK 8u74. Penyedia adalah www.identrust.com , tetapi itu bukan domain yang saya coba sambungkan. Domain itu telah mendapatkan sertifikat dari penyedia ini. Lihat Akankah cross root percaya dengan daftar default di JDK / JRE? - Baca beberapa entri. Juga lihat Browser dan sistem operasi mana yang mendukung Let's Encrypt .

Jadi, untuk menghubungkan ke domain saya tertarik, yang memiliki sertifikat yang dikeluarkan dari identrust.comsaya lakukan langkah-langkah berikut. Pada dasarnya, saya harus mendapatkan identrust.com (DST Root CA X3 sertifikat ) agar dipercaya oleh JVM. Saya dapat melakukannya dengan menggunakan Apache HttpComponents 4.5 seperti:

1: Dapatkan sertifikat dari indettrust di Petunjuk Unduhan Rantai Sertifikat . Klik tautan DST Root CA X3 .

2: Simpan string ke file bernama "DST Root CA X3.pem". Pastikan untuk menambahkan baris "----- BEGIN CERTIFICATE -----" dan "----- END CERTIFICATE -----" dalam file di awal dan akhir.

3: Buat file java keystore, cacerts.jks dengan perintah berikut:

keytool -import -v -trustcacerts -alias IdenTrust -keypass yourpassword -file dst_root_ca_x3.pem -keystore cacerts.jks -storepass yourpassword

4: Salin keystore cacerts.jks yang dihasilkan ke direktori sumber daya aplikasi java / (maven) Anda.

5: Gunakan kode berikut untuk memuat file ini dan lampirkan ke Apache 4.5 HttpClient. Ini akan menyelesaikan masalah untuk semua domain yang memiliki sertifikat yang dikeluarkan dari indetrust.comutil oracle menyertakan sertifikat ke dalam keystore default JRE.

SSLContext sslcontext = SSLContexts.custom()
        .loadTrustMaterial(new File(CalRestClient.class.getResource("/cacerts.jks").getFile()), "yourpasword".toCharArray(),
                new TrustSelfSignedStrategy())
        .build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        sslcontext,
        new String[] { "TLSv1" },
        null,
        SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom()
        .setSSLSocketFactory(sslsf)
        .build();

Ketika proyek dibangun maka cacerts.jks akan disalin ke classpath dan diambil dari sana. Saya tidak, pada saat ini, menguji terhadap situs ssl lain, tetapi jika kode di atas "rantai" dalam sertifikat ini maka mereka akan bekerja juga, tapi sekali lagi, saya tidak tahu.

Referensi: Konteks SSL khusus dan Bagaimana cara saya menerima sertifikat yang ditandatangani sendiri dengan Java HttpsURLConnection?


Pertanyaannya adalah tentang sertifikat yang ditandatangani sendiri. Jawaban ini bukan.
Marquis of Lorne

4
Baca pertanyaan (dan jawab) sedikit lebih dekat.
K.Nicholas

9

Apache HttpClient 4.5 mendukung penerimaan sertifikat yang ditandatangani sendiri:

SSLContext sslContext = SSLContexts.custom()
    .loadTrustMaterial(new TrustSelfSignedStrategy())
    .build();
SSLConnectionSocketFactory socketFactory =
    new SSLConnectionSocketFactory(sslContext);
Registry<ConnectionSocketFactory> reg =
    RegistryBuilder.<ConnectionSocketFactory>create()
    .register("https", socketFactory)
    .build();
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);        
CloseableHttpClient httpClient = HttpClients.custom()
    .setConnectionManager(cm)
    .build();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse sslResponse = httpClient.execute(httpGet);

Ini membangun pabrik soket SSL yang akan menggunakan TrustSelfSignedStrategy, mendaftar dengan manajer koneksi kustom kemudian melakukan GET HTTP menggunakan manajer koneksi itu.

Saya setuju dengan mereka yang menyapa "jangan lakukan ini dalam produksi", namun ada kasus penggunaan untuk menerima sertifikat yang ditandatangani sendiri di luar produksi; kami menggunakannya dalam pengujian integrasi otomatis, sehingga kami menggunakan SSL (seperti dalam produksi) bahkan ketika tidak berjalan pada perangkat keras produksi.


6

Daripada mengatur pabrik soket default (yang IMO adalah hal yang buruk) - dia hanya akan mempengaruhi koneksi saat ini daripada setiap koneksi SSL yang Anda coba buka:

URLConnection connection = url.openConnection();
    // JMD - this is a better way to do it that doesn't override the default SSL factory.
    if (connection instanceof HttpsURLConnection)
    {
        HttpsURLConnection conHttps = (HttpsURLConnection) connection;
        // Set up a Trust all manager
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager()
        {

            public java.security.cert.X509Certificate[] getAcceptedIssuers()
            {
                return null;
            }

            public void checkClientTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
            {
            }

            public void checkServerTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
            {
            }
        } };

        // Get a new SSL context
        SSLContext sc = SSLContext.getInstance("TLSv1.2");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        // Set our connection to use this SSL context, with the "Trust all" manager in place.
        conHttps.setSSLSocketFactory(sc.getSocketFactory());
        // Also force it to trust all hosts
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        // and set the hostname verifier.
        conHttps.setHostnameVerifier(allHostsValid);
    }
InputStream stream = connection.getInputStream();

2
Anda checkServerTrustedtidak menerapkan logika yang diperlukan untuk benar-benar mempercayai sertifikat dan memastikan sertifikat yang tidak dipercaya ditolak. Ini mungkin membuat pembacaan yang baik: Kode paling berbahaya di dunia: memvalidasi sertifikat SSL dalam perangkat lunak non-browser .
jww

1
Anda getAcceptedIssuers()metode tidak sesuai dengan specifcation, dan ini 'solusi' tetap radikal tidak aman.
Marquis of Lorne

4

Ada alternatif yang lebih baik untuk mempercayai semua sertifikat: Buat sertifikat TrustStoreyang secara khusus mempercayai sertifikat yang diberikan dan gunakan ini untuk membuat SSLContextdari mana untuk mendapatkan SSLSocketFactoryset ke pada HttpsURLConnection. Berikut kode lengkapnya:

File crtFile = new File("server.crt");
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("server", certificate);

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());

Anda juga dapat memuat KeyStorelangsung dari file atau mengambil Sertifikat X.509 dari sumber tepercaya.

Perhatikan bahwa dengan kode ini, sertifikat di cacertstidak akan digunakan. Khusus ini HttpsURLConnectionhanya akan mempercayai sertifikat khusus ini.


1

Percayai semua sertifikat SSL: - Anda dapat mengabaikan SSL jika Anda ingin menguji pada server pengujian. Tetapi jangan gunakan kode ini untuk produksi.

public static class NukeSSLCerts {
protected static final String TAG = "NukeSSLCerts";

public static void nuke() {
    try {
        TrustManager[] trustAllCerts = new TrustManager[] { 
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    X509Certificate[] myTrustedAnchors = new X509Certificate[0];  
                    return myTrustedAnchors;
                }

                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {}

                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {}
            }
        };

        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }
        });
    } catch (Exception e) { 
    }
}

}

Silakan panggil fungsi ini di fungsi onCreate () di Activity atau di Kelas Aplikasi Anda.

NukeSSLCerts.nuke();

Ini dapat digunakan untuk Volley di Android.


Tidak sepenuhnya yakin mengapa Anda tidak dipilih, kecuali kodenya tidak berfungsi. Mungkin itu asumsi bahwa Android entah bagaimana terlibat ketika pertanyaan awal tidak menandai Android.
Lo-Tan

1

Jawaban yang diterima baik-baik saja, tetapi saya ingin menambahkan sesuatu ke ini karena saya menggunakan IntelliJ di Mac dan tidak bisa membuatnya bekerja menggunakan JAVA_HOMEvariabel path.

Ternyata Java Home berbeda ketika menjalankan aplikasi dari IntelliJ.

Untuk mencari tahu persis di mana letaknya, Anda bisa melakukan apa saja System.getProperty("java.home")di mana sertifikat tepercaya dibaca.


0

Jika 'mereka' menggunakan sertifikat yang ditandatangani sendiri, tergantung pada mereka untuk mengambil langkah-langkah yang diperlukan agar server mereka dapat digunakan. Khususnya itu berarti memberikan sertifikat kepada Anda secara offline dengan cara yang dapat dipercaya. Jadi suruh mereka melakukan itu. Anda kemudian mengimpornya ke truststore Anda menggunakan keytool seperti yang dijelaskan dalam Panduan Referensi JSSE. Jangan pernah berpikir tentang TrustManager yang tidak aman yang diposting di sini.

EDIT Untuk kepentingan tujuh belas (!) Downvoters, dan banyak komentator di bawah ini, yang jelas belum benar-benar membaca apa yang saya tulis di sini, ini bukan jeremiad terhadap sertifikat yang ditandatangani sendiri. Tidak ada yang salah dengan sertifikat yang ditandatangani sendiri ketika diterapkan dengan benar. Tetapi, cara yang benar untuk mengimplementasikannya adalah memiliki sertifikat yang dikirimkan secara aman melalui proses offline, daripada melalui saluran yang tidak diautentikasi yang akan mereka gunakan untuk mengotentikasi. Tentunya ini jelas? Jelas sekali bagi setiap organisasi yang sadar akan keamanan yang pernah saya bekerja, dari bank dengan ribuan cabang hingga perusahaan saya sendiri. 'Solusi' basis kode sisi klien untuk mempercayai semuasertifikat, termasuk sertifikat yang ditandatangani sendiri yang ditandatangani oleh siapa saja, atau badan arbiter yang mengatur dirinya sendiri sebagai CA, ipso factotidak aman Itu hanya bermain di keamanan. Tidak ada gunanya. Anda memiliki percakapan pribadi, tamperproof, proof-proof, proof-injection dengan ... seseorang. Siapa saja. Seorang pria di tengah. Peniru. Siapa saja. Anda juga bisa menggunakan plaintext.


2
Hanya karena beberapa server memutuskan untuk menggunakan https, tidak berarti bahwa orang dengan klien memberikan omong kosong tentang keamanan untuk tujuan mereka sendiri.
Gus

3
Saya terkejut jawaban ini begitu banyak dipilih. Saya ingin memahami sedikit lebih banyak alasannya. Sepertinya EJP menyarankan bahwa Anda tidak boleh melakukan opsi 2 karena memungkinkan untuk kelemahan keamanan utama. Bisakah seseorang menjelaskan mengapa (selain pengaturan pra-penempatan) mengapa jawaban ini berkualitas rendah?
cr1pto

2
Yah, saya kira semua itu. Apa maksudnya "terserah mereka untuk mengambil langkah-langkah yang diperlukan agar server mereka dapat digunakan" artinya? Apa yang harus dilakukan oleh operator server agar dapat digunakan? Apa langkah-langkah yang ada dalam pikiran Anda? Bisakah Anda memberikan daftar mereka? Tapi mundur ke 1.000 kaki, bagaimana itu bahkan berhubungan dengan masalah yang ditanyakan OP? Dia ingin tahu bagaimana membuat klien menerima sertifikat yang ditandatangani sendiri di Jawa. Itu masalah sederhana untuk memberikan kepercayaan pada kode karena OP menganggap sertifikat itu dapat diterima.
jww

2
@ EJP - Perbaiki saya jika saya salah, tetapi menerima sertifikat yang ditandatangani sendiri adalah keputusan kebijakan pihak klien. Ini tidak ada hubungannya dengan operasi sisi server. Klien harus membuat keputusan. Paritas harus bekerja bersama untuk menyelesaikan masalah distribusi utama, tetapi itu tidak ada hubungannya dengan membuat server dapat digunakan.
jww

3
@ EJP - Saya tentu saja tidak mengatakan, "percaya semua sertifikat" . Mungkin saya kehilangan sesuatu (atau Anda terlalu banyak membaca hal-hal) ... OP memiliki sertifikat, dan dapat diterima olehnya. Dia ingin tahu bagaimana memercayainya. Saya mungkin memisahkan rambut, tetapi sertifikat tidak perlu dikirim secara offline. Verifikasi out-of-band harus digunakan, tetapi itu tidak sama dengan "harus dikirim ke klien offline" . Dia bahkan mungkin toko memproduksi server dan klien, sehingga ia memiliki syarat apriori pengetahuan.
jww


0

Alih-alih menggunakan keytool seperti yang disarankan oleh komentar teratas, di RHEL Anda dapat menggunakan pembaruan-ca-trust mulai dalam versi yang lebih baru dari RHEL 6. Anda harus memiliki sertifikat dalam format pem. Kemudian

trust anchor <cert.pem>

Edit /etc/pki/ca-trust/source/cert.p11-kit dan ubah "kategori sertifikat: entri-lain" menjadi "kategori sertifikat: otoritas". (Atau gunakan sed untuk melakukan ini dalam skrip.) Lalu lakukan

update-ca-trust

Beberapa peringatan:

  • Saya tidak dapat menemukan "kepercayaan" pada server RHEL 6 saya dan yum tidak menawarkan untuk menginstalnya. Saya akhirnya menggunakannya di server RHEL 7 dan menyalin file .p11-kit.
  • Untuk membuat ini bekerja untuk Anda, Anda mungkin perlu melakukannya update-ca-trust enable. Ini akan menggantikan / etc / pki / java / cacerts dengan tautan simbolik yang menunjuk ke / etc / pki / ca-trust / extracted / java / cacerts. (Jadi, Anda mungkin ingin mencadangkan yang pertama.)
  • Jika klien java Anda menggunakan cacerts yang disimpan di beberapa lokasi lain, Anda ingin menggantinya secara manual dengan symlink ke / etc / pki / ca-trust / diekstraksi / java / cacerts, atau ganti dengan file itu.

0

Saya memiliki masalah ketika saya mengirimkan URL ke perpustakaan yang memanggil url.openConnection();saya mengadaptasi jawaban jon-daniel,

public class TrustHostUrlStreamHandler extends URLStreamHandler {

    private static final Logger LOG = LoggerFactory.getLogger(TrustHostUrlStreamHandler.class);

    @Override
    protected URLConnection openConnection(final URL url) throws IOException {

        final URLConnection urlConnection = new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile()).openConnection();

        // adapated from
        // /programming/2893819/accept-servers-self-signed-ssl-certificate-in-java-client
        if (urlConnection instanceof HttpsURLConnection) {
            final HttpsURLConnection conHttps = (HttpsURLConnection) urlConnection;

            try {
                // Set up a Trust all manager
                final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {

                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }

                    @Override
                    public void checkClientTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
                    }

                    @Override
                    public void checkServerTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
                    }
                } };

                // Get a new SSL context
                final SSLContext sc = SSLContext.getInstance("TLSv1.2");
                sc.init(null, trustAllCerts, new java.security.SecureRandom());
                // Set our connection to use this SSL context, with the "Trust all" manager in place.
                conHttps.setSSLSocketFactory(sc.getSocketFactory());
                // Also force it to trust all hosts
                final HostnameVerifier allHostsValid = new HostnameVerifier() {
                    @Override
                    public boolean verify(final String hostname, final SSLSession session) {
                        return true;
                    }
                };

                // and set the hostname verifier.
                conHttps.setHostnameVerifier(allHostsValid);

            } catch (final NoSuchAlgorithmException e) {
                LOG.warn("Failed to override URLConnection.", e);
            } catch (final KeyManagementException e) {
                LOG.warn("Failed to override URLConnection.", e);
            }

        } else {
            LOG.warn("Failed to override URLConnection. Incorrect type: {}", urlConnection.getClass().getName());
        }

        return urlConnection;
    }

}

Menggunakan kelas ini dimungkinkan untuk membuat URL baru dengan:

trustedUrl = new URL(new URL(originalUrl), "", new TrustHostUrlStreamHandler());
trustedUrl.openConnection();

Ini memiliki keuntungan bahwa itu dilokalkan dan tidak menggantikan default URL.openConnection.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.