diff options
author | cyberta <cyberta@riseup.net> | 2023-04-18 12:21:50 +0000 |
---|---|---|
committer | cyberta <cyberta@riseup.net> | 2023-04-18 12:21:50 +0000 |
commit | 7e1d7e2b0f4358be1e34cceef1282e6f8feb8a9e (patch) | |
tree | b27c49d88dc1e176823ab452177e3af712390809 | |
parent | 821cac0b60b85d0956cbe97de84766f660b907a6 (diff) | |
parent | 3ce9d2a5df2a193fd85f82b8201de57f1026302b (diff) |
Merge branch 'DoH' into 'master'
DoH
See merge request leap/bitmask_android!239
7 files changed, 128 insertions, 43 deletions
diff --git a/app/build.gradle b/app/build.gradle index 6db759b4..07cf4ca5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -426,7 +426,9 @@ dependencies { compileOnly 'com.squareup.dagger:dagger-compiler:1.2.2' implementation 'com.github.pedrovgs:renderers:1.5' implementation 'com.google.code.gson:gson:2.8.6' - implementation 'com.squareup.okhttp3:okhttp:3.12.12' + implementation 'com.squareup.okhttp3:okhttp:4.10.0' + implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.10.0' + implementation 'org.conscrypt:conscrypt-android:2.5.2' implementation 'androidx.legacy:legacy-support-core-utils:1.0.0' implementation 'androidx.annotation:annotation:1.4.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' diff --git a/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java b/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java index 828ef27d..0ccef0ae 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java @@ -34,6 +34,10 @@ import androidx.appcompat.app.AppCompatDelegate; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.multidex.MultiDexApplication; +import org.conscrypt.Conscrypt; + +import java.security.Security; + import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.appUpdate.DownloadBroadcastReceiver; import se.leap.bitmaskclient.base.models.ProviderObservable; @@ -59,6 +63,7 @@ public class BitmaskApp extends MultiDexApplication { super.onCreate(); // Normal app init code...*/ PRNGFixes.apply(); + Security.insertProviderAt(Conscrypt.newProvider(), 1); SharedPreferences preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); providerObservable = ProviderObservable.getInstance(); providerObservable.updateProvider(getSavedProviderFromSharedPreferences(preferences)); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java index d65f6b52..2412efdd 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/ConfigHelper.java @@ -277,7 +277,7 @@ public class ConfigHelper { } public static String getDomainFromMainURL(@NonNull String mainUrl) throws NullPointerException { - return PublicSuffixDatabase.get().getEffectiveTldPlusOne(mainUrl).replaceFirst("http[s]?://", "").replaceFirst("/.*", ""); + return PublicSuffixDatabase.Companion.get().getEffectiveTldPlusOne(mainUrl).replaceFirst("http[s]?://", "").replaceFirst("/.*", ""); } public static boolean isCalyxOSWithTetheringSupport(Context context) { diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java index 5655e7b7..e8249692 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java @@ -1,5 +1,9 @@ package se.leap.bitmaskclient.providersetup.connectivity; +import static java.net.InetAddress.getByName; + +import android.util.Log; + import androidx.annotation.NonNull; import java.net.InetAddress; @@ -9,34 +13,109 @@ import java.util.List; import de.blinkt.openvpn.core.VpnStatus; import okhttp3.Dns; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.dnsoverhttps.DnsOverHttps; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.ProviderObservable; import se.leap.bitmaskclient.base.utils.IPAddress; -class DnsResolver implements Dns { +public class DnsResolver implements Dns { + OkHttpClient dohHttpClient; + boolean preferDoH; + + public DnsResolver(OkHttpClient dohHttpClient, boolean preferDoH) { + this.dohHttpClient = dohHttpClient; + this.preferDoH = preferDoH; + } + @NonNull @Override public List<InetAddress> lookup(@NonNull String hostname) throws UnknownHostException { + Log.d("DNS", "trying to resolve DNS for " + hostname); + List<InetAddress> list = null; + if (preferDoH) { + if ((list = tryLookupDoH(hostname)) == null) { + list = tryLookupSystemDNS(hostname); + } + } else { + if ((list = tryLookupSystemDNS(hostname)) == null) { + list = tryLookupDoH(hostname); + } + } + + if (list != null) { + return list; + } + + Log.d("DNS", "try hard coded IPs"); + // let's check if there's an hard-coded IP we can use + ProviderObservable observable = ProviderObservable.getInstance(); + Provider currentProvider; + if (observable.getProviderForDns() != null) { + currentProvider = observable.getProviderForDns(); + } else { + currentProvider = observable.getCurrentProvider(); + } + String ip = currentProvider.getIpForHostname(hostname); + if (!ip.isEmpty()) { + VpnStatus.logWarning("[API] Normal DNS resolution for " + hostname + " seems to be blocked. Circumventing."); + ArrayList<InetAddress> addresses = new ArrayList<>(); + addresses.add(InetAddress.getByAddress(hostname, IPAddress.asBytes(ip))); + return addresses; + } else { + VpnStatus.logWarning("[API] Could not resolve DNS for " + hostname); + throw new UnknownHostException("Hostname " + hostname + " not found"); + } + } + + private List<InetAddress> tryLookupSystemDNS(@NonNull String hostname) throws RuntimeException, UnknownHostException { try { + Log.d("DNS", "trying to resolve " + hostname + "with system DNS"); return Dns.SYSTEM.lookup(hostname); } catch (UnknownHostException e) { - ProviderObservable observable = ProviderObservable.getInstance(); - Provider currentProvider; - if (observable.getProviderForDns() != null) { - currentProvider = observable.getProviderForDns(); - } else { - currentProvider = observable.getCurrentProvider(); - } - String ip = currentProvider.getIpForHostname(hostname); - if (!ip.isEmpty()) { - VpnStatus.logWarning("[API] Normal DNS resolution for " + hostname + " seems to be blocked. Circumventing."); - ArrayList<InetAddress> addresses = new ArrayList<>(); - addresses.add(InetAddress.getByAddress(hostname, IPAddress.asBytes(ip))); - return addresses; - } else { - VpnStatus.logWarning("[API] Could not resolve DNS for " + hostname); - throw new UnknownHostException("Hostname " + hostname + " not found"); - } + e.printStackTrace(); + return null; + } + } + + private List<InetAddress> tryLookupDoH(@NonNull String hostname) throws UnknownHostException { + DnsOverHttps njallaDoH = new DnsOverHttps.Builder().client(dohHttpClient) + .url(HttpUrl.get("https://dns.njal.la/dns-query")) + .bootstrapDnsHosts(getByName("95.215.19.53"), getByName("2001:67c:2354:2::53")) + .build(); + try { + Log.d("DNS", "DoH via dns.njal.la"); + return njallaDoH.lookup(hostname); + } catch (UnknownHostException e) { + e.printStackTrace(); + Log.e("DNS", "DoH via dns.njal.la failed"); + } + + DnsOverHttps quad9 = new DnsOverHttps.Builder().client(dohHttpClient) + .url(HttpUrl.get("https://dns.quad9.net/dns-query")) + .bootstrapDnsHosts(getByName("9.9.9.9"), getByName("149.112.112.112"), getByName("2620:fe::fe"), getByName("2620:fe::9")) + .build(); + try { + Log.d("DNS", "DoH via dns.quad9.net"); + return quad9.lookup(hostname); + } catch (UnknownHostException e) { + e.printStackTrace(); + Log.e("DNS", "DoH via dns.quad9.net failed"); + } + + DnsOverHttps cloudFlareDoHClient = new DnsOverHttps.Builder().client(dohHttpClient) + .url(HttpUrl.get("https://1.1.1.1/dns-query")) + .bootstrapDnsHosts(getByName("1.1.1.1"), getByName("1.0.0.1")) + .build(); + + try { + Log.d("DNS", "DoH via cloudflare 1.1.1.1"); + return cloudFlareDoHClient.lookup(hostname); + } catch (UnknownHostException e) { + e.printStackTrace(); + Log.e("DNS", "DoH via cloudflare failed"); } + return null; } } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/OkHttpClientGenerator.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/OkHttpClientGenerator.java index ea619263..97393551 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/OkHttpClientGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/OkHttpClientGenerator.java @@ -17,8 +17,16 @@ package se.leap.bitmaskclient.providersetup.connectivity; +import static android.text.TextUtils.isEmpty; +import static se.leap.bitmaskclient.R.string.certificate_error; +import static se.leap.bitmaskclient.R.string.error_io_exception_user_message; +import static se.leap.bitmaskclient.R.string.error_no_such_algorithm_exception_user_message; +import static se.leap.bitmaskclient.R.string.keyChainAccessError; +import static se.leap.bitmaskclient.R.string.server_unreachable_message; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.getProviderFormattedString; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; + import android.content.res.Resources; -import android.net.LocalSocketAddress; import android.os.Build; import androidx.annotation.NonNull; @@ -29,7 +37,6 @@ import org.json.JSONObject; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; -import java.net.SocketAddress; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.KeyStoreException; @@ -49,16 +56,6 @@ import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.TlsVersion; -import static android.text.TextUtils.isEmpty; -import static se.leap.bitmaskclient.R.string.certificate_error; -import static se.leap.bitmaskclient.R.string.error_io_exception_user_message; -import static se.leap.bitmaskclient.R.string.error_no_such_algorithm_exception_user_message; -import static se.leap.bitmaskclient.R.string.keyChainAccessError; -import static se.leap.bitmaskclient.R.string.proxy; -import static se.leap.bitmaskclient.R.string.server_unreachable_message; -import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; -import static se.leap.bitmaskclient.base.utils.ConfigHelper.getProviderFormattedString; - /** * Created by cyberta on 08.01.18. */ @@ -68,7 +65,7 @@ public class OkHttpClientGenerator { Resources resources; private final static String PROXY_HOST = "127.0.0.1"; - public OkHttpClientGenerator(/*SharedPreferences preferences,*/ Resources resources) { + public OkHttpClientGenerator(Resources resources) { this.resources = resources; } @@ -133,13 +130,15 @@ public class OkHttpClientGenerator { } else { sslCompatFactory = new TLSCompatSocketFactory(); } - sslCompatFactory.initSSLSocketFactory(clientBuilder); clientBuilder.cookieJar(getCookieJar()) .connectionSpecs(Collections.singletonList(spec)); - clientBuilder.dns(new DnsResolver()); + if (proxyPort != -1) { clientBuilder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_HOST, proxyPort))); } + + clientBuilder.dns(new DnsResolver(clientBuilder.build(), true)); + sslCompatFactory.initSSLSocketFactory(clientBuilder); return clientBuilder.build(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java index cc68b5a8..1420d666 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/TLSCompatSocketFactory.java @@ -28,8 +28,7 @@ import se.leap.bitmaskclient.base.utils.ConfigHelper; /** * Created by cyberta on 24.10.17. - * This class ensures that modern TLS algorithms will also be used on old devices (Android 4.1 - Android 4.4.4) in order to avoid - * attacks like POODLE. + * This class ensures that modern TLS algorithms will also be used on old devices */ public class TLSCompatSocketFactory extends SSLSocketFactory { @@ -150,9 +149,8 @@ public class TLSCompatSocketFactory extends SSLSocketFactory { } private Socket enableTLSOnSocket(Socket socket) throws IllegalArgumentException { - if(socket != null && (socket instanceof SSLSocket)) { - ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.2"}); - //TODO: add a android version check as soon as a new Android API or bcjsse supports TLSv1.3 + if((socket instanceof SSLSocket)) { + ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.3", "TLSv1.2"}); } return socket; diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java index d30e8b7e..c272970d 100644 --- a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java @@ -74,6 +74,7 @@ import se.leap.bitmaskclient.base.utils.ConfigHelper; import se.leap.bitmaskclient.base.utils.FileHelper; import se.leap.bitmaskclient.base.utils.InputStreamHelper; import se.leap.bitmaskclient.base.utils.PreferenceHelper; +import se.leap.bitmaskclient.providersetup.connectivity.DnsResolver; import se.leap.bitmaskclient.providersetup.connectivity.OkHttpClientGenerator; import se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider; import se.leap.bitmaskclient.testutils.matchers.BundleMatcher; @@ -577,13 +578,14 @@ public class MockHelper { public static OkHttpClientGenerator mockClientGenerator(boolean resolveDNS) throws UnknownHostException { OkHttpClientGenerator mockClientGenerator = mock(OkHttpClientGenerator.class); - OkHttpClient mockedOkHttpClient = mock(OkHttpClient.class, RETURNS_DEEP_STUBS); + OkHttpClient mockedOkHttpClient = mock(OkHttpClient.class); + DnsResolver mockedDnsResolver = mock(DnsResolver.class); when(mockClientGenerator.initCommercialCAHttpClient(any(JSONObject.class), anyInt())).thenReturn(mockedOkHttpClient); when(mockClientGenerator.initSelfSignedCAHttpClient(anyString(), anyInt(), any(JSONObject.class))).thenReturn(mockedOkHttpClient); if (resolveDNS) { - when(mockedOkHttpClient.dns().lookup(anyString())).thenReturn(new ArrayList<>()); + when(mockedDnsResolver.lookup(anyString())).thenReturn(new ArrayList<>()); } else { - when(mockedOkHttpClient.dns().lookup(anyString())).thenThrow(new UnknownHostException()); + when(mockedDnsResolver.lookup(anyString())).thenThrow(new UnknownHostException()); } return mockClientGenerator; } |