From 6a015d337d6a786adb319c3f9de7b9b7e9ae80bb Mon Sep 17 00:00:00 2001 From: cyberta Date: Tue, 21 Jan 2020 16:10:59 -0600 Subject: implement no-dns fallback using okhttp's Dns interface --- .../java/se/leap/bitmaskclient/DnsResolver.java | 33 ++++++ .../se/leap/bitmaskclient/EipSetupObserver.java | 4 + .../leap/bitmaskclient/OkHttpClientGenerator.java | 1 + .../main/java/se/leap/bitmaskclient/Provider.java | 41 ++++++-- .../bitmaskclient/ProviderListBaseActivity.java | 3 +- .../se/leap/bitmaskclient/ProviderManager.java | 24 +++-- .../se/leap/bitmaskclient/ProviderObservable.java | 12 ++- .../se/leap/bitmaskclient/utils/IPAddress.java | 117 +++++++++++++++++++++ .../leap/bitmaskclient/utils/PreferenceHelper.java | 7 +- 9 files changed, 222 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/se/leap/bitmaskclient/DnsResolver.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/utils/IPAddress.java (limited to 'app/src/main') diff --git a/app/src/main/java/se/leap/bitmaskclient/DnsResolver.java b/app/src/main/java/se/leap/bitmaskclient/DnsResolver.java new file mode 100644 index 00000000..55cf1123 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/DnsResolver.java @@ -0,0 +1,33 @@ +package se.leap.bitmaskclient; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; + +import okhttp3.Dns; + +class DnsResolver implements Dns { + + @Override + public List lookup(String hostname) throws UnknownHostException { + try { + return Dns.SYSTEM.lookup(hostname); + } catch (UnknownHostException e) { + ProviderObservable observable = ProviderObservable.getInstance(); + Provider currentProvider; + if (observable.getProviderToSetup() != null) { + currentProvider = observable.getProviderToSetup(); + } else { + currentProvider = observable.getCurrentProvider(); + } + if (currentProvider != null && currentProvider.hasProviderIp()) { + ArrayList addresses = new ArrayList<>(); + addresses.add(InetAddress.getByAddress(hostname, currentProvider.getProviderIpAsBytes())); + return addresses; + } else { + throw new UnknownHostException("Hostname " + hostname + " not found"); + } + } + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/EipSetupObserver.java b/app/src/main/java/se/leap/bitmaskclient/EipSetupObserver.java index eceba0db..486c26bb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipSetupObserver.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipSetupObserver.java @@ -47,6 +47,7 @@ import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE; import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.ERRORS; +import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_NOK; import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences; /** @@ -155,6 +156,9 @@ class EipSetupObserver extends BroadcastReceiver implements VpnStatus.StateListe PreferenceHelper.storeProviderInPreferences(preferences, provider); EipCommand.startVPN(context.getApplicationContext(), true); break; + case PROVIDER_NOK: + ProviderObservable.getInstance().setProviderToSetup(null); + break; default: break; } diff --git a/app/src/main/java/se/leap/bitmaskclient/OkHttpClientGenerator.java b/app/src/main/java/se/leap/bitmaskclient/OkHttpClientGenerator.java index 69270140..9d1168aa 100644 --- a/app/src/main/java/se/leap/bitmaskclient/OkHttpClientGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/OkHttpClientGenerator.java @@ -88,6 +88,7 @@ public class OkHttpClientGenerator { sslCompatFactory.initSSLSocketFactory(clientBuilder); clientBuilder.cookieJar(getCookieJar()) .connectionSpecs(Collections.singletonList(spec)); + clientBuilder.dns(new DnsResolver()); return clientBuilder.build(); } catch (IllegalArgumentException e) { e.printStackTrace(); diff --git a/app/src/main/java/se/leap/bitmaskclient/Provider.java b/app/src/main/java/se/leap/bitmaskclient/Provider.java index b98c3fd3..3dfee72c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Provider.java +++ b/app/src/main/java/se/leap/bitmaskclient/Provider.java @@ -29,6 +29,8 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Locale; +import se.leap.bitmaskclient.utils.IPAddress; + import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static se.leap.bitmaskclient.Constants.CAPABILITIES; import static se.leap.bitmaskclient.Constants.GATEWAYS; @@ -49,6 +51,7 @@ public final class Provider implements Parcelable { private JSONObject eipServiceJson = new JSONObject(); private DefaultedURL mainUrl = new DefaultedURL(); private DefaultedURL apiUrl = new DefaultedURL(); + private String providerIp = ""; private String certificatePin = ""; private String certificatePinEncoding = ""; private String caCert = ""; @@ -73,7 +76,8 @@ public final class Provider implements Parcelable { NAME = "name", DESCRIPTION = "description", DOMAIN = "domain", - MAIN_URL = "main_url"; + MAIN_URL = "main_url", + PROVIDER_IP = "provider_ip"; private static final String API_TERM_NAME = "name"; @@ -87,12 +91,16 @@ public final class Provider implements Parcelable { } } - public Provider(URL mainUrl) { + public Provider(URL mainUrl, String providerIp) { this.mainUrl.setUrl(mainUrl); + this.providerIp = providerIp; } - public Provider(URL mainUrl, String caCert, String definition) { + public Provider(URL mainUrl, String providerIp, String caCert, String definition) { this.mainUrl.setUrl(mainUrl); + if (this.providerIp != null) { + this.providerIp = providerIp; + } if (caCert != null) { this.caCert = caCert; } @@ -146,6 +154,18 @@ public final class Provider implements Parcelable { return false; } + public void setProviderIp(String providerIp) { + this.providerIp = providerIp; + } + + public String getProviderIp() { + return this.providerIp; + } + + public byte[] getProviderIpAsBytes() { + return IPAddress.asBytes(providerIp); + } + public void setMainUrl(URL url) { mainUrl.setUrl(url); } @@ -192,7 +212,7 @@ public final class Provider implements Parcelable { } - protected String getApiUrlString() { + public String getApiUrlString() { return getApiUrl().toString(); } @@ -265,6 +285,7 @@ public final class Provider implements Parcelable { @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeString(getMainUrlString()); + parcel.writeString(getProviderIp()); parcel.writeString(getDefinitionString()); parcel.writeString(getCaCert()); parcel.writeString(getEipServiceJsonString()); @@ -281,6 +302,7 @@ public final class Provider implements Parcelable { definition.toString().equals(p.getDefinition().toString()) && eipServiceJson.toString().equals(p.getEipServiceJson().toString())&& mainUrl.equals(p.getMainUrl()) && + providerIp.equals(p.getProviderIp()) && apiUrl.equals(p.getApiUrl()) && certificatePin.equals(p.getCertificatePin()) && certificatePinEncoding.equals(p.getCertificatePinEncoding()) && @@ -298,7 +320,6 @@ public final class Provider implements Parcelable { JSONObject json = new JSONObject(); try { json.put(Provider.MAIN_URL, mainUrl); - //TODO: add other fields here? } catch (JSONException e) { e.printStackTrace(); } @@ -320,6 +341,10 @@ public final class Provider implements Parcelable { try { mainUrl.setUrl(new URL(in.readString())); String tmpString = in.readString(); + if (!tmpString.isEmpty()) { + providerIp = tmpString; + } + tmpString = in.readString(); if (!tmpString.isEmpty()) { definition = new JSONObject((tmpString)); parseDefinition(definition); @@ -444,8 +469,12 @@ public final class Provider implements Parcelable { return getCertificatePinEncoding() + ":" + getCertificatePin(); } + public boolean hasProviderIp() { + return !providerIp.isEmpty(); + } + /** - * resets everything except the main url + * resets everything except the main url and the providerIp */ public void reset() { definition = new JSONObject(); diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java index c8070e18..bae237f6 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java @@ -109,7 +109,7 @@ public abstract class ProviderListBaseActivity extends ProviderSetupBaseActivity public void showAndSelectProvider(String newURL) { try { - provider = new Provider(new URL((newURL))); + provider = new Provider(new URL((newURL)), ""); autoSelectProvider(); } catch (MalformedURLException e) { e.printStackTrace(); @@ -165,6 +165,7 @@ public abstract class ProviderListBaseActivity extends ProviderSetupBaseActivity if (provider != null && !provider.isDefault()) { //TODO Code 2 pane view providerConfigState = SETTING_UP_PROVIDER; + ProviderObservable.getInstance().setProviderToSetup(provider); showProgressBar(); onItemSelectedLogic(); } else { diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java index dd9070af..134e2d76 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java @@ -21,6 +21,8 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import static se.leap.bitmaskclient.Provider.MAIN_URL; +import static se.leap.bitmaskclient.Provider.PROVIDER_IP; import static se.leap.bitmaskclient.utils.FileHelper.createFile; import static se.leap.bitmaskclient.utils.FileHelper.persistFile; import static se.leap.bitmaskclient.utils.InputStreamHelper.getInputStreamFrom; @@ -84,19 +86,21 @@ public class ProviderManager implements AdapteeCollection { for (String file : relativeFilePaths) { String mainUrl = null; + String providerIp = null; String certificate = null; String providerDefinition = null; try { String provider = file.substring(0, file.length() - ".url".length()); - InputStream provider_file = assetsManager.open(directory + "/" + file); - mainUrl = extractMainUrlFromInputStream(provider_file); + InputStream providerFile = assetsManager.open(directory + "/" + file); + mainUrl = extractKeyFromInputStream(providerFile, MAIN_URL); + providerIp = extractKeyFromInputStream(providerFile, PROVIDER_IP); certificate = loadInputStreamAsString(assetsManager.open(provider + EXT_PEM)); providerDefinition = loadInputStreamAsString(assetsManager.open(provider + EXT_JSON)); } catch (IOException e) { e.printStackTrace(); } try { - providers.add(new Provider(new URL(mainUrl), certificate, providerDefinition)); + providers.add(new Provider(new URL(mainUrl), providerIp, certificate, providerDefinition)); } catch (MalformedURLException e) { e.printStackTrace(); } @@ -118,8 +122,10 @@ public class ProviderManager implements AdapteeCollection { Set providers = new HashSet<>(); try { for (String file : files) { - String mainUrl = extractMainUrlFromInputStream(getInputStreamFrom(externalFilesDir.getAbsolutePath() + "/" + file)); - providers.add(new Provider(new URL(mainUrl))); + InputStream inputStream = getInputStreamFrom(externalFilesDir.getAbsolutePath() + "/" + file); + String mainUrl = extractKeyFromInputStream(inputStream, MAIN_URL); + String providerIp = extractKeyFromInputStream(inputStream, PROVIDER_IP); + providers.add(new Provider(new URL(mainUrl), providerIp)); } } catch (MalformedURLException | FileNotFoundException | NullPointerException e) { e.printStackTrace(); @@ -128,13 +134,13 @@ public class ProviderManager implements AdapteeCollection { return providers; } - private String extractMainUrlFromInputStream(InputStream inputStream) { - String mainUrl = ""; + private String extractKeyFromInputStream(InputStream inputStream, String key) { + String value = ""; JSONObject fileContents = inputStreamToJson(inputStream); if (fileContents != null) - mainUrl = fileContents.optString(Provider.MAIN_URL); - return mainUrl; + value = fileContents.optString(key); + return value; } private JSONObject inputStreamToJson(InputStream inputStream) { diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderObservable.java b/app/src/main/java/se/leap/bitmaskclient/ProviderObservable.java index 776c0e92..f20599ba 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderObservable.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderObservable.java @@ -2,14 +2,13 @@ package se.leap.bitmaskclient; import java.util.Observable; -import se.leap.bitmaskclient.utils.PreferenceHelper; - /** * Created by cyberta on 05.12.18. */ public class ProviderObservable extends Observable { private static ProviderObservable instance; private Provider currentProvider; + private Provider providerToSetup; public static ProviderObservable getInstance() { if (instance == null) { @@ -20,6 +19,7 @@ public class ProviderObservable extends Observable { public synchronized void updateProvider(Provider provider) { instance.currentProvider = provider; + instance.providerToSetup = null; instance.setChanged(); instance.notifyObservers(); } @@ -28,4 +28,12 @@ public class ProviderObservable extends Observable { return instance.currentProvider; } + public void setProviderToSetup(Provider provider) { + this.providerToSetup = provider; + } + + public Provider getProviderToSetup() { + return instance.providerToSetup; + } + } diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/IPAddress.java b/app/src/main/java/se/leap/bitmaskclient/utils/IPAddress.java new file mode 100644 index 00000000..8b419a2c --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/utils/IPAddress.java @@ -0,0 +1,117 @@ +package se.leap.bitmaskclient.utils; + +/* + * Copyright (C) 2006-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +//package net.gqu.utils; + +import java.util.StringTokenizer; + +/** + * TCP/IP Address Utility Class + * + * @author gkspencer + */ +public class IPAddress { + + + /** + * Convert a TCP/IP address string into a byte array + * + * @param addr String + * @return byte[] + */ + public final static byte[] asBytes(String addr) { + + // Convert the TCP/IP address string to an integer value + + int ipInt = parseNumericAddress(addr); + if ( ipInt == 0) + return null; + + // Convert to bytes + + byte[] ipByts = new byte[4]; + + ipByts[3] = (byte) (ipInt & 0xFF); + ipByts[2] = (byte) ((ipInt >> 8) & 0xFF); + ipByts[1] = (byte) ((ipInt >> 16) & 0xFF); + ipByts[0] = (byte) ((ipInt >> 24) & 0xFF); + + // Return the TCP/IP bytes + + return ipByts; + } + /** + * Check if the specified address is a valid numeric TCP/IP address and return as an integer value + * + * @param ipaddr String + * @return int + */ + public final static int parseNumericAddress(String ipaddr) { + + // Check if the string is valid + + if ( ipaddr == null || ipaddr.length() < 7 || ipaddr.length() > 15) + return 0; + + // Check the address string, should be n.n.n.n format + + StringTokenizer token = new StringTokenizer(ipaddr,"."); + if ( token.countTokens() != 4) + return 0; + + int ipInt = 0; + + while ( token.hasMoreTokens()) { + + // Get the current token and convert to an integer value + + String ipNum = token.nextToken(); + + try { + + // Validate the current address part + + int ipVal = Integer.valueOf(ipNum).intValue(); + if ( ipVal < 0 || ipVal > 255) + return 0; + + // Add to the integer address + + ipInt = (ipInt << 8) + ipVal; + } + catch (NumberFormatException ex) { + return 0; + } + } + + // Return the integer address + + return ipInt; + } + + +} \ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java index 40bb2eca..b6e39c50 100644 --- a/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java @@ -2,7 +2,6 @@ package se.leap.bitmaskclient.utils; import android.content.Context; import android.content.SharedPreferences; -import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -24,6 +23,7 @@ import se.leap.bitmaskclient.Provider; import static android.content.Context.MODE_PRIVATE; import static se.leap.bitmaskclient.Constants.ALWAYS_ON_SHOW_DIALOG; import static se.leap.bitmaskclient.Constants.DEFAULT_SHARED_PREFS_BATTERY_SAVER; +import static se.leap.bitmaskclient.Constants.EXCLUDED_APPS; import static se.leap.bitmaskclient.Constants.LAST_USED_PROFILE; import static se.leap.bitmaskclient.Constants.PREFERENCES_APP_VERSION; import static se.leap.bitmaskclient.Constants.PROVIDER_CONFIGURED; @@ -33,7 +33,6 @@ import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.Constants.SU_PERMISSION; import static se.leap.bitmaskclient.Constants.USE_PLUGGABLE_TRANSPORTS; -import static se.leap.bitmaskclient.Constants.EXCLUDED_APPS; /** * Created by cyberta on 18.03.18. @@ -48,6 +47,7 @@ public class PreferenceHelper { Provider provider = new Provider(); try { provider.setMainUrl(new URL(preferences.getString(Provider.MAIN_URL, ""))); + provider.setProviderIp(preferences.getString(Provider.PROVIDER_IP, "")); provider.define(new JSONObject(preferences.getString(Provider.KEY, ""))); provider.setCaCert(preferences.getString(Provider.CA_CERT, "")); provider.setVpnCertificate(preferences.getString(PROVIDER_VPN_CERTIFICATE, "")); @@ -131,6 +131,7 @@ public class PreferenceHelper { //FIXME: don't save private keys in shared preferences! use the keystore public static void storeProviderInPreferences(SharedPreferences preferences, Provider provider) { preferences.edit().putBoolean(PROVIDER_CONFIGURED, true). + putString(Provider.PROVIDER_IP, provider.getProviderIp()). putString(Provider.MAIN_URL, provider.getMainUrlString()). putString(Provider.KEY, provider.getDefinitionString()). putString(Provider.CA_CERT, provider.getCaCert()). @@ -141,6 +142,7 @@ public class PreferenceHelper { String providerDomain = provider.getDomain(); preferences.edit().putBoolean(PROVIDER_CONFIGURED, true). + putString(Provider.PROVIDER_IP + "." + providerDomain, provider.getProviderIp()). putString(Provider.MAIN_URL + "." + providerDomain, provider.getMainUrlString()). putString(Provider.KEY + "." + providerDomain, provider.getDefinitionString()). putString(Provider.CA_CERT + "." + providerDomain, provider.getCaCert()). @@ -206,6 +208,7 @@ public class PreferenceHelper { remove(Provider.KEY + "." + providerDomain). remove(Provider.CA_CERT + "." + providerDomain). remove(Provider.CA_CERT_FINGERPRINT + "." + providerDomain). + remove(Provider.PROVIDER_IP + "." + providerDomain). remove(Provider.MAIN_URL + "." + providerDomain). remove(Provider.KEY + "." + providerDomain). remove(Provider.CA_CERT + "." + providerDomain). -- cgit v1.2.3