2014-12-19 2 views
9

Ci sono molte domande su questo argomento su StackOverflow, ma non sembra che ne trovi uno relativo al mio problema.Aggiunge un certificato all'autorità di certificazione mantenendo i certificati SSL del sistema Android

Ho un'applicazione Android che deve comunicare con i server HTTPS: alcuni firmati con una CA registrata nel keystore del sistema Android (siti Web HTTPS comuni) e alcuni firmati con una CA che possiedo ma non nel keystore del sistema Android (un server con un certificato autosigned per esempio).

So come aggiungere la mia CA a livello di codice e forzare ogni connessione HTTPS per usarlo. Io uso il seguente codice:

public class SslCertificateAuthority { 

    public static void addCertificateAuthority(InputStream inputStream) { 

     try { 
      // Load CAs from an InputStream 
      // (could be from a resource or ByteArrayInputStream or ...) 
      CertificateFactory cf = CertificateFactory.getInstance("X.509"); 
      InputStream caInput = new BufferedInputStream(inputStream); 
      Certificate ca; 
      try { 
       ca = cf.generateCertificate(caInput); 
      } finally { 
       caInput.close(); 
      } 

      // Create a KeyStore containing our trusted CAs 
      String keyStoreType = KeyStore.getDefaultType(); 
      KeyStore keyStore = KeyStore.getInstance(keyStoreType); 
      keyStore.load(null, null); 
      keyStore.setCertificateEntry("ca", ca); 

      // Create a TrustManager that trusts the CAs in our KeyStore 
      String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); 
      TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); 
      tmf.init(keyStore); 

      // Create an SSLContext that uses our TrustManager 
      SSLContext context = SSLContext.getInstance("TLS"); 
      context.init(null, tmf.getTrustManagers(), null); 

      // Tell the URLConnection to use a SocketFactory from our SSLContext 
      HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory()); 
     } catch (CertificateException e) { 
      e.printStackTrace(); 
     } catch (NoSuchAlgorithmException e) { 
      e.printStackTrace(); 
     } catch (KeyStoreException e) { 
      e.printStackTrace(); 
     } catch (KeyManagementException e) { 
      e.printStackTrace(); 
     } catch (MalformedURLException e) { 
      e.printStackTrace(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

    } 

} 

Tuttavia, facendo che disabilita l'utilizzo del sistema Android chiavi, e non posso interrogare HTTPS siti firmati con altre CA più.

Ho provato ad aggiungere la mia CA nel keystore Android, utilizzando:

KeyStore.getInstance("AndroidCAStore") 

