I'm trying for a several days to find out how to get the certificate from a server, we are working on a SSL communication and in order for me to identify the server I need to check its certification.
Few thing about the code, I'm using HttpClient and - I DON'T want to create a key store out of the certification and add it to the "trust store" as this link and many other suggesting.
So, what I did in order to get the certification is to implement X509HostnameVerifier, and in its verify() method to do:
session.getPeerCertificates();
but that function through exception:
An exception occurred: javax.net.ssl.SSLPeerUnverifiedException
Here is the code:
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 {
}
}
and usage example:
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);
Ok guys,
Here is the solution,
So first of all you should understand how the TrustManager works, each certified ssl communication is being checked against the TrustManager. Now, defaultly there's the system TrustManager that contains all the already certified certificates(you can find it easily in the Settings).
Next, http communication uses Socket so we need to find a way to connect our TrustManager to the used socket - you can found the implementation below.
So in order to actually get the certificate and compare it to a local hardcoded one you'll need to implement TrustManager.
By the way, I know it's obvious but I'll say it anyway, never save hardcoded passwords/certificates etc.. always save its SHA1/SHA256 in order to fight hacking.
Here is the code:
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();
}
}
Usually for every certified request there's a path of certifications, from the top Certification Authority, to other sub-authorities(companies, proxies..), your certificate - that is why your certificate will probably be in the first cell of the array(I'm basing this theory on some tests, not real deep investigation).
In order to connect the TrustManager to the socket use the following code:
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();
}
}
Now, in order to connect the socket to the HttpClient use this code:
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());
Don't forget to test it against a certified communication.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With