java - 以编程方式添加证书颁发机构,同时保留 Android 系统 SSL 证书

标签 java android https ssl-certificate ca

在 StackOverflow 上有很多关于这个主题的问题,但我似乎没有找到与我的问题相关的问题。

我有一个需要与 HTTPS 服务器通信的 Android 应用程序:一些使用在 Android 系统 keystore (常见的 HTTPS 网站)中注册的 CA 签名,一些使用我拥有但不在 Android 系统 keystore 中的 CA 签名(a例如带有自动签名证书的服务器)。

我知道如何以编程方式添加我的 CA 并强制每个 HTTPS 连接使用它。我使用以下代码:

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

    }

}

但是,这样做会禁用 Android 系统 keystore 的使用,而且我无法再查询与其他 CA 签名的 HTTPS 站点。

我尝试在 Android keystore 中添加我的 CA,使用:

KeyStore.getInstance("AndroidCAStore")

...但我无法在其中添加我的 CA(启动异常)。

我可以使用实例方法 HttpsURLConnection.setSSLSocketFactory(...) 而不是静态全局 HttpsURLConnection.setDefaultSSLSocketFactory(...) 来讲述一个案例必须使用我的 CA 时的案例基础。

但这根本不实用,更何况有时我无法将预配置的 HttpsURLConnection 对象传递给某些库。

关于如何做到这一点的一些想法?


编辑 - 回答

好的,按照给定的建议,这是我的工作代码。它可能需要一些改进,但它似乎可以作为一个起点。

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

}

最佳答案

这是一个老问题,但我遇到了同样的问题,所以可能值得发布我的答案。您尝试将证书添加到 KeyStore.getInstance("AndroidCAStore"),但出现异常。实际上,您应该做相反的事情——将该 keystore 中的条目添加到您创建的 keystore 中。 我的代码与您的有点不同,我只是为了完整的答案而发布它,即使只有中间部分很重要。

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

关于java - 以编程方式添加证书颁发机构,同时保留 Android 系统 SSL 证书,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27562666/

相关文章:

java:使用Raw类型作为方法参数会删除参数成员中的所有参数化类型信息

android - 按肯定按钮为什么对话框关闭?

WordPress Woocommerce 网站 : Force SSL on Account page causes Internal Server Error

android - 无法获取 android.permission.CLEAR_APP_USER_DATA

android - 安卓上的服务?

javascript - window.opener.document.getElementById ("parentId1").value = myvalue 不起作用

iis - 301 重写规则导致 SEO 警报

java - 如何将单词的指定最后一个字母匹配并替换为句子中的不同字母

Java Regex Pattern Matcher.matches() before Matcher.find() 奇怪的行为

java - 使用 Wea​​kReferenced 监听器