Belirli bağlantılarda farklı sertifikaları nasıl kullanabilirim?


164

Büyük Java uygulamamıza eklediğim bir modül, başka bir şirketin SSL güvenli web sitesiyle sohbet etmek zorunda. Sorun, sitenin kendinden imzalı bir sertifika kullanmasıdır. Ortadaki bir adamla karşılaşmadığımı doğrulamak için sertifikanın bir kopyası var ve sunucuya bağlantı başarılı olacak şekilde bu sertifikayı kodumuza dahil etmem gerekiyor.

Temel kod şöyledir:

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();
}

Kendinden imzalı sertifika için herhangi bir ek işlem yapılmadan bu durum conn.getOutputStream () öğesinde aşağıdaki istisna dışında ölür:

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

İdeal olarak, kodumun Java'ya bu kendinden imzalı sertifikayı, uygulamadaki bu nokta için ve başka hiçbir yerde kabul etmesini öğretmesi gerekiyor.

Sertifikayı JRE'nin sertifika yetkilisi deposuna aktarabileceğimi biliyorum, bu da Java'nın kabul etmesine izin verecek. Bu yardımcı olabilirsem almak istediğim bir yaklaşım değil; tüm müşterilerimizin makinelerinde kullanamayacakları bir modül için çok invaziv görünüyor; aynı JRE'yi kullanan diğer tüm Java uygulamalarını etkileyecektir ve bu siteye erişen başka herhangi bir Java uygulamasının olasılığı sıfır olsa bile bunu sevmiyorum. Ayrıca önemsiz bir işlem değil: UNIX'te JRE'yi bu şekilde değiştirmek için erişim hakları elde etmeliyim.

Ayrıca bazı özel kontrol yapan bir TrustManager örneği oluşturabildiğimi gördüm. Hatta bu sertifika dışındaki tüm durumlarda gerçek TrustManager'a yetki veren bir TrustManager bile oluşturabilirim. Ancak TrustManager'ın global olarak yüklendiği anlaşılıyor ve başvurumuzdaki diğer tüm bağlantıları etkileyeceğini düşünüyorum ve bu da benim için oldukça doğru kokmuyor.

Kendinden imzalı bir sertifikayı kabul etmek için bir Java uygulaması kurmanın tercih edilen, standart veya en iyi yolu nedir? Yukarıda aklımdaki tüm hedeflere ulaşabilir miyim yoksa uzlaşmak zorunda mıyım? Dosya ve dizinleri, yapılandırma ayarlarını ve çok az kod içeren bir seçenek var mı?


küçük, çalışan düzeltme: rgagnon.com/javadetails/…

20
@ Hasenpriester: Lütfen bu sayfayı önermeyin. Tüm güven doğrulamasını devre dışı bırakır. Sadece istediğiniz kendinden imzalı sertifikayı kabul etmekle kalmayacak, aynı zamanda bir MITM saldırganının size sunacağı herhangi bir sertifikayı da kabul edeceksiniz.
Bruno

Yanıtlar:


168

SSLSocketKendiniz bir fabrika oluşturun ve HttpsURLConnectionbağlanmadan önce onu ayarlayın .

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

Bir tane oluşturmak SSLSocketFactoryve onu saklamak isteyeceksiniz . İşte nasıl başlatılacağına dair bir taslak:

