Jak Mogę używać różnych certyfikatów na określonych połączeniach?

Moduł, który dodaję do naszej dużej aplikacji Java, musi rozmawiać z witryną zabezpieczoną SSL innej firmy. Problem polega na tym, że witryna używa certyfikatu podpisanego samodzielnie. Mam kopię certyfikatu, aby sprawdzić, czy nie napotykam ataku man-in-the-middle i muszę włączyć ten certyfikat do naszego kodu w taki sposób, aby połączenie z serwerem przebiegło pomyślnie.

Oto Podstawowy kod:

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

Bez dodatkowej obsługi dla własnoręcznie podpisanego certyfikatu, to umiera w conn.getOutputStream () z następującym wyjątkiem:

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

Idealnie, mój kod musi nauczyć Javę, aby akceptowała ten jeden certyfikat z własnym podpisem, dla tego jednego miejsca w aplikacji i nigdzie indziej.

Wiem, że mogę zaimportować certyfikat do sklepu JRE certificate authority, co pozwoli Javie go zaakceptować. To nie jest podejście, które chcę podjąć, jeśli mogę pomóc; wydaje się bardzo inwazyjne, aby zrobić na wszystkich naszych klientów maszyny dla jednego modułu mogą nie używać; miałoby to wpływ na wszystkie inne aplikacje Java używające tego samego JRE, i nie podoba mi się to, mimo że szanse na jakikolwiek inny program Java kiedykolwiek dostęp do tej strony są zerowe. Nie jest to również trywialna operacja: na Unixie muszę uzyskać prawa dostępu, aby zmodyfikować JRE w ten sposób.

Widziałem również, że mogę utworzyć instancję TrustManager, która wykonuje niestandardowe sprawdzanie. Wygląda na to, że mogę nawet stworzyć TrustManager, który deleguje do prawdziwy TrustManager we wszystkich instancjach poza tym jednym certyfikatem. Ale wygląda na to, że TrustManager zostanie zainstalowany globalnie i przypuszczam, że wpłynie to na wszystkie inne połączenia z naszej aplikacji, i to też nie pachnie dobrze dla mnie.

Jaki jest preferowany, standardowy lub najlepszy sposób skonfigurowania aplikacji Java, aby akceptowała certyfikat z podpisem własnym? Czy mogę osiągnąć wszystkie cele, które mam na myśli powyżej, czy też będę musiał pójść na kompromis? Czy istnieje opcja polegająca na pliki i katalogi, ustawienia konfiguracyjne, i mało-do-żadnego kodu?

Author: erickson, 2009-05-13

5 answers

Utwórz fabrykę SSLSocket i ustaw ją na HttpsURLConnection przed połączeniem.

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

Będziesz chciał stworzyć jeden SSLSocketFactory i zachować go wokół. Oto szkic jak go zainicjować:

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

Jeśli potrzebujesz pomocy przy tworzeniu magazynu kluczy, skomentuj.


Oto przykład ładowania magazynu kluczy:
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();

Aby utworzyć magazyn kluczy z certyfikatem w formacie PEM, możesz napisać własny kod za pomocą CertificateFactory, lub po prostu zaimportować go za pomocą keytool z JDK (keytool nie będzie działać dla "wpisu kluczowego" , ale jest w porządku dla "wpisu zaufanego").

keytool -import -file selfsigned.pem -alias server -keystore server.jks
 154
Author: erickson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-14 20:07:01

Jeśli tworzenie SSLSocketFactory nie jest opcją, po prostu zaimportuj klucz do JVM

  1. Odzyskaj klucz publiczny: $openssl s_client -connect dev-server:443, następnie utwórz plik dev-server.pem wygląda jak

    -----BEGIN CERTIFICATE----- 
    lklkkkllklklklklllkllklkl
    lklkkkllklklklklllkllklkl
    lklkkkllklk....
    -----END CERTIFICATE-----
    
  2. Import klucza: #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem. Hasło: changeit

  3. Restart JVM

Źródło: Jak rozwiązać javax.net. ssl. SSLHandshakeException?

 13
Author: user454322,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-29 01:21:25

Przeczytałem wiele miejsc w sieci, żeby to rozwiązać. To jest kod, który napisałem, aby to działało:

                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 to łańcuch zawierający certyfikat, na przykład:

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

Przetestowałem, że możesz umieścić dowolne znaki w łańcuchu certyfikatu, jeśli jest on podpisany samodzielnie, o ile zachowasz dokładną strukturę powyżej. Uzyskałem ciąg certyfikatu za pomocą linii poleceń terminala mojego laptopa.

 13
Author: Josh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-05 15:51:55

Kopiujemy truststore JRE i dodajemy nasze niestandardowe certyfikaty do tego truststore, a następnie każemy aplikacji używać custom truststore z właściwością systemową. W ten sposób zostawiamy domyślny truststore JRE.

Minusem jest to, że po aktualizacji JRE nie otrzymujesz jego nowego truststore automatycznie połączonego z niestandardowym.

Możesz poradzić sobie z tym scenariuszem, mając instalator lub procedurę startową, która weryfikuje truststore/jdk i sprawdza, czy niezgodność lub automatycznie aktualizuje truststore. Nie wiem, co się stanie, jeśli zaktualizujesz truststore podczas działania aplikacji.

To rozwiązanie nie jest w 100% eleganckie i niezawodne, ale jest proste, działa i nie wymaga kodu.

 12
Author: Mr. Shiny and New 安宇,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-13 17:17:23

Musiałem zrobić coś takiego, używając commons-httpclient, aby uzyskać dostęp do wewnętrznego serwera https z własnym podpisem. Tak, naszym rozwiązaniem było stworzenie niestandardowego Trustmanagera, który po prostu przekazał wszystko (rejestrując komunikat debugowania).

Sprowadza się to do posiadania własnego SSLSocketFactory, który tworzy gniazda SSL z naszego lokalnego SSLContext, który jest skonfigurowany tak, aby był z nim powiązany tylko nasz lokalny TrustManager. Nie musisz zbliżać się do keystore/certstore w wszystkie.

Więc to jest w naszym 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);
}

Wraz z innymi metodami implementującymi SecureProtocolSocketFactory. LocalSSLTrustManager jest wyżej wspomnianą implementacją dummy trust manager.

 12
Author: araqnid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-13 17:25:22