Modul yang saya tambahkan ke aplikasi Java kami yang besar harus berkomunikasi dengan situs web yang dilindungi SSL perusahaan lain. Masalahnya adalah bahwa situs tersebut menggunakan sertifikat yang ditandatangani sendiri. Saya memiliki salinan sertifikat untuk memverifikasi bahwa saya tidak menghadapi serangan manusia di tengah, dan saya perlu memasukkan sertifikat ini ke dalam kode kami sedemikian rupa sehingga koneksi ke server akan berhasil.
Berikut kode dasarnya:
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();
}
Tanpa ada penanganan tambahan di tempat untuk sertifikat yang ditandatangani sendiri, ini mati di conn.getOutputStream () dengan pengecualian berikut:
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
Idealnya, kode saya perlu mengajarkan Java untuk menerima sertifikat yang ditandatangani sendiri ini, untuk tempat ini dalam aplikasi, dan tidak di tempat lain.
Saya tahu bahwa saya dapat mengimpor sertifikat ke toko otoritas sertifikat JRE, dan itu akan memungkinkan Java untuk menerimanya. Itu bukan pendekatan yang ingin saya ambil jika saya bisa membantu; tampaknya sangat invasif untuk dilakukan pada semua mesin pelanggan kami untuk satu modul yang mungkin tidak mereka gunakan; itu akan mempengaruhi semua aplikasi Java lainnya menggunakan JRE yang sama, dan saya tidak suka itu meskipun kemungkinan aplikasi Java lain yang pernah mengakses situs ini adalah nol. Ini juga bukan operasi sepele: pada UNIX saya harus mendapatkan hak akses untuk memodifikasi JRE dengan cara ini.
Saya juga melihat bahwa saya dapat membuat instance TrustManager yang melakukan pengecekan khusus. Sepertinya saya bahkan dapat membuat TrustManager yang mendelegasikan ke TrustManager yang sebenarnya dalam semua hal kecuali sertifikat yang satu ini. Tetapi sepertinya TrustManager terinstal secara global, dan saya kira akan mempengaruhi semua koneksi lain dari aplikasi kita, dan itu juga tidak terasa benar bagi saya.
Apa yang disukai, standar, atau cara terbaik untuk mengatur aplikasi Java untuk menerima sertifikat yang ditandatangani sendiri? Dapatkah saya mencapai semua tujuan yang saya pikirkan di atas, atau apakah saya harus berkompromi? Apakah ada opsi yang melibatkan file dan direktori serta pengaturan konfigurasi, dan sedikit kode untuk tidak ada?
Jawaban:
Buat
SSLSocket
pabrik sendiri, dan aturHttpsURLConnection
sebelum menghubungkan.Anda ingin membuatnya
SSLSocketFactory
dan menyimpannya. Berikut ini sketsa cara menginisialisasi:Jika Anda memerlukan bantuan untuk membuat penyimpanan kunci, silakan komentar.
Berikut ini contoh memuat penyimpanan kunci:
Untuk membuat penyimpanan kunci dengan sertifikat format PEM, Anda dapat menulis kode Anda sendiri
CertificateFactory
, atau hanya mengimpornya dengankeytool
dari JDK (keytool tidak akan berfungsi untuk "entri kunci", tetapi tidak apa-apa untuk "entri tepercaya" ).sumber
Saya membaca BANYAK tempat daring untuk menyelesaikan masalah ini. Ini adalah kode yang saya tulis untuk membuatnya berfungsi:
app.certificateString adalah sebuah String yang berisi Sertifikat, misalnya:
Saya telah menguji bahwa Anda dapat menempatkan karakter apa pun di string sertifikat, jika ditandatangani sendiri, selama Anda tetap menggunakan struktur yang tepat di atas. Saya memperoleh string sertifikat dengan baris perintah Terminal laptop saya.
sumber
Jika membuat
SSLSocketFactory
bukan pilihan, cukup impor kunci ke JVMAmbil kunci publik:,
$openssl s_client -connect dev-server:443
lalu buat file dev-server.pem yang terlihat sepertiImpor kunci:
#keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem
. Kata sandi: changeitMulai ulang JVM
Sumber: Bagaimana mengatasi javax.net.ssl.SSLHandshakeException?
sumber
Kami menyalin toko kepercayaan JRE dan menambahkan sertifikat khusus kami ke toko kepercayaan itu, lalu memberi tahu aplikasi untuk menggunakan toko kepercayaan khusus dengan properti sistem. Dengan cara ini kita membiarkan truststore JRE default sendirian.
Kelemahannya adalah ketika Anda memperbarui JRE, Anda tidak mendapatkan truststore baru secara otomatis bergabung dengan custom Anda.
Anda mungkin bisa menangani skenario ini dengan memiliki installer atau rutin startup yang memverifikasi truststore / jdk dan memeriksa ketidakcocokan atau secara otomatis memperbarui truststore. Saya tidak tahu apa yang terjadi jika Anda memperbarui truststore saat aplikasi sedang berjalan.
Solusi ini tidak 100% elegan atau sangat mudah tetapi sederhana, berfungsi, dan tidak memerlukan kode.
sumber
Saya harus melakukan sesuatu seperti ini ketika menggunakan commons-httpclient untuk mengakses server https internal dengan sertifikat yang ditandatangani sendiri. Ya, solusi kami adalah membuat TrustManager khusus yang hanya melewatkan segalanya (mencatat pesan debug).
Ini terjadi karena memiliki SSLSocketFactory kami sendiri yang membuat soket SSL dari konteks SSLC lokal kami, yang diatur agar hanya TrustManager lokal kami yang terkait dengannya. Anda tidak perlu pergi dekat keystore / certstore sama sekali.
Jadi ini ada di LocalSSLSocketFactory kami:
Seiring dengan metode lain menerapkan SecureProtocolSocketFactory. LocalSSLTrustManager adalah implementasi dummy trust manager yang disebutkan di atas.
sumber