I am working on an app where at some point I need to retrieve the website title given the URL. The following code does that
InputStream response = null;
try {
response = new URL(urlString).openStream();
Scanner scanner = new Scanner(response);
String responseBody = scanner.useDelimiter("\\A").next();
title = responseBody.substring(responseBody.indexOf("<title>") + 7, responseBody.indexOf("</title>"));
}
catch (IOException e) {
didWeGetTitle = true;
CustomLogger.log("UrlDataExtractor: retrieveWebsiteTitleAndFavicon: Retrieve title: Error IOException. " + e,'e');
e.printStackTrace();
}
The problem is that for certain web pages (For example CreditCheckTotal.com) IOException is being thrown. Here is the exception
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
It is my understanding that the reason this is happening is that SSL Certificate is not trusted. I was looking for a way to address this problem and came around a post that suggested to run the following code before establishing the connection.
private static void trustEveryone() {
try {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){
public boolean verify(String hostname, SSLSession session) {
return true;
}});
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new X509TrustManager[]{new X509TrustManager(){
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}}}, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(
context.getSocketFactory());
} catch (Exception e) { // should never happen
e.printStackTrace();
}
}
Now, from the post, it became obvious that this solution can pose a security threat because it creates a possibility of MIMA. Now, my question is, since the only point of my above-mentioned code is to grab the title of the webpage, is it a problem for me to just trust all the certificates?
Certificate validation is done to make sure that you are talking to the correct server, i.e. protect against man in the middle attacks. If you want to grab only the title of a web page and don't transfer any sensitive data the risk of disabling certificate validation can be considered acceptable, as long as you accept the risk that you get a different content (which might result in a different title) in case of a man in the middle attack.
Related
I am working on an application where I have to send the request to another server running. Whenever I try to create the client so that I can send my API request to server, it stuck there. It doesn't throw exception also and doesn't get executed. That thread just dies without doing anything. I debugged a lot by putting loggers after each line and found out SSL build line is having issues.
If I try to execute same code from local machine, it gets executed cleanly but from my application, it fails. Can someone please let me know what is the exact issue.Here is the code which I have written for creating the client.I think the issue is related to certificates but in verify methods, it is saying setting everything as true.
private HttpClient createClient() throws Exception {
X509TrustManager x509TrustManager = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
};
TrustManager[] trustManagers = new TrustManager[] {x509TrustManager};
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, trustManagers, new SecureRandom());
// code works fine till here and after that this thread just dies.
return HttpClients.custom().setSSLHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}).setSSLContext(sslContext).build();
}
The issue was with the version of httpcore version. Since I was using httpclient version greater than 4.5 and I have httpcore version lesser than 4. There were no issues with the code. If someone is creating a client using apache libraries, please make sure you are using versions updated.
I am using below code to trust all certificates and the code is running in a containerized environment, I am getting exception as Access denied ("javax.net.ssl.SSLPermission" "setDefaultSSLContext") and same code which is running on normal tomcat server is working fine
URL destinationURL = null;
SSLContext context = null;
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
#Override
public X509Certificate[] getAcceptedIssuers() {
//return new X509Certificate[1];
return null;
}
#Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//DO
}
#Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
//DO
}
}
};
try {
context = SSLContext.getInstance("SSL");
context.init(null, trustAllCerts, null);
SSLContext.setDefault(context);
//proxy details here
destinationURL = new URL('url');
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
} catch (Exception e) {
e.printStackTrace();
}
You can use specified SSLContext to build socketfactory which can be used for URL Connections, changing the default one is not recommended.
From SSLContext:
setDefault
Throws: SecurityException - if a security manager exists and its checkPermission method does not allow SSLPermission("setDefaultSSLContext")
This permission is not granted by default, as it is considered unsafe. From SSLPermission:
Malicious code can set a context that monitors the opening of connections or the plaintext data that is transmitted.
The recommended way to change the default SSLContext is via JVM start-up options. However, you're attempting to effectively disable all trust, which is also unsafe and not supported via system properties.
If you're really really sure you want to do this, you'll need to grant your application the necessary permissions. This would e.g. be via a policy file:
grant codeBase "file:/home/ajay/myunsafecode" {
permission javax.net.ssl.SSLPermission "setDefaultSSLContext";
};
Or, just don't change the default SSLContext and use your unsafe one directly.
(all links for JDK 11)
I am developing an Android application under Android Studio and I need to establish HTTPS connection. So far I've succeeded, but with current implementation I am trusting all hosts, which could easily lead to man-in-the-middle attack. So I was wondering is there a way to trust an exact certificate and no other? So far my code looks like this:
/**
* Trust every server - dont check for any certificate
*/
private 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) {
e.printStackTrace();
}
}
And I am using HttpsURLConnection like this:
private void postText(String URLAddress) {
try {
URL obj = new URL(URLAddress);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
con.setHostnameVerifier(DO_NOT_VERIFY);
con.setRequestMethod("POST");
con.setRequestProperty("User-Agent", "Mozilla/5.0");
int responseCode = con.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) { //success
BufferedReader in = new BufferedReader(new InputStreamReader(
con.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
boolean First = true;
while ((inputLine = in.readLine()) != null) {
if(First)
First=false;
else
response.append("\n");
response.append(inputLine);
}
in.close();
RequestResponse=response.toString();
}
} catch (Exception e) {
e.printStackTrace();
}
}
What should I do to be able to trust only the certificate that I want? What information for that certificate do I need and what I must use in order to achive this?
Thanks
If you want to handle the verification yourself the easy part is to pin the public key. But then you have to think about the revocation and then your problems starts.
Why not simply using a certificate trusted by the device?
What should I do to be able to trust only the certificate that I want?
The process of trusting only a specific certificate or public key is called certificate pinning or public key pinning. Since I don't want to repeat all the good information which already exist: please head over to the specific article at OWASP which also includes sample code for Android.
And in case this link goes down or moves - just search for android certificate pinning example.
The correct way to do this is to verify the identity of the subject of the certificate as being the identity that is authorized to be connected to, rather than abuse the authentication process as you are doing by trusting a single certificate, which among other things gives you a problem at renewal time.
At the SSL level this is done by installing a handshake listener.
At the HTTPS level this is done by installing a HostnameVerifier.
Is there a way to get images from a https url with Java?
What I am trying so far:
URL url = new URL("https://ns6.host.md:8443/sitepreview/http/zugo.md/media/images/thumb/23812__yu400x250.jpg");
System.out.println("Image: " + ImageIO.read(url));
But, I am getting:
Exception in thread "main" javax.imageio.IIOException: Can't get input stream from URL!
Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No
Caused by: java.security.cert.CertificateException: No name matching ns6.host.md found
How can I go through that? I have more than 6k images on that url that I have to fetch.
There are two problems. You can use your browser to access the site, and see the errors.
The server certificate is self-signed, not trusted by Java. you can add it to the trust store.
The server certificate does not match the host name "ns6.host.md", and you need a HostnameVerifier that ignores it.
The other answer says the same thing, and it provides code, which unfortunately uses some private APIs.
Example how to solve it in bayou HttpClient, if anyone is interested:
https://gist.github.com/zhong-j-yu/22af353e2c5a5aed5857
public static void main(String[] args) throws Exception
{
HttpClient client = new HttpClientConf()
.sslContext(new SslConf().trustAll().createContext()) // trust self-signed certs
.sslEngineConf(engine -> disableHostNameVerification(engine))
.trafficDump(System.out::print)
.newClient();
// typically, app creates one client and use it for all requests
String url = "https://ns6.host.md:8443/sitepreview/http/zugo.md/media/images/thumb/23812__yu400x250.jpg";
HttpResponse response = client.doGet(url).sync();
ByteBuffer bb = response.bodyBytes(Integer.MAX_VALUE).sync();
InputStream is = new ByteArrayInputStream(bb.array(), bb.arrayOffset()+bb.position(), bb.remaining());
BufferedImage image = ImageIO.read(is);
}
static void disableHostNameVerification(SSLEngine engine)
{
SSLParameters sslParameters = engine.getSSLParameters();
{
// by default, it's set to "HTTPS", and the server certificate must match the request host.
// disable it for this example, since the server certificate is ill constructed.
sslParameters.setEndpointIdentificationAlgorithm(null);
}
engine.setSSLParameters(sslParameters);
}
You have a SSL problem which you have to solve first, before you fetch the Image. It says that didn't find any trusted host with the name ns6.host.md in your trusted store in JAVA_HOME/jre/lib/security. You could add to your TrustStore the public key of that host or just ignore SSL errors if that's the case:
HttpsURLConnection.setDefaultHostnameVerifier(getUnsecureHostNameVerifier());
try {
SSLContext e = SSLContext.getInstance("TLS");
e.init(new KeyManager[0], new TrustManager[]{new DefaultTrustManager()}, new SecureRandom());
SSLContext.setDefault(e);
HttpsURLConnection.setDefaultSSLSocketFactory(e.getSocketFactory());
} catch (Exception var1) {
throw new Exception("SSL Error", var1);
}
public static class DefaultTrustManager implements X509TrustManager {
public DefaultTrustManager() {
}
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
I've been testing a system that accesses a group of https servers with different keys, some of which are invalid and all of them are not in the local key store for my JVM. I am really only testing things out, so I don't care about the security at this stage. Is there a good way to make POST calls to the server and tell Java not to worry about the security certificates?
My google searches for this have brought up some code examples that make a class to do the validation, that always works, but I cannot get it to connect to any of the servers.
As per the comments:
With Googled examples, you mean among others this one?
Update: the link broke, so here's an extract of relevance which I saved from the internet archive:
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
// Install the all-trusting trust manager
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
}
// Now you can access an https URL without having the certificate in the truststore
try {
URL url = new URL("https://hostname/index.html");
} catch (MalformedURLException e) {
}
You need to create a X509TrustManager which bypass all the security check. You can find an example in my answer to this question,
How to ignore SSL certificate errors in Apache HttpClient 4.0