Muszę połączyć się z usługą HTTP hostowaną na serwerze sieciowym z niepoprawnym certyfikatem SSL. W dev importuję certyfikat za pomocą keytool, ale certyfikat będzie inny w przypadku każdej instalacji klienta, więc nie mogę go po prostu spakować.Ignorowanie sprawdzania poprawności SSL w Javie
Przedmowa: I DO wiem, że pomijanie sprawdzania poprawności SSL jest naprawdę brzydkie. W tym konkretnym przypadku nie potrzebowałbym nawet protokołu SSL, a cała inna komunikacja w systemie odbywa się za pomocą prostego protokołu HTTP. Więc naprawdę nie dbam o ataki MITM lub takie. Osoba atakująca nie musi iść tak daleko, aby złamać SSL, ponieważ nie ma SSL dla danych. Jest to wsparcie dla dotychczasowego systemu, nad którym nie mam kontroli.
Używam HttpURLConnection
z SSLSocketFactory
, który ma NaiveTrustManager
i NaiveHostnameVerifier
. Działa to na niektórych samopodpisanych serwerach, których próbowałem, ale nie na stronie klienta. Błąd Dostaję jest:
javax.net.ssl.SSLKeyException: [Security:090477]Certificate chain received from xxxxxxxxxx was not trusted causing SSL handshake failure.
at com.certicom.tls.interfaceimpl.TLSConnectionImpl.fireException(Unknown Source)
at com.certicom.tls.interfaceimpl.TLSConnectionImpl.fireAlertSent(Unknown Source)
at com.certicom.tls.record.handshake.HandshakeHandler.fireAlert(Unknown Source)
at com.certicom.tls.record.handshake.HandshakeHandler.fireAlert(Unknown Source)
at com.certicom.tls.record.handshake.ClientStateReceivedServerHello.handle(Unknown Source)
at com.certicom.tls.record.handshake.HandshakeHandler.handleHandshakeMessage(Unknown Source)
at com.certicom.tls.record.handshake.HandshakeHandler.handleHandshakeMessages(Unknown Source)
at com.certicom.tls.record.MessageInterpreter.interpretContent(Unknown Source)
at com.certicom.tls.record.MessageInterpreter.decryptMessage(Unknown Source)
at com.certicom.tls.record.ReadHandler.processRecord(Unknown Source)
at com.certicom.tls.record.ReadHandler.readRecord(Unknown Source)
at com.certicom.tls.record.ReadHandler.readUntilHandshakeComplete(Unknown Source)
at com.certicom.tls.interfaceimpl.TLSConnectionImpl.completeHandshake(Unknown Source)
at com.certicom.tls.record.WriteHandler.write(Unknown Source)
at com.certicom.io.OutputSSLIOStreamWrapper.write(Unknown Source)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
at java.io.FilterOutputStream.flush(FilterOutputStream.java:123)
at weblogic.net.http.HttpURLConnection.writeRequests(HttpURLConnection.java:154)
at weblogic.net.http.HttpURLConnection.getInputStream(HttpURLConnection.java:358)
at weblogic.net.http.SOAPHttpsURLConnection.getInputStream(SOAPHttpsURLConnection.java:37)
at weblogic.net.http.HttpURLConnection.getResponseCode(HttpURLConnection.java:947)
at (my own code)
My SimpleSocketFactory
wygląda następująco:
public static final SSLSocketFactory getSocketFactory()
{
if (sslSocketFactory == null) {
try {
// get ssl context
SSLContext sc = SSLContext.getInstance("SSL");
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[]{
new NaiveTrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
log.debug("getAcceptedIssuers");
return new java.security.cert.X509Certificate[0];
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
log.debug("checkClientTrusted");
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
log.debug("checkServerTrusted");
}
}
};
sc.init(null, trustAllCerts, new java.security.SecureRandom());
// EDIT: fixed the following line that was redeclaring SSLSocketFactory sslSocketFactory, returning null every time. Same result though.
sslSocketFactory = sc.getSocketFactory();
HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);
// EDIT: The following line has no effect
//HttpsURLConnection.setDefaultHostnameVerifier(new NaiveHostNameVerifier());
} catch (KeyManagementException e) {
log.error ("No SSL algorithm support: " + e.getMessage(), e);
} catch (NoSuchAlgorithmException e) {
log.error ("Exception when setting up the Naive key management.", e);
}
}
return sslSocketFactory;
}
NaiveHostnameVerifier
ma sposób, aby ograniczyć poprawne gospodarze ale pozostało null, więc w zasadzie przyjmowania czegokolwiek:
public class NaiveHostnameVerifier implements HostnameVerifier {
String[] patterns;
public NaiveHostnameVerifier() {
this.patterns=null;
}
public NaiveHostnameVerifier (String[] patterns) {
this.patterns = patterns;
}
public boolean verify(String urlHostName,SSLSession session) {
if (patterns==null || patterns.length==0) {
return true;
} else {
for (String pattern : patterns) {
if (urlHostName.matches(pattern)) {
return true;
}
}
return false;
}
}
}
użycie jest tak:
try {
conn = (HttpURLConnection)url.openConnection();
if (conn instanceof HttpsURLConnection) {
((HttpsURLConnection)conn).setSSLSocketFactory(SimpleSSLSocketFactory.getSocketFactory());
// EDIT: added this line, the HV has to be set on connection, not on the factory.
((HttpsURLConnection)conn).setHostnameVerifier(new NaiveHostnameVerifier());
}
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-type","application/x-www-form-urlencoded");
conn.connect();
StringBuffer sbContent = new StringBuffer();
// (snip)
DataOutputStream stream = new DataOutputStream(conn.getOutputStream());
stream.writeBytes(sbContent.toString());
stream.flush();
stream.close();
} catch (ClassCastException e) {
log.error("The URL does not seem to point to a HTTP connection");
return null;
} catch (IOException e) {
log.error("Error accessing the requested URL", e);
return null;
}
Kiedy szukam w komunikacie o błędzie, większość ludzi po prostu zaimportuje certyfikat do swojego sklepu, ale znowu nie mogę tego zrobić, ponieważ nie wiem, który certyfikat będzie. Moją jedyną alternatywą, jeśli to nie zadziała, jest stworzenie narzędzia, które może pobrać certyfikat i dodać go w łatwiejszy sposób, dzięki ukrytym wierszom poleceń, ale wolę, aby mój kod Java zignorował nieważny certyfikat.
Każdy pomysł?
Czy zdajesz sobie sprawę, że ignorowanie weryfikacji certyfikatu (i nazwy hosta) otwiera połączenia z potencjalnymi atakami MITM? Wygląda na to, że w twoich (administracyjnych) procedurach bezpieczeństwa istnieje usterka, jeśli musisz zignorować błędy certyfikatu. Ponadto powinieneś spojrzeć na opcje TLS Certicom, ponieważ w widoczny sposób nie korzystasz z domyślnego dostawcy JSSE. – Bruno
Jestem w pełni świadomy ataków MITM. W rzeczywistości, jeśli ktoś jest w stanie wykonać atak MITM w tej sieci, mamy znacznie większe problemy niż przechwytywanie komunikacji. Początkowo rozważałem krok w oprogramowaniu, w którym pobieramy certyfikat, a jeśli został on zweryfikowany przez użytkownika, należy go przechowywać do dalszego użytku. Jest to jednak większe wyzwanie niż mały problem, który próbuję rozwiązać. Zajrzę do opcji Certicom TLS. –
WebLogic zrobił straszne, okropne rzeczy z SSL i żal mi, że musisz spróbować sobie z tym poradzić. Nie postępują zgodnie ze standardami Java, mają swój własny spaczony i zepsuty sposób na wszystko. – erickson