Https获取服务器证书

时间:2014-06-25 17:55:53

标签: android ssl httpclient

我尝试了几天来了解如何从服务器获取证书,我们正在进行SSL通信,以便我确定需要检查其认证的服务器。

代码很少,我使用HttpClient并且 - 我不想在认证中创建一个密钥库并将其添加到"信任商店"如this链接和许多其他建议。

所以,我为获得认证所做的是实现X509HostnameVerifier,并在其verify()方法中执行:

session.getPeerCertificates();

但该功能通过例外:

 An exception occurred: javax.net.ssl.SSLPeerUnverifiedException    

以下是代码:

import java.io.IOException;
import java.security.cert.Certificate;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;

public class MyHostnameVerifier implements ch.boye.httpclientandroidlib.conn.ssl.X509HostnameVerifier {

    @Override
    public boolean verify(String hostname, SSLSession session) {
        Certificate[] certificates;
        try {
            certificates = session.getPeerCertificates();

            // if connection doesn't contain any certificate - drop it, it might be an hacker.
            if (certificates == null || certificates.length == 1)
                return true;
        } catch (SSLPeerUnverifiedException e) {
        }

        return true;
    }

    @Override
    public void verify(String hostname, SSLSocket socket) throws IOException {
        socket.getSession().getPeerCertificates(); // exception
    }

    @Override
    public void verify(String hostname, String[] arg1, String[] arg2) throws SSLException {
    }

    @Override
    public void verify(String arg0, java.security.cert.X509Certificate arg1) throws SSLException {
    }
}

和用法示例:

PoolingClientConnectionManager cm = new PoolingClientConnectionManager();

// Increase max total connection to 10
cm.setMaxTotal(GlobalConstants.HTTP_CLIENT_MAX_TOTAL_CONNECTIONS);
HttpParams httpParameters = new BasicHttpParams();

int timeoutConnection = CONNECTION_TIMEOUT_MS_DEFAULT;
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);

HostnameVerifier hostnameVerifier = new MyHostnameVerifier();

SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
cm.getSchemeRegistry().register(new ch.boye.httpclientandroidlib.conn.scheme.Scheme("https", 443, socketFactory));
DefaultHttpClient httpClient = new DefaultHttpClient(cm, httpParameters);

1 个答案:

答案 0 :(得分:3)

好的,

这是解决方案,

首先,您应该了解TrustManager的工作原理,每个经过认证的ssl通信都是针对TrustManager进行检查的。现在,默认情况下系统TrustManager包含所有已经过认证的证书(您可以在设置中轻松找到它)。

接下来,http通信使用Socket,因此我们需要找到一种方法将TrustManager连接到使用的套接字 - 您可以在下面找到实现。

因此,为了实际获取证书并将其与本地硬编码证书进行比较,您需要实现TrustManager。

顺便说一句,我知道这很明显,但无论如何我都会说,从不保存硬编码的密码/证书等。总是保存它的SHA1 / SHA256以打击黑客攻击。

以下是代码:

public class X509TrustManager implements X509TrustManager {

private final static String TAG = "X509TrustManager";

private static final boolean DEAFULT_TRUST_ALL_SSL_CONNECTIONS = true;

private X509TrustManager standardTrustManager = null;

private boolean trustAllSSLConnections;

/**
 * Constructor for EasyX509TrustManager.
 */
public X509TrustManager(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException {

    trustAllSSLConnections = DEAFULT_TRUST_ALL_SSL_CONNECTIONS;

    TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    factory.init(keystore);
    TrustManager[] trustmanagers = factory.getTrustManagers();
    if (trustmanagers.length == 0) {
        throw new NoSuchAlgorithmException("no trust manager found");
    }
    this.standardTrustManager = (X509TrustManager) trustmanagers[0];
}

@Override
public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException {
    standardTrustManager.checkClientTrusted(certificates, authType);
}

/**
 * verified the server certificate
 */
@Override
public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException {


        X509Certificate certificate = certificates[0];
        byte[] bytes = certificate.getTBSCertificate();

        // Compare your the certificate’s bytes to yours hardcoded certificate.         
}

/**
 * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
 */
@Override
public X509Certificate[] getAcceptedIssuers() {
    return this.standardTrustManager.getAcceptedIssuers();
}

}

通常,对于每个经过认证的请求,都有一条认证路径,从最高认证机构到其他子机构(公司,代理商......),您的证书 - 这就是为什么您的证书可能在第一个阵列的细胞(我将这个理论基于某些测试,而不是真正的深入研究)。

要将TrustManager连接到套接字,请使用以下代码:

public class SSLSocketFactory implements LayeredSocketFactory {

private SSLContext sslcontext = null;

private static SSLContext createEasySSLContext() throws IOException {
    try {
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, new TrustManager[] { new X509TrustManager(null) }, null);
        return context;
    } catch (Exception e) {
        throw new IOException(e.getMessage());
    }
}

private SSLContext getSSLContext() throws IOException {
    if (this.sslcontext == null) {
        this.sslcontext = createEasySSLContext();
    }
    return this.sslcontext;
}

public Socket connectSocket(Socket sock, String host, int port, InetAddress localAddress, int localPort, HttpParams params)
        throws IOException, UnknownHostException, ConnectTimeoutException {
    int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
    int soTimeout = HttpConnectionParams.getSoTimeout(params);

    InetSocketAddress remoteAddress = new InetSocketAddress(host, port);
    SSLSocket sslsock = (SSLSocket) ((sock != null) ? sock : createSocket());

    if ((localAddress != null) || (localPort > 0)) {
        // we need to bind explicitly
        if (localPort < 0) {
            localPort = 0; // indicates "any"
        }
        InetSocketAddress isa = new InetSocketAddress(localAddress, localPort);
        sslsock.bind(isa);
    }

    sslsock.connect(remoteAddress, connTimeout);
    sslsock.setSoTimeout(soTimeout);
    return sslsock;

}

public Socket createSocket() throws IOException {
    return getSSLContext().getSocketFactory().createSocket();
}

public boolean isSecure(Socket socket) throws IllegalArgumentException {
    return true;
}

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

// -------------------------------------------------------------------
// javadoc in org.apache.http.conn.scheme.SocketFactory says :
// Both Object.equals() and Object.hashCode() must be overridden
// for the correct operation of some connection managers
// -------------------------------------------------------------------

public boolean equals(Object obj) {
    return ((obj != null) && obj.getClass().equals(SSLSocketFactory.class));
}

public int hashCode() {
    return SSLSocketFactory.class.hashCode();
}

}

现在,为了将套接字连接到HttpClient,请使用以下代码:

SchemeRegistry schemeRegistry = new SchemeRegistry();
    HttpParams params = new BasicHttpParams();

    params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, HTTP_CLIENT_MAX_TOTAL_CONNECTIONS);
    params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(HTTP_CLIENT_MAX_TOTAL_CONNECTIONS));


    params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);

    HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);

    schemeRegistry.register(new Scheme("https", new SSLSocketFactory(), 443));
    schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));

    ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);

    DefaultHttpClient client = new DefaultHttpClient(cm, params);

    // enable proxy web debugging ("sniffing")
    ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(client.getConnectionManager().getSchemeRegistry(),
            ProxySelector.getDefault());
    client.setRoutePlanner(routePlanner);

    // disable retries
    client.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));

    // setup  User-Agent
    client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, getAppContext());

不要忘记对经过认证的通信进行测试。