1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-23 18:02:15 -05:00

Added self-signed/mismatched domain handling for HTTP over SSL connections in WebDav.

This commit is contained in:
Bradley Young 2009-01-01 08:56:19 +00:00
parent 4cc1ea489a
commit 51b6a03f8c
3 changed files with 188 additions and 68 deletions

View File

@ -11,8 +11,14 @@ import java.io.InputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.ParsePosition; import java.text.ParsePosition;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -21,11 +27,16 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Stack; import java.util.Stack;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import com.android.email.Email; import com.android.email.Email;
import com.android.email.mail.CertificateValidationException;
import com.android.email.mail.FetchProfile; import com.android.email.mail.FetchProfile;
import com.android.email.mail.Flag; import com.android.email.mail.Flag;
import com.android.email.mail.Folder; import com.android.email.mail.Folder;
@ -37,6 +48,7 @@ import com.android.email.mail.internet.MimeBodyPart;
import com.android.email.mail.internet.MimeMessage; import com.android.email.mail.internet.MimeMessage;
import com.android.email.mail.internet.TextBody; import com.android.email.mail.internet.TextBody;
import com.android.email.mail.transport.EOLConvertingOutputStream; import com.android.email.mail.transport.EOLConvertingOutputStream;
import com.android.email.mail.transport.TrustedSocketFactory;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
@ -45,6 +57,9 @@ import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
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.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
@ -77,6 +92,7 @@ public class WebDavStore extends Store {
private String alias; private String alias;
private String mPassword; /* Stores the password for authentications */ private String mPassword; /* Stores the password for authentications */
private String mUrl; /* Stores the base URL for the server */ private String mUrl; /* Stores the base URL for the server */
private String mHost; /* Stores the host name for the server */
private CookieStore mAuthCookies; /* Stores cookies from authentication */ private CookieStore mAuthCookies; /* Stores cookies from authentication */
private boolean mAuthenticated = false; /* Stores authentication state */ private boolean mAuthenticated = false; /* Stores authentication state */
@ -84,6 +100,8 @@ public class WebDavStore extends Store {
private long mAuthTimeout = 5 * 60; private long mAuthTimeout = 5 * 60;
private HashMap<String, WebDavFolder> mFolderList = new HashMap<String, WebDavFolder>(); private HashMap<String, WebDavFolder> mFolderList = new HashMap<String, WebDavFolder>();
private boolean mSecure;
/** /**
* webdav://user:password@server:port CONNECTION_SECURITY_NONE * webdav://user:password@server:port CONNECTION_SECURITY_NONE
* webdav+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL * webdav+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
@ -116,12 +134,11 @@ public class WebDavStore extends Store {
throw new MessagingException("Unsupported protocol"); throw new MessagingException("Unsupported protocol");
} }
String host = uri.getHost(); mHost = uri.getHost();
if (mHost.startsWith("http")) {
if (host.startsWith("http")) { String[] hostParts = mHost.split("://", 2);
String[] hostParts = host.split("://", 2);
if (hostParts.length > 1) { if (hostParts.length > 1) {
host = hostParts[1]; mHost = hostParts[1];
} }
} }
@ -129,9 +146,9 @@ public class WebDavStore extends Store {
mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED || mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED ||
mConnectionSecurity == CONNECTION_SECURITY_TLS_OPTIONAL || mConnectionSecurity == CONNECTION_SECURITY_TLS_OPTIONAL ||
mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) { mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) {
this.mUrl = "https://" + host; this.mUrl = "https://" + mHost;
} else { } else {
this.mUrl = "http://" + host; this.mUrl = "http://" + mHost;
} }
if (uri.getUserInfo() != null) { if (uri.getUserInfo() != null) {
@ -148,6 +165,7 @@ public class WebDavStore extends Store {
mPassword = userInfoParts[1]; mPassword = userInfoParts[1];
} }
} }
mSecure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
} }
@ -360,8 +378,9 @@ public class WebDavStore extends Store {
/** /**
* Performs Form Based authentication regardless of the current * Performs Form Based authentication regardless of the current
* authentication state * authentication state
* @throws MessagingException
*/ */
public void authenticate() { public void authenticate() throws MessagingException {
try { try {
this.mAuthCookies = doAuthentication(this.mUsername, this.mPassword, this.mUrl); this.mAuthCookies = doAuthentication(this.mUsername, this.mPassword, this.mUrl);
} catch (IOException ioe) { } catch (IOException ioe) {
@ -398,9 +417,10 @@ public class WebDavStore extends Store {
/** /**
* Performs the Form Based Authentication * Performs the Form Based Authentication
* Returns the CookieStore object for later use or null * Returns the CookieStore object for later use or null
* @throws MessagingException
*/ */
public CookieStore doAuthentication(String username, String password, public CookieStore doAuthentication(String username, String password,
String url) throws IOException { String url) throws IOException, MessagingException {
String authPath = "/exchweb/bin/auth/owaauth.dll"; String authPath = "/exchweb/bin/auth/owaauth.dll";
CookieStore cookies = null; CookieStore cookies = null;
String[] urlParts = url.split("/"); String[] urlParts = url.split("/");
@ -414,60 +434,66 @@ public class WebDavStore extends Store {
} }
} }
/* Browser Client */
DefaultHttpClient httpclient = new DefaultHttpClient();
/* Post Method */
HttpPost httppost = new HttpPost(finalUrl + authPath);
/** Build the POST data to use */
ArrayList<BasicNameValuePair> pairs = new ArrayList();
pairs.add(new BasicNameValuePair("username", username));
pairs.add(new BasicNameValuePair("password", password));
pairs.add(new BasicNameValuePair("destination", finalUrl + "/Exchange/"));
pairs.add(new BasicNameValuePair("flags", "0"));
pairs.add(new BasicNameValuePair("SubmitCreds", "Log+On"));
pairs.add(new BasicNameValuePair("forcedownlevel", "0"));
pairs.add(new BasicNameValuePair("trusted", "0"));
try { try {
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(pairs); /* Browser Client */
DefaultHttpClient httpclient = getTrustedHttpClient();
/* Post Method */
HttpPost httppost = new HttpPost(finalUrl + authPath);
httppost.setEntity(formEntity); /** Build the POST data to use */
ArrayList<BasicNameValuePair> pairs = new ArrayList();
pairs.add(new BasicNameValuePair("username", username));
pairs.add(new BasicNameValuePair("password", password));
pairs.add(new BasicNameValuePair("destination", finalUrl + "/Exchange/"));
pairs.add(new BasicNameValuePair("flags", "0"));
pairs.add(new BasicNameValuePair("SubmitCreds", "Log+On"));
pairs.add(new BasicNameValuePair("forcedownlevel", "0"));
pairs.add(new BasicNameValuePair("trusted", "0"));
/** Perform the actual POST */ try {
HttpResponse response = httpclient.execute(httppost); UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(pairs);
HttpEntity entity = response.getEntity();
int status_code = response.getStatusLine().getStatusCode();
/** Verify success */ httppost.setEntity(formEntity);
if (status_code > 300 ||
status_code < 200) {
throw new IOException("Error during authentication: "+status_code);
}
cookies = httpclient.getCookieStore(); /** Perform the actual POST */
if (cookies == null) { HttpResponse response = httpclient.execute(httppost);
throw new IOException("Error during authentication: No Cookies"); HttpEntity entity = response.getEntity();
} int status_code = response.getStatusLine().getStatusCode();
/** Get the URL for the mailbox and set it for the store */ /** Verify success */
if (entity != null) { if (status_code > 300 ||
InputStream istream = entity.getContent(); status_code < 200) {
throw new IOException("Error during authentication: "+status_code);
}
BufferedReader reader = new BufferedReader(new InputStreamReader(istream), 8192); cookies = httpclient.getCookieStore();
String tempText = ""; if (cookies == null) {
throw new IOException("Error during authentication: No Cookies");
}
while ((tempText = reader.readLine()) != null) { /** Get the URL for the mailbox and set it for the store */
if (tempText.indexOf("BASE href") >= 0) { if (entity != null) {
String[] tagParts = tempText.split("\""); InputStream istream = entity.getContent();
this.mUrl = tagParts[1];
}
}
}
} catch (UnsupportedEncodingException uee) { BufferedReader reader = new BufferedReader(new InputStreamReader(istream), 8192);
Log.e(Email.LOG_TAG, "Error encoding POST data for authencation"); String tempText = "";
while ((tempText = reader.readLine()) != null) {
if (tempText.indexOf("BASE href") >= 0) {
String[] tagParts = tempText.split("\"");
this.mUrl = tagParts[1];
}
}
}
} catch (UnsupportedEncodingException uee) {
Log.e(Email.LOG_TAG, "Error encoding POST data for authencation");
}
} catch (SSLException e) {
throw new CertificateValidationException(e.getMessage(), e);
} catch (GeneralSecurityException gse) {
throw new MessagingException(
"Unable to open connection to SMTP server due to security error.", gse);
} }
return cookies; return cookies;
} }
@ -484,6 +510,15 @@ public class WebDavStore extends Store {
return mUrl; return mUrl;
} }
public DefaultHttpClient getTrustedHttpClient() throws KeyManagementException, NoSuchAlgorithmException{
DefaultHttpClient httpclient = new DefaultHttpClient();
SchemeRegistry reg = httpclient.getConnectionManager().getSchemeRegistry();
reg.unregister("https");
Scheme s = new Scheme("https",new TrustedSocketFactory(mHost,mSecure),443);
reg.register(s);
return httpclient;
}
/** /**
* Performs an httprequest to the supplied url using the supplied method. * Performs an httprequest to the supplied url using the supplied method.
* messageBody and headers are optional as not all requests will need them. * messageBody and headers are optional as not all requests will need them.
@ -495,15 +530,27 @@ public class WebDavStore extends Store {
private ParsedDataSet processRequest(String url, String method, String messageBody, HashMap<String, String> headers, boolean needsParsing) { private ParsedDataSet processRequest(String url, String method, String messageBody, HashMap<String, String> headers, boolean needsParsing) {
ParsedDataSet dataset = new ParsedDataSet(); ParsedDataSet dataset = new ParsedDataSet();
DefaultHttpClient httpclient = new DefaultHttpClient(); DefaultHttpClient httpclient;
if (url == null || if (url == null ||
method == null) { method == null) {
return dataset; return dataset;
} }
try {
httpclient = getTrustedHttpClient();
} catch (KeyManagementException e) {
Log.e(Email.LOG_TAG, "Generated KeyManagementException during authentication" + e.getStackTrace());
return dataset;
} catch (NoSuchAlgorithmException e) {
Log.e(Email.LOG_TAG, "Generated NoSuchAlgorithmException during authentication" + e.getStackTrace());
return dataset;
}
if (needAuth()) { if (needAuth()) {
authenticate(); try {
authenticate();
} catch (MessagingException e) {
Log.e(Email.LOG_TAG, "Generated MessagingException during authentication" + e.getStackTrace());
}
} }
if (this.mAuthenticated == false || if (this.mAuthenticated == false ||
@ -620,7 +667,11 @@ public class WebDavStore extends Store {
* Perform an authentication to get the appropriate URLs in place again * Perform an authentication to get the appropriate URLs in place again
*/ */
if (needAuth()) { if (needAuth()) {
authenticate(); try {
authenticate();
} catch (MessagingException e) {
Log.e(Email.LOG_TAG, "Generated MessagingException during authentication" + e.getStackTrace());
}
} }
if (encodedName.equals("INBOX")) { if (encodedName.equals("INBOX")) {
@ -901,7 +952,14 @@ public class WebDavStore extends Store {
* Fetches the full messages or up to lines lines and passes them to the message parser. * Fetches the full messages or up to lines lines and passes them to the message parser.
*/ */
private void fetchMessages(Message[] messages, MessageRetrievalListener listener, int lines) throws MessagingException { private void fetchMessages(Message[] messages, MessageRetrievalListener listener, int lines) throws MessagingException {
DefaultHttpClient httpclient = new DefaultHttpClient(); DefaultHttpClient httpclient;
try {
httpclient = getTrustedHttpClient();
} catch (KeyManagementException e) {
throw new MessagingException("KeyManagement Exception in fetchMessages()."+ e.getStackTrace());
} catch (NoSuchAlgorithmException e) {
throw new MessagingException("NoSuchAlgorithm Exception in fetchMessages():" + e.getStackTrace());
}
/** /**
* We can't hand off to processRequest() since we need the stream to parse. * We can't hand off to processRequest() since we need the stream to parse.

View File

@ -0,0 +1,56 @@
package com.android.email.mail.transport;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.params.HttpParams;
import com.android.email.mail.store.TrustManagerFactory;
public class TrustedSocketFactory implements SocketFactory {
private SSLSocketFactory mSocketFactory;
private org.apache.http.conn.ssl.SSLSocketFactory mSchemeSocketFactory;
public TrustedSocketFactory(String host, boolean secure) throws NoSuchAlgorithmException, KeyManagementException{
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] {
TrustManagerFactory.get(host, secure)
}, new SecureRandom());
mSocketFactory = sslContext.getSocketFactory();
mSchemeSocketFactory = org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory();
mSchemeSocketFactory.setHostnameVerifier(
org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
}
@Override
public Socket connectSocket(Socket sock, String host, int port,
InetAddress localAddress, int localPort, HttpParams params)
throws IOException, UnknownHostException, ConnectTimeoutException {
return mSchemeSocketFactory.connectSocket(sock, host, port, localAddress, localPort, params);
}
@Override
public Socket createSocket() throws IOException {
// TODO Auto-generated method stub
return mSocketFactory.createSocket();
}
@Override
public boolean isSecure(Socket arg0) throws IllegalArgumentException {
// TODO Auto-generated method stub
return false;
}
}

View File

@ -16,6 +16,8 @@ import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
@ -118,7 +120,16 @@ public class WebDavTransport extends Transport {
public void sendMessage(Message message) throws MessagingException { public void sendMessage(Message message) throws MessagingException {
Log.d(Email.LOG_TAG, ">>> sendMessage called."); Log.d(Email.LOG_TAG, ">>> sendMessage called.");
DefaultHttpClient httpclient = new DefaultHttpClient(); DefaultHttpClient httpclient;
try {
httpclient = store.getTrustedHttpClient();
} catch (KeyManagementException e) {
Log.e(Email.LOG_TAG, "KeyManagementException while creating HttpClient: " + e);
throw new MessagingException("KeyManagementException while creating HttpClient: " + e);
} catch (NoSuchAlgorithmException e) {
Log.e(Email.LOG_TAG, "NoSuchAlgorithmException while creating HttpClient: " + e);
throw new MessagingException("NoSuchAlgorithmException while creating HttpClient: " + e);
}
HttpGeneric httpmethod; HttpGeneric httpmethod;
HttpResponse response; HttpResponse response;
HttpEntity responseEntity; HttpEntity responseEntity;
@ -160,11 +171,6 @@ public class WebDavTransport extends Transport {
throw new IOException("Error sending message, status code was " + statusCode); throw new IOException("Error sending message, status code was " + statusCode);
} }
//responseEntity = response.getEntity();
//DefaultHttpClient movehttpclient = new DefaultHttpClient();
//HttpGeneric movehttpmethod;
//HttpResponse moveresponse;
//HttpEntity moveresponseEntity;
httpmethod = store.new HttpGeneric(generateTempURI(subject)); httpmethod = store.new HttpGeneric(generateTempURI(subject));
httpmethod.setMethod("MOVE"); httpmethod.setMethod("MOVE");
httpmethod.setHeader("Destination", generateSendURI()); httpmethod.setHeader("Destination", generateSendURI());