最近发布了一个关于 HttpClient
over Https 的问题 (found here)。我取得了一些进展,但遇到了新问题。至于我的最后一个问题,我似乎无法在任何地方找到适合我的例子。基本上,我希望我的客户端接受任何证书(因为我只指向一台服务器)但我不断收到 javax.net.ssl.SSLException: Not trusted server certificate exception.
这就是我所拥有的:
public void connect() throws A_WHOLE_BUNCH_OF_EXCEPTIONS {
HttpPost post = new HttpPost(new URI(PROD_URL));
post.setEntity(new StringEntity(BODY));
KeyStore trusted = KeyStore.getInstance("BKS");
trusted.load(null, "".toCharArray());
SSLSocketFactory sslf = new SSLSocketFactory(trusted);
sslf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme ("https", sslf, 443));
SingleClientConnManager cm = new SingleClientConnManager(post.getParams(),
schemeRegistry);
HttpClient client = new DefaultHttpClient(cm, post.getParams());
HttpResponse result = client.execute(post);
}
这是我遇到的错误:
W/System.err( 901): javax.net.ssl.SSLException: Not trusted server certificate
W/System.err( 901): at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:360)
W/System.err( 901): at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:92)
W/System.err( 901): at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:321)
W/System.err( 901): at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:129)
W/System.err( 901): at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164)
W/System.err( 901): at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119)
W/System.err( 901): at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:348)
W/System.err( 901): at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
W/System.err( 901): at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)
W/System.err( 901): at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465)
W/System.err( 901): at me.harrisonlee.test.ssl.MainActivity.connect(MainActivity.java:129)
W/System.err( 901): at me.harrisonlee.test.ssl.MainActivity.access$0(MainActivity.java:77)
W/System.err( 901): at me.harrisonlee.test.ssl.MainActivity$2.run(MainActivity.java:49)
W/System.err( 901): Caused by: java.security.cert.CertificateException: java.security.InvalidAlgorithmParameterException: the trust anchors set is empty
W/System.err( 901): at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:157)
W/System.err( 901): at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:355)
W/System.err( 901): ... 12 more
W/System.err( 901): Caused by: java.security.InvalidAlgorithmParameterException: the trust anchors set is empty
W/System.err( 901): at java.security.cert.PKIXParameters.checkTrustAnchors(PKIXParameters.java:645)
W/System.err( 901): at java.security.cert.PKIXParameters.<init>(PKIXParameters.java:89)
W/System.err( 901): at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.<init>(TrustManagerImpl.java:89)
W/System.err( 901): at org.apache.harmony.xnet.provider.jsse.TrustManagerFactoryImpl.engineGetTrustManagers(TrustManagerFactoryImpl.java:134)
W/System.err( 901): at javax.net.ssl.TrustManagerFactory.getTrustManagers(TrustManagerFactory.java:226)W/System.err( 901): at org.apache.http.conn.ssl.SSLSocketFactory.createTrustManagers(SSLSocketFactory.java:263)
W/System.err( 901): at org.apache.http.conn.ssl.SSLSocketFactory.<init>(SSLSocketFactory.java:190)
W/System.err( 901): at org.apache.http.conn.ssl.SSLSocketFactory.<init>(SSLSocketFactory.java:216)
W/System.err( 901): at me.harrisonlee.test.ssl.MainActivity.connect(MainActivity.java:107)
W/System.err( 901): ... 2 more
最佳答案
您基本上有四种可能的解决方案来使用 httpclient 修复 Android 上的“不可信”异常:
- 信任所有证书。不要这样做,除非你真的知道你在做什么。
- 创建一个只信任您的证书的自定义 SSLSocketFactory。只要您确切知道要连接到哪些服务器,这就可以工作,但是一旦您需要连接到具有不同 SSL 证书的新服务器,您就需要更新您的应用。
- 创建一个包含 Android 证书“主列表”的 keystore 文件,然后添加您自己的证书。如果这些证书中的任何一个在未来到期,您有责任在您的应用程序中更新它们。我想不出这样做的理由。
- 创建一个使用内置证书 KeyStore 的自定义 SSLSocketFactory,但对于无法使用默认值进行验证的任何内容,都会回退到备用 KeyStore。
这个答案使用了解决方案 #4,在我看来它是最可靠的。
解决方案是使用可以接受多个 KeyStore 的 SSLSocketFactory,从而允许您为自己的 KeyStore 提供自己的证书。这允许您加载其他顶级证书,例如某些 Android 设备上可能缺少的 Thawte。它还允许您加载自己的自签名证书。它将首先使用内置的默认设备证书,并仅在必要时回退到您的其他证书。
首先,您需要确定您的 keystore 中缺少哪个证书。运行以下命令:
openssl s_client -connect www.yourserver.com:443
你会看到如下输出:
Certificate chain
0 s:/O=www.yourserver.com/OU=Go to
https://www.thawte.com/repository/index.html/OU=Thawte SSL123
certificate/OU=Domain Validated/CN=www.yourserver.com
i:/C=US/O=Thawte, Inc./OU=Domain Validated SSL/CN=Thawte DV SSL CA
1 s:/C=US/O=Thawte, Inc./OU=Domain Validated SSL/CN=Thawte DV SSL 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
如您所见,我们的根证书来自 Thawte。转到提供商的网站并找到相应的证书。对我们来说,它是here ,您可以看到我们需要的是 Copyright 2006。
如果您使用的是自签名证书,则无需执行上一步,因为您已经拥有签名证书。
然后,创建一个包含丢失的签名证书的 keystore 文件。 Crazybob 有 details how to do this on Android ,但想法是执行以下操作:
如果您还没有,请从以下网址下载充气城堡提供程序库:http://www.bouncycastle.org/latest_releases.html .这将在下面的类路径中进行。
运行命令从服务器提取证书并创建 pem 文件。在这种情况下,mycert.pem。
echo | openssl s_client -connect ${MY_SERVER}:443 2>&1 | \
sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > mycert.pem
然后运行以下命令来创建 keystore 。
export CLASSPATH=/path/to/bouncycastle/bcprov-jdk15on-155.jar
CERTSTORE=res/raw/mystore.bks
if [ -a $CERTSTORE ]; then
rm $CERTSTORE || exit 1
fi
keytool \
-import \
-v \
-trustcacerts \
-alias 0 \
-file <(openssl x509 -in mycert.pem) \
-keystore $CERTSTORE \
-storetype BKS \
-provider org.bouncycastle.jce.provider.BouncyCastleProvider \
-providerpath /path/to/bouncycastle/bcprov-jdk15on-155.jar \
-storepass some-password
您会注意到上面的脚本将结果放在 res/raw/mystore.bks
中。现在你有一个文件,你将加载到你的 Android 应用程序中,它提供了丢失的证书。
为此,请为 SSL 方案注册您的 SSLSocketFactory:
final SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https", createAdditionalCertsSSLSocketFactory(), 443));
// and then however you create your connection manager, I use ThreadSafeClientConnManager
final HttpParams params = new BasicHttpParams();
...
final ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params,schemeRegistry);
创建您的 SSLSocketFactory:
protected org.apache.http.conn.ssl.SSLSocketFactory createAdditionalCertsSSLSocketFactory() {
try {
final KeyStore ks = KeyStore.getInstance("BKS");
// the bks file we generated above
final InputStream in = context.getResources().openRawResource( R.raw.mystore);
try {
// don't forget to put the password used above in strings.xml/mystore_password
ks.load(in, context.getString( R.string.mystore_password ).toCharArray());
} finally {
in.close();
}
return new AdditionalKeyStoresSSLSocketFactory(ks);
} catch( Exception e ) {
throw new RuntimeException(e);
}
}
最后是 AdditionalKeyStoresSSLSocketFactory 代码,它接受您的新 KeyStore 并检查内置 KeyStore 是否无法验证 SSL 证书:
/**
* Allows you to trust certificates from additional KeyStores in addition to
* the default KeyStore
*/
public class AdditionalKeyStoresSSLSocketFactory extends SSLSocketFactory {
protected SSLContext sslContext = SSLContext.getInstance("TLS");
public AdditionalKeyStoresSSLSocketFactory(KeyStore keyStore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(null, null, null, null, null, null);
sslContext.init(null, new TrustManager[]{new AdditionalKeyStoresTrustManager(keyStore)}, null);
}
@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}
@Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
/**
* Based on http://download.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#X509TrustManager
*/
public static class AdditionalKeyStoresTrustManager implements X509TrustManager {
protected ArrayList<X509TrustManager> x509TrustManagers = new ArrayList<X509TrustManager>();
protected AdditionalKeyStoresTrustManager(KeyStore... additionalkeyStores) {
final ArrayList<TrustManagerFactory> factories = new ArrayList<TrustManagerFactory>();
try {
// The default Trustmanager with default keystore
final TrustManagerFactory original = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
original.init((KeyStore) null);
factories.add(original);
for( KeyStore keyStore : additionalkeyStores ) {
final TrustManagerFactory additionalCerts = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
additionalCerts.init(keyStore);
factories.add(additionalCerts);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
/*
* Iterate over the returned trustmanagers, and hold on
* to any that are X509TrustManagers
*/
for (TrustManagerFactory tmf : factories)
for( TrustManager tm : tmf.getTrustManagers() )
if (tm instanceof X509TrustManager)
x509TrustManagers.add( (X509TrustManager)tm );
if( x509TrustManagers.size()==0 )
throw new RuntimeException("Couldn't find any X509TrustManagers");
}
/*
* Delegate to the default trust manager.
*/
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
final X509TrustManager defaultX509TrustManager = x509TrustManagers.get(0);
defaultX509TrustManager.checkClientTrusted(chain, authType);
}
/*
* Loop over the trustmanagers until we find one that accepts our server
*/
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for( X509TrustManager tm : x509TrustManagers ) {
try {
tm.checkServerTrusted(chain,authType);
return;
} catch( CertificateException e ) {
// ignore
}
}
throw new CertificateException();
}
public X509Certificate[] getAcceptedIssuers() {
final ArrayList<X509Certificate> list = new ArrayList<X509Certificate>();
for( X509TrustManager tm : x509TrustManagers )
list.addAll(Arrays.asList(tm.getAcceptedIssuers()));
return list.toArray(new X509Certificate[list.size()]);
}
}
}
关于java - 通过 HTTPS 使用 HttpClient 信任所有证书,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46419956/