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