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

432

最近我在stackoverflow上发布了一个有关使用HttpClient访问Https的问题(链接)。我已经取得了一些进展,但是我遇到了新的问题。和我的上一个问题一样,我似乎找不到任何适用于我的示例。基本上,我希望我的客户端接受任何证书(因为我只会指向一个服务器),但我一直收到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

18
我需要这样做是为了内部使用。我真心希望你们不会让公司外的用户使用你们的应用程序,因为你们已经开放了中间人攻击漏洞,他们将容易受到会话劫持。无论如何,我需要做这个临时的测试直到我拿到真正的证书......希望你们也是出于同样的临时原因或者应用程序只在内部使用。 - Dean Hiller
我在4.3 Apache HTTP客户端上尝试了这些解决方案,但它们大多已经过时了。这里是一个未过时的解决方案:https://dev59.com/e1jUa4cB1Zd3GeqPSY_b#18941950 - Alexander C
Java 1.6没有SNI支持,在这些情况下也存在问题 - 如果您没有正确构造请求,可能会得到与请求不匹配的证书。请参见https://issues.apache.org/jira/browse/HTTPCLIENT-1119 - Bron Davies
3
这个问题被引用在《世界上最危险的代码》论文中,作为谬误推理的例子。(研究论文:http://www.cs.utexas.edu/~shmat/shmat_ccs12.pdf) - b4da
22个回答

531
您在使用httpclient时,遇到"Not Trusted"异常的情况,有四种潜在解决方案:
  1. 信任所有证书。除非您确实知道自己在做什么,否则不要这样做。
  2. 创建一个自定义SSLSocketFactory,只信任您的证书。只要您知道要连接哪些服务器,这个方法就可以工作。但是,一旦您需要连接具有不同SSL证书的新服务器,就必须更新应用程序。
  3. 创建一个包含Android“主列表”证书的密钥库文件,然后添加您自己的证书。如果其中任何证书在未来过期,您需要负责在应用程序中更新它们。我想不出为什么要这样做。
  4. 创建一个自定义SSLSocketFactory,它使用内置证书KeyStore,但对于任何无法验证默认KeyStore的内容,则会回退到备用KeyStore。
本答案使用第四种解决方案,我认为这是最强大的。
解决方案是使用可以接受多个KeyStores的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上执行此操作,但其思路是执行以下操作:

如果您还没有它,请从此处下载Bouncy Castle提供程序库。这将位于您的类路径下。

运行命令从服务器提取证书并创建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中。现在你有了一个文件,可以加载到你的安卓应用程序中提供缺失的证书。
为此,请注册你的SSLSocketFactory以获取SSL方案:
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()]);
        }
    }

}

3
@emmby,请问我应该把这段代码放在哪里?export CLASSPATH=bcprov-jdk16-145.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 /usr/share/java/bcprov.jar
-storepass some-password
- Rikki Tikki Tavi
嗨@Devgeeks,我也遇到了同样的问题。也许这个链接http://developer.android.com/training/articles/security-ssl.html#UnknownCa可以帮助你。如果你已经找到了,请忽略这个消息。 - Ankit
2
嘿@emmby。我正在我的应用程序中使用您的解决方案,并使用自签名证书来验证我的服务器,但在checkServerTrusted()方法中出现了CertificateException()异常。我尝试注释掉抛出异常的代码,这样就可以正常工作了。如果它无法验证我的服务器证书,那么我能以其他方式处理吗?请问在这种情况下最好的解决方案是什么? - Ankit
@Kevin 我的情况实际问题是来自服务器端,他们没有签署有效或正确的证书,而我必须将其放在我的资源/资产中,当他们签署并给我正确的证书后,我使用了上述解决方案,它对我奏效了。因此,在某些情况下,自签名证书存在一些问题。 - Ankit
7
应将此标记为正确答案。这是我在SO上看过的最详细和写得最好的答案之一。很棒! - Kachi
显示剩余16条评论

441
注意:不要在你完全信任的网络之外的生产代码中实现此功能。特别是任何通过公共互联网进行的操作。

你的问题正是我想知道的。经过一些搜索,得出以下结论:

在HttpClient中,你应该创建一个自定义类从org.apache.http.conn.ssl.SSLSocketFactory而不是org.apache.http.conn.ssl.SSLSocketFactory本身。这篇文章Custom SSL handling stopped working on 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 Connection Android

我已在Froyo上测试了上述两种解决方案,在我的情况下都能完美地运行。最后,使用HttpURLConnection可能会遇到重定向问题,但这超出了本主题的范畴。

注意:在您决定信任所有证书之前,您可能应该充分了解该站点,确保对终端用户没有危害。

事实上,您需要仔细考虑您所承担的风险,包括下面评论中提到的黑客模拟站点的影响,我非常感激。在某些情况下,尽管可能很难处理所有证书,但最好知道相信所有证书的隐含缺点。


158
这个回答应该指出,信任所有证书是极其不安全的,并且使 SSL 的整个目的失效。 - yanokwa
22
除非你能确保正在与预期的服务器通信,否则无法保证通信的安全性。如果有人篡改了 DNS 服务器,你就可能会与黑客的服务器进行加密密钥通信。 - Richard Szalay
13
换句话说,现在你容易成为中间人攻击的目标。你还应该注意到这段代码没有符合规范:请查看Javadoc。getAcceptedIssuers()不允许返回null值。 - user207421
27
接受所有证书是一个可怕的想法。让人很遗憾的是,有太多的博客和教程愉快地引导Java开发者走上错误的道路。 - Tim Bender
58
+1是因为我仅需要快速解决调试问题。由于其他人提到的安全问题,我不会在生产环境中使用它,但这正是我测试所需的东西。谢谢! - Danation
显示剩余14条评论

99

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

5
这是理想的快速简单解决方案。简短而且“只要起作用就行”。 - Steve Smith
6
测试目的下这是一个完美的答案!!! 但是在生产环境中使用它是个坏主意,不过......这应该对于看到问题标题的每个人都很清楚。它仍然是最佳/最短/具有相同(不)安全级别的答案! - Levite
在添加后,这个应用程序需要在Playstore上授予权限才能上传吗? - Gaurav Mandlik
困惑了。它说“在HttpsURLConnection之前添加此代码”,但OP并没有使用HttpsURLConnection... - ndtreviv

37

这是个不好的想法。相信任何证书只比根本不使用SSL略好一些。当你说“我希望我的客户端接受任何证书(因为我只会指向一个服务器)”时,你假定这意味着在公共网络上只指向“一个服务器”是安全的,但实际上并非如此。

相信任何证书都会让你容易成为中间人攻击的目标。任何人都可以通过与你和最终服务器建立独立的SSL连接来代理你的连接。中间人攻击者就能够访问你整个的请求和响应。除非你第一次使用SSL时不需要真正保护敏感信息或进行身份验证,否则你不应该盲目相信所有证书。

你应该考虑使用keytool将公共证书添加到jks中,并使用它来构建你的socket工厂,例如:

    KeyStore ks = KeyStore.getInstance("JKS");

    // get user password and file input stream
    char[] password = ("mykspassword")).toCharArray();
    ClassLoader cl = this.getClass().getClassLoader();
    InputStream stream = cl.getResourceAsStream("myjks.jks");
    ks.load(stream, password);
    stream.close();

    SSLContext sc = SSLContext.getInstance("TLS");
    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");

    kmf.init(ks, password);
    tmf.init(ks);

    sc.init(kmf.getKeyManagers(), tmf.getTrustManagers(),null);

    return sc.getSocketFactory();

需要注意的是,这里有一个例外情况。证书最终会过期,届时代码将停止工作。通过查看证书,您可以轻松确定此事发生的时间。


5
如果您没有使用客户端证书身份验证,则在客户端端,您不需要使用密钥管理器(在 SSLContext.init 中使用 null)。您还应该使用默认算法(KMF/TMF.getDefaultAlgorithm()),而不是硬编码 SunX509(更何况在 Sun/Oracle JVM 上,TMF 的默认值实际上是 PKIX)。 - Bruno
有没有现成的根证书文件可供使用?(就像浏览器一样) - dani herrera
myjks.jks 是从哪里来的? - zionpi
1
@zionpi 使用Java的"keytool"生成。 - Dan
你如何在Windows上运行“export”命令?我下载了BouncyCastle的JAR文件,需要将其安装到Windows吗? - HaiNguyen

28

自API 8以来,您可以通过以下方式禁用HttpURLConnection SSL检查以进行测试:

    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    if (conn instanceof HttpsURLConnection) {
        HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
        httpsConn.setSSLSocketFactory(SSLCertificateSocketFactory.getInsecure(0, null));
        httpsConn.setHostnameVerifier(new AllowAllHostnameVerifier());
    }

2
org.apache.http.conn.ssl.AllowAllHostnameVerifier 已经被弃用。 - zackygaurav
3
根据javadoc,“AllowAllHostnameVerifier”被“NoopHostnameVerifier”替换。 - DLight

11

除了必须调用主机名验证程序之外,上面的代码(https://dev59.com/b3E85IYBdhLWcg3wtV_1#6378872)是正确的:

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

我特意注册了stackoverflow账户来添加这个修复。请注意我的警告!


第一次连接时,通过这种方式验证证书后,下一次连接该怎么办?您是否利用从第一次连接中获得的知识?如果在第三次连接尝试中使用了相同名称的伪造证书,该怎么办? - jww

10

我为那些使用httpclient-4.5的人添加了一个响应,可能对于4.4也适用。

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.fluent.ContentResponseHandler;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;



public class HttpClientUtils{

public static HttpClient getHttpClientWithoutSslValidation_UsingHttpClient_4_5_2() {
    try {
        SSLContextBuilder builder = new SSLContextBuilder();
        builder.loadTrustMaterial(null, new TrustStrategy() {
            @Override
            public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                return true;
            }
        });
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build(), new NoopHostnameVerifier());
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); 
        return httpclient;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
}

什么是新的 NoopHostnameVerifier() 类? - Mushtakim Ahmed Ansari
1
从文档中得知:NO_OP HostnameVerifier 本质上是关闭主机名验证。这个实现是一个无操作的,永远不会抛出 SSLException 异常。@MushtakimAhmedAnsari - raisercostin
感谢你给出的精彩答案。这个问题应该会得到更多点赞。 - Abhay Dwivedi
我该如何使用它?或者您是在暗示仅拥有该类就可以覆盖SSL证书验证吗? - behelit
是的,当使用httpClient时,它不会验证https证书。 - raisercostin

9

HttpComponents的API已经发生了变化。以下代码可以使用。

public static HttpClient getTestHttpClient() {
    try {
        SSLSocketFactory sf = new SSLSocketFactory(new TrustStrategy(){
            @Override
            public boolean isTrusted(X509Certificate[] chain,
                    String authType) throws CertificateException {
                return true;
            }
        }, new AllowAllHostnameVerifier());

        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("https",8444, sf));
        ClientConnectionManager ccm = new ThreadSafeClientConnManager(registry);
        return new DefaultHttpClient(ccm);
    } catch (Exception e) {
        e.printStackTrace();
        return new DefaultHttpClient();
    }
}

使用自定义的信任策略是正确的选择。谢谢。 - Matt Friedman

6

对我来说,信任所有证书并不是一个真正的选择,所以我采取了以下步骤来让HttpsURLConnection信任新证书(见http://nelenkov.blogspot.jp/2011/12/using-custom-certificate-trust-store-on.html)。

  1. Get the certificate; I got this done by exporting the certificate in Firefox (click on the little lock icon, get certificate details, click export), then used portecle to export a truststore (BKS).

  2. Load the Truststore from /res/raw/geotrust_cert.bks with the following code:

        final KeyStore trustStore = KeyStore.getInstance("BKS");
        final InputStream in = context.getResources().openRawResource(
                R.raw.geotrust_cert);
        trustStore.load(in, null);
    
        final TrustManagerFactory tmf = TrustManagerFactory
                .getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(trustStore);
    
        final SSLContext sslCtx = SSLContext.getInstance("TLS");
        sslCtx.init(null, tmf.getTrustManagers(),
                new java.security.SecureRandom());
    
        HttpsURLConnection.setDefaultSSLSocketFactory(sslCtx
                .getSocketFactory());
    

我在执行完上述设置后,使用HttpClient进行实际执行调用时,报错信息为IOExceptionjavax.net.ssl.SSLPeerUnverifiedException: No peer certificate - Michael

3
我正在寻求来自“emmby”(回答时间为2011年6月16日21:29),第4项的响应:“创建一个自定义的SSLSocketFactory,它使用内置证书KeyStore,但在任何未能通过默认验证的情况下会回退到备用KeyStore。”
这是一个简化的实现。加载系统密钥库并与应用程序密钥库合并。
public HttpClient getNewHttpClient() {
    try {
        InputStream in = null;
        // Load default system keystore
        KeyStore trusted = KeyStore.getInstance(KeyStore.getDefaultType()); 
        try {
            in = new BufferedInputStream(new FileInputStream(System.getProperty("javax.net.ssl.trustStore"))); // Normally: "/system/etc/security/cacerts.bks"
            trusted.load(in, null); // no password is "changeit"
        } finally {
            if (in != null) {
                in.close();
                in = null;
            }
        }

        // Load application keystore & merge with system
        try {
            KeyStore appTrusted = KeyStore.getInstance("BKS"); 
            in = context.getResources().openRawResource(R.raw.mykeystore);
            appTrusted.load(in, null); // no password is "changeit"
            for (Enumeration<String> e = appTrusted.aliases(); e.hasMoreElements();) {
                final String alias = e.nextElement();
                final KeyStore.Entry entry = appTrusted.getEntry(alias, null);
                trusted.setEntry(System.currentTimeMillis() + ":" + alias, entry, null);
            }
        } finally {
            if (in != null) {
                in.close();
                in = null;
            }
        }

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

        SSLSocketFactory sf = new SSLSocketFactory(trusted);
        sf.setHostnameVerifier(SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);

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

将JKS转换为BKS的简单模式:

keytool -importkeystore -destkeystore cacerts.bks -deststoretype BKS -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath bcprov-jdk16-141.jar -deststorepass changeit -srcstorepass changeit -srckeystore $JAVA_HOME/jre/lib/security/cacerts -srcstoretype JKS -noprompt

*注意:在Android 4.0(ICS)中,信任存储已更改,更多信息请参见:http://nelenkov.blogspot.com.es/2011/12/ics-trust-store-implementation.html

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接