Android HttpsUrlConnection validate SSL - java

I have a question about validating SSL in Android using HttpsUrlConnection class. I need to connect to a web server using secure connection and validate the ssl. I have to check if it has expired or not, and also if the name of the certificate matches to a custom one. Optionally - it will be great if the SSL Certificate thumbnail can also be validated (to a predefined one). Here is the code which I'm using for now to connect to the server :
public void UseHttpsConnection(String url, String charset, String query) {
try {
final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
#Override
public void checkClientTrusted( final X509Certificate[] chain, final String authType ) {
}
#Override
public void checkServerTrusted( final X509Certificate[] chain, final String authType ) {
}
#Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
} };
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance( "TLS" );
sslContext.init( null, trustAllCerts, new java.security.SecureRandom() );
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
System.setProperty("http.keepAlive", "false");
HttpsURLConnection connection = (HttpsURLConnection) new URL(url)
.openConnection();
connection.setSSLSocketFactory( sslSocketFactory );
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Charset", charset);
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded;charset=" + charset);
OutputStream output = null;
try {
output = connection.getOutputStream();
output.write(query.getBytes(charset));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (output != null)
try {
output.close();
} catch (IOException logOrIgnore) {
logOrIgnore.printStackTrace();
}
}
int status = ((HttpsURLConnection) connection).getResponseCode();
Log.i("", "Status : " + status);
for (Entry<String, List<String>> header : connection
.getHeaderFields().entrySet()) {
Log.i("Headers",
"Headers : " + header.getKey() + "="
+ header.getValue());
}
InputStream response = new BufferedInputStream(
connection.getInputStream());
int bytesRead = -1;
byte[] buffer = new byte[30 * 1024];
while ((bytesRead = response.read(buffer)) > 0) {
byte[] buffer2 = new byte[bytesRead];
System.arraycopy(buffer, 0, buffer2, 0, bytesRead);
handleDataFromSync(buffer2);
}
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
I need a little help here, because I'm new with SSL validation and the basic things to do with it. Thanks for any kind of help!

If you want to validate the certificate the first thing you have to do is throw away that insecure TrustManager that trusts anything at all. Instead write one that checks the certificate received in the manner you require.

Related

Custom Implementation of Trust Manager and Hostname Verifier

I am making post request to a third party service setting the hostname verifier and trust manager. The default pass all implementation however doesn't pass sonarcloud checks and gives errors which are attached in screenshots below. Have browsed for hours searching for custom implementation but haven't found anything. Please suggest of any resources or implementation you may have with yourself. Here is the code for the same:
public static class DummyTrustManager implements X509TrustManager {
public DummyTrustManager() {
}
public boolean isClientTrusted(X509Certificate cert[]) {
return true;
}
public boolean isServerTrusted(X509Certificate cert[]) {
return true;
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
}
public static class DummyHostnameVerifier implements HostnameVerifier {
public boolean verify(String urlHostname, String certHostname) {
return true;
}
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
}
public String nsdlResponseLine(String data, String signature){
String line = null;
try {
String urlOfNsdl = nsdlKycVerificationUrl;
final String version = nsdlKycVerificationVersion;
SSLContext sslcontext = SSLContext.getInstance("TLSv1.2");
sslcontext.init(new KeyManager[0],
new TrustManager[]{new DummyTrustManager()},
new SecureRandom());
SSLSocketFactory factory = sslcontext.getSocketFactory();
String urlParameters = getUrlParameters(data, signature, version);
URL url = new URL(urlOfNsdl);
connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Length", "" + Integer.toString(urlParameters.getBytes().length));
connection.setRequestProperty("Content-Language", "en-US");
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setSSLSocketFactory(factory);
connection.setHostnameVerifier(new DummyHostnameVerifier());
OutputStream os = connection.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os);
osw.write(urlParameters);
osw.flush();
osw.close();
InputStream is = connection.getInputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(is));
line = in.readLine();
is.close();
in.close();
} catch (Exception e) {
log.debug("::Exception: {}",e.getMessage());
}
return line;
}
private String getUrlParameters(String data, String signature, String version) throws UnsupportedEncodingException {
return "data=" + URLEncoder.encode(data, "UTF-8") + "&signature=" + URLEncoder.encode(signature, "UTF-8") + "&version=" + URLEncoder.encode(version, "UTF-8");
}
Errors which come in Sonarcloud:
1: https://i.stack.imgur.com/y5qWJ.png
ChatGPT came to rescue and solved the issue. I used default JSSE implementation for both. For sslcontext How do I provide a specific TrustStore while using the default KeyStore in Java (JSSE) ,this answer served as guiding light while for hostname verifier HttpsURLConnection.getDefaultHostnameVerifier() method can be used. My final code looks like this:
public String nsdlResponseLine(String data, String signature){
String line = null;
try {
String urlOfNsdl = nsdlKycVerificationUrl;
final String version = nsdlKycVerificationVersion;
SSLContext sslcontext = SSLContext.getInstance("TLSv1.2");
String keyStore = System.getProperty("javax.net.ssl.keyStore");
String keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType());
String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword","");
KeyManager[] kms = null;
if (keyStore != null) {
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(keyStoreType);
if (keyStore != null && !keyStore.equals("NONE")) {
FileInputStream fs = new FileInputStream(keyStore);
ks.load(fs, keyStorePassword.toCharArray());
if (fs != null)
fs.close();
char[] password = null;
if (keyStorePassword.length() > 0)
password = keyStorePassword.toCharArray();
kmf.init(ks, password);
kms = kmf.getKeyManagers();
}
sslcontext.init(kms, null, null);
}
SSLSocketFactory factory = sslcontext.getSocketFactory();
String urlParameters = getUrlParameters(data, signature, version);
URL url = new URL(urlOfNsdl);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Length", "" + Integer.toString(urlParameters.getBytes().length));
connection.setRequestProperty("Content-Language", "en-US");
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setSSLSocketFactory(factory);
connection.setHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier());
OutputStream os = connection.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os);
osw.write(urlParameters);
osw.flush();
osw.close();
InputStream is = connection.getInputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(is));
line = in.readLine();
is.close();
in.close();
} catch (Exception e) {
log.debug("::Exception: {}",e.getMessage());
}
log.debug("line;{}",line);
return line;
}
private String getUrlParameters(String data, String signature, String version) throws UnsupportedEncodingException {
return "data=" + URLEncoder.encode(data, "UTF-8") + "&signature=" + URLEncoder.encode(signature, "UTF-8") + "&version=" + URLEncoder.encode(version, "UTF-8");
}

