使用HttpClient通过HTTPS信任所有证书

最近通过https发布了一个关于HttpClient的问题(在这里找到)。 我取得了一些进展,但我遇到了新的问题。 和我最后一个问题一样,我似乎无法在任何地方找到适合我的例子。 基本上,我希望我的客户端接受任何证书(因为我只是指向一台服务器),但我不断收到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方式中,您应该从org.apache.http.conn.ssl.SSLSocketFactory创建一个自定义类,而不是一个org.apache.http.conn.ssl.SSLSocketFactory本身。 一些线索可以在这篇文章中找到自定义SSL处理停止在Android 2.2 FroYo上工作。

一个例子是...

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.conn.ssl.SSLSocketFactory;
public class MySSLSocketFactory extends SSLSocketFactory {
    SSLContext sslContext = SSLContext.getInstance("TLS");

    public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);

        TrustManager tm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };

        sslContext.init(null, new TrustManager[] { tm }, null);
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    @Override
    public Socket createSocket() throws IOException {
        return sslContext.getSocketFactory().createSocket();
    }
}

并在创建HttpClient的实例时使用这个类。

public HttpClient getNewHttpClient() {
    try {
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(null, null);

        MySSLSocketFactory sf = new MySSLSocketFactory(trustStore);
        sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

        HttpParams params = new BasicHttpParams();
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        registry.register(new Scheme("https", sf, 443));

        ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

        return new DefaultHttpClient(ccm, params);
    } catch (Exception e) {
        return new DefaultHttpClient();
    }
}

顺便说一句,下面的链接是为寻找HttpURLConnection解决方案的人。 Https连接Android

我已经在froyo上测试了上述两种解决方案,并且他们在我的案例中都像魅力一样工作。 最后,使用HttpURLConnection可能会面临重定向问题,但这超出了主题。

注意:在您决定信任所有证书之前,您可能应该充分了解该网站,并且不会对最终用户造成危害。

事实上,您应该仔细考虑您所承担的风险,包括我深深感谢的以下评论中提到的黑客模拟网站的影响。 在某些情况下,虽然可能很难照顾所有证书,但您最好知道信任所有证书的隐含缺陷。


您基本上有四种可能的解决方案来修复Android上使用httpclient的“不受信任”异常:

  • 信任所有证书。 不要这样做,除非你真的知道你在做什么。
  • 创建一个只信任你的证书的自定义SSLSocketFactory。 只要您确切知道要连接哪些服务器,只要您需要使用不同的SSL证书连接到新服务器,就需要更新您的应用。
  • 创建一个包含Android的证书“主列表”的密钥库文件,然后添加自己的。 如果这些证书中的任何证书过期,您有责任在您的应​​用中对其进行更新。 我想不出有这个理由。
  • 创建一个使用内置证书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。 转到您的提供商网站并找到相应的证书。 对我们来说,它就在这里,你可以看到我们需要的是2006版权。

    如果您使用的是自签名证书,则您不必执行上一步,因为您已经拥有签名证书。

    然后,创建一个包含缺失签名证书的密钥库文件。 Crazybob详细说明了如何在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
    

    然后运行以下命令来创建密钥库。

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

    HttpsURLConnection之前添加此代码并完成。 我知道了。

    private void trustEveryone() { 
        try { 
                HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){ 
                        public boolean verify(String hostname, SSLSession session) { 
                                return true; 
                        }}); 
                SSLContext context = SSLContext.getInstance("TLS"); 
                context.init(null, new X509TrustManager[]{new X509TrustManager(){ 
                        public void checkClientTrusted(X509Certificate[] chain, 
                                        String authType) throws CertificateException {} 
                        public void checkServerTrusted(X509Certificate[] chain, 
                                        String authType) throws CertificateException {} 
                        public X509Certificate[] getAcceptedIssuers() { 
                                return new X509Certificate[0]; 
                        }}}, new SecureRandom()); 
                HttpsURLConnection.setDefaultSSLSocketFactory( 
                                context.getSocketFactory()); 
        } catch (Exception e) { // should never happen 
                e.printStackTrace(); 
        } 
    } 
    

    我希望这可以帮助你。

    链接地址: http://www.djcxy.com/p/5271.html

    上一篇: Trusting all certificates using HttpClient over HTTPS

    下一篇: Java HTTPS client certificate authentication