/* 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();

Anahtar deposunu oluşturma konusunda yardıma ihtiyacınız varsa, lütfen yorum yapın.


Anahtar deposunu yüklemeye bir örnek:

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

Bir PEM biçimi sertifikası ile anahtar deposunu oluşturmak için, kullanarak kendi kodu yazabilirsiniz CertificateFactory, ya da sadece birlikte ithal keytool(JDK keytool olmaz bir "anahtar girişi" için çalışır, ancak bir "güvenilen girişi" için sadece para cezası ).

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

3
Çok teşekkür ederim! Diğer adamlar beni doğru yöne yönlendirmeye yardım ettiler, ama sonunda senin yaklaşımım benim. PEM sertifika dosyasını bir JKS Java anahtar deposu dosyasına dönüştürme konusunda yorucu bir görevim vardı ve bunun için yardım buldum: stackoverflow.com/questions/722931/…
skiphoppy

1
Çalıştığına sevindim. Anahtar deposuyla boğuştuğunuz için üzgünüm; Cevabımı yeni eklemeliydim. CertificateFactory ile çok zor değil. Aslında, daha sonra gelen herkes için bir güncelleme yapacağımı düşünüyorum.
erickson

1
Tüm bu kod, JSSE Refernence Guide'da açıklanan üç sistem özelliğini ayarlayarak gerçekleştirebileceğiniz şeyleri çoğaltır.
Lorne Marquis

2
@EJP: Bu bir süre önceydi, bu yüzden kesin olarak hatırlamıyorum, ama mantıklı bir "büyük Java uygulaması", diğer HTTP bağlantıları kurulmuş olması sanırım. Global özelliklerin ayarlanması, çalışan bağlantıları engelleyebilir veya bu tarafın sunucuları aldatmasına izin verebilir. Bu, yerleşik güven yöneticisini duruma göre kullanma örneğidir.
erickson

2
OP'nin dediği gibi, "kodumun Java'ya bu kendinden imzalı sertifikayı, uygulamadaki bu tek nokta için ve başka hiçbir yerde kabul etmesini öğretmesi gerekiyor."
erickson

18

Bu şeyi çözmek için çok sayıda çevrimiçi yer okudum. Bu çalışması için yazdım kod:

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, Sertifikayı içeren bir Dizedir, örneğin:

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

Yukarıdaki yapıyı tam olarak sakladığınız sürece, kendinden imzalı ise, sertifika dizesine herhangi bir karakter koyabileceğinizi test ettim. Sertifika dizesini dizüstü bilgisayarımın Terminal komut satırı ile aldım.


1
@Josh'u paylaştığınız için teşekkürler. Kodunuzu kullanımda gösteren küçük bir Github projesi oluşturdum: github.com/aasaru/ConnectToTrustedServerExample
Master Drools

14

A oluşturmak bir SSLSocketFactoryseçenek değilse, anahtarı JVM'ye aktarın

  1. Ortak anahtarı alın:, $openssl s_client -connect dev-server:443ardından benzeyen bir dosya dev-server.pem dosyası oluşturun

    -----BEGIN CERTIFICATE----- 
    lklkkkllklklklklllkllklkl
    lklkkkllklklklklllkllklkl
    lklkkkllklk....
    -----END CERTIFICATE-----
  2. Anahtarı alın: #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem. Şifre: changeit

  3. JVM'yi yeniden başlat

Kaynak: javax.net.ssl.SSLHandshakeException nasıl çözülür?


1
Bu çözer orijinal soruyu sanmıyorum ama çözüldü benim çok teşekkürler, sorunu!
Ed Norris

Bunu yapmak da harika bir fikir değil: şimdi tüm Java işlemlerinin varsayılan olarak güveneceği bu rastgele ekstra sistem çapında kendinden imzalı sertifikaya sahipsiniz .
user268396

12

JRE'nin güven deposunu kopyalarız ve özel sertifikalarımızı bu güven deposuna ekleriz, ardından uygulamaya özel güven deposunu bir sistem özelliğiyle kullanmasını söyleriz. Bu şekilde varsayılan JRE güven mağazasını yalnız bırakıyoruz.

Dezavantajı, JRE'yi güncellediğinizde yeni güven deposunu otomatik olarak özel olanla birleştirmemenizdir.

Bu senaryoyu, truststore / jdk'yi doğrulayan ve uyumsuzluğu kontrol eden veya otomatik olarak truststore'u güncelleyen bir yükleyici veya başlatma yordamına sahip olabilirsiniz. Truststore'u uygulama çalışırken güncellerseniz ne olacağını bilmiyorum.

Bu çözüm% 100 zarif veya kusursuz değildir, ancak basittir, çalışır ve kod gerektirmez.


12

Kendinden imzalı bir sertifikaya sahip dahili bir https sunucusuna erişmek için commons-httpclient kullanırken böyle bir şey yapmak zorunda kaldım. Evet, çözümümüz her şeyi basitçe ileten özel bir TrustManager oluşturmaktı (bir hata ayıklama mesajını günlüğe kaydetmek).

Bu, yalnızca yerel TrustManager'ımızla ilişkilendirilecek şekilde ayarlanmış yerel SSLContextimizden SSL yuvaları oluşturan kendi SSLSocketFactory'imize sahip olmaktır. Hiç bir anahtar deposu / certstore yakın gitmek gerekmez.

Bu bizim LocalSSLSocketFactory'mizde:

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);
}

SecureProtocolSocketFactory uygulama diğer yöntemleri ile birlikte. LocalSSLTrustManager, yukarıda bahsedilen kukla güven yöneticisi uygulamasıdır.


8
Tüm güven doğrulamasını devre dışı bırakırsanız, ilk önce SSL / TLS kullanmanın pek bir anlamı yoktur. Yerel olarak test için sorun değil, ancak dışarıda bağlanmak istiyorsanız değil.
Bruno

Java 7 üzerinde çalışırken bu istisnayı alıyorum. Javax.net.ssl.SSLHandshakeException: hiçbir şifre suit ortak değil Yardımcı olabilir misiniz?
Uri Lukach
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.