package de.kp.wsclient.soap; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.security.KeyStore; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.scheme.SocketFactory; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.entity.BufferedHttpEntity; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import android.content.Context; import de.kp.wsclient.security.SecConstants; import de.kp.wsclient.security.SecCryptoParam; import de.kp.wsclient.security.SecCryptoParams; /** * Implementation of {@link SOAPSender}, using the Apache HTTP Client * * This class is an adapted version of the ApacheSOAPRequestor class * from the icesoap project from Alex Gillerian * * @author Alex Gilleran * * @author Stefan Krusche (krusche@dr-kruscheundpartner.de) * @author Peter Arwanitis (arwanitis@dr-kruscheundpartner.de) * */ // TODO: Remove test configuration public class SOAPSenderImpl implements SOAPSender { /** Soap action to use if none is specified. */ private static final String BLANK_SOAP_ACTION = ""; /** Port for HTTPS communication */ private static final int DEFAULT_HTTPS_PORT = 443; /** Port for HTTP communication */ // __TEST__ private static final int DEFAULT_HTTP_PORT = 8080; //80; /** Name of HTTPS */ private static final String HTTPS_NAME = "https"; /** Name of HTTP */ private static final String HTTP_NAME = "http"; /** HTTP content type submitted in HTTP POST request for SOAP calls */ private static final String XML_CONTENT_TYPE = "text/xml; charset=UTF-8"; /** Label for content-type header */ private static final String CONTENT_TYPE_LABEL = "Content-type"; /** Key for SOAP action header */ private static final String HEADER_KEY_SOAP_ACTION = "SOAPAction"; /** Timeout for making a connection */ private static final int DEFAULT_CONN_TIMEOUT = 5000; /** Timeout for recieving data */ private static final int DEFAULT_SOCKET_TIMEOUT = 20000; /** Apache HTTP Client for making HTTP requests */ private HttpClient httpClient = null; /** reference to keystore and truststore */ private KeyStore keyStore; private KeyStore trustStore; private Context context; private SecCryptoParam trustStoreParam; private SecCryptoParam keyStoreParam; public SOAPSenderImpl() { this(null); } public SOAPSenderImpl(Context context) { this.context = context; } /** * {@inheritDoc} * @throws Exception */ @Override public SOAPResponse doSoapRequest(SOAPMessage message, String targetUrl) throws Exception { return doSoapRequest(message, targetUrl, BLANK_SOAP_ACTION); } /** * {@inheritDoc} * @throws Exception */ public SOAPResponse doSoapRequest(SOAPMessage message, String url, String soapAction) throws Exception { return doHttpPost(buildPostRequest(url, message.toXML(), soapAction)); } /** * Performs an HTTP POST request * * @param httpPost * The {@link HttpPost} to perform. * @return An {@link InputStream} of the response. * @throws Exception * @throws SOAPException */ private SOAPResponse doHttpPost(HttpPost httpPost) throws Exception { // lazy initialization if (httpClient == null) httpClient = buildHttpClient(); // Execute HTTP Post Request HttpResponse response = httpClient.execute(httpPost); HttpEntity res = new BufferedHttpEntity(response.getEntity()); return new SOAPResponse(res.getContent(), response.getStatusLine().getStatusCode()); } /** * Lazy initialization of Android context for resource accessing * * @param keyStoreParam * @param trustStoreParam * @throws Exception */ public void init(SecCryptoParams cryptoParams) throws Exception { if (cryptoParams == null) return; /* * The key- and truststore params are registered for later use */ keyStoreParam = cryptoParams.get(SecCryptoParams.KEYSTORE); trustStoreParam = cryptoParams.get(SecCryptoParams.TRUSTSTORE); loadKeyStore(); loadTrustStore(); } /** * This method distinguishes between PKCS11 und PKCS12 compliant * keystores; for PKCS12 keystores, the respective type MUST be * set to BKS (Bouncycastle) * * @throws Exception */ protected void loadKeyStore() throws Exception { String keyStoreType = keyStoreParam.getType(); char[] keyStorePass = keyStoreParam.getPassword().toCharArray(); if (keyStoreType.equals(SecConstants.KS_TYPE_BKS)) { // the keystore refers to PKCS12 soft token keyStore = KeyStore.getInstance(keyStoreType); InputStream keyStoreStream = null; try { keyStoreStream = context.getResources().openRawResource(keyStoreParam.getResource()); keyStore.load(keyStoreStream, keyStorePass); } finally { if (keyStoreStream != null) keyStoreStream.close(); } } else if (keyStoreType.equals(SecConstants.KS_TYPE_PKCS11)) { // the keystore refers to a smartcard token (hard token) keyStore = KeyStore.getInstance(keyStoreType); keyStore.load(null, keyStorePass); } else { // the keystore type refers to an unknown format throw new Exception("[SOAPSenderImpl] " + keyStoreType + " is not supported."); } } /** * This method distinguishes between PKCS11 und PKCS12 compliant * keystores; for PKCS12 keystores, the respective type MUST be * set to BKS (Bouncycastle) * * @throws Exception */ protected void loadTrustStore() throws Exception { String trustStoreType = trustStoreParam.getType(); char[] trustStorePass = trustStoreParam.getPassword().toCharArray(); if (trustStoreType.equals(SecConstants.KS_TYPE_BKS)) { // the truststore refers to PKCS12 soft token trustStore = KeyStore.getInstance(trustStoreType); InputStream trustStoreStream = null; try { trustStoreStream = context.getResources().openRawResource(trustStoreParam.getResource()); trustStore.load(trustStoreStream, trustStorePass); } finally { if (trustStoreStream != null) trustStoreStream.close(); } } else if (trustStoreType.equals(SecConstants.KS_TYPE_PKCS11)) { // the keystore refers to a smartcard token (hard token) trustStore = KeyStore.getInstance(trustStoreType); trustStore.load(null, trustStorePass); } else { // the truststore type refers to an unknown format throw new Exception("[SOAPSenderImpl] " + trustStoreType + " is not supported."); } } /** * Builds an Apache {@link HttpClient} from defaults. * * @return An implementation of {@link HttpClient} * @throws Exception */ private HttpClient buildHttpClient() throws Exception { HttpParams httpParameters = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParameters, DEFAULT_CONN_TIMEOUT); HttpConnectionParams.setSoTimeout(httpParameters, DEFAULT_SOCKET_TIMEOUT); SchemeRegistry schemeRegistry = getSchemeRegistry(); ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(httpParameters, schemeRegistry); return new DefaultHttpClient(cm, httpParameters); } /** * Builds a {@link SchemeRegistry}, which determines the * {@link SocketFactory} that will be used for different ports. * * This is very important because it will need to be overridden by an * extension class if custom ports or factories (which are used for * self-signed certificates) are to be used. * * @return A {@link SchemeRegistry} with the necessary port and factories * registered. * @throws Exception */ protected SchemeRegistry getSchemeRegistry() throws Exception { SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme(HTTP_NAME, PlainSocketFactory.getSocketFactory(), DEFAULT_HTTP_PORT)); // __TEST__ //schemeRegistry.register(new Scheme(HTTPS_NAME, CertClientSslSocketFactory(), DEFAULT_HTTPS_PORT)); return schemeRegistry; } /** * Initializes a SSLSocketFactory ready for mutual authentication via https CLIENT-CERT * * @return * @throws Exception */ private SSLSocketFactory CertClientSslSocketFactory() throws Exception { /* Pass the keystore to the SSLSocketFactory. The factory is responsible for the verification of the server certificate. */ if ((keyStore == null) || (trustStore == null)) throw new Exception("[SOAPSenderImpl] keystore initialization missing"); SSLSocketFactory socketFactory = new SSLSocketFactory(SSLSocketFactory.TLS, // String algorithm keyStore, // KeyStore keystore keyStoreParam.getPassword(), // String keystorePassword trustStore, // KeyStore truststore null, // SecureRandom random null // HostNameResolver nameResolver ); // Hostname verification from certificate // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506 socketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); // .STRICT_HOSTNAME_VERIFIER); return socketFactory; } /** * Builds an {@link HttpPost} request. * * @param url * the URL to POST to * @param envelopeString * The envelope to post, as a serialized string. * @param soapAction * SOAPAction for the header. * @return An {@link HttpPost} object representing the supplied information. * @throws UnsupportedEncodingException */ private HttpPost buildPostRequest(String url, String envelopeString, String soapAction) throws UnsupportedEncodingException { // Create a new HttpClient and Post Header HttpPost httppost = new HttpPost(url); httppost.setHeader(CONTENT_TYPE_LABEL, XML_CONTENT_TYPE); httppost.setHeader(HEADER_KEY_SOAP_ACTION, soapAction); HttpEntity entity = new StringEntity(envelopeString); httppost.setEntity(entity); return httppost; } /** * {@inheritDoc} */ @Override public void setConnectionTimeout(int timeout) { HttpConnectionParams.setConnectionTimeout(httpClient.getParams(), timeout); } /** * {@inheritDoc} */ @Override public void setSocketTimeout(int timeout) { HttpConnectionParams.setSoTimeout(httpClient.getParams(), timeout); } }