Android - SSL Handshake Exception on using Volley / java code

When trying to access web service from secured testing environment with SSL certificate getting the issue below.
com.android.volley.NoConnectionError: javax.net.ssl.SSLProtocolException: Read error: ssl=0xa35dad40: Failure in SSL library, usually a protocol error
error:100000d7:SSL routines:OPENSSL_internal:SSL_HANDSHAKE_FAILURE (external/boringssl/src/ssl/s3_pkt.c:402 0xa3630912:0x00000000)
I have tried with volley and basic java code, still getting the same issue. When I used the same code for secured development environment with different certificate its working fine. Whereas its not working in testing environment for specific bandwidths (Airtel 3G, 4G). It is working fine with all the environments(Testing & Dev) for 2G bandwidths.
Don't know where the problem occurs. Help me in sorting out this issue.
I have added the code snippet below,
Responsecallback responsecallback;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
testing(mBase_Url);
}
public void testing(String urls) {
String result = "";
try {
URL url = new URL(urls);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(getSSLCertificate()); // Tell the URLConnection to use a SocketFactory from our SSLContext
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setConnectTimeout(30000);
connection.setReadTimeout(30000);
connection.setHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
Uri.Builder builder = new Uri.Builder()
.appendQueryParameter("country", "IN");
String query = builder.build().getEncodedQuery();
PrintWriter out = new PrintWriter(connection.getOutputStream());
out.println(query);
out.close();
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); //,8192
String inputLine;
while ((inputLine = in.readLine()) != null) {
result = result.concat(inputLine);
}
responsecallback.displayResponse(result);
in.close();
} catch (IOException e) {
result = e.toString();
Log.e(TAG, "HTTP Error Result=" + result);
responsecallback.displayResponse(result);
}
}
private SSLSocketFactory getSSLCertificate() {
try {
// Get an instance of the Bouncy Castle KeyStore format
KeyStore trusted = KeyStore.getInstance("PKCS12");
// your trusted certificates (root and any intermediate certs)
InputStream in = getResources().openRawResource(R.raw.xxxxxx); //SSL Certificate - P12 formate
String password = "XXXXXXX"; // Certificate password
char[] pwd = password.toCharArray();
try {
trusted.load(in, pwd);
} finally {
in.close();
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(trusted, pwd);
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(trusted);
SSLContext context = SSLContext.getInstance("SSL");
context.init(kmf.getKeyManagers(), getWrappedTrustManagers(), new SecureRandom());
return context.getSocketFactory();
} catch (Exception e) {
Log.e(TAG, "Exception e=" + e.toString());
throw new AssertionError(e);
}
}
private TrustManager[] getWrappedTrustManagers() {
return new TrustManager[]{
new X509TrustManager() {
#Override
public void checkClientTrusted(java.security.cert.X509Certificate[] x509Certificates, String s)
throws java.security.cert.CertificateException {
}
#Override
public void checkServerTrusted(java.security.cert.X509Certificate[] x509Certificates, String s)
throws java.security.cert.CertificateException {
}
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
X509Certificate[] myTrustedAnchors = new X509Certificate[0];
return null;
}
}
};
}

Can't reset method: already connected

I have seen this error will throw if called getInputStream before, but I have not in my code and I have checked the connected property is false, but still show this error. the code is as follow:
private void testConnect() throws Exception{
HttpURLConnection con = null;
OutputStream httpsend = null;
String url = "https://xxxxx/goldapi/rs/checkPaymentService";
TrustManager[] trustall = new TrustManager[] {
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted( java.security.cert.X509Certificate[] certs, String authType) {}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
}
};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustall, new SecureRandom());
HostnameVerifier allHostsValid = new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
System.out.println("URL Host: " + hostname + " vs. " + session.getPeerHost());
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
con = (HttpsURLConnection)(new URL(url).openConnection());
con.setConnectTimeout(100000);
// Set HTTP parameters for SOAP call
//con.disconnect(); // test
con.setAllowUserInteraction(true);
con.setRequestMethod("POST");
con.setDoOutput(true);
con.setDoInput(true);
con.setUseCaches(false);
byte[] b = "test".getBytes();
con.setRequestProperty( "Content-Length", String.valueOf( b.length));
con.setRequestProperty("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
//Send request to server
httpsend = con.getOutputStream();
httpsend.write(b);
httpsend.flush();
try{
httpsend.close();
}catch(Exception e){
e.printStackTrace();
}
}
it will throws Exception
Exception in thread "main" java.net.ProtocolException: Can't reset method:
at java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:320)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.setRequestMethod (HttpsURLConnectionImpl.java:354)
at Test.PSGConnectTest.testConnect(PSGConnectTest.java:62)
at Test.PSGConnectTest.main(PSGConnectTest.java:24)
already connectedI found that I need to explicit write
con.disconnect();
to prevent this error, but then another strange exception throw when running
httpsend = con.getOutputStream();
this statement will throws exception as follow
java.net.ProtocolException: Cannot write output after reading input.
but I have not reading any input (no getInputStream before), so what is the problem?
Update:
I found this error only occur when I use debug mode in eclipse, no this error if run in eclipse run mode.

Android SSL HostName Was Not Verified

I have a self signed server hardcoded port 52428. My client app keeps getting "Hostname Was Not Verified" even when I override the HostNameVerifier to always return true. When I changed the hostname from IP Address to DNS, another error pops up that says "Unable to resolve host: No Address associated with hostname"
Here's my code:
private class SSLConnect extends AsyncTask<Void, Void, String> {
#Override
protected String doInBackground(Void... values) {
//String https_url = "https://www.google.com/";
//String https_url = "https://192.168.0.106:52428/webserveradmin/preferences";
String https_url = "https://home-pc:52428/webserveradmin/preferences/";
String response;
try {
TrustManager[] tm = new TrustManager[]{
new X509TrustManager() {
#Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
#Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
#Override
public X509Certificate[] getAcceptedIssuers() {
//return new X509Certificate[0];
return null;
}
}
};
URL url;
try {
url = new URL(https_url);
}
catch (MalformedURLException e) {
return "Error URL: " + e.getMessage();
}
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
try {
conn.setDefaultHostnameVerifier(new NullHostNameVerifier());
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, tm, new SecureRandom());
conn.setSSLSocketFactory(sc.getSocketFactory());
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Basic " + Base64.encode("sa:sa".getBytes(), Base64.DEFAULT));
conn.connect();
InputStream in = conn.getInputStream();
BufferedReader r = new BufferedReader(new InputStreamReader(in));
StringBuilder sb = new StringBuilder();
String line;
while ((line = r.readLine()) != null) {
sb.append(line);
}
response = sb.toString();
} catch (GeneralSecurityException e) {
return "Error Security: " + e.getMessage();
}
}
catch(Exception e){
return "Error SSL: " + e.getMessage();
}
return response;
}
#Override
protected void onProgressUpdate(Void... values) {
}
#Override
protected void onPostExecute(String result) {
Toast.makeText(ctxt, result, Toast.LENGTH_LONG).show();
}
}
public class NullHostNameVerifier implements HostnameVerifier{
#Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
The hostname verifier cares about verifying the hostname only, not the trust chain. But with self-signed certificates you don't have a trust chain leading to a locally trusted certificate.
Apart from that, just disabling the certificate checking is a very bad idea, because this way you will not only accept your self-signed certificate but instead any certificates and thus you will be open to man-in-the-middle attacks. See also SSL Vulnerability in ******** VU#582497.
To do it correctly use instead certificate/public key pinning. For a more detailed explanation and also sample code see OWSAP.

getting different response when send post request from web and from desktop to https

I am sending post request to a secure website. When i do it from web like
<body>
<form method=POST action= "https://www.abc.com" >
<textarea name="Request" rows="30%" cols="80%"></textarea>
<br>
<br>
<br>
<input type="Submit">
</form>
</body>
What i do, i paste the xml in the textares and submit the form and get the response. Fine. Now when i try to do the same thing from plain java, then i get the certificate issue
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid
certification path to requested target
If i bypass the ssl, the i get the responce with systemError. Why ?
Here what i am doing
#Override
public HttpsURLConnection getHttpsConnection() throws Exception {
HttpsURLConnection urlConnection = null;
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public void checkClientTrusted( final X509Certificate[] chain, final String authType ) {
}
public void checkServerTrusted( final X509Certificate[] chain, final String authType ) {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
} ;
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
try {
URL myUrl = new URL(this.url);
urlConnection = (HttpsURLConnection) myUrl.openConnection();
} catch (Exception e) {
throw new Exception("Error in getting connecting to url: " + url + " :: " + e.getMessage());
}
return urlConnection;
} //end of getHttpsConnection()
private void processHttpRequest(HttpsURLConnection connection, String method, Map<String, String> params) throws Exception {
StringBuffer requestParams = new StringBuffer();
if (params != null && params.size() > 0) {
Iterator<String> paramIterator = params.keySet().iterator();
while (paramIterator.hasNext()) {
String key = paramIterator.next();
String value = params.get(key);
requestParams.append(URLEncoder.encode(key, "UTF-8"));
requestParams.append("=").append(URLEncoder.encode(value, "UTF-8"));
requestParams.append("&");
}
}
try {
connection.setUseCaches(false);
connection.setDoInput(true);
if ("POST".equals(method)) {
// set request method to POST
connection.setDoOutput(true);
} else {
// set request method to GET
connection.setDoOutput(false);
}
String parameters = requestParams.toString();
if ("POST".equals(method) && params != null && params.size() > 0) {
OutputStream os = connection.getOutputStream();
DataOutputStream wr = new DataOutputStream(os);
wr.writeBytes(URLEncoder.encode(parameters, "UTF-8"));
wr.writeBytes(parameters);
wr.flush();
wr.close();
/**
*
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
writer.write(requestParams.toString());
writer.flush();
writer.close();
*/
}
// reads response, store line by line in an array of Strings
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
List<String> response = new ArrayList<String>();
String line = "";
while ((line = reader.readLine()) != null) {
response.add(line);
}
reader.close();
String[] myResponse = (String[]) response.toArray(new String[0]);
if (myResponse != null && myResponse.length > 0) {
System.out.println("RESPONSE FROM: " + this.url);
for (String myLine : response) {
System.out.println(myLine);
}
}
} catch(Exception e) {
throw new Exception("Error in sending Post :: " + e.getMessage());
}
}
Here how i am calling it
String req = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
req = req + "<abc>";
req = req + "
....
req = req + " </xyz>";
req = req + "</abc>";
StringBuffer buffer = new StringBuffer();
buffer.append(req);
HttpServices httpServices = context.getBean("httpService", HttpServices.class);
String method = "POST";
Map<String, String> params = new HashMap<String, String>();
params.put("request", buffer.toString());
try {
HttpsURLConnection connection = httpServices.getHttpsConnection();
httpServices.processHttpRequest(connection, method, params);
} catch (Exception e) {
System.out.println(e.getMessage());
}
i get response something like this. Ofcourse i can not show the exact responce. But it looks like this.
<?xml version="1.0" encoding="UTF-8"?>
<Result>
<SystemError>
<Message>87b24972</Message>
</SystemError>
</Result>
Why i am getting different response from plain java? I also tried the same thing with the Apache http client, by pass the SSL, but from Apache client i am getting the same response.
Thanks.
The body of your post must lok like
Request=<?xml ...
You forgot the name of the request parameter in your POST.
Also for your ValidatorException: PKIX path building failed you need at least the root certificate of your communication partner. It looks like the default certificates of Java do not include the root certificate needed.
I solved the problem :). The problem was i was not setting the content type
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setRequestProperty("Content-Type", "text/xml"); //added
if ("POST".equals(method)) {
// set request method to POST
connection.setDoOutput(true);
} else {
// set request method to GET
connection.setDoOutput(false);
}
After adding content type, i start getting response from the server.

Categories