summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorArne Schwabe <arne@rfc2549.org>2019-04-05 17:15:21 +0200
committerArne Schwabe <arne@rfc2549.org>2019-08-05 16:01:34 +0200
commit03d93d1fb7281da7d57af9a21f9b16dd580b276b (patch)
tree2d1d797159f239d5ad7ca64ec285269e87ef7cd5 /main
parent4c31594443fc630b86c7d75f73da2004f7bfdc09 (diff)
Fix KeyChain not selecting EC and fix manifest
Also convert another file to Kotlin
Diffstat (limited to 'main')
-rw-r--r--main/src/main/AndroidManifest.xml3
-rw-r--r--main/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java2
-rw-r--r--main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java249
-rw-r--r--main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.kt249
-rw-r--r--main/src/main/res/layout/keystore_selector.xml25
-rwxr-xr-xmain/src/main/res/values/strings.xml1
6 files changed, 273 insertions, 256 deletions
diff --git a/main/src/main/AndroidManifest.xml b/main/src/main/AndroidManifest.xml
index 92120282..9cf728f3 100644
--- a/main/src/main/AndroidManifest.xml
+++ b/main/src/main/AndroidManifest.xml
@@ -171,7 +171,7 @@
<data android:pathPattern=".*.ovpn" />
</intent-filter>
</activity>
- // https://commonsware.com/blog/2019/03/27/death-external-storage-stay-away-files.html
+ <!-- https://commonsware.com/blog/2019/03/27/death-external-storage-stay-away-files.html -->
<activity-alias
android:enabled="@bool/supportFileScheme"
android:name=".activities.ConfigConverterFile"
@@ -190,7 +190,6 @@
<data android:pathPattern=".*..*..*.ovpn" />
<data android:pathPattern=".*..*.ovpn" />
<data android:pathPattern=".*.ovpn" />
- />
</intent-filter>
</activity-alias>
diff --git a/main/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java b/main/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java
index 8d0c5433..38b47b5a 100644
--- a/main/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java
+++ b/main/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java
@@ -263,7 +263,7 @@ public class ConfigConverter extends BaseActivity implements FileSelectCallback,
},
- new String[]{"RSA"}, // List of acceptable key types. null for any
+ new String[]{"RSA", "EC"}, // List of acceptable key types. null for any
null, // issuer, null for any
mResult.mServerName, // host name of server requesting the cert, null if unavailable
-1, // port of server requesting the cert, -1 if unavailable
diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java b/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java
deleted file mode 100644
index 16f0438d..00000000
--- a/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (c) 2012-2018 Arne Schwabe
- * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
- */
-
-package de.blinkt.openvpn.fragments;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.security.KeyChain;
-import android.security.KeyChainException;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.Spinner;
-import android.widget.TextView;
-import android.widget.Toast;
-import de.blinkt.openvpn.R;
-import de.blinkt.openvpn.VpnProfile;
-import de.blinkt.openvpn.api.ExternalCertificateProvider;
-import de.blinkt.openvpn.core.ExtAuthHelper;
-import de.blinkt.openvpn.core.X509Utils;
-
-import java.security.cert.X509Certificate;
-
-abstract class KeyChainSettingsFragment extends Settings_Fragment implements View.OnClickListener, Handler.Callback {
- private static final int UPDATE_ALIAS = 20;
- private static final int UPDATEE_EXT_ALIAS = 210;
-
-
- private TextView mAliasCertificate;
- private TextView mAliasName;
- private Handler mHandler;
- private TextView mExtAliasName;
- private Spinner mExtAuthSpinner;
-
-
- private void setKeyStoreAlias() {
- if (mProfile.mAlias == null) {
- mAliasName.setText(R.string.client_no_certificate);
- mAliasCertificate.setText("");
- } else {
- mAliasCertificate.setText("Loading certificate from Keystore...");
- mAliasName.setText(mProfile.mAlias);
- setCertificate(false);
- }
- }
-
- private void setExtAlias() {
- if (mProfile.mAlias == null) {
- mExtAliasName.setText(R.string.extauth_not_configured);
- mAliasCertificate.setText("");
- } else {
- mAliasCertificate.setText("Querying certificate from external provider...");
- mExtAliasName.setText("");
- setCertificate(true);
- }
- }
-
- private void fetchExtCertificateMetaData() {
- new Thread() {
- @Override
- public void run() {
- try {
- Bundle b = ExtAuthHelper.getCertificateMetaData(getActivity(), mProfile.mExternalAuthenticator, mProfile.mAlias);
- mProfile.mAlias = b.getString(ExtAuthHelper.EXTRA_ALIAS);
- getActivity().runOnUiThread(() -> setAlias());
- } catch (KeyChainException e) {
- e.printStackTrace();
- }
-
- }
- }.start();
- }
-
-
- protected void setCertificate(boolean external) {
- new Thread() {
- public void run() {
- String certstr = "";
- Bundle metadata= null;
- try {
- X509Certificate cert;
-
- if (external) {
- if (!TextUtils.isEmpty(mProfile.mExternalAuthenticator) && !TextUtils.isEmpty(mProfile.mAlias)) {
- cert = ExtAuthHelper.getCertificateChain(getActivity(), mProfile.mExternalAuthenticator, mProfile.mAlias)[0];
- metadata = ExtAuthHelper.getCertificateMetaData(getActivity(), mProfile.mExternalAuthenticator, mProfile.mAlias);
- } else {
- cert = null;
- certstr = getString(R.string.extauth_not_configured);
- }
- } else {
- cert = KeyChain.getCertificateChain(getActivity().getApplicationContext(), mProfile.mAlias)[0];
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
- {
- if (isInHardwareKeystore())
- certstr += getString(R.string.hwkeychain);
- }
- }
- }
- if (cert!=null) {
- certstr += X509Utils.getCertificateValidityString(cert, getResources());
- certstr += X509Utils.getCertificateFriendlyName(cert);
- }
-
-
-
- } catch (Exception e) {
- certstr = "Could not get certificate from Keystore: " + e.getLocalizedMessage();
- }
-
- final String certStringCopy = certstr;
- Bundle finalMetadata = metadata;
- getActivity().runOnUiThread(() -> {
- mAliasCertificate.setText(certStringCopy);
- if (finalMetadata!=null)
- mExtAliasName.setText(finalMetadata.getString(ExtAuthHelper.EXTRA_DESCRIPTION));
-
- });
-
- }
- }.start();
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
- private boolean isInHardwareKeystore() throws KeyChainException, InterruptedException {
- String algorithm = KeyChain.getPrivateKey(getActivity().getApplicationContext(), mProfile.mAlias).getAlgorithm();
- return KeyChain.isBoundKeyAlgorithm(algorithm);
- }
-
- protected void initKeychainViews(View v) {
- v.findViewById(R.id.select_keystore_button).setOnClickListener(this);
- v.findViewById(R.id.configure_extauth_button).setOnClickListener(this);
- mAliasCertificate = v.findViewById(R.id.alias_certificate);
- mExtAuthSpinner = v.findViewById(R.id.extauth_spinner);
- mExtAuthSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- ExtAuthHelper.ExternalAuthProvider selectedProvider = (ExtAuthHelper.ExternalAuthProvider) parent.getItemAtPosition(position);
- if (!selectedProvider.packageName.equals(mProfile.mExternalAuthenticator)) {
- mProfile.mAlias = "";
- }
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
-
- }
- });
- mExtAliasName = v.findViewById(R.id.extauth_detail);
- mAliasName = v.findViewById(R.id.aliasname);
- if (mHandler == null) {
- mHandler = new Handler(this);
- }
- ExtAuthHelper.setExternalAuthProviderSpinnerList(mExtAuthSpinner, mProfile.mExternalAuthenticator);
- }
-
- @Override
- public void onClick(View v) {
- if (v == v.findViewById(R.id.select_keystore_button)) {
- showCertDialog();
- } else if (v == v.findViewById(R.id.configure_extauth_button)) {
- startExternalAuthConfig();
- }
- }
-
- private void startExternalAuthConfig() {
- ExtAuthHelper.ExternalAuthProvider eAuth = (ExtAuthHelper.ExternalAuthProvider) mExtAuthSpinner.getSelectedItem();
- mProfile.mExternalAuthenticator = eAuth.packageName;
- if (!eAuth.configurable) {
- fetchExtCertificateMetaData();
- return;
- }
- Intent extauth = new Intent(ExtAuthHelper.ACTION_CERT_CONFIGURATION);
- extauth.setPackage(eAuth.packageName);
- extauth.putExtra(ExtAuthHelper.EXTRA_ALIAS, mProfile.mAlias);
- startActivityForResult(extauth, UPDATEE_EXT_ALIAS);
- }
-
- @Override
- protected void savePreferences() {
-
- }
-
- @Override
- public void onStart() {
- super.onStart();
- loadPreferences();
- }
-
- @SuppressWarnings("WrongConstant")
- public void showCertDialog() {
- try {
- KeyChain.choosePrivateKeyAlias(getActivity(),
- alias -> {
- // Credential alias selected. Remember the alias selection for future use.
- mProfile.mAlias = alias;
- mHandler.sendEmptyMessage(UPDATE_ALIAS);
- },
- new String[]{"RSA"}, // List of acceptable key types. null for any
- null, // issuer, null for any
- mProfile.mServerName, // host name of server requesting the cert, null if unavailable
- -1, // port of server requesting the cert, -1 if unavailable
- mProfile.mAlias); // alias to preselect, null if unavailable
- } catch (ActivityNotFoundException anf) {
- AlertDialog.Builder ab = new AlertDialog.Builder(getActivity());
- ab.setTitle(R.string.broken_image_cert_title);
- ab.setMessage(R.string.broken_image_cert);
- ab.setPositiveButton(android.R.string.ok, null);
- ab.show();
- }
- }
-
- protected void loadPreferences() {
- setAlias();
-
- }
-
- private void setAlias() {
- if (mProfile.mAuthenticationType == VpnProfile.TYPE_EXTERNAL_APP)
- setExtAlias();
- else
- setKeyStoreAlias();
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- setAlias();
- return true;
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
-
- if (requestCode == UPDATEE_EXT_ALIAS && resultCode == Activity.RESULT_OK) {
- mProfile.mAlias = data.getStringExtra(ExtAuthHelper.EXTRA_ALIAS);
- mExtAliasName.setText(data.getStringExtra(ExtAuthHelper.EXTRA_DESCRIPTION));
- }
- }
-}
diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.kt b/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.kt
new file mode 100644
index 00000000..fe074c63
--- /dev/null
+++ b/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.kt
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2012-2018 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.fragments
+
+import android.annotation.TargetApi
+import android.app.Activity
+import android.app.AlertDialog
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.os.Message
+import android.security.KeyChain
+import android.security.KeyChainException
+import android.text.TextUtils
+import android.view.View
+import android.widget.AdapterView
+import android.widget.Spinner
+import android.widget.TextView
+import android.widget.Toast
+import de.blinkt.openvpn.R
+import de.blinkt.openvpn.VpnProfile
+import de.blinkt.openvpn.api.ExternalCertificateProvider
+import de.blinkt.openvpn.core.ExtAuthHelper
+import de.blinkt.openvpn.core.X509Utils
+
+import java.security.cert.X509Certificate
+
+internal abstract class KeyChainSettingsFragment : Settings_Fragment(), View.OnClickListener, Handler.Callback {
+
+
+ private lateinit var mAliasCertificate: TextView
+ private lateinit var mAliasName: TextView
+ private var mHandler: Handler? = null
+ private lateinit var mExtAliasName: TextView
+ private lateinit var mExtAuthSpinner: Spinner
+
+ private val isInHardwareKeystore: Boolean
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
+ @Throws(KeyChainException::class, InterruptedException::class)
+ get() {
+ val algorithm = KeyChain.getPrivateKey(activity.applicationContext, mProfile.mAlias)!!.algorithm
+ return KeyChain.isBoundKeyAlgorithm(algorithm)
+ }
+
+
+ private fun setKeyStoreAlias() {
+ if (mProfile.mAlias == null) {
+ mAliasName.setText(R.string.client_no_certificate)
+ mAliasCertificate.text = ""
+ } else {
+ mAliasCertificate.text = "Loading certificate from Keystore..."
+ mAliasName.text = mProfile.mAlias
+ setCertificate(false)
+ }
+ }
+
+ private fun setExtAlias() {
+ if (mProfile.mAlias == null) {
+ mExtAliasName.setText(R.string.extauth_not_configured)
+ mAliasCertificate.text = ""
+ } else {
+ mAliasCertificate.text = "Querying certificate from external provider..."
+ mExtAliasName.text = ""
+ setCertificate(true)
+ }
+ }
+
+ private fun fetchExtCertificateMetaData() {
+ object : Thread() {
+ override fun run() {
+ try {
+ val b = ExtAuthHelper.getCertificateMetaData(activity, mProfile.mExternalAuthenticator, mProfile.mAlias)
+ mProfile.mAlias = b.getString(ExtAuthHelper.EXTRA_ALIAS)
+ activity.runOnUiThread { setAlias() }
+ } catch (e: KeyChainException) {
+ e.printStackTrace()
+ }
+
+ }
+ }.start()
+ }
+
+
+ protected fun setCertificate(external: Boolean) {
+ object : Thread() {
+ override fun run() {
+ var certstr = ""
+ var metadata: Bundle? = null
+ try {
+ val cert: X509Certificate?
+
+ if (external) {
+ if (!TextUtils.isEmpty(mProfile.mExternalAuthenticator) && !TextUtils.isEmpty(mProfile.mAlias)) {
+ cert = ExtAuthHelper.getCertificateChain(activity, mProfile.mExternalAuthenticator, mProfile.mAlias)!![0]
+ metadata = ExtAuthHelper.getCertificateMetaData(activity, mProfile.mExternalAuthenticator, mProfile.mAlias)
+ } else {
+ cert = null
+ certstr = getString(R.string.extauth_not_configured)
+ }
+ } else {
+ cert = KeyChain.getCertificateChain(activity.applicationContext, mProfile.mAlias)!![0]
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ run {
+ if (isInHardwareKeystore)
+ certstr += getString(R.string.hwkeychain)
+ }
+ }
+ }
+ if (cert != null) {
+ certstr += X509Utils.getCertificateValidityString(cert, resources)
+ certstr += X509Utils.getCertificateFriendlyName(cert)
+ }
+
+
+ } catch (e: Exception) {
+ certstr = "Could not get certificate from Keystore: " + e.localizedMessage!!
+ }
+
+ val certStringCopy = certstr
+ val finalMetadata = metadata
+ activity.runOnUiThread {
+ mAliasCertificate.text = certStringCopy
+ if (finalMetadata != null)
+ mExtAliasName.text = finalMetadata.getString(ExtAuthHelper.EXTRA_DESCRIPTION)
+
+ }
+
+ }
+ }.start()
+ }
+
+ protected fun initKeychainViews(v: View) {
+ v.findViewById<View>(R.id.select_keystore_button).setOnClickListener(this)
+ v.findViewById<View>(R.id.configure_extauth_button).setOnClickListener(this)
+ v.findViewById<View>(R.id.install_keystore_button).setOnClickListener(this)
+ mAliasCertificate = v.findViewById(R.id.alias_certificate)
+ mExtAuthSpinner = v.findViewById(R.id.extauth_spinner)
+ mExtAuthSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
+ val selectedProvider = parent.getItemAtPosition(position) as ExtAuthHelper.ExternalAuthProvider
+ if (selectedProvider.packageName != mProfile.mExternalAuthenticator) {
+ mProfile.mAlias = ""
+ }
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>) {
+
+ }
+ }
+ mExtAliasName = v.findViewById(R.id.extauth_detail)
+ mAliasName = v.findViewById(R.id.aliasname)
+ if (mHandler == null) {
+ mHandler = Handler(this)
+ }
+ ExtAuthHelper.setExternalAuthProviderSpinnerList(mExtAuthSpinner, mProfile.mExternalAuthenticator)
+
+ v.findViewById<View>(R.id.install_keystore_button).setOnClickListener {
+ startActivity(KeyChain.createInstallIntent()) };
+ }
+
+ override fun onClick(v: View) {
+ if (v === v.findViewById<View>(R.id.select_keystore_button)) {
+ showCertDialog()
+ } else if (v === v.findViewById<View>(R.id.configure_extauth_button)) {
+ startExternalAuthConfig()
+ }
+ }
+
+ private fun startExternalAuthConfig() {
+ val eAuth = mExtAuthSpinner!!.selectedItem as ExtAuthHelper.ExternalAuthProvider
+ mProfile.mExternalAuthenticator = eAuth.packageName
+ if (!eAuth.configurable) {
+ fetchExtCertificateMetaData()
+ return
+ }
+ val extauth = Intent(ExtAuthHelper.ACTION_CERT_CONFIGURATION)
+ extauth.setPackage(eAuth.packageName)
+ extauth.putExtra(ExtAuthHelper.EXTRA_ALIAS, mProfile.mAlias)
+ startActivityForResult(extauth, UPDATEE_EXT_ALIAS)
+ }
+
+ override fun savePreferences() {
+
+ }
+
+ override fun onStart() {
+ super.onStart()
+ loadPreferences()
+ }
+
+ fun showCertDialog() {
+ try {
+ KeyChain.choosePrivateKeyAlias(activity,
+ { alias ->
+ // Credential alias selected. Remember the alias selection for future use.
+ mProfile.mAlias = alias
+ mHandler!!.sendEmptyMessage(UPDATE_ALIAS)
+ },
+ arrayOf("RSA", "EC"), null, // issuer, null for any
+ mProfile.mServerName, // host name of server requesting the cert, null if unavailable
+ -1, // port of server requesting the cert, -1 if unavailable
+ mProfile.mAlias)// List of acceptable key types. null for any
+ // alias to preselect, null if unavailable
+ } catch (anf: ActivityNotFoundException) {
+ val ab = AlertDialog.Builder(activity)
+ ab.setTitle(R.string.broken_image_cert_title)
+ ab.setMessage(R.string.broken_image_cert)
+ ab.setPositiveButton(android.R.string.ok, null)
+ ab.show()
+ }
+
+ }
+
+ protected open fun loadPreferences() {
+ setAlias()
+
+ }
+
+ private fun setAlias() {
+ if (mProfile.mAuthenticationType == VpnProfile.TYPE_EXTERNAL_APP)
+ setExtAlias()
+ else
+ setKeyStoreAlias()
+ }
+
+ override fun handleMessage(msg: Message): Boolean {
+ setAlias()
+ return true
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
+ super.onActivityResult(requestCode, resultCode, data)
+
+ if (requestCode == UPDATEE_EXT_ALIAS && resultCode == Activity.RESULT_OK) {
+ mProfile.mAlias = data.getStringExtra(ExtAuthHelper.EXTRA_ALIAS)
+ mExtAliasName.text = data.getStringExtra(ExtAuthHelper.EXTRA_DESCRIPTION)
+ }
+ }
+
+ companion object {
+ private val UPDATE_ALIAS = 20
+ private val UPDATEE_EXT_ALIAS = 210
+ }
+}
diff --git a/main/src/main/res/layout/keystore_selector.xml b/main/src/main/res/layout/keystore_selector.xml
index 28a51836..d0b24892 100644
--- a/main/src/main/res/layout/keystore_selector.xml
+++ b/main/src/main/res/layout/keystore_selector.xml
@@ -28,7 +28,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
- android:text="@string/select"/>
+ android:text="@string/select"
+ android:layout_alignParentEnd="true" />
<TextView
android:id="@+id/title"
@@ -38,7 +39,9 @@
android:layout_alignParentTop="true"
android:layout_toLeftOf="@+id/select_keystore_button"
android:text="@string/client_certificate_title"
- android:textStyle="bold"/>
+ android:textStyle="bold"
+ android:layout_alignParentStart="true"
+ android:layout_toStartOf="@+id/select_keystore_button" />
<TextView
android:id="@+id/aliasname"
@@ -50,7 +53,10 @@
android:layout_toLeftOf="@+id/select_keystore_button"
android:ellipsize="end"
android:singleLine="true"
- android:text="@string/no_certificate"/>
+ android:text="@string/no_certificate"
+ android:layout_alignParentStart="true"
+ android:layout_marginStart="16dip"
+ android:layout_toStartOf="@+id/select_keystore_button" />
<TextView
android:id="@+id/alias_certificate"
@@ -62,7 +68,18 @@
android:layout_toLeftOf="@+id/select_keystore_button"
android:ellipsize="end"
android:singleLine="true"
- />
+ android:layout_alignParentStart="true"
+ android:layout_marginStart="16dip"
+ android:layout_toStartOf="@+id/select_keystore_button" />
<!-- android:textColor="@color/text_secondary_color" -->
+ <Button
+ android:id="@+id/install_keystore_button"
+ style="@style/accountSetupButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/alias_certificate"
+ android:layout_alignParentRight="true"
+ android:text="@string/install_keychain"
+ android:layout_alignParentEnd="true" />
</RelativeLayout> \ No newline at end of file
diff --git a/main/src/main/res/values/strings.xml b/main/src/main/res/values/strings.xml
index ab8c57af..5b14dbd1 100755
--- a/main/src/main/res/values/strings.xml
+++ b/main/src/main/res/values/strings.xml
@@ -491,5 +491,6 @@
<string name="faq_killswitch">It is often desired to block connections without VPN. Other apps often use markting terms like \"Killswitch\" or \"Seamless tunnel\" for this feature. OpenVPN and this app offer persist-tun, a feature to implement this functionality.&lt;p>The problem with all these methods offered by apps is that they can only provide best effort and are no complete solutions. On boot, app crashing and other corner cases the app cannot ensure that this block of non VPN connection works. Thus giving the user a false sense of security.&lt;p>The &lt;b>only&lt;/b> reliable way to ensure non VPN connections are blocked is to use Android 8.0 or later and use the \"block connections without VPN\" setting that can be found under Settings > Network &amp; Internet > Advanced/VPN > OpenVPN for Android > Enable Always ON VPN, Enable Block Connections without VPN</string>
<string name="summary_block_address_families">This option instructs Android to not allow protocols (IPv4/IPv6) if the VPN does not set any IPv4 or IPv6 addresses.</string>
<string name="title_block_address_families">Block IPv6 (or IPv4) if not used by the VPN</string>
+ <string name="install_keychain">Install new certificate</string>
</resources>