Moduł, który dodam do naszej dużej aplikacji Java, musi komunikować się z witryną innej firmy zabezpieczoną protokołem SSL. Problem polega na tym, że witryna używa certyfikatu z podpisem własnym. Mam kopię certyfikatu, aby sprawdzić, czy nie mam ataku typu man-in-the-middle i muszę włączyć ten certyfikat do naszego kodu w taki sposób, aby połączenie z serwerem zakończyło się sukcesem.
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 żadnej dodatkowej obsługi certyfikatu z podpisem własnym umiera to 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ę akceptować ten jeden certyfikat z podpisem własnym dla tego jednego miejsca w aplikacji i nigdzie indziej.
Wiem, że mogę zaimportować certyfikat do magazynu urzędu certyfikacji JRE, a to pozwoli Javie go zaakceptować. To nie jest podejście, które chcę przyjąć, jeśli mogę pomóc; wydaje się to bardzo inwazyjne na wszystkich maszynach naszych klientów dla jednego modułu, którego mogą nie używać; wpłynęłoby to na wszystkie inne aplikacje Java korzystające z tego samego środowiska JRE i nie podoba mi się to, mimo że prawdopodobieństwo uzyskania dostępu do tej witryny przez jakąkolwiek inną aplikację Java jest zerowe. Nie jest to również prosta operacja: na UNIX-ie muszę uzyskać prawa dostępu, aby zmodyfikować środowisko JRE w ten sposób.
Widziałem również, że mogę utworzyć instancję TrustManager, która wykonuje niestandardowe sprawdzanie. Wygląda na to, że mógłbym nawet stworzyć TrustManager, który deleguje do prawdziwego TrustManagera we wszystkich przypadkach z wyjątkiem tego jednego certyfikatu. Ale wygląda na to, że TrustManager jest instalowany globalnie i przypuszczam, że wpłynie to na wszystkie inne połączenia z naszej aplikacji, i to też nie pachnie dobrze.
Jaki jest preferowany, standardowy lub najlepszy sposób skonfigurowania aplikacji Java tak, aby akceptowała certyfikat z podpisem własnym? Czy mogę osiągnąć wszystkie cele, o których myślę powyżej, czy będę musiał pójść na kompromis? Czy jest opcja obejmująca pliki i katalogi oraz ustawienia konfiguracyjne i mało kodu?
Odpowiedzi:
Utwórz
SSLSocket
fabrykę samodzielnie i ustaw jąHttpsURLConnection
przed podłączeniem.Będziesz chciał go utworzyć
SSLSocketFactory
i zachować. Oto szkic, jak go zainicjować:Jeśli potrzebujesz pomocy przy tworzeniu magazynu kluczy, skomentuj.
Oto przykład ładowania magazynu kluczy:
Aby utworzyć magazyn kluczy z certyfikatem w formacie PEM, możesz napisać własny kod przy użyciu
CertificateFactory
lub po prostu zaimportować go zkeytool
JDK (narzędzie keytool nie będzie działać dla „wpisu klucza”, ale jest dobre dla „zaufanego wpisu” ).źródło
Przeczytałem WIELE miejsc w Internecie, aby rozwiązać ten problem. Oto kod, który napisałem, aby działał:
app.certificateString to ciąg zawierający certyfikat, na przykład:
Przetestowałem, że możesz umieścić dowolne znaki w ciągu certyfikatu, jeśli jest on samopodpisany, o ile zachowasz dokładną strukturę powyżej. Otrzymałem ciąg certyfikatu z wiersza poleceń terminala mojego laptopa.
źródło
Jeśli tworzenie
SSLSocketFactory
nie wchodzi w grę, po prostu zaimportuj klucz do maszyny JVMOdzyskaj klucz publiczny:,
$openssl s_client -connect dev-server:443
a następnie utwórz plik dev-server.pem, który wygląda jakZaimportować klucz:
#keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem
. Hasło: changeitUruchom ponownie JVM
Źródło: Jak rozwiązać wyjątek javax.net.ssl.SSLHandshakeException?
źródło
Kopiujemy magazyn zaufanych certyfikatów środowiska JRE i dodajemy do niego niestandardowe certyfikaty, a następnie informujemy aplikację, aby używała niestandardowego magazynu zaufanych certyfikatów z właściwością systemową. W ten sposób pozostawiamy w spokoju domyślny magazyn zaufanych certyfikatów JRE.
Wadą jest to, że po zaktualizowaniu środowiska JRE nowy magazyn zaufanych certyfikatów nie jest automatycznie łączony z niestandardowym.
Możesz sobie poradzić z tym scenariuszem, korzystając z instalatora lub procedury startowej, która weryfikuje magazyn zaufanych certyfikatów / jdk i sprawdza, czy nie ma niezgodności lub automatycznie aktualizuje magazyn zaufanych certyfikatów. Nie wiem, co się stanie, jeśli zaktualizujesz truststore podczas działania aplikacji.
To rozwiązanie nie jest w 100% eleganckie ani niezawodne, ale jest proste, działa i nie wymaga kodu.
źródło
Musiałem zrobić coś takiego, używając commons-httpclient, aby uzyskać dostęp do wewnętrznego serwera https z certyfikatem z podpisem własnym. Tak, naszym rozwiązaniem było stworzenie niestandardowego TrustManagera, który po prostu przekazywał wszystko (rejestrował 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. W ogóle nie musisz zbliżać się do keystore / certstore.
Więc to jest w naszej LocalSSLSocketFactory:
Wraz z innymi metodami implementującymi SecureProtocolSocketFactory. LocalSSLTrustManager to wspomniana wcześniej fałszywa implementacja menedżera zaufania.
źródło