I am writing an Android application which has to connect to a server through HTTPS. The first solution I tried was this one:
(Don't mind the security flaws)
final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
private static void trustAllHosts() {
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[] {};
}
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
} };
// Install the all-trusting trust manager
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
Log.d("USR_SSL", e.getMessage());
}
}
//...
#Override
protected String doInBackground(String... params) {
try {
URL url = new URL(params[0]);
//This is an HTTPS url
String jsonStr = "";
if(params.length > 1) {
jsonStr = params[1];
}
HttpsURLConnection urlConn = (HttpsURLConnection) url.openConnection();
trustAllHosts();
urlConn.setHostnameVerifier(DO_NOT_VERIFY);
urlConn.setRequestProperty("Content-Type", "application/json");
urlConn.setRequestProperty("Accept", "application/json");
urlConn.setRequestMethod("POST");
OutputStream os = urlConn.getOutputStream();
os.write(jsonStr.getBytes());
os.flush();
//...
All fine and dandy (almost), until I realised that I also have to use authentication, session, and all that good stuff. It should have been really fine using:
CookieManager cookieManager = new CookieManager();
CookieHandler.setDefault(cookieManager);
but unfortunately we have to support Android API level 8, which means the above two lines of code will not work. Given that, I've scoured the Internet for a few hours trying to build a solution using Apache classes, which seemingly support both HTTPS and Cookies.
This is the code I've managed to sew together:
public class ConnectionMediator {
public class MySSLSocketFactory extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(null, new TrustManager[] { tm }, null);
}
#Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}
#Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
}
public void tryConnect(String url, String data) {
try {
//SSL Stuff
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
DefaultHttpClient httpClient = new DefaultHttpClient(ccm, params);
//Cookie stuff
HttpContext localContext = new BasicHttpContext();
HttpResponse response = null;
HttpPost httpPost = null;
StringEntity tmp = null;
httpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.RFC_2109);
httpPost = new HttpPost(url);
tmp = new StringEntity(data,"UTF-8");
httpPost.setEntity(tmp);
response = httpClient.execute(httpPost,localContext);
} catch(Exception e) {
Log.d("USR_DEBUG", e.getClass().toString() + ": " + e.getMessage());
}
}
}
At the time of writing this, I get a NetworkOnMainThreadException, but this is rather unimportant; what matters and what I wish to point out is that i have no idea what I'm doing, as in, to simply connect through by means of HTTPS and also use cookies, one has to use 13 different classes which I've never heard of. Obviously, my knowledge of HTTPS/Java net classes is bordering null, but in spite of this I would have expected something more intuitive. So rather than a "this doesn't work" type of question, my question is "what should I be doing", or even, "how do I learn what I have to do?".
Thank you very much,
a very confused coder
My initial question had two parts to it: first, how to use HTTPS and second, how to also use cookies along with it.
My question wasn't thorough enough, as I had partly answered it already - the code which I had initially posted worked with respect to HTTPS, and the NetworkOnMainThreadException occurred because I was not running the code in a separate thread, for example, using AsyncTask.
However, to also make proper use of cookies, one should make use of a solution similar to the following:
public class State {
private static HttpContext httpContext;
public static HttpContext getHttpContext() {
if(httpContext == null) {
httpContext = new BasicHttpContext();
CookieStore cookieStore = getCookieStore();
httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
}
return httpContext;
}
private static CookieStore cookieStore;
public static CookieStore getCookieStore() {
if(cookieStore == null) {
cookieStore = new BasicCookieStore();
}
return cookieStore;
}
}
I am not sure if this is the 'Android' way of doing it (using a static class) but it works:
//...
//Connection objects
DefaultHttpClient httpClient = new DefaultHttpClient(ccm, params);
HttpPost httpPost = null;
//Cookie stuff
HttpContext httpContext = State.getHttpContext();
httpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.RFC_2109);
//....
It seems using another HttpClient every time preserves the session, provided that the same CookieStore and HttpContext are used.
While this answer makes 'things work' (which is what I needed right now), it is not a thorough answer as it does not explain at all why more than 10 classes are needed to connect everything together.
Related
I use RestTemplate config like this :
private RestTemplate createRestTemplate() throws Exception {
final String username = "admin";
final String password = "admin";
final String proxyUrl = "localhost";
final int port = 443;
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(proxyUrl, port),
new UsernamePasswordCredentials(username, password));
HttpHost host = new HttpHost(proxyUrl, port, "https");
HttpClientBuilder clientBuilder = HttpClientBuilder.create();
clientBuilder.setProxy(host).setDefaultCredentialsProvider(credsProvider).disableCookieManagement();
HttpClient httpClient = clientBuilder.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(httpClient);
return new RestTemplate(factory);
}
And the this is how my method work:
public String receiveMessage(String message) {
try {
restTemplate = createRestTemplate();
ObjectMapper mapper = new ObjectMapper();
Class1 class1 = null;
String json2 = "";
class1= mapper.readValue(message, Class1.class);
Class1 class2 = restTemplate.getForObject(URL_SERVICE_1 + "/class1/findByName?name=" + class1.getName(),
Class1.class);
System.out.println("Server 1 : " + message);
json2 = mapper.writeValueAsString(class2);
return "Error - " + json2;
} catch (Exception e) {
// TODO Auto-generated catch block
return e.getMessage();
}
}
URL_SERVICE_1 contains https://localhost
When I tried to call function GET, I always get return like this :
I/O error on GET request for "https://localhost/class1/findByName?name=20-1P": Host name 'localhost' does not match the certificate subject provided by the peer (CN=*.webku-cool.com, OU=EssentialSSL Wildcard, OU=Domain Control Validated); nested exception is javax.net.ssl.SSLPeerUnverifiedException: Host name 'localhost' does not match the certificate subject provided by the peer (CN=*.webku-cool.com, OU=EssentialSSL Wildcard, OU=Domain Control Validated)
I don't know the correct setting for restTemplate with https. I already tried 23 references about SSL Settings and got same error.
1. Reference
2. Reference
3. Reference
As accepted answer has deprecated code, this is what I found helpful:
SSLContextBuilder sslcontext = new SSLContextBuilder();
sslcontext.loadTrustMaterial(null, new TrustSelfSignedStrategy());
httpclient = HttpAsyncClients.custom().setSSLContext(sslcontext.build()).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.build();
The correct solution for this problem is to correct the ssl certificate by adding localhost to the list of subjects. However, if your intent is to bypass ssl for development purpose, you would need to define a connection factory which always returns the result of hostname verification as true.
SSLClientHttpRequestFactory
public class SSLClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
#Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
try {
if (!(connection instanceof HttpsURLConnection)) {
throw new RuntimeException("An instance of HttpsURLConnection is expected");
}
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} };
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));
httpsConnection.setHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
super.prepareConnection(httpsConnection, httpMethod);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* We need to invoke sslSocket.setEnabledProtocols(new String[] {"SSLv3"});
* see
* http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566
* -2342133.html (Java 8 section)
*/
private static class MyCustomSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
#Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
#Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
#Override
public Socket createSocket(final Socket socket, final String host, final int port, final boolean autoClose)
throws IOException {
final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final String host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final String host, final int port, final InetAddress localAddress,
final int localPort) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final InetAddress host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress,
final int localPort) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
private Socket overrideProtocol(final Socket socket) {
if (!(socket instanceof SSLSocket)) {
throw new RuntimeException("An instance of SSLSocket is expected");
}
((SSLSocket) socket).setEnabledProtocols(new String[] { "TLSv1" });
return socket;
}
}
}
And use the above mentioned connection factory as the constructor argument for RestTemplate. The part of the code which overrides the host name verification to always return true is as follows:
httpsConnection.setHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
Happy coding!
Here is how I made it to work:
1. This bean ignores SSL check
2. It also ignores certificate mismatch
#Bean
public RestTemplate restTemplate()
throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContextBuilder sslcontext = new SSLContextBuilder();
sslcontext.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom().setSSLContext(sslcontext.build()).setSSLHostnameVerifier(
NoopHostnameVerifier.INSTANCE)
.build();
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
This option worked for me after trying lot of different options from online... Thanks a lot ...
SSLContextBuilder sslcontext = new SSLContextBuilder();
sslcontext.loadTrustMaterial(null, new TrustSelfSignedStrategy());
HttpClient httpClient = HttpAsyncClients.custom().setSSLContext(sslcontext.build()).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.build();
I am trying to implement an android application.Therefore, I've called a web-service HTTPS.However, I am not able to get the response data.It showing an error
" connection closed by peer "
Here is my code for HTTPS:
public class MySSLSocketFactory extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(null, new TrustManager[] { tm }, null);
}
#Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}
#Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
}
Here's the code which I wrote for DefaultHttpClient:
public static DefaultHttpClient getClient() {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore
.getDefaultType());
trustStore.load(null, null);
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory
.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(
params, registry);
return new DefaultHttpClient(ccm, params);
} catch (Exception e) {
return new DefaultHttpClient();
}
}
For getting response:
public static String getData(final HttpPost httpost) throws IOException,
URISyntaxException {
String inputLine = "Error";
final StringBuffer buf = new StringBuffer();
{
InputStream ins = null;
ins = HTTPHandler.getUrlData(httpost);
final InputStreamReader isr = new InputStreamReader(ins);
final BufferedReader in = new BufferedReader(isr);
while ((inputLine = in.readLine()) != null) {
buf.append(inputLine);
}
in.close();
}
return buf.toString();
}
I would like to ask you for any kind of help.
Thanks in advance.
I am trying to interact with a webservice which is a HTTPS call that works totally fine on different variants of 4.0(I havent checked it below 4.0 so I cant say about them) and its perfectly working. The issue I am facing is on Android 5.0 and the device I was able to grab was Nexus 5 and below is the exception i get when doing connectivity
javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
at org.apache.harmony.xnet.provider.jsse.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:146)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:93)
After tonnes of searching and analyzing our production server SSL certificate i figured out that the server accept TLSv1 and the only cipher suite it supports is TLS_RSA_WITH_3DES_EDE_CBC_SHA. Though i understand that its not safe and it should be upgraded but right now i have to find out some way to get my Android app connected with the server.
I tried through the way suggested on this page
https://code.google.com/p/android-developer-preview/issues/attachmentText?id=1200&aid=12000009000&name=CompatSSLSocketFactory.java&token=ABZ6GAcWKpRZhuG6Skof32VtvF0Lzv3Z-A%3A1435550700632
And replaced my required algorithm i.e TLS_RSA_WITH_3DES_EDE_CBC_SHA but now the problem is that i am seeing this exception
Caused by: java.lang.IllegalArgumentException: cipherSuite
TLS_RSA_WITH_3DES_EDE_CBC_SHA is not supported.
at com.android.org.conscrypt.NativeCrypto.checkEnabledCipherSuites(NativeCrypto.java:1091)
at com.android.org.conscrypt.SSLParametersImpl.setEnabledCipherSuites(SSLParametersImpl.java:244)
at com.android.org.conscrypt.OpenSSLSocketImpl.setEnabledCipherSuites(OpenSSLSocketImpl.java:822)
So according to this exception the cipher suite i required is not supported by Android 5.0. But i got puzzled after seeing it in Android 5.0's supported list on this page
http://developer.android.com/reference/javax/net/ssl/SSLEngine.html
Anybody any idea whats this mystery?
I got the answer finally after working out on the issue for three days. Posting out the correct solution for people who gets stuck in a similar issue in future
First implement CustomTrustManager
public class CustomX509TrustManager implements X509TrustManager {
#Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
#Override
public void checkServerTrusted(X509Certificate[] certs,
String authType) throws CertificateException {
// Here you can verify the servers certificate. (e.g. against one which is stored on mobile device)
// InputStream inStream = null;
// try {
// inStream = MeaApplication.loadCertAsInputStream();
// CertificateFactory cf = CertificateFactory.getInstance("X.509");
// X509Certificate ca = (X509Certificate)
// cf.generateCertificate(inStream);
// inStream.close();
//
// for (X509Certificate cert : certs) {
// // Verifing by public key
// cert.verify(ca.getPublicKey());
// }
// } catch (Exception e) {
// throw new IllegalArgumentException("Untrusted Certificate!");
// } finally {
// try {
// inStream.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
Than implement your own Socket Factory
public class CustomSSLSocketFactory extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public CustomSSLSocketFactory(KeyStore truststore)
throws NoSuchAlgorithmException, KeyManagementException,
KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new CustomX509TrustManager();
sslContext.init(null, new TrustManager[] { tm }, null);
}
public CustomSSLSocketFactory(SSLContext context)
throws KeyManagementException, NoSuchAlgorithmException,
KeyStoreException, UnrecoverableKeyException {
super(null);
sslContext = context;
}
#Override
public Socket createSocket(Socket socket, String host, int port,
boolean autoClose) throws IOException, UnknownHostException {
Socket newSocket = sslContext.getSocketFactory().createSocket(socket, host, port,
autoClose);
((SSLSocket) newSocket).setEnabledCipherSuites(((SSLSocket) newSocket).getSupportedCipherSuites());
AdjustSocket(newSocket);
return newSocket;
}
#Override
public Socket createSocket() throws IOException {
Socket socket = sslContext.getSocketFactory().createSocket();
((SSLSocket) socket).setEnabledCipherSuites(((SSLSocket) socket).getSupportedCipherSuites());
adjustSocket(socket);
return socket;
}
private void adjustSocket(Socket socket)
{
String[] cipherSuites = ((SSLSocket) socket).getSSLParameters().getCipherSuites();
ArrayList<String> cipherSuiteList = new ArrayList<String>(Arrays.asList(cipherSuites));
cipherSuiteList.add("TLS_RSA_WITH_3DES_EDE_CBC_SHA");
cipherSuites = cipherSuiteList.toArray(new String[cipherSuiteList.size()]);
((SSLSocket) socket).getSSLParameters().setCipherSuites(cipherSuites);
String[] protocols = ((SSLSocket) socket).getSSLParameters().getProtocols();
ArrayList<String> protocolList = new ArrayList<String>(Arrays.asList(protocols));
for (int ii = protocolList.size() - 1; ii >= 0; --ii )
{
if ((protocolList.get(ii).contains("SSLv3")) || (protocolList.get(ii).contains("TLSv1.1")) || (protocolList.get(ii).contains("TLSv1.2")))
protocolList.remove(ii);
}
protocols = protocolList.toArray(new String[protocolList.size()]);
((SSLSocket)socket).setEnabledProtocols(protocols);
}
}
Now add a function in the class to create a HttpClient
public HttpClient createHttpClient(){
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
CustomSSLSocketFactory sf = new CustomSSLSocketFactory(trustStore);
sf.setHostnameVerifier(CustomSSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params, 15000);
HttpConnectionParams.setSoTimeout(params, 5000);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
return new DefaultHttpClient(ccm, params);
} catch (Exception e) {
return new DefaultHttpClient();
}
And now write below lines to call the server/webservice
HttpClient httpClient = createHttpClient();
HttpPost httpost = new HttpPost(url);
HttpResponse response = null;
try {
response = httpClient.execute(httpost);
StatusLine statusLine = response.getStatusLine();
} catch (IOException e) {
e.printStackTrace();
}
I have gone to almost all the post related to this exception. Actually my problem is I have an java application through which I am hitting an URL and getting response from it.
code to hit URL is :
HttpGet getRequest = new HttpGet("https://urlto.esb.com");
HttpResponse httpResponse = null;
DefaultHttpClient httpClient = new DefaultHttpClient();
httpResponse = httpClient.execute(getRequest);
Here I am getting javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
So after some Google search I come to know that I can import certificate in keystore of java where the application is running. so I imported certificate in keystore and this code is working. but i don't want this solution so after some more searching I come to know that I can use TrustManager for the same thing without importing certificate into keystore. So I have written code like:
#Test
public void withTrustManeger() throws Exception {
DefaultHttpClient httpclient = buildhttpClient();
HttpGet httpGet = new HttpGet("https://urlto.esb.com");
HttpResponse response = httpclient.execute( httpGet );
HttpEntity httpEntity = response.getEntity();
InputStream inputStream = httpEntity.getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(
inputStream));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
inputStream.close();
String jsonText = sb.toString();
System.out.println(jsonText);
}
private DefaultHttpClient buildhttpClient() throws Exception {
DefaultHttpClient httpclient = new DefaultHttpClient();
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, getTrustingManager(), new java.security.SecureRandom());
SSLSocketFactory socketFactory = new SSLSocketFactory(sc);
Scheme sch = new Scheme("https", 443, socketFactory);
httpclient.getConnectionManager().getSchemeRegistry().register(sch);
return httpclient;
}
private TrustManager[] getTrustingManager() {
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
#Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
#Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
// Do nothing
}
#Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
// Do nothing
}
} };
return trustAllCerts;
}
This code is also working but My question is I am not checking anything related to certificates then how connection is trusted. after debugging I come to know that only checkServerTrusted is hitting. So I have write something in checkServerTrusted to validate certificates that come in certs and the one which is in my application like some .cer or .crt file.
Every Help will be appreciated.
Update after #EpicPandaForce (Using Apache HttpClient 4.3)
try
{
keyStore = KeyStore.getInstance("JKS");
InputStream inputStream = new FileInputStream("E:\\Desktop\\esbcert\\keystore.jks");
keyStore.load(inputStream, "key".toCharArray());
SSLContextBuilder sslContextBuilder = SSLContexts.custom().loadTrustMaterial(keyStore, new TrustSelfSignedStrategy());
sslcontext = sslContextBuilder.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext);
HttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
HttpGet httpGet = new HttpGet("https://url.esb.com");
HttpResponse response = httpclient.execute( httpGet );
HttpEntity httpEntity = response.getEntity();
InputStream httpStram = httpEntity.getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(
httpStram));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
httpStram.close();
inputStream.close();
String jsonText = sb.toString();
System.out.println(jsonText);
}
catch(Exception e)
{
System.out.println("Loading keystore failed.");
e.printStackTrace();
}
Technically, seeing as you are using Apache HttpClient 4.x, a simpler solution would be the following:
SSLContext sslcontext = null;
try {
SSLContextBuilder sslContextBuilder = SSLContexts.custom()
.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy());
sslcontext = sslContextBuilder.build();
Where trustStore is initialized like this
KeyStore keyStore = null;
try {
keyStore = KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME); //you can use JKS if that is what you have
InputStream inputStream = new File("pathtoyourkeystore");
try {
keyStore.load(inputStream, "password".toCharArray());
} finally {
inputStream.close();
}
} catch(Exception e) {
System.out.println("Loading keystore failed.");
e.printStackTrace();
}
return keyStore;
}
And then create the HttpClient
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext);
httpclient = HttpClients
.custom()
.setSSLSocketFactory(sslsf).build();
EDIT: Exact code for me was this:
SSLContextBuilder sslContextBuilder = SSLContexts.custom()
.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy());
sslcontext = sslContextBuilder.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext, new String[] {"TLSv1"}, null,
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
);
httpclient = HttpClients
.custom()
.setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
.setSSLSocketFactory(sslsf).build();
I'm integrating with a Merchant Account called CommWeb and I'm sending an SSL post to their URL (https://migs.mastercard.com.au/vpcdps). When I try to send the post, I get the following exception:
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
The code (which I didn't write, and that already exists in our codebase) that performs the post is:
public static HttpResponse sendHttpPostSSL(String url, Map<String, String> params) throws IOException {
PostMethod postMethod = new PostMethod(url);
for (Map.Entry<String, String> entry : params.entrySet()) {
postMethod.addParameter(entry.getKey(), StringUtils.Nz(entry.getValue()));
}
HttpClient client = new HttpClient();
int status = client.executeMethod(postMethod);
if (status == 200) {
StringBuilder resultBuffer = new StringBuilder();
resultBuffer.append(postMethod.getResponseBodyAsString());
return new HttpResponse(resultBuffer.toString(), "");
} else {
throw new IOException("Invalid response code: " + status);
}
}
The documentation for the Merchant Account integration says nothing about certificates. They did provide some sample JSP code that seems to blindly accept certificates:
<%! // Define Static Constants
// ***********************
public static X509TrustManager s_x509TrustManager = null;
public static SSLSocketFactory s_sslSocketFactory = null;
static {
s_x509TrustManager = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[] {}; }
public boolean isClientTrusted(X509Certificate[] chain) { return true; }
public boolean isServerTrusted(X509Certificate[] chain) { return true; }
};
java.security.Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new X509TrustManager[] { s_x509TrustManager }, null);
s_sslSocketFactory = context.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
...
...
// write output to VPC
SSLSocket ssl = (SSLSocket)s_sslSocketFactory.createSocket(s, vpc_Host, vpc_Port, true);
ssl.startHandshake();
os = ssl.getOutputStream();
// get response data from VPC
is = ssl.getInputStream();
...
...
%>
Our webapp has a keystore, and I tried adding the certificate (which I exported from firefox) using the keytool command, but that didn't work and I got the same error. I've tried solutions on the web (importing the key and using System.setProperty) but that seems kind of clunky and it didn't work (gave me a NoSuchAlgorithmError). Any help is appreciated!
Evidently the valicert class 3 CA certificate is not in your default truststore (which is probably the cacerts file in your JRE lib/security directory, but see the JSSE documentation for the full story).
You could add this certificate to the cacerts file, but I don't recommend this. Instead, I think you should create your own truststore file (which can be a copy of the cacerts file) and add the valicert root ca to this. Then point to this file with the javax.net.ssl.trustStore system property.
I figure I should update this answer with what I actually did. Using the documentation that GregS provided, I created a trust manager for valicert. In the trust manager, I load the certificate files:
public class ValicertX509TrustManager implements X509TrustManager {
X509TrustManager pkixTrustManager;
ValicertX509TrustManager() throws Exception {
String valicertFile = "/certificates/ValicertRSAPublicRootCAv1.cer";
String commwebDRFile = "/certificates/DR_10570.migs.mastercard.com.au.crt";
String commwebPRODFile = "/certificates/PROD_10549.migs.mastercard.com.au.new.crt";
Certificate valicert = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(valicertFile));
Certificate commwebDR = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(commwebDRFile));
Certificate commwebPROD = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(commwebPRODFile));
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, "".toCharArray());
keyStore.setCertificateEntry("valicert", valicert);
keyStore.setCertificateEntry("commwebDR", commwebDR);
keyStore.setCertificateEntry("commwebPROD", commwebPROD);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
trustManagerFactory.init(keyStore);
TrustManager trustManagers[] = trustManagerFactory.getTrustManagers();
for(TrustManager trustManager : trustManagers) {
if(trustManager instanceof X509TrustManager) {
pkixTrustManager = (X509TrustManager) trustManager;
return;
}
}
throw new Exception("Couldn't initialize");
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
pkixTrustManager.checkServerTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
pkixTrustManager.checkServerTrusted(chain, authType);
}
public X509Certificate[] getAcceptedIssuers() {
return pkixTrustManager.getAcceptedIssuers();
}
}
Now, using this trust manager, I had to create a socket factory:
public class ValicertSSLProtocolSocketFactory implements ProtocolSocketFactory {
private SSLContext sslContext = null;
public ValicertSSLProtocolSocketFactory() {
super();
}
private static SSLContext createValicertSSLContext() {
try {
ValicertX509TrustManager valicertX509TrustManager = new ValicertX509TrustManager();
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new ValicertX509TrustManager[] { valicertX509TrustManager}, null);
return context;
}
catch(Exception e) {
Log.error(Log.Context.Net, e);
return null;
}
}
private SSLContext getSSLContext() {
if(this.sslContext == null) {
this.sslContext = createValicertSSLContext();
}
return this.sslContext;
}
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException {
return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
}
public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort, final HttpConnectionParams params) throws IOException {
if(params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
SocketFactory socketFactory = getSSLContext().getSocketFactory();
if(timeout == 0) {
return socketFactory.createSocket(host, port, localAddress, localPort);
}
else {
Socket socket = socketFactory.createSocket();
SocketAddress localAddr = new InetSocketAddress(localAddress, localPort);
SocketAddress remoteAddr = new InetSocketAddress(host, port);
socket.bind(localAddr);
socket.connect(remoteAddr, timeout);
return socket;
}
}
public Socket createSocket(String host, int port) throws IOException {
return getSSLContext().getSocketFactory().createSocket(host, port);
}
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}
public boolean equals(Object obj) {
return ((obj != null) && obj.getClass().equals(ValicertSSLProtocolSocketFactory.class));
}
public int hashCode() {
return ValicertSSLProtocolSocketFactory.class.hashCode();
}
}
Now I just register a new protocol:
Protocol.registerProtocol("vhttps", new Protocol("vhttps", new ValicertSSLProtocolSocketFactory(), 443));
PostMethod postMethod = new PostMethod(url);
for (Map.Entry<String, String> entry : params.entrySet()) {
postMethod.addParameter(entry.getKey(), StringUtils.Nz(entry.getValue()));
}
HttpClient client = new HttpClient();
int status = client.executeMethod(postMethod);
if (status == 200) {
StringBuilder resultBuffer = new StringBuilder();
resultBuffer.append(postMethod.getResponseBodyAsString());
return new HttpResponse(resultBuffer.toString(), "");
} else {
throw new IOException("Invalid response code: " + status);
}
The only disadvantage is that I had to create a specific protocol (vhttps) for this particular certificate.