... ma non posso aggiungere il mio CA in esso (un'eccezione viene lanciata).

È possibile utilizzare il metodo di istanza HttpsURLConnection.setSSLSocketFactory(...) anziché il valore statico globale HttpsURLConnection.setDefaultSSLSocketFactory(...) da indicare caso per caso quando è necessario utilizzare la CA.

Ma non è affatto pratico, tanto più che a volte non riesco a passare un oggetto preconfigurato HttpsURLConnection ad alcune librerie.

Alcune idee su come potrei farlo?


EDIT - RISPOSTA

Ok, seguendo il consiglio dato, ecco il mio codice di lavoro. Potrebbe aver bisogno di alcuni miglioramenti, ma sembra funzionare come punto di partenza.

public class SslCertificateAuthority { 

    private static class UnifiedTrustManager implements X509TrustManager { 
     private X509TrustManager defaultTrustManager; 
     private X509TrustManager localTrustManager; 
     public UnifiedTrustManager(KeyStore localKeyStore) throws KeyStoreException { 
      try { 
       this.defaultTrustManager = createTrustManager(null); 
       this.localTrustManager = createTrustManager(localKeyStore); 
      } catch (NoSuchAlgorithmException e) { 
       e.printStackTrace(); 
      } 
     } 
     private X509TrustManager createTrustManager(KeyStore store) throws NoSuchAlgorithmException, KeyStoreException { 
      String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); 
      TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); 
      tmf.init((KeyStore) store); 
      TrustManager[] trustManagers = tmf.getTrustManagers(); 
      return (X509TrustManager) trustManagers[0]; 
     } 
     public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 
      try { 
       defaultTrustManager.checkServerTrusted(chain, authType); 
      } catch (CertificateException ce) { 
       localTrustManager.checkServerTrusted(chain, authType); 
      } 
     } 
     @Override 
     public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 
      try { 
       defaultTrustManager.checkClientTrusted(chain, authType); 
      } catch (CertificateException ce) { 
       localTrustManager.checkClientTrusted(chain, authType); 
      } 
     } 
     @Override 
     public X509Certificate[] getAcceptedIssuers() { 
      X509Certificate[] first = defaultTrustManager.getAcceptedIssuers(); 
      X509Certificate[] second = localTrustManager.getAcceptedIssuers(); 
      X509Certificate[] result = Arrays.copyOf(first, first.length + second.length); 
      System.arraycopy(second, 0, result, first.length, second.length); 
      return result; 
     } 
    } 

    public static void setCustomCertificateAuthority(InputStream inputStream) { 

     try { 
      // Load CAs from an InputStream 
      // (could be from a resource or ByteArrayInputStream or ...) 
      CertificateFactory cf = CertificateFactory.getInstance("X.509"); 
      InputStream caInput = new BufferedInputStream(inputStream); 
      Certificate ca; 
      try { 
       ca = cf.generateCertificate(caInput); 
       System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); 
      } finally { 
       caInput.close(); 
      } 

      // Create a KeyStore containing our trusted CAs 
      String keyStoreType = KeyStore.getDefaultType(); 
      KeyStore keyStore = KeyStore.getInstance(keyStoreType); 
      keyStore.load(null, null); 
      keyStore.setCertificateEntry("ca", ca); 

      // Create a TrustManager that trusts the CAs in our KeyStore and system CA 
      UnifiedTrustManager trustManager = new UnifiedTrustManager(keyStore); 

      // Create an SSLContext that uses our TrustManager 
      SSLContext context = SSLContext.getInstance("TLS"); 
      context.init(null, new TrustManager[]{trustManager}, null); 

      // Tell the URLConnection to use a SocketFactory from our SSLContext 
      HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory()); 

     } catch (CertificateException e) { 
      e.printStackTrace(); 
     } catch (NoSuchAlgorithmException e) { 
      e.printStackTrace(); 
     } catch (KeyStoreException e) { 
      e.printStackTrace(); 
     } catch (KeyManagementException e) { 
      e.printStackTrace(); 
     } catch (MalformedURLException e) { 
      e.printStackTrace(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 

} 
+0

Sei sicuro che il tuo codice funzioni con entrambi i certificati attendibili e autofirmato? Ho provato, funziona solo con quel certificato dallo stream di input; quando passo al server con certificato attendibile, il metodo "checkServerTrusted" aumenta l'eccezione –

+0

Il test è stato eseguito con Android N o una versione precedente? –

+0

Ho eseguito il test su 4.4, 6.0 –

risposta

3

cercare di attuare un gestore di fiducia personalizzato, in modo che controlli i certificati personalizzati e se questo fallisce certificati incorporati Android.

Dai un'occhiata a questo articolo: Using a Custom Certificate Trust Store on Android.

Penso che il paragrafo "Creazione di un TrustManager dinamico" gestisca esattamente ciò che si sta chiedendo.

-1

Questo potrebbe essere troppo tardi, ma questo è un approccio collaudato che aiuta a superare il controllo del certificato eseguito da Java.

Non posso rivendicare il credito per questo codice, è stato scritto da uno dei miei colleghi :). Può essere utilizzato durante lo sviluppo per testare il codice. Nel caso in cui non si desideri trattare con i certificati, è possibile rendere i certificati Java sempre da qualsiasi host per l'oggetto HttpURLConnection. Che sembra essere esattamente quello che stai cercando di fare qui.

