2014-12-19 5 views
9

Es gibt viele Fragen zu diesem Thema in StackOverflow, aber ich finde keine, die sich auf mein Problem beziehen.Programmgesteuertes Hinzufügen einer Zertifizierungsstelle bei Beibehaltung der SSL-Zertifikate für Android-Systeme

Ich habe eine Android-Anwendung, die mit HTTPS-Servern kommunizieren muss: einige signiert mit einer im Android-System Keystore (gemeinsame HTTPS-Websites) registriert, und einige mit einer CA unterzeichnet ich besitze aber nicht im Android-System keystore (ein Server mit einem automatisch signierten Zertifikat für Beispiel).

Ich weiß, wie ich meine CA programmgesteuert hinzufügen und erzwinge jede HTTPS-Verbindung, um es zu verwenden. Ich verwende den folgenden Code ein:

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

    } 

} 

jedoch tun, dass die Verwendung der Android-System deaktiviert Schlüsselspeicher, und ich kann nicht abfragen HTTPS-Sites mit anderen CA unterzeichnet mehr.

Ich versuchte, meine CA im Android Schlüsselspeicher hinzuzufügen, mit:

KeyStore.getInstance("AndroidCAStore") 

... aber ich kann meine CA darin (eine Ausnahme gestartet wird) nicht dann hinzuzufügen.

Ich könnte die Instanzmethode HttpsURLConnection.setSSLSocketFactory(...) anstelle der statischen globalen HttpsURLConnection.setDefaultSSLSocketFactory(...) verwenden, um von Fall zu Fall zu sagen, wann meine CA verwendet werden muss.

Aber es ist überhaupt nicht praktisch, um so mehr, da ich manchmal ein vorkonfiguriertes HttpsURLConnection Objekt an einige Bibliotheken nicht übergeben kann.

Einige Ideen, wie ich das tun könnte?


EDIT - ANTWORT

Ok, nach dem gegebenen Rat, hier ist mein Arbeitscode. Es könnte einige Verbesserungen benötigen, aber es scheint als Ausgangspunkt zu arbeiten.

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

Sind Sie sicher, dass Ihr Code mit vertrauenswürdigem Zertifikat und selbstsignierter Signatur arbeitet? Ich habe versucht, es funktioniert nur mit diesem Zertifikat aus dem Eingabestream; Wenn ich auf einen Server mit vertrauenswürdigem Zertifikat umschalte, wird die Methode "checkServerTrusted" ausgelöst. Ausnahme –

+0

Wurde der Test mit Android N oder einer früheren Version durchgeführt? –

+0

Ich testete auf 4.4, 6.0 –

Antwort

3

versuchen, einen benutzerdefinierten Trust-Manager zu implementieren, so dass es Ihre benutzerdefinierten Zertifikate überprüft und, wenn dies nicht gelingt Android eingebaute Zertifikate.

Schauen Sie sich diesen Artikel an: Using a Custom Certificate Trust Store on Android.

Ich denke, der Absatz "Erstellen eines dynamischen TrustManager" behandelt genau das, was Sie fragen.

-1

Dies könnte zu spät sein, aber dies ist ein bewährter Ansatz, mit dem die von Java durchgeführte Zertifikatsprüfung umgangen werden kann.

Ich kann keinen Kredit für diesen Code beanspruchen, er wurde von einem meiner Kollegen geschrieben :). Es kann während der Entwicklung verwendet werden, um Ihren Code zu testen. Falls Sie überhaupt keine Zertifikate verwenden möchten, können Sie für jedes HttpURLConnection-Objekt immer von jedem Host Java-Zertifikate erstellen. Das scheint genau das zu sein, was Sie hier versuchen wollen.

Hier ist eine Klasse, die Sie, dass helfen soll:

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

Alles, was Sie tun müssen, ist die Funktion relaxHostChecking() aufrufen, wie folgt aus:

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

Das in Java zu vertrauen führen wird je nachdem, welcher Host, mit dem Sie versuchen, eine Verbindung zur Verwendung von HttpURLConnection herzustellen.

+2

Schlechte Idee. Es geht nicht darum, die Sicherheit zu * deaktivieren *, sondern die Berechtigungen * custom * und * system * gleichzeitig zu verwenden. –

+0

@ChrisStratton Der Punkt meines Codes war, die Sicherheit selbst zu deaktivieren. Es ist nur für Entwicklungszwecke gedacht, falls man nicht ständig ein Zertifikat in einen Keystore für jede Maschine einlegen möchte, die es zu benutzen versucht. –

+1

Dann haben Sie die Frage nicht gelesen - das Ziel hier ist ** nicht ** um die Sicherheit zu deaktivieren, daher ist Ihr Vorschlag nicht hilfreich, vor allem wenn man bedenkt, dass das * tatsächliche * Ziel vor langer Zeit erreicht wurde. –