Https获取服务器证书

Posted

技术标签:

【中文标题】Https获取服务器证书【英文标题】:Https getting server certificate 【发布时间】:2014-06-25 17:55:52 【问题描述】:

我尝试了几天来了解如何从服务器获取证书,我们正在处理 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);

【问题讨论】:

我认为您通常在 Java 中使用自定义 TrustManager。调用TrustManager 方法时会提供服务器的证书。 @jww 你说得对,我设法解决了这个问题,并会在接下来的几天内发布解决方案。 你介意在这里分享解决方案吗? @RSC 我发布了我的解决方案。我希望它会很清楚,如果你觉得它有用 - 请评价它:) 谢谢达尔维克。因此,您的解决方案通过 Socket 连接。我也使用套接字做了类似的事情,并且正在测试我对 Main in the Middle 攻击的连接,我注意到如果攻击者使用 HTTP 代理(如 Fiddler),套接字连接将直接连接到服务器(然后验证 SSL 证书) 虽然所有的 http 通信仍然会被攻击者捕获(假设他能够在用户设备上安装他的自定义证书)。你遇到过这样的事情吗? 【参考方案1】:

好吧,伙计们,

这是解决方案,

因此,首先您应该了解 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());

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

【讨论】:

以上是关于Https获取服务器证书的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Go HTTPS 服务器中获取客户端证书

购买免费ssl证书并配置

获取服务端https证书 - Java版

如何申请https证书,搭建https网站

第三讲:https免费证书获取

第三讲:https免费证书获取