2012-11-20 11 views
10

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ł?

+4

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

+0

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. –

+2

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

Odpowiedz

2

W powyższym kodzie nie ma nic złego. Wydaje się, że problem tkwi w Weblogic i tym module TLS Certicom. Kiedy patrzę na serwerze opcje SSL i zaawansowane widzę, że mogę określić niestandardową HostnameVerifier (SSLMBean.HostnameVerifier), ale jedynym elementem, sugerując możliwość ingerowania walidacji certyfikatów jest przestarzała.

Próbowałem powyższego kodu poza Weblogic i działało pięknie (naprawiono HostnameVerifier w poście).

Następnie próbowałem dodać "-DUseSunHttpHandler = true" do parametrów Weblogic zgodnie z sugestią ipolevoy w this other question. Zaczęło działać.

Mimo to przełączanie procedury obsługi HTTP na serwerze usługi Oracle Service Bus wydaje się być nieco ryzykowne. Mogą pojawić się efekty uboczne, które powracają, by ugryźć mnie za kilka tygodni ...

Podjęto również próbę zdefiniowania własnego magazynu zaufania i wskazania go jssecacert, który zawierał wymagany klucz. Zostało to również zignorowane przez Weblogic, ponieważ ma własne ustawienie TrustStore dla każdego serwera. Tak więc uciekam się do administratora, aby ręcznie zaimportował wymagane klucze lub wskazał Weblogic na mój własny sklep.

+0

Swoją drogą, właśnie odkryłem, że HostnameVerifier nie może być spersonalizowany i kończy się niepowodzeniem z "javax.net.ssl.SSLKeyException: [Security: 090504] Łańcuch certyfikatów odebrany z intranet.company.com - sprawdzanie weryfikacji nazwy hosta xxxx. zawiera * .company.com, ale sprawdź oczekiwaną intranet.company.com ". Weblogic, nienawidzę cię. Naprawdę. –

0

W rzeczywistości jest to znany błąd w wersjach Weblogic poniżej 10.3.5, dla którego istnieje łata dostępna od Oracle. Więcej informacji można znaleźć w dokumencie 1474989.1 w dziale My Oracle Support.

Powyższa poprawka jest rozwiązaniem zalecanym (ale obsługiwanym) przez Oracle, które zadziała, ale nie jest preferowanym rozwiązaniem.

Preferowanym rozwiązaniem jest pobranie poprawki wspomnianej w artykule Oracle i zastąpienie weryfikatora nazwy hosta SSL nowym, który jest również częścią Weblogic 10.3.5 i nowszych. Jeśli chcesz zachować zgodność z Oracle w zakresie wsparcia, to jest droga do zrobienia.

Powiązane problemy