Loading app/src/main/java/net/fabiszewski/ulogger/TlsSocketFactory.java 0 → 100644 +108 −0 Original line number Diff line number Diff line /* * Copyright (c) 2017 Bartek Fabiszewski * http://www.fabiszewski.net * * This file is part of μlogger-android. * Licensed under GPL, either version 3, or any later. * See <http://www.gnu.org/licenses/> */ package net.fabiszewski.ulogger; import android.content.Context; import android.net.SSLCertificateSocketFactory; import android.net.SSLSessionCache; import android.util.Log; import java.io.IOException; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.Socket; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; /** * This custom ssl socket factory will be used only with API < 19 * to solve problem with connecting to SSL-disabled servers. */ @SuppressWarnings("deprecation") class TlsSocketFactory extends SSLSocketFactory { private static final String TAG = TlsSocketFactory.class.getSimpleName(); private static HostnameVerifier hostnameVerifier; private static SSLSocketFactory factory; TlsSocketFactory(Context context) { SSLSessionCache cache = new SSLSessionCache(context); factory = SSLCertificateSocketFactory.getDefault(WebHelper.SOCKET_TIMEOUT, cache); hostnameVerifier = new org.apache.http.conn.ssl.BrowserCompatHostnameVerifier(); } @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException { if (autoClose) { socket.close(); } socket = factory.createSocket(InetAddress.getByName(host), port); if (socket != null && socket instanceof SSLSocket) { if (Logger.DEBUG) { Log.d(TAG, "[Preparing TLS socket]"); } SSLSocket sslSocket = (SSLSocket) socket; // set all protocols including TLS (disabled by default on older APIs) sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols()); sslSocket.setEnabledCipherSuites(sslSocket.getSupportedCipherSuites()); if (host != null && !host.isEmpty()) { // set hostname for SNI if (Logger.DEBUG) { Log.d(TAG, "[Setting SNI for host " + host + "]"); } try { Method setHostnameMethod = sslSocket.getClass().getMethod("setHostname", String.class); setHostnameMethod.invoke(sslSocket, host); } catch (Exception e) { if (Logger.DEBUG) { Log.d(TAG, "[Setting SNI failed: " + e.getMessage() + "]"); } } } // verify hostname and certificate SSLSession session = sslSocket.getSession(); if (!hostnameVerifier.verify(host, session)) { throw new SSLPeerUnverifiedException("Hostname '" + host + "' was not verified (" + session.getPeerPrincipal() + ")"); } if (Logger.DEBUG) { Log.d(TAG, "Connected to " + session.getPeerHost() + " using " + session.getProtocol() + " (" + session.getCipherSuite() + ")"); } } return socket; } @Override public String[] getDefaultCipherSuites() { throw new UnsupportedOperationException("Not implemented"); } @Override public String[] getSupportedCipherSuites() { throw new UnsupportedOperationException("Not implemented"); } @Override public Socket createSocket(String host, int port) throws IOException { throw new UnsupportedOperationException("Not implemented"); } @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { throw new UnsupportedOperationException("Not implemented"); } @Override public Socket createSocket(InetAddress host, int port) throws IOException { throw new UnsupportedOperationException("Not implemented"); } @Override public Socket createSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException { throw new UnsupportedOperationException("Not implemented"); } } app/src/main/java/net/fabiszewski/ulogger/WebHelper.java +56 −23 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ package net.fabiszewski.ulogger; import android.content.Context; import android.content.SharedPreferences; import android.os.Build; import android.preference.PreferenceManager; import android.util.Log; Loading @@ -32,6 +33,9 @@ import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; /** * Web server communication * Loading Loading @@ -77,6 +81,10 @@ class WebHelper { private final String userAgent; private final Context context; private static boolean tlsSocketInitialized = false; // Socket timeout in milliseconds static final int SOCKET_TIMEOUT = 30 * 1000; /** * Constructor Loading @@ -87,13 +95,27 @@ class WebHelper { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); user = prefs.getString("prefUsername", "NULL"); pass = prefs.getString("prefPass", "NULL"); host = prefs.getString("prefHost", "NULL"); userAgent = context.getString(R.string.app_name_ascii) + "; " + System.getProperty("http.agent"); host = prefs.getString("prefHost", "NULL").replaceAll("/+$", ""); userAgent = context.getString(R.string.app_name_ascii) + "/" + BuildConfig.VERSION_NAME + "; " + System.getProperty("http.agent"); if (cookieManager == null) { cookieManager = new CookieManager(); CookieHandler.setDefault(cookieManager); } // On API < 19 connection fails if SSL is disabled on server // Try with TLS enabled socket if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT && !tlsSocketInitialized) { try { if (Logger.DEBUG) { Log.d(TAG, "[init TLS socket factory]"); } SSLSocketFactory tlsFactory = new TlsSocketFactory(context); HttpsURLConnection.setDefaultSSLSocketFactory(tlsFactory); tlsSocketInitialized = true; } catch (Exception e) { if (Logger.DEBUG) { Log.d(TAG, "[TLS socket setup error (ignored): " + e.getMessage() + "]"); } } } } /** Loading Loading @@ -121,6 +143,8 @@ class WebHelper { byte[] data = dataString.getBytes(); HttpURLConnection connection = null; InputStream in = null; OutputStream out = null; try { boolean redirect; int redirectTries = 5; Loading @@ -131,10 +155,13 @@ class WebHelper { connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setRequestProperty("Content-Length", Integer.toString(data.length)); connection.setRequestProperty("User-agent", userAgent); connection.setInstanceFollowRedirects(true); connection.setRequestProperty("User-Agent", userAgent); connection.setInstanceFollowRedirects(false); connection.setConnectTimeout(SOCKET_TIMEOUT); connection.setReadTimeout(SOCKET_TIMEOUT); connection.setUseCaches(true); OutputStream out = new BufferedOutputStream(connection.getOutputStream()); out = new BufferedOutputStream(connection.getOutputStream()); out.write(data); out.flush(); Loading @@ -157,6 +184,13 @@ class WebHelper { if (h1 != null && !h1.equalsIgnoreCase(h2)) { throw new IOException(context.getString(R.string.e_illegal_redirect, responseCode)); } try { out.close(); connection.getInputStream().close(); connection.disconnect(); } catch (final IOException e) { if (Logger.DEBUG) { Log.d(TAG, "[connection cleanup failed (ignored)]"); } } } else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { throw new WebAuthException(context.getString(R.string.e_auth_failure, responseCode)); Loading @@ -166,7 +200,7 @@ class WebHelper { } } while (redirect); InputStream in = new BufferedInputStream(connection.getInputStream()); in = new BufferedInputStream(connection.getInputStream()); StringBuilder sb = new StringBuilder(); BufferedReader br = new BufferedReader(new InputStreamReader(in)); Loading @@ -175,21 +209,20 @@ class WebHelper { sb.append(inputLine); } response = sb.toString(); } catch (IOException e) { int responseCode; if (connection != null && (responseCode = connection.getResponseCode()) > 0) { if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { // eg. IOException: Received authentication challenge is null throw new WebAuthException(context.getString(R.string.e_auth_failure, responseCode)); } else { throw new IOException(context.getString(R.string.e_http_code, responseCode)); } finally { try { if (out != null) { out.close(); } if (in != null) { in.close(); } throw e; } finally { if (connection != null) { connection.disconnect(); } } catch (final IOException e) { if (Logger.DEBUG) { Log.d(TAG, "[connection cleanup failed (ignored)]"); } } } if (Logger.DEBUG) { Log.d(TAG, "[postWithParams response: " + response + "]"); } return response; Loading Loading
app/src/main/java/net/fabiszewski/ulogger/TlsSocketFactory.java 0 → 100644 +108 −0 Original line number Diff line number Diff line /* * Copyright (c) 2017 Bartek Fabiszewski * http://www.fabiszewski.net * * This file is part of μlogger-android. * Licensed under GPL, either version 3, or any later. * See <http://www.gnu.org/licenses/> */ package net.fabiszewski.ulogger; import android.content.Context; import android.net.SSLCertificateSocketFactory; import android.net.SSLSessionCache; import android.util.Log; import java.io.IOException; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.Socket; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; /** * This custom ssl socket factory will be used only with API < 19 * to solve problem with connecting to SSL-disabled servers. */ @SuppressWarnings("deprecation") class TlsSocketFactory extends SSLSocketFactory { private static final String TAG = TlsSocketFactory.class.getSimpleName(); private static HostnameVerifier hostnameVerifier; private static SSLSocketFactory factory; TlsSocketFactory(Context context) { SSLSessionCache cache = new SSLSessionCache(context); factory = SSLCertificateSocketFactory.getDefault(WebHelper.SOCKET_TIMEOUT, cache); hostnameVerifier = new org.apache.http.conn.ssl.BrowserCompatHostnameVerifier(); } @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException { if (autoClose) { socket.close(); } socket = factory.createSocket(InetAddress.getByName(host), port); if (socket != null && socket instanceof SSLSocket) { if (Logger.DEBUG) { Log.d(TAG, "[Preparing TLS socket]"); } SSLSocket sslSocket = (SSLSocket) socket; // set all protocols including TLS (disabled by default on older APIs) sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols()); sslSocket.setEnabledCipherSuites(sslSocket.getSupportedCipherSuites()); if (host != null && !host.isEmpty()) { // set hostname for SNI if (Logger.DEBUG) { Log.d(TAG, "[Setting SNI for host " + host + "]"); } try { Method setHostnameMethod = sslSocket.getClass().getMethod("setHostname", String.class); setHostnameMethod.invoke(sslSocket, host); } catch (Exception e) { if (Logger.DEBUG) { Log.d(TAG, "[Setting SNI failed: " + e.getMessage() + "]"); } } } // verify hostname and certificate SSLSession session = sslSocket.getSession(); if (!hostnameVerifier.verify(host, session)) { throw new SSLPeerUnverifiedException("Hostname '" + host + "' was not verified (" + session.getPeerPrincipal() + ")"); } if (Logger.DEBUG) { Log.d(TAG, "Connected to " + session.getPeerHost() + " using " + session.getProtocol() + " (" + session.getCipherSuite() + ")"); } } return socket; } @Override public String[] getDefaultCipherSuites() { throw new UnsupportedOperationException("Not implemented"); } @Override public String[] getSupportedCipherSuites() { throw new UnsupportedOperationException("Not implemented"); } @Override public Socket createSocket(String host, int port) throws IOException { throw new UnsupportedOperationException("Not implemented"); } @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { throw new UnsupportedOperationException("Not implemented"); } @Override public Socket createSocket(InetAddress host, int port) throws IOException { throw new UnsupportedOperationException("Not implemented"); } @Override public Socket createSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException { throw new UnsupportedOperationException("Not implemented"); } }
app/src/main/java/net/fabiszewski/ulogger/WebHelper.java +56 −23 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ package net.fabiszewski.ulogger; import android.content.Context; import android.content.SharedPreferences; import android.os.Build; import android.preference.PreferenceManager; import android.util.Log; Loading @@ -32,6 +33,9 @@ import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; /** * Web server communication * Loading Loading @@ -77,6 +81,10 @@ class WebHelper { private final String userAgent; private final Context context; private static boolean tlsSocketInitialized = false; // Socket timeout in milliseconds static final int SOCKET_TIMEOUT = 30 * 1000; /** * Constructor Loading @@ -87,13 +95,27 @@ class WebHelper { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); user = prefs.getString("prefUsername", "NULL"); pass = prefs.getString("prefPass", "NULL"); host = prefs.getString("prefHost", "NULL"); userAgent = context.getString(R.string.app_name_ascii) + "; " + System.getProperty("http.agent"); host = prefs.getString("prefHost", "NULL").replaceAll("/+$", ""); userAgent = context.getString(R.string.app_name_ascii) + "/" + BuildConfig.VERSION_NAME + "; " + System.getProperty("http.agent"); if (cookieManager == null) { cookieManager = new CookieManager(); CookieHandler.setDefault(cookieManager); } // On API < 19 connection fails if SSL is disabled on server // Try with TLS enabled socket if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT && !tlsSocketInitialized) { try { if (Logger.DEBUG) { Log.d(TAG, "[init TLS socket factory]"); } SSLSocketFactory tlsFactory = new TlsSocketFactory(context); HttpsURLConnection.setDefaultSSLSocketFactory(tlsFactory); tlsSocketInitialized = true; } catch (Exception e) { if (Logger.DEBUG) { Log.d(TAG, "[TLS socket setup error (ignored): " + e.getMessage() + "]"); } } } } /** Loading Loading @@ -121,6 +143,8 @@ class WebHelper { byte[] data = dataString.getBytes(); HttpURLConnection connection = null; InputStream in = null; OutputStream out = null; try { boolean redirect; int redirectTries = 5; Loading @@ -131,10 +155,13 @@ class WebHelper { connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setRequestProperty("Content-Length", Integer.toString(data.length)); connection.setRequestProperty("User-agent", userAgent); connection.setInstanceFollowRedirects(true); connection.setRequestProperty("User-Agent", userAgent); connection.setInstanceFollowRedirects(false); connection.setConnectTimeout(SOCKET_TIMEOUT); connection.setReadTimeout(SOCKET_TIMEOUT); connection.setUseCaches(true); OutputStream out = new BufferedOutputStream(connection.getOutputStream()); out = new BufferedOutputStream(connection.getOutputStream()); out.write(data); out.flush(); Loading @@ -157,6 +184,13 @@ class WebHelper { if (h1 != null && !h1.equalsIgnoreCase(h2)) { throw new IOException(context.getString(R.string.e_illegal_redirect, responseCode)); } try { out.close(); connection.getInputStream().close(); connection.disconnect(); } catch (final IOException e) { if (Logger.DEBUG) { Log.d(TAG, "[connection cleanup failed (ignored)]"); } } } else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { throw new WebAuthException(context.getString(R.string.e_auth_failure, responseCode)); Loading @@ -166,7 +200,7 @@ class WebHelper { } } while (redirect); InputStream in = new BufferedInputStream(connection.getInputStream()); in = new BufferedInputStream(connection.getInputStream()); StringBuilder sb = new StringBuilder(); BufferedReader br = new BufferedReader(new InputStreamReader(in)); Loading @@ -175,21 +209,20 @@ class WebHelper { sb.append(inputLine); } response = sb.toString(); } catch (IOException e) { int responseCode; if (connection != null && (responseCode = connection.getResponseCode()) > 0) { if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { // eg. IOException: Received authentication challenge is null throw new WebAuthException(context.getString(R.string.e_auth_failure, responseCode)); } else { throw new IOException(context.getString(R.string.e_http_code, responseCode)); } finally { try { if (out != null) { out.close(); } if (in != null) { in.close(); } throw e; } finally { if (connection != null) { connection.disconnect(); } } catch (final IOException e) { if (Logger.DEBUG) { Log.d(TAG, "[connection cleanup failed (ignored)]"); } } } if (Logger.DEBUG) { Log.d(TAG, "[postWithParams response: " + response + "]"); } return response; Loading