Ecco una classe che dovrebbe aiutare a farlo:

import javax.net.ssl.*; 
import java.net.HttpURLConnection; 
import java.security.KeyManagementException; 
import java.security.NoSuchAlgorithmException; 
import java.security.cert.CertificateException; 
import java.security.cert.X509Certificate; 

/*** 
* Should only be used in development, this class will allow connections to an HTTPS server with unverified certificates. 
* obviously this should not be used in the real world 
*/ 
public class TrustModifier { 
private static final TrustingHostnameVerifier TRUSTING_HOSTNAME_VERIFIER = new TrustingHostnameVerifier(); 
private static SSLSocketFactory factory; 

/** 
* Call this with any HttpURLConnection, and it will modify the trust settings if it is an HTTPS connection. 
* 
* @param conn the {@link HttpURLConnection} instance 
* @throws KeyManagementException if an error occurs while initializing the context object for the TLS protocol 
* @throws NoSuchAlgorithmException if no Provider supports a TrustManagerFactorySpi implementation for the TLS protocol. 
*/ 
public static void relaxHostChecking(HttpURLConnection conn) throws KeyManagementException, NoSuchAlgorithmException { 
    if (conn instanceof HttpsURLConnection) { 
     HttpsURLConnection httpsConnection = (HttpsURLConnection) conn; 
     SSLSocketFactory factory = prepFactory(); 
     httpsConnection.setSSLSocketFactory(factory); 
     httpsConnection.setHostnameVerifier(TRUSTING_HOSTNAME_VERIFIER); 
    } 
} 

/** 
* Returns an {@link SSLSocketFactory} instance for the protocol being passed, this represents a secure communication context 
* 
* @return a {@link SSLSocketFactory} object for the TLS protocol 
* @throws NoSuchAlgorithmException if no Provider supports a TrustManagerFactorySpi implementation for the specified protocol. 
* @throws KeyManagementException if an error occurs while initializing the context object 
*/ 
static synchronized SSLSocketFactory prepFactory() throws NoSuchAlgorithmException, KeyManagementException { 
    if (factory == null) { 
     SSLContext ctx = SSLContext.getInstance("TLS"); 
     ctx.init(null, new TrustManager[]{new AlwaysTrustManager()}, null); 
     factory = ctx.getSocketFactory(); 
    } 
    return factory; 
} 

private static final class TrustingHostnameVerifier implements HostnameVerifier { 
    public boolean verify(String hostname, SSLSession session) { 
     return true; 
    } 
} 

private static class AlwaysTrustManager implements X509TrustManager { 
    public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { 
    } 

    public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { 
    } 

    public X509Certificate[] getAcceptedIssuers() { 
     return null; 
    } 
    } 
} 

Tutto quello che dovete fare è chiamare la funzione relaxHostChecking() in questo modo:

if (conn instanceof HttpsURLConnection) { 
     TrustModifier.relaxHostChecking(conn); 
    } 

questo si tradurrà in java confidando qualunque host a cui stai cercando di connettersi utilizzando HttpURLConnection.

+2

Cattiva idea. Il punto non è quello di disabilitare la sicurezza, bensì di usare contemporaneamente le autorità * custom * e * system *. –

+0

@ChrisStratton Il punto del mio codice era disabilitare la sicurezza stessa. È puramente a scopo di sviluppo nel caso in cui non si desideri inserire costantemente un certificato in un keystore per ogni macchina che tenta di utilizzare. –

+1

Quindi non sei riuscito a leggere la domanda - l'obiettivo qui è ** non ** disabilitare la sicurezza, quindi la tua proposta non è utile, specialmente considerando che l'obiettivo * effettivo * è stato raggiunto molto tempo fa. –

Problemi correlati