2012-11-20 4 views
10

Devo chiamare un servizio HTTP ospitato sul server Web con un certificato SSL non valido. In dev, sto importando il certificato con keytool ma il certificato sarà diverso su ogni installazione client, quindi non posso limitarlo.Ignorare la convalida SSL in Java

Prefazione: I DO sapere che saltare la convalida SSL è davvero brutto. In questo caso specifico, non avrei nemmeno bisogno di SSL e tutte le altre comunicazioni nel sistema si basano su un semplice HTTP. Quindi davvero non mi importa degli attacchi MITM o simili. Un utente malintenzionato non dovrebbe andare fino al punto di interrompere SSL perché non c'è SSL per i dati. Questo è il supporto per un sistema legacy su cui non ho controllo.

Sto usando HttpURLConnection con un SSLSocketFactory che ha un NaiveTrustManager e un NaiveHostnameVerifier. Funziona su alcuni server autofirmati che ho provato ma non sul sito del cliente. L'errore che sto ricevendo è:

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) 

mio SimpleSocketFactory assomiglia:

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

Il NaiveHostnameVerifier ha un modo per limitare i padroni di casa valido ma resta nulla, in modo sostanzialmente accettando qualsiasi cosa:

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

L'utilizzo è simile a questo:

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

Durante la ricerca del messaggio di errore, la maggior parte delle persone semplicemente importa il certificato nel proprio negozio ma, di nuovo, non posso farlo perché non so quale certificato sarà. La mia unica alternativa se non funziona è quella di creare uno strumento in grado di scaricare il certificato e aggiungerlo in un modo più semplice a righe di comando criptico, ma preferirei lasciare che il mio codice Java ignorasse semplicemente il certificato non valido.

Qualche idea?

+4

Ti rendi conto che ignorando la verifica del certificato (e del nome host) si aprono le connessioni a potenziali attacchi MITM? Sembra che ci sia un difetto nelle tue procedure di sicurezza (amministrative) se devi ignorare gli errori del certificato. Inoltre, dovresti considerare le opzioni di Certicom TLS, dal momento che non stai utilizzando visibilmente il provider JSSE predefinito. – Bruno

+0

Sono ben consapevole degli attacchi MITM. Infatti, se qualcuno è in grado di eseguire un attacco MITM su quella rete, abbiamo problemi molto più grandi di quelli che intercettano la comunicazione. Inizialmente, stavo considerando un passaggio nel software in cui recuperiamo il certificato e, se convalidato dall'utente, lo memorizzo per un ulteriore utilizzo. Questa è comunque una sfida più grande del piccolo problema che sto cercando di affrontare. Vedrò le opzioni di Certicom TLS. –

+2

WebLogic ha fatto cose orribili e orribili con SSL, e mi dispiace per aver dovuto provare ad affrontarlo. Non seguono gli standard Java hanno il loro modo rovinato e rovinato di incasinare tutto. – erickson

risposta

2

In realtà non c'è nulla di sbagliato nel codice sopra. Il problema sembra riguardare Weblogic e questo modulo Certicom TLS. Quando guardo il server opzioni, SSL e avanzate vedo che posso specificare un HostnameVerifier personalizzato (SSLMBean.HostnameVerifier), ma l'unico elemento che suggerisce la capacità di interferire con la convalida certificato è deprecato.

Ho provato il codice di cui sopra al di fuori di Weblogic e ha funzionato magnificamente (ha però risolto il nome hostname nel post).

Quindi ho provato ad aggiungere "-DUseSunHttpHandler = true" ai parametri Weblogic come suggerito da ipolevoy in this other question. Ha iniziato a funzionare.

Detto questo, il passaggio del gestore HTTP su un server Oracle Service Bus sembra un po 'rischioso. Potrebbero esserci effetti collaterali che tornano a mordermi tra qualche settimana ...

Ho anche tentato di definire il mio trustStore e puntarlo su un jssecacert che conteneva la chiave richiesta. È stato anche ignorato da Weblogic perché ha le proprie impostazioni di trustStore per ogni server. Così sto ricorrendo per chiedere all'amministratore di importare manualmente le chiavi richieste o puntare Weblogic al mio negozio.

+0

A proposito, ho appena scoperto che HostnameVerifier non può essere personalizzato e non riesce con "javax.net.ssl.SSLKeyException: [Sicurezza: 090504] Catena di certificati ricevuta da intranet.company.com - xxxx controllo verifica hostname fallito. contenuto * .company.com ma controlla intranet.company.com previsto ". Weblogic, ti odio. Lo faccio davvero –

0

In realtà, questo è un bug noto nelle versioni di Weblogic sotto 10.3.5, per il quale esiste una patch disponibile da Oracle. Per i dettagli vedere il documento 1474989.1 in My Oracle Support.

La correzione di cui sopra è una soluzione alternativa consigliata (ma supportata) da Oracle, che funzionerà, ma non è la soluzione preferita.

La soluzione preferita è scaricare la patch menzionata nell'articolo Oracle e sostituire il verificatore del nome host SSL con il nuovo che fa anche parte di Weblogic 10.3.5 e versioni successive. Se desideri rimanere conforme a Oracle in termini di supporto, questa è la strada da percorrere.