Frage

Ein Modul, das ich unserer großen Java-Anwendung hinzufüge, muss mit der SSL-gesicherten Website eines anderen Unternehmens kommunizieren.Das Problem besteht darin, dass die Site ein selbstsigniertes Zertifikat verwendet.Ich habe eine Kopie des Zertifikats, um sicherzustellen, dass ich nicht auf einen Man-in-the-Middle-Angriff stoße, und ich muss dieses Zertifikat so in unseren Code integrieren, dass die Verbindung zum Server erfolgreich ist.

Hier ist der Basiscode:

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 Handhabung des selbstsignierten Zertifikats stirbt dieses bei conn.getOutputStream() mit der folgenden Ausnahme ab:

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

Im Idealfall muss mein Code Java beibringen, dieses eine selbstsignierte Zertifikat an dieser einen Stelle in der Anwendung und nirgendwo sonst zu akzeptieren.

Ich weiß, dass ich das Zertifikat in den Zertifizierungsstellenspeicher der JRE importieren kann, sodass Java es akzeptieren kann.Das ist kein Ansatz, den ich wählen möchte, wenn ich helfen kann;Es erscheint sehr aufwändig, auf allen Maschinen unserer Kunden ein Modul zu reparieren, das sie möglicherweise nicht verwenden.Es würde sich auf alle anderen Java-Anwendungen auswirken, die dieselbe JRE verwenden, und das gefällt mir nicht, obwohl die Wahrscheinlichkeit, dass jemals eine andere Java-Anwendung 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 ändern.

Ich habe auch gesehen, dass ich eine TrustManager-Instanz erstellen kann, die einige benutzerdefinierte Prüfungen durchführt.Es sieht so aus, als könnte ich vielleicht sogar einen TrustManager erstellen, der in allen Instanzen außer diesem einen Zertifikat an den echten TrustManager delegiert.Aber es sieht so aus, als ob TrustManager global installiert wird und sich vermutlich auf alle anderen Verbindungen unserer Anwendung auswirkt, und das riecht für mich auch nicht ganz richtig.

Was ist die bevorzugte, standardmäßige oder beste Möglichkeit, eine Java-Anwendung so einzurichten, dass sie ein selbstsigniertes Zertifikat akzeptiert?Kann ich alle oben genannten Ziele erreichen oder muss ich Kompromisse eingehen?Gibt es eine Option, die Dateien und Verzeichnisse sowie Konfigurationseinstellungen und wenig bis gar keinen Code umfasst?

War es hilfreich?

Lösung

Eine SSLSocket Fabrik selbst, und stellt sie auf dem HttpsURLConnection vor dem Anschluss.

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

Sie wollen ein SSLSocketFactory schaffen und halten Sie es um. Hier ist eine Skizze, wie es zu initialisieren:

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

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


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

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

Um den Schlüsselspeicher mit einem PEM-Format Zertifikat zu erstellen, können Sie Ihren eigenen Code verwenden CertificateFactory schreiben oder importieren Sie es nur mit keytool aus dem JDK (keytool wird nicht Arbeit für eine „Tasteneingabe “, aber es ist nur gut für einen‚vertrauenswürdigen Eintrag‘).

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

Andere Tipps

Ich lese viele Orte, Online, diese Sache zu lösen. Dies ist der Code, den ich schrieb, damit es funktioniert:

                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 habe getestet, dass Sie alle Zeichen in das Zertifikat Zeichenfolge setzen können, wenn es sich von selbst signiert ist, so lange wie Sie die genaue Struktur oben halten. Ich erhielt das Zertifikat Zeichenfolge mit meinem Laptop Terminal-Befehlszeile.

Beim Erstellen eines SSLSocketFactory ist keine Option, importieren Sie einfach den Schlüssel in die JVM

  1. Rufen Sie den öffentlichen Schlüssel ab:$openssl s_client -connect dev-server:443, und erstellen Sie dann eine Datei dev-server.pem das sieht aus wie

    -----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: ändern Sie es

  3. Starten Sie die JVM neu

Quelle: Wie löse ich javax.net.ssl.SSLHandshakeException?

Wir kopieren den Trusts JRE und unsere eigenen Zertifikate zu diesem Trusts hinzufügen, dann der Anwendung sagen das benutzerdefinierte-Vertrauen mit einer Systemeigenschaft zu verwenden. Auf diese Weise verlassen wir das Standard-JRE-Vertrauen allein.

Der Nachteil ist, dass, wenn Sie die JRE aktualisieren Sie erhalten ihre neue Trusts nicht automatisch zusammengeführt mit Ihrem eigenen ein.

Sie könnten vielleicht dieses Szenario umgehen, indem sie ein Installateur oder Startroutine aufweist, die Trusts / jdk und Kontrollen für eine Nichtübereinstimmung überprüft oder aktualisiert automatisch die Trusts. Ich weiß nicht, was passiert, wenn Sie die Aktualisierung während der Trusts Anwendung ausgeführt wird.

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

Ich habe so etwas wie dies zu tun ist, wenn mit commons-Httpclient einen internen HTTPS-Server mit einem selbstsignierten Zertifikat zuzugreifen. Ja, unsere Lösung war einen eigenen Trustmanager zu erstellen, die einfach alles übergab (eine Debug-Nachricht protokolliert).

Das kommt auf unsere eigene SSLSocketFactory zu haben, die SSL-Sockets von unserem lokalen SSL-Kontext erzeugt, der eingerichtet ist, nur unsere lokalen Trustmanager mit ihm verbunden zu haben. Sie brauchen nicht auf alle in der Nähe von einem Schlüsselspeicher / certstore zu gehen.

Das ist also 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 bereits erwähnte Dummy-Trust-Manager-Implementierung.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top