Hello Everybody,
I thought it'd be a great idea to share my code with people who s*ck with the same problem, maybe it'll help... :) It still needs security improovements, but since I'm not a Java guru, I'm a little slow with the solution... :) Any help would be appreciated! :)
So, my duty was to develop a business app on Android where the server runs webservices (asmx). We've decided to use ksoap2 library to process the soap part of stuff, since it seemed faster to develop. Well, yea, that's what we've thought... :) Anyways, after lots and lots of Google-ing we've succeeded to weld together some code and push down the https on the throat of our Android. :)
We've solved it with 4 classes:
- ConvertStreamToString.java
- ServiceCall.java
- EasyX509TrustManager.java
- EasySSLSocketFactory.java
ConvertStreamToString:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class ConvertStreamToString {
String string = "";
public ConvertStreamToString() { }
public ConvertStreamToString(InputStream is) {
setConvertedString(is);
}
public String getConvertedString () {
return this.string;
}
protected void setConvertedString(InputStream is) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
this.string = sb.toString();
} catch (IOException e) {
this.string = "Error: " + e.getMessage();
} finally {
try {
is.close();
} catch (IOException e) {
string = "Error: " + e.getMessage();
}
}
}
}
EasyX509TrustManager:
import javax.net.ssl.X509TrustManager;
import javax.security.cert.CertificateException;
import javax.security.cert.X509Certificate;
public class EasyX509TrustManager implements X509TrustManager {
private java.security.cert.X509Certificate[] acceptedIssuers = null;
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return this.acceptedIssuers;
}
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException { }
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException { }
}
EasySSLSocketFactory:
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.scheme.LayeredSocketFactory;
import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
public class EasySSLSocketFactory implements SocketFactory, LayeredSocketFactory {
private SSLContext sslcontext = null;
private static SSLContext createEasySSLContext() throws IOException {
try {
//SSLContext context = SSLContext.getInstance("SSL");
//SSLContext context = SSLContext.getInstance("BKS");
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[] { new EasyX509TrustManager() }, 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( EasySSLSocketFactory.class));
}
public int hashCode() {
return EasySSLSocketFactory.class.hashCode();
}
}
ServiceCall:
- This has been tricky, because we've have had to overwrite the static(!) HttpsURLConneciton class's defaultHostnameVerifier and SSLSocketFactory fields. Actually the thing what we do here is step throught the "Untrusted certificate" part. Note: this is not safe! And if your server requires client side certification, you'll s*ck. Like us. :) This is the part where we need some good description (we haven't found any under 3 months) about processing the .crt and .p12 files. I've read somewhere that Android doesn't support .crt format & we have to convert it into bla-bla-bla. So if you know any Android/Java guru to develop this code further, who knows what he or she is doing, would be great!!! :)
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.cert.X509Certificate;
import java.util.Hashtable;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.Marshal;
import org.ksoap2.serialization.MarshalDate;
import org.ksoap2.serialization.MarshalFloat;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpsTransportSE;
import android.content.Context;
import android.util.Log;
public class ServiceCall {
private static final String SOAP_ACTION = "http://tempuri.org/";
private static final String NAMESPACE = "http://tempuri.org/";
private static final String SERVICE = "etcService.asmx";
private static final String URL = "your.domain.etc";
private boolean isResultVector = false;
protected Object call(String soapAction, SoapSerializationEnvelope envelope){
Object result = null;
final HttpsTransportSE transportSE = new HttpsTransportSE(URL, PORT, SERVICE, TIMEOUT);
HttpsURLConnection.setDefaultHostnameVerifier(new X509HostnameVerifier () {
@Override
public boolean verify(String host, SSLSession session) {
return true;
}
@Override
public void verify(String host, SSLSocket ssl) throws IOException { }
@Override
public void verify(String host, X509Certificate cert) throws SSLException { }
@Override
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException { }
});
HttpsURLConnection.setDefaultSSLSocketFactory(new SSLSocketFactory () {
private SSLContext sslcontext = null;
private SSLContext createEasySSLContext() throws IOException {
try {
SSLContext context = SSLContext.getInstance("TLS"); //or "BKS", or "SSL", it worked with TLS for us
context.init(null, new TrustManager[] { new EasyX509TrustManager() }, 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)) {
if (localPort < 0) { // we need to bind explicitly
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( EasySSLSocketFactory.class)); }
public int hashCode() { return EasySSLSocketFactory.class.hashCode(); }
@Override
public String[] getDefaultCipherSuites() { return null; }
@Override
public String[] getSupportedCipherSuites() { return null; }
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException { return null; }
@Override
public Socket createSocket(InetAddress host, int port) throws IOException { return null; }
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { return null; }
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { return null; }
});
transportSE.debug = false;
//call and Parse Result.
try{
transportSE.call(soapAction, envelope); //the call itself
if (!isResultVector){
result = envelope.getResponse();
Log.e("RESULT: ", result.toString());
} else {
result = envelope.bodyIn;
Log.e("RESULT: ", envelope.bodyIn.toString());
}
} catch (final IOException e){
// TODO Auto-generated catch block
e.printStackTrace();
} catch (final Exception e){
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
public Object CallGetRiasztas (String sn, Context ctx) {
//we're using our own classes, "Riasztas" would be in english like "Alert"
Hashtable<Integer, Riasztas> riasztas = new Hashtable<Integer, Riasztas>();
final String sGetRiasztas = "GetRiasztas";
// Create the outgoing message
final SoapObject requestObject = new SoapObject(NAMESPACE, sGetRiasztas);
requestObject.addProperty("sn", sn); //adding some property
// Create soap envelop .use version 1.1 of soap
final SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
envelope.dotNet = true;
// add the outgoing object as the request
envelope.setOutputSoapObject(requestObject);
envelope.addMapping(NAMESPACE, Riasztas.RIASZTAS_CLASS.getSimpleName(), Riasztas.RIASZTAS_CLASS);
// call and Parse Result.
final Object response = this.call(SOAP_ACTION + sGetRiasztas, envelope);
SoapObject soapObj = (SoapObject)response;
if (response != null){
for(int i = 0; i < soapObj.getPropertyCount(); ++i) {
Log.e("LOOP:", String.valueOf(i));
try {
SoapObject so = (SoapObject) soapObj.getProperty(i);
riasztas.put(i, new Riasztas(so));
} catch (Exception e) {
// TODO: handle exception
Log.e("EROOR:", e.getMessage());
}
}
}
return riasztas;
}
}
And I've put it on a separate thread to call the stuff (somewhere in your Activity):
Hashtable<Integer, Raisztas> result = null;
try {
ServiceCall call = new ServiceCall();
result = (Hashtable<Integer, Riasztas>)call.CallGetChartData(ViewRiasztasok.this);
} catch (Exception e) {
Toast.makeText(ViewRiasztasok.this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
Some for the time, here it is, I hope I haven't share any "state secrets" and it'll be usefull for somebody! :)
Best regards,
Ben