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获取服务器证书的主要内容,如果未能解决你的问题,请参考以下文章