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

Java 以编程方式添加证书颁发机构,同时保留Android系统SSL证书,java,android,https,ssl-certificate,ca,Java,Android,Https,Ssl Certificate,Ca,关于StackOverflow这个主题有很多问题,但我似乎没有找到一个与我的问题相关的问题 我有一个需要与HTTPS服务器通信的Android应用程序:一些使用在Android系统密钥库(常见HTTPS网站)中注册的CA进行签名,另一些使用我拥有但不在Android系统密钥库中的CA进行签名(例如,使用自动签名证书的服务器) 我知道如何以编程方式添加CA,并强制每个HTTPS连接使用它。我使用以下代码: public class SslCertificateAuthority { pu

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

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

我知道如何以编程方式添加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系统密钥库的使用,我无法再查询与其他CA签署的HTTPS站点

我尝试在Android密钥库中添加我的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();
        }
    }

}

这可能太晚了,但这是一种经过尝试和测试的方法,有助于绕过Java完成的证书检查

我不能为这段代码索赔,它是我的一位同事写的:)。它可以在开发过程中用来测试代码。如果您根本不想处理证书,您可以为您的HttpURLConnection对象使Java始终从任何主机获得证书。这似乎正是你在这里想要做的

这里有一个课程可以帮助你做到这一点:

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;
    }
  }
}
您只需调用函数relaxHostChecking(),如下所示:

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

这将导致java信任您试图使用HttpURLConnection连接到的任何主机。

这是一个老问题,但我遇到了相同的问题,因此可能值得发布我的答案。您试图将证书添加到
KeyStore.getInstance(“AndroidCAStore”)
,但出现异常。实际上,您应该做相反的事情——将该密钥库中的条目添加到您创建的密钥库中。 我的代码和你的代码有点不同,我只是为了完整的答案才发布它,即使只有中间部分很重要

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();
keystorekeystore=KeyStore.getInstance(“BKS”);
InputStream in=activity.getResources().openRawResource(R.raw.my_ca);
尝试
{
加载(在“PASSWORD_HERE.toCharArray()中);
}
最后
{
in.close();
}
keystoredefaultcas=KeyStore.getInstance(“AndroidCAStore”);
if(defaultCAs!=null)
{
defaultCAs.load(null,null);
枚举键别名=defaultCAs.alias();
while(keyalases.hasMoreElements())
{
字符串别名=keyAlias.nextElement();
证书cert=defaultCAs.getCertificate(别名);
尝试
{
如果(!keyStore.containsAlias(别名))
keyStore.setCertificateEntry(别名、证书);
}
捕获(例外e)
{
System.out.println(“添加错误”+e);
}
}
}
TrustManagerFactory tmf=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(密钥库);
//获取新的SSL上下文
SSLContext ctx=SSLContext.getInstance(“SSL”);
init(null,tmf.getTrustManager(),new java.security.SecureRandom());
返回ctx.getSocketFactory();

您确定您的代码用户同时使用可信证书和自签名证书吗?我已经尝试过了,它只对来自输入流的证书有效;当我切换到具有可信证书的服务器时,方法“checkServerTrusted”会出现异常。您的测试是用Android N还是以前的版本完成的?我在4.4、6.0上进行了测试。您是如何检查证书是否已安装的?我进入了设置->安全和位置->加密和凭据->受信任凭据/用户凭据,但看到没有安装证书。但是当我从设置->Wifi首选项->安装证书(手动安装)安装证书时,它们会显示出来。没有安装证书。此(旧)代码用于在嵌入应用程序的文件中动态加载证书。坏主意。重点不是要禁用安全性,而是要同时使用自定义权限和系统权限。@ChrisStratton我的代码的重点是禁用安全性本身。这纯粹是为了开发目的,以防人们不想为他们尝试使用的每台机器不断地在密钥库中插入证书。然后你没有读到这个问题-这里的目标不是禁用安全性,因此你的建议没有帮助,特别是考虑到实际目标早已实现。