web-dev-qa-db-fra.com

Ajouter par programme une autorité de certification tout en conservant les certificats SSL du système Android

Il y a beaucoup de questions sur ce sujet sur StackOverflow, mais je ne semble pas en trouver une liée à mon problème.

J'ai une application Android qui doit communiquer avec les serveurs HTTPS: certaines sont signées avec une autorité de certification enregistrée dans le magasin de clés du système Android (sites Web HTTPS courants), et d'autres certificat autosigné par exemple).

Je sais comment ajouter mon autorité de certification par programme et forcer chaque connexion HTTPS à l'utiliser. J'utilise le code suivant:

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

    }

}

Cependant, cela désactive l'utilisation du magasin de clés du système Android et je ne peux plus interroger les sites HTTPS signés avec une autre autorité de certification.

J'ai essayé d'ajouter mon autorité de certification dans le magasin de clés Android à l'aide de:

KeyStore.getInstance("AndroidCAStore")

... mais je ne peux pas y ajouter mon autorité de certification (une exception est lancée).

Je pourrais utiliser la méthode d'instance HttpsURLConnection.setSSLSocketFactory(...) à la place de la méthode globale HttpsURLConnection.setDefaultSSLSocketFactory(...) statique pour indiquer au cas par cas quand mon autorité de certification doit être utilisée.

Mais ce n’est pas du tout pratique, d’autant plus que je ne peux parfois pas transmettre un objet HttpsURLConnection préconfiguré à certaines bibliothèques.

Quelques idées sur la façon dont je pourrais faire ça?


EDIT - REPONSE

Ok, suite aux conseils donnés, voici mon code de travail. Certaines améliorations pourraient être nécessaires, mais cela semble fonctionner comme point de départ.

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

}
12
Vincent Hiribarren

Essayez d'implémenter un gestionnaire de confiance personnalisé afin qu'il vérifie vos certificats personnalisés et, le cas échéant, les certificats intégrés à Android.

Consultez cet article: Utilisation d'un certificat personnalisé Trust Store sur Android .

Je pense que le paragraphe "Créer un TrustManager dynamique" traite exactement de ce que vous demandez.

3
Okas

C'est une vieille question, mais j'ai rencontré le même problème, il est donc probablement intéressant de poster ma réponse. Vous avez essayé d'ajouter votre certificat à KeyStore.getInstance("AndroidCAStore"), mais vous avez une exception. En réalité, vous auriez dû faire le contraire: ajoutez des entrées de ce magasin de clés à celui que vous avez créé. Mon code est un peu différent du vôtre, je le publie simplement afin de donner une réponse complète même si seule la partie centrale importe.

KeyStore keyStore=KeyStore.getInstance("BKS");
InputStream in=activity.getResources().openRawResource(R.raw.my_ca);
try
{
  keyStore.load(in,"PASSWORD_HERE".toCharArray());
}
finally
{
  in.close();
}
KeyStore defaultCAs=KeyStore.getInstance("AndroidCAStore");
if(defaultCAs!=null)
{
  defaultCAs.load(null,null);
  Enumeration<String> keyAliases=defaultCAs.aliases();
  while(keyAliases.hasMoreElements())
  {
    String alias=keyAliases.nextElement();
    Certificate cert=defaultCAs.getCertificate(alias);
    try
    {
      if(!keyStore.containsAlias(alias))
        keyStore.setCertificateEntry(alias,cert);
    }
    catch(Exception e)
    {
      System.out.println("Error adding "+e);
    }
  }
}
TrustManagerFactory tmf=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
// Get a new SSL context
SSLContext ctx = SSLContext.getInstance("SSL");
ctx.init(null,tmf.getTrustManagers(),new Java.security.SecureRandom());
return ctx.getSocketFactory();
0
Dmitry