2014-12-19 13 views
9

Istnieje wiele pytań na ten temat w StackOverflow, ale nie wydaje mi się, aby jeden był związany z moim problemem.Programowe dodawanie urzędu certyfikacji przy zachowaniu certyfikatów SSL systemu Android

Mam aplikację na Androida, która musi komunikować się z serwerami HTTPS: niektóre są podpisane za pomocą urzędu certyfikacji zarejestrowanego w magazynie kluczy systemu Android (typowe witryny HTTPS), a niektóre są podpisane przy użyciu urzędu certyfikacji, który jest właścicielem, ale nie w magazynie kluczy systemu Android (serwer z certyfikatem z autouzupełnieniem na przykład).

Wiem, jak dodać program CA i zmusić każde połączenie HTTPS do korzystania z niego. Używam następujący kod:

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

    } 

} 

Jednak robi to wyłącza korzystanie z systemu Android kluczy, a ja nie mogę kwerendy HTTPS strony podpisały z innymi CA więcej.

Próbowałem dodać swój urząd w Android kluczy, używając:

KeyStore.getInstance("AndroidCAStore") 

... ale nie można wtedy dodać swój urząd w nim (wyjątek jest uruchomiony).

Mogę użyć metody instancji HttpsURLConnection.setSSLSocketFactory(...) zamiast statycznego globalnego HttpsURLConnection.setDefaultSSLSocketFactory(...) do opowiedzenia w każdym przypadku, kiedy mój CA musi być użyty.

Ale to wcale nie jest praktyczne, tym bardziej, że czasami nie mogę przekazać wstępnie skonfigurowanego obiektu HttpsURLConnection do niektórych bibliotek.

Kilka pomysłów na temat tego, w jaki sposób mogę to zrobić?


EDIT - ODPOWIEDŹ

Ok, po danej rady, tu jest mój kod roboczych. Może wymagać pewnych ulepszeń, ale wydaje się działać jako punkt wyjścia.

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

Czy jesteś pewien, że Twój codee działa zarówno z zaufanym certyfikatem, jak i samopodpisanym? Próbowałem, działa tylko z tym certyfikatem ze strumienia wejściowego; kiedy przełączam się na serwer z zaufanym certyfikatem, niż metoda "checkServerTrusted" rośnie wyjątek –

+0

Czy Twój test został wykonany z Androidem N lub poprzednią wersją? –

+0

Przetestowałem na 4.4, 6.0 –

Odpowiedz

3

próby wdrożenia niestandardowego menedżera zaufania, tak że sprawdza swoje własne certyfikaty, a jeśli to się nie powiedzie android certyfikaty wbudowanych.

Proszę spojrzeć na ten artykuł: Using a Custom Certificate Trust Store on Android.

Myślę, że akapit "Tworzenie dynamicznego TrustManager" obsługuje dokładnie to, o co prosisz.

-1

To może być za późno, ale jest to wypróbowane i przetestowane podejście, które pomaga ominąć sprawdzanie certyfikatu wykonane przez Javę.

Nie mogę pochwalić się tym kodem, napisałem przez jednego z moich kolegów :). Może być używany podczas programowania w celu przetestowania kodu. Jeśli nie chcesz w ogóle zajmować się certyfikatami, możesz ustawić Javę zawsze na certyfikaty z dowolnego hosta dla swojego obiektu HttpURLConnection. Co wydaje się być dokładnie tym, co próbujesz tutaj zrobić.

Oto klasa, która powinna pomóc zrobić:

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

Wszystko, co musisz zrobić, to zadzwonić do relaxHostChecking function() tak:

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

To spowoduje java ufając w zależności host, z którym próbujesz się połączyć za pomocą HttpURLConnection.

+2

Zły pomysł. Nie chodzi o to, aby * wyłączyć * bezpieczeństwo, ale raczej używać jednocześnie * uprawnień * systemu * systemu *. –

+0

@ChrisStratton Celem mojego kodu było wyłączenie bezpieczeństwa. Jest to wyłącznie do celów rozwojowych, jeśli nie chcesz ciągle wstawiać certyfikatu do magazynu kluczy dla każdej maszyny, której próbujesz użyć. –

+1

Następnie nie udało się odczytać pytania - celem jest ** nie ** wyłączenie ochrony, więc twoja propozycja nie jest pomocna, szczególnie biorąc pod uwagę, że * rzeczywisty * cel został osiągnięty dawno temu. –

Powiązane problemy