如何在Android 4.X中以编程方式导入全局CA证书?

时间:2016-06-06 12:26:46

标签: java android ssl certificate ssl-certificate

我们的应用程序使用http / https连接其REST服务器。在我们切换到https之前,一切正常。 Min SDK version = 14。 IOS版本没有任何问题。

以下是openssl s_client -connect test_server.ru:443输出:

Certificate chain
 0 s:/CN=test_server.ru
   i:/C=US/O=thawte, Inc./OU=Domain Validated SSL/CN=thawte DV SSL CA - G2
 1 s:/C=US/O=thawte, Inc./OU=Domain Validated SSL/CN=thawte DV SSL CA - G2
   i:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA
 2 s:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA
   i:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA

Android 4-5 Chrome显示网站证书无效(红色和交叉)。应用程序捕获异常:"未找到证书路径的信任锚"

所以我将两个键添加到asset文件夹并使用快速静态类来使用这些键:

public class CustomTrustCA {
    private static SSLContext mSSLContext = null;

    public static SSLSocketFactory getInstance() {
        if (mSSLContext == null && Init() == null) return null;
        return mSSLContext.getSocketFactory();
    }

    public static SSLContext Init() {
        Certificate ca = null;
        Certificate ca2 = null;
        InputStream caInput = null;
        InputStream caInput2 = null;
        KeyStore keyStore = null;
        TrustManagerFactory tmf = null;
        mSSLContext = null;

        //noinspection TryFinallyCanBeTryWithResources
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            caInput = Application.AppContext.getAssets().open("thawte.cer");
            ca = cf.generateCertificate(caInput);
            caInput2 = Application.AppContext.getAssets().open("thawte2.cer");
            ca2 = cf.generateCertificate(caInput2);
        } catch (Exception e) {
            Logger.Exception(e);
        } finally {
            try {
                if (caInput != null) caInput.close();
                if (caInput2 != null) caInput2.close();
            } catch (Exception ignored) {}
        }

        if (ca == null) return null;
        if (ca2 == null) return null;
        try {
            // Create a KeyStore containing our trusted CAs
            String keyStoreType = KeyStore.getDefaultType();
            keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("ca", ca);
            keyStore.setCertificateEntry("ca2", ca2);
        } catch (Exception e) {
            Logger.Exception(e);
        }

        try {
            // Create a TrustManager that trusts the CAs in our KeyStore
            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);
        } catch (Exception e) {
            Logger.Exception(e);
        }

        if (tmf == null) return null;
        try {
            // Create an SSLContext that uses our TrustManager
            mSSLContext = SSLContext.getInstance("TLS");
            mSSLContext.init(null, tmf.getTrustManagers(), null);
        } catch (Exception e) {
            Logger.Exception(e);
        }

        return mSSLContext;
    }
}

然后我将其添加到低级别的http代码中:

HttpsURLConnection urlConnection = (HttpsURLConnection) full_url.openConnection();
SSLSocketFactory instance = CustomTrustCA.getInstance();
if (instance != null) urlConnection.setSSLSocketFactory(instance);

这是固定的东西,但Universal Image Loader也断了,所以我写了一些代码来修复它(自定义图像下载器类):

public class SecureImageDownloader extends BaseImageDownloader {
    public static final String TAG = SecureImageDownloader.class.getName();


    public SecureImageDownloader(Context context, int connectTimeout, int readTimeout) {
        super(context, connectTimeout, readTimeout);
    }

    @Override
    protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {

        URL url = null;
        try {
            url = new URL(imageUri);
        } catch (MalformedURLException e) {
            Logger.Exception(e);
        }
        HttpURLConnection http = null;
        if (url == null) return null;

        if (Scheme.ofUri(imageUri) == Scheme.HTTPS) {
            HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
            https.setSSLSocketFactory(CustomTrustCA.getInstance());
            http = https;
            http.connect();
        } else {
            http = (HttpURLConnection) url.openConnection();
        }

        http.setConnectTimeout(connectTimeout);
        http.setReadTimeout(readTimeout);
        return new FlushedInputStream(new BufferedInputStream(http.getInputStream()));
    }
}

确定。主代码现在有效。 UIL也有效...但是......

最后我发现DownloadManager&附件的Intent.ACTION_VIEW也停止工作(应用程序通过它们https url!),但无法弄清楚如何修复它们。这是我的代码:

if (isNotEmpty(documentUri)) {
    DownloadManager.Request downloadReq = new DownloadManager.Request(Uri.parse(documentUri));
    downloadReq.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, attachment.Name());
    downloadReq.allowScanningByMediaScanner();
    downloadReq.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
    DownloadManager dm = (DownloadManager) Application.AppContext.getSystemService(Context.DOWNLOAD_SERVICE);
    dm.enqueue(downloadReq);
}

有没有办法使用代码添加系统范围的CA证书(即启动某些设置意图或其他内容)?

1 个答案:

答案 0 :(得分:0)

我编写了简单的lib ssl-utils-android来加载来自以下资产的特定证书:

SSLContext sslContext = SslUtils.getSslContextForCertificateFile(context, "BPClass2RootCA-sha2.cer");

您可以在my blog post上了解更多相关信息。

编辑: 将您自己的http客户端实例加载到Universal Image Loader,如:

ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
    ...
    .imageDownloader(new HttpClientImageDownloader(context, yourHttpClient))
    ...
    .build();
ImageLoader.getInstance().init(config);

我从未使用过UIL。希望它能奏效。