java 以编程方式添加证书颁发机构,同时保留 Android 系统 SSL 证书
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27562666/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Programmatically add a certificate authority while keeping Android system SSL certificates
提问by Vincent Hiribarren
There are lots of questions about this topic on StackOverflow, but I do not seem to find one related to my problem.
StackOverflow 上有很多关于这个主题的问题,但我似乎没有找到与我的问题相关的问题。
I have an Android application that needs to communicate with HTTPS servers: some signed with a CA registered in the Android system keystore (common HTTPS websites), and some signed with a CA I own but not in the Android system keystore (a server with an autosigned certificate for instance).
我有一个需要与 HTTPS 服务器通信的 Android 应用程序:一些使用在 Android 系统密钥库(常见 HTTPS 网站)中注册的 CA 签名,有些使用我拥有但不在 Android 系统密钥库中的 CA 签名(具有例如自动签名证书)。
I know how to add my CA programmatically and force every HTTPS connection to use it. I use the following code:
我知道如何以编程方式添加我的 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();
}
}
}
However, doing that disables the use of the Android system keystore, and I cannot query HTTPS sites signed with other CA any more.
但是,这样做会禁用 Android 系统密钥库的使用,并且我无法再查询使用其他 CA 签名的 HTTPS 站点。
I tried to add my CA in the Android keystore, using:
我尝试在 Android 密钥库中添加我的 CA,使用:
KeyStore.getInstance("AndroidCAStore")
... but I cannot then add my CA in it (an exception is launched).
...但我无法在其中添加我的 CA(启动了一个例外)。
I could use the instance method HttpsURLConnection.setSSLSocketFactory(...)
instead of the static global HttpsURLConnection.setDefaultSSLSocketFactory(...)
to tell on a case by case basis when my CA has to be used.
我可以使用实例方法HttpsURLConnection.setSSLSocketFactory(...)
而不是静态全局方法来根据具体HttpsURLConnection.setDefaultSSLSocketFactory(...)
情况判断何时必须使用我的 CA。
But it isn't practical at all, all the more since sometimes I cannot pass a preconfigured HttpsURLConnection
object to some libraries.
但这根本不实用,尤其是因为有时我无法将预配置的HttpsURLConnection
对象传递给某些库。
Some ideas about how I could do that?
关于我如何做到这一点的一些想法?
EDIT - ANSWER
编辑 - 答案
Ok, following the given advice, here is my working code. It might need some enhancements, but it seems to work as a starting point.
好的,按照给定的建议,这是我的工作代码。它可能需要一些增强功能,但它似乎可以作为一个起点。
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();
}
}
}
回答by Dmitry
It is an old question, but I met the same problem, so probably it is worth posting my answer. You tried to add your certificate to KeyStore.getInstance("AndroidCAStore")
, but got an exception. Actually you should have done the opposite - add entries from that keystore to the one you created.
My code is a bit different from yours, I just post it for the sake of complete answer even though only middle part matters.
这是一个老问题,但我遇到了同样的问题,所以可能值得发布我的答案。您尝试将证书添加到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();
回答by Aditya Satyavada
This might be too late but this is a tried and tested approach that helps bypass the certificate check done by Java.
这可能为时已晚,但这是一种久经考验的方法,有助于绕过 Java 完成的证书检查。
I cannot claim credit for this code, it was written by one of my colleagues :) . It can be used during development to test your code. In case you don't want to deal with certificates at all, you can make Java always certificates from any host for your HttpURLConnection object. Which seems to be exactly what you're trying to do here.
我不能声称此代码的功劳,它是由我的一位同事编写的 :) 。它可以在开发过程中用于测试您的代码。如果您根本不想处理证书,您可以让 Java 始终从任何主机为您的 HttpURLConnection 对象创建证书。这似乎正是你在这里想要做的。
Here's a class that should help you do that :
这是一个应该可以帮助您做到这一点的课程:
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;
}
}
}
All you need to do is call the function relaxHostChecking() like this :
您需要做的就是像这样调用函数relaxHostChecking():
if (conn instanceof HttpsURLConnection) {
TrustModifier.relaxHostChecking(conn);
}
This will result in java trusting whichever host you're trying to connect to using HttpURLConnection.
这将导致 Java 信任您尝试使用 HttpURLConnection 连接的任何主机。