我尝试了几天来了解如何从服务器获取证书,我们正在进行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);
答案 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());
不要忘记对经过认证的通信进行测试。