Android 上的 HttpClient 和自定义 TrustManager

Posted

技术标签:

【中文标题】Android 上的 HttpClient 和自定义 TrustManager【英文标题】:HttpClient and custom TrustManager on android 【发布时间】:2011-03-02 16:17:01 【问题描述】:

我一直在尝试使用 apache HttpClient 库注册我的自定义 TrustManger。以下链接包含有关如何执行此操作的说明:Https Connection android

不幸的是,我想使用的构造函数 ( public SSLSocketFactory(SSLContext sslContext) ) 在 Android 版本的 HttpClient 中不可用。我会使用 sslContext 来初始化我的自定义 TrustManager。似乎 Android 已将其替换为“KeyStore”。

我的问题是:(如何)在 Android 中使用 DefaultHttpClient 注册自定义 TrustManger? KeyStore 类中是否有替代方案?

最后我想暂时忽略证书检查... 请只考虑 HttpClient 库,因为我的整个应用程序都是基于它的。

【问题讨论】:

安装自定义 TrustManager 的全部原因是为了避免证书检查。在查看博客、java 代码和 android 源代码一周后,发现问题实际上出在我连接的站点上的 ssl 证书的顺序上。因此,如果您遇到证书验证问题,请务必仔细检查证书是否以正确的顺序呈现给您,以及根证书是否存在于 android 设备上! 我自己仍然对这个问题的答案感兴趣...... @ivo 我想我也有乱序证书的问题!你做了什么来解决这个问题?你会做某种排序吗? @elton 我要求站点管理员重新排序证书,一切都很好。证书的顺序可以很容易地检查,它在一个博客中描述,我现在不记得名字了...... 【参考方案1】:

解决方案是创建自己的套接字工厂。

public class NetworkSSLSocketFactory implements LayeredSocketFactory 

    private SSLContext sslContext;
    private SSLSocketFactory socketFactory;
    private X509HostnameVerifier hostnameVerifier;

    /**
     * Creates a socket factory that will use the @link SSLContext and
     * @link X509HostnameVerifier specified. The SSLContext provided should
     * have the @link NetworkTrustManager associated with it.
     * 
     * @param sslContext
     * @param hostnameVerifier
     */
    public NetworkSSLSocketFactory(SSLContext sslContext,
            X509HostnameVerifier hostnameVerifier) 
        this.sslContext = sslContext;
        this.socketFactory = sslContext.getSocketFactory();
        this.hostnameVerifier = hostnameVerifier;
      

然后创建一个使用您的 TrustManager 的 SSLContext,然后创建一个 AndroidHttpClient 并将其 https 架构替换为使用您的 SocketFactory 的架构。

    /**
     * Return the SSLContext for use with our HttpClient or create a new Context
     * if needed.
     * <p>
     * This context uses our @link NetworkTrustManager
     * 
     * @return an @link SSLContext
     */
    public SSLContext getSSLContext() 

        if (mSSLContextInstance != null)
            return mSSLContextInstance;

        try 
            mSSLContextInstance = SSLContext.getInstance("TLS");
            TrustManager trustManager = new NetworkTrustManager(getKeyStore());
            TrustManager[] tms = new TrustManager[]  trustManager ;
            mSSLContextInstance.init(null, tms, new SecureRandom());
         catch (NoSuchAlgorithmException e) 
            Log.e(TAG, e.getMessage());
         catch (KeyManagementException e) 
            Log.e(TAG, e.getMessage());
        

        return mSSLContextInstance;
    

现在是客户

/**
 * Return an HttpClient using our @link NetworkTrustManager and
 * @link NetworkHostnameVerifier
 * 
 * @return an @link HttpClient
 */
public HttpClient getHttpClient() 

    if (mHttpClientInstance != null)
        return mHttpClientInstance;

    SSLContext sslContext = getSSLContext();

    // Now create our socket factory using our context.
    X509HostnameVerifier hostnameVerifier = new NetworkHostnameVerifier();
    NetworkSSLSocketFactory sslSocketFactory = new NetworkSSLSocketFactory(
            sslContext, hostnameVerifier);

    // Some services (like the KSOAP client) use the HttpsURLConnection
    // class
    // to establish SSL connections.
    HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
    HttpsURLConnection.setDefaultSSLSocketFactory(sslContext
            .getSocketFactory());

    // Generate the Client for the Server
    mHttpClientInstance = AndroidHttpClient.newInstance(getAgent(),
            mContext);

    // Get the registry from the AndroidHttpClient and change the
    // HTTPS scheme to use our socket factory. This way we can
    // control the certificate authority and trust system.
    SchemeRegistry schemeRegistry = mHttpClientInstance
            .getConnectionManager().getSchemeRegistry();

    schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));

    return mHttpClientInstance;

如果您不知道如何创建新的密钥库,请看这里:

    /**
     * Get the current KeyStore or if not yet created, create a new one. This
     * will <b>NOT</b> load the KeyStore file identified by
     * @link #KEYSTORE_NAME. To load the KeyStore file, use the function
     * @link #loadKeyStore() which will automatically call this function (so
     * you don't need to).
     * <p>
     * 
     * @return a @link KeyStore
     */
    public KeyStore getKeyStore() 

        if (mKeyStore != null)
            return mKeyStore;

        try 
            String defaultType = KeyStore.getDefaultType();
            mKeyStore = KeyStore.getInstance(defaultType);
            mKeyStore.load(null, null);
         catch (Exception e) 
            Log.w(TAG, e.getMessage());
        

        return mKeyStore;
    

上述解决方案是一个解决方案的开始,它允许您创建一个 TrustManager,它将验证“系统密钥库”的证书以及您拥有的“私有密钥库”(两个密钥库)。然后,您无需尝试将证书添加到系统密钥库。您可以在 getFilesDir() 文件夹中创建自己的 KeyStore。

我仍然没有完成从 HttpResult = HttpClient.execute(HttpPost); 捕获证书的逻辑。方法,但我现在正在积极写这个。如果您需要帮助,我现在也许可以与您合作。

如果有人知道如何从 HttpRequestBase 对象中的 SSLSocekt 捕获/获取证书,请告诉我。我正在努力追捕这个。

【讨论】:

从 SSLSocket 获得认证是否成功?【参考方案2】:

实际上,该方法存在但被隐藏。它由 getHttpSocketFactory() 在内部使用。我通过简单地使用反射来让它工作。不安全,但可以用于开发目的。

Class<org.apache.http.conn.ssl.SSLSocketFactory> c = org.apache.http.conn.ssl.SSLSocketFactory.class;
Constructor<org.apache.http.conn.ssl.SSLSocketFactory> ctor = c.getConstructor(javax.net.ssl.SSLSocketFactory.class);
SSLSocketFactory ssf = ctor.newInstance(SSLCertificateSocketFactory.getInsecure(getSSLHandshakeTimeout(), null));

【讨论】:

以上是关于Android 上的 HttpClient 和自定义 TrustManager的主要内容,如果未能解决你的问题,请参考以下文章

Apache HttpClient 4.0 无法在 Android 上的套接字超时

使用 firebase_messaging 自定义通知(Android 和 IOS)

如何处理 Android 中远程视图的异常(自定义小部件或自定义通知)?

在 Android 3.2 上 HttpClient 的 HttpPost 执行速度明显慢于 2.3.3

android和httpClient

使用 HttpClient 4 通过 WiFi 进行 HTTPS 身份验证