2009-05-13 6 views
147

Ein Modul, das ich zu unserer großen Java-Anwendung hinzufüge, muss sich mit der SSL-gesicherten Website eines anderen Unternehmens unterhalten. Das Problem besteht darin, dass die Site ein selbstsigniertes Zertifikat verwendet. Ich habe eine Kopie des Zertifikats, um zu verifizieren, dass ich keinen Man-in-the-Middle-Angriff erlebe, und ich muss dieses Zertifikat so in unseren Code einbinden, dass die Verbindung zum Server erfolgreich ist.Wie kann ich verschiedene Zertifikate für bestimmte Verbindungen verwenden?

Hier ist der Grundcode:

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

Ohne zusätzliche Behandlung an Ort und Stelle für das selbst signiertes Zertifikat, das bei conn.getOutputStream stirbt() mit folgenden Ausnahme:

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 

Idealer Mein Code muss Java beibringen, dieses selbstsignierte Zertifikat zu akzeptieren, für diesen einen Punkt in der Anwendung und nirgendwo sonst.

Ich weiß, dass ich das Zertifikat in den Zertifikatsautoritätsspeicher der JRE importieren kann, und das erlaubt Java, es zu akzeptieren. Das ist kein Ansatz, den ich machen möchte, wenn ich helfen kann. es scheint sehr invasiv zu sein, auf allen Maschinen unserer Kunden für ein Modul zu arbeiten, das sie nicht benutzen können; Es würde alle anderen Java-Anwendungen betreffen, die dieselbe JRE verwenden, und das mag ich nicht, obwohl die Wahrscheinlichkeit, dass eine andere Java-Anwendung jemals auf diese Site zugreift, gleich Null ist. Es ist auch keine triviale Operation: Unter UNIX muss ich Zugriffsrechte erhalten, um die JRE auf diese Weise zu modifizieren.

Ich habe auch gesehen, dass ich eine TrustManager-Instanz erstellen kann, die einige benutzerdefinierte Überprüfung durchführt. Es sieht so aus, als könnte ich sogar einen TrustManager erstellen, der in allen Instanzen mit Ausnahme dieses einen Zertifikats an den echten TrustManager delegiert. Aber es sieht so aus, als ob TrustManager global installiert wird, und ich nehme an, dass dies Auswirkungen auf alle anderen Verbindungen unserer Anwendung haben würde, und das riecht auch nicht richtig für mich.

Was ist der bevorzugte, standardmäßige oder beste Weg, eine Java-Anwendung so einzurichten, dass ein selbstsigniertes Zertifikat akzeptiert wird? Kann ich alle oben genannten Ziele erreichen, oder muss ich Kompromisse eingehen? Gibt es eine Option für Dateien und Verzeichnisse und Konfigurationseinstellungen und wenig bis gar keinen Code?

+0

kleine, Arbeitsfix: http://www.rgagnon.com/javadetails/java-fix-certificate-problem-in-HTTPS.html –

+16

@Hasenpriester: bitte nicht diese Seite vorschlagen. Es deaktiviert die Vertrauensverifizierung. Sie akzeptieren nicht nur das selbstsignierte Zertifikat, das Sie möchten, sondern Sie akzeptieren auch jedes Zertifikat, das ein MITM-Angreifer Ihnen präsentiert. – Bruno

Antwort

150

Erstellen Sie eine SSLSocket Fabrik selbst, und stellen Sie sie auf die HttpsURLConnection vor dem Anschließen ein.

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

Sie wollen erstellen SSLSocketFactory und halten Sie sie um.Hier ist eine Skizze, wie man es initialisiert:

Wenn Sie Hilfe benötigen, den Schlüsselspeicher zu schaffen, kommentieren Sie bitte.


Hier ist ein Beispiel für den Schlüsselspeicher geladen:

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

den Schlüsselspeicher mit einem PEM-Format Zertifikat zu erstellen, können Sie Ihren eigenen Code verwenden CertificateFactory, schreiben oder einfach importieren mit keytool aus das JDK (keytool wird nicht arbeiten für einen "Schlüsseleintrag", aber ist gut für einen "vertrauenswürdigen Eintrag").

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

Vielen Dank! Die anderen Jungs haben mir geholfen, in die richtige Richtung zu zeigen, aber am Ende ist dein Ansatz der richtige. Ich hatte eine anstrengende Aufgabe, die PEM-Zertifikatsdatei in eine JKS-Java-Keystore-Datei umzuwandeln, und ich fand Hilfe dafür: http://stackoverflow.com/questions/722931/ssl-socket-php-code-needs-to- be-converted-to-java – skiphoppy

+1

Ich bin froh, dass es geklappt hat. Es tut mir leid, dass du mit dem Schlüsselspeicher gekämpft hast. Ich hätte es nur in meine Antwort aufnehmen sollen. Mit CertificateFactory ist das nicht schwer. Tatsächlich denke ich, dass ich für alle, die später kommen, ein Update machen werde. – erickson

+0

Wow. Die Umwandlung von PEM in JKS ist wesentlich einfacher als das, was ich getan habe! Vielen Dank! :) – skiphoppy

12

Wir kopieren den Truststore der JRE und fügen unsere benutzerdefinierten Zertifikate zu diesem Truststore hinzu. Weisen Sie dann die Anwendung an, den benutzerdefinierten Truststore mit einer Systemeigenschaft zu verwenden. Auf diese Weise verlassen wir den standardmäßigen JRE-Truststore.

Der Nachteil ist, dass Sie beim Aktualisieren der JRE den neuen Truststore nicht automatisch mit Ihrem benutzerdefinierten Truststore zusammenführen.

Sie könnten dieses Szenario möglicherweise behandeln, indem Sie ein Installationsprogramm oder eine Startroutine verwenden, die den Truststore/jdk überprüft und nach einem Konflikt sucht oder den Truststore automatisch aktualisiert. Ich weiß nicht, was passiert, wenn Sie den Truststore aktualisieren, während die Anwendung läuft.

Diese Lösung ist nicht 100% elegant oder narrensicher, aber sie ist einfach, funktioniert und erfordert keinen Code.

12

Ich habe so etwas wie dies zu tun ist, wenn commons-Httpclient für den Zugriff auf einen internen HTTPS-Server mit einem selbstsignierten Zertifikat. Ja, unsere Lösung bestand darin, einen benutzerdefinierten TrustManager zu erstellen, der einfach alles weiterleitete (Protokollierung einer Debug-Nachricht).

Das kommt daher, dass wir unsere eigene SSLSocketFactory haben, die SSL-Sockets von unserem lokalen SSLContext erzeugt, der so eingerichtet ist, dass nur unser lokaler TrustManager damit verbunden ist. Sie müssen nicht in der Nähe eines Keystores/Certstores gehen.

So ist dies in unserem LocalSSLSocketFactory:

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

Zusammen mit anderen Methoden der Umsetzung SecureProtocolSocketFactory. LocalSSLTrustManager ist die zuvor erwähnte Dummy-Trust-Manager-Implementierung.

+7

Wenn Sie die Vertrauensverifizierung deaktivieren, ist die Verwendung von SSL/TLS an erster Stelle wenig sinnvoll. Es ist in Ordnung, lokal zu testen, aber nicht, wenn Sie eine Verbindung nach draußen herstellen möchten. – Bruno

+0

Ich bekomme diese Ausnahme, wenn sie auf Java 7 ausgeführt wird. Javax.net.ssl.SSLHandshakeException: keine Cipher-Suites gemeinsam Können Sie helfen? – Uri

14

Wenn eine SSLSocketFactory Schaffung keine Option ist, importieren Sie einfach den Schlüssel in die JVM

  1. Abrufen des öffentlichen Schlüssels: $openssl s_client -connect dev-server:443, dann erstellen Sie eine Datei dev-server.pem, die wie

    aussieht
    -----BEGIN CERTIFICATE----- 
    lklkkkllklklklklllkllklkl 
    lklkkkllklklklklllkllklkl 
    lklkkkllklk.... 
    -----END CERTIFICATE----- 
    
  2. Importieren Sie den Schlüssel: #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem. Passwort: changeit

  3. Restart JVM

Quelle: How to solve javax.net.ssl.SSLHandshakeException?

+1

Ich glaube nicht, dass dies die ursprüngliche Frage löst, aber es hat * mein * Problem gelöst, also danke! –

11

ich durch viele Plätze gingen in SO und die Bahn diese Sache zu lösen. Dies ist der Code, der für mich gearbeitet:

   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 ist ein String, der das Zertifikat enthält, zum Beispiel:

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

ich getestet habe, dass Sie alle Zeichen in der Zertifikat-String setzen können , wenn es selbst signiert ist, solange Sie die genaue Struktur oben halten. Ich habe die Zertifikat-Zeichenfolge mit der Terminal-Befehlszeile meines Laptops erhalten.

Verwandte Themen