获取Https服务器证书

3
我已经尝试了几天,想知道如何从服务器获取证书。我们正在进行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
我认为你通常在Java中使用自定义的TrustManager。当调用TrustManager方法时,服务器的证书会被提供。 - jww
@jww 你说得完全正确,我成功解决了这个问题,并将在接下来的几天内发布解决方案。 - Nativ
你介意在这里分享解决方案吗? - rsc
@RSC 我已经发布了我的解决方案。希望它能够清晰明了,如果你觉得有用的话,请给个评分 :) - Nativ
@RSC 我不确定我是否理解了你的情况。如果连接是Http,我们无法采取任何措施来防范中间人攻击。如果黑客使用代理捕获ssl请求,他实际上无法对其进行任何操作。 - Nativ
显示剩余2条评论
1个回答

4

大家好,

以下是解决方案,

首先你需要了解TrustManager的工作原理,每个经过认证的ssl通信都会被TrustManager检查。现在,默认情况下有一个系统TrustManager,其中包含所有已认证的证书(可以在设置中轻松找到)。

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

因此,为了实际获得证书并将其与本地硬编码的证书进行比较,您需要实现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());

别忘了对其进行与认证通信的测试。


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