summaryrefslogtreecommitdiff
path: root/main/src
diff options
context:
space:
mode:
authorArne Schwabe <arne@rfc2549.org>2018-08-06 18:19:41 +0200
committerArne Schwabe <arne@rfc2549.org>2018-08-06 18:19:41 +0200
commitb9ac2b15eac3e5e5f9dc89c948ec8278e2e7c1f9 (patch)
tree63f371b4ae6555b15f76c5a13ed2e26c192895dc /main/src
parent7b7940186fafcdf4bb15ea8e087b8cf345cd53c8 (diff)
Implement certificate authentication via external provider
Diffstat (limited to 'main/src')
-rw-r--r--main/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl25
-rw-r--r--main/src/main/java/android/support/v4n/view/ViewPager.java7
-rw-r--r--main/src/main/java/de/blinkt/openvpn/VpnProfile.java238
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java249
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java2
-rw-r--r--main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java143
-rw-r--r--main/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java338
-rw-r--r--main/src/main/java/de/blinkt/openvpn/fragments/ShowConfigFragment.java24
-rw-r--r--main/src/main/res/layout/basic_settings.xml10
-rw-r--r--main/src/main/res/layout/extauth_provider.xml70
-rwxr-xr-xmain/src/main/res/values-de/arrays.xml1
-rw-r--r--main/src/main/res/values/arrays.xml1
-rwxr-xr-xmain/src/main/res/values/strings.xml4
-rw-r--r--main/src/ovpn3/java/de/blinkt/openvpn/core/OpenVPNThreadv3.java4
14 files changed, 788 insertions, 328 deletions
diff --git a/main/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl b/main/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl
index d1e1a4bf..c6db965b 100644
--- a/main/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl
+++ b/main/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl
@@ -12,17 +12,28 @@ interface ExternalCertificateProvider {
* for RSA certficate and with NONEwithECDSA for EC certificates
* @parm alias the parameter that
*/
- byte[] getSignedData(String alias, in byte[] data);
+ byte[] getSignedData(in String alias, in byte[] data);
/**
- * Requests a
+ * Requests the certificate chain for the selected alias
+ * The first certifcate returned is assumed to be
+ * the user certificate
*/
- String[] getCertificateChain(in String alias);
+ byte[] getCertificateChain(in String alias);
/**
- * request an Intent that should be started when user uses the select certificate box
- * the already selected alias will be provided in the extra android.security.extra.KEY_ALIAS
- * if applicable
+ * This function is called for the app to get additional meta information from the
+ * external provider and will be called with the stored alias in the app
+ *
+ * For external app provider that do not provide an activity to configure them, this
+ * is used to get the alias that should be used.
+ * The format is the same as the activity should return, i.e.
+ *
+ * EXTRA_ALIAS = "de.blinkt.openvpn.api.KEY_ALIAS"
+ * EXTRA_DESCRIPTION = "de.blinkt.openvpn.api.KEY_DESCRIPTION"
+ *
+ * as the keys for the bundle.
+ *
*/
-
+ Bundle getCertificateMetaData(in String alias);
}
diff --git a/main/src/main/java/android/support/v4n/view/ViewPager.java b/main/src/main/java/android/support/v4n/view/ViewPager.java
index 943b1396..6009af62 100644
--- a/main/src/main/java/android/support/v4n/view/ViewPager.java
+++ b/main/src/main/java/android/support/v4n/view/ViewPager.java
@@ -1013,7 +1013,7 @@ public class ViewPager extends ViewGroup {
mAdapter.destroyItem(this, pos, ii.object);
if (DEBUG) {
Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
- " view: " + ((View) ii.object));
+ " view: " + ii.object);
}
itemIndex--;
curIndex--;
@@ -1047,7 +1047,7 @@ public class ViewPager extends ViewGroup {
mAdapter.destroyItem(this, pos, ii.object);
if (DEBUG) {
Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
- " view: " + ((View) ii.object));
+ " view: " + (ii.object));
}
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
}
@@ -1596,9 +1596,12 @@ public class ViewPager extends ViewGroup {
MeasureSpec.EXACTLY);
child.measure(widthSpec, heightSpec);
}
+ int id = -1;
+
if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
+ ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
+ "x" + child.getMeasuredHeight());
+
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
diff --git a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java
index d89bb362..031e77ca 100644
--- a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java
+++ b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java
@@ -12,10 +12,12 @@ import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
+import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.security.KeyChain;
import android.security.KeyChainException;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Base64;
@@ -58,14 +60,9 @@ public class VpnProfile implements Serializable, Cloneable {
public static final String EXTRA_PROFILEUUID = "de.blinkt.openvpn.profileUUID";
public static final String INLINE_TAG = "[[INLINE]]";
public static final String DISPLAYNAME_TAG = "[[NAME]]";
-
- private static final long serialVersionUID = 7085688938959334563L;
public static final int MAXLOGLEVEL = 4;
public static final int CURRENT_PROFILE_VERSION = 7;
public static final int DEFAULT_MSSFIX_SIZE = 1280;
- public static String DEFAULT_DNS1 = "8.8.8.8";
- public static String DEFAULT_DNS2 = "8.8.4.4";
-
public static final int TYPE_CERTIFICATES = 0;
public static final int TYPE_PKCS12 = 1;
public static final int TYPE_KEYSTORE = 2;
@@ -74,17 +71,20 @@ public class VpnProfile implements Serializable, Cloneable {
public static final int TYPE_USERPASS_CERTIFICATES = 5;
public static final int TYPE_USERPASS_PKCS12 = 6;
public static final int TYPE_USERPASS_KEYSTORE = 7;
+ public static final int TYPE_EXTERNAL_APP = 8;
public static final int X509_VERIFY_TLSREMOTE = 0;
public static final int X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING = 1;
public static final int X509_VERIFY_TLSREMOTE_DN = 2;
public static final int X509_VERIFY_TLSREMOTE_RDN = 3;
public static final int X509_VERIFY_TLSREMOTE_RDN_PREFIX = 4;
-
-
public static final int AUTH_RETRY_NONE_FORGET = 0;
- private static final int AUTH_RETRY_NONE_KEEP = 1;
public static final int AUTH_RETRY_NOINTERACT = 2;
+ public static final boolean mIsOpenVPN22 = false;
+ private static final long serialVersionUID = 7085688938959334563L;
+ private static final int AUTH_RETRY_NONE_KEEP = 1;
private static final int AUTH_RETRY_INTERACT = 3;
+ public static String DEFAULT_DNS1 = "8.8.8.8";
+ public static String DEFAULT_DNS2 = "8.8.4.4";
// variable named wrong and should haven beeen transient
// but needs to keep wrong name to guarante loading of old
// profiles
@@ -101,7 +101,6 @@ public class VpnProfile implements Serializable, Cloneable {
public String mPKCS12Filename;
public String mPKCS12Password;
public boolean mUseTLSAuth = false;
-
public String mDNS1 = DEFAULT_DNS1;
public String mDNS2 = DEFAULT_DNS2;
public String mIPv4Address;
@@ -135,13 +134,7 @@ public class VpnProfile implements Serializable, Cloneable {
public String mAuth = "";
public int mX509AuthType = X509_VERIFY_TLSREMOTE_RDN;
public String mx509UsernameField = null;
-
- private transient PrivateKey mPrivateKey;
- // Public attributes, since I got mad with getter/setter
- // set members to default values
- private UUID mUuid;
public boolean mAllowLocalLAN;
- private int mProfileVersion;
public String mExcludedRoutes;
public String mExcludedRoutesv6;
public int mMssFix = 0; // -1 is default,
@@ -149,30 +142,25 @@ public class VpnProfile implements Serializable, Cloneable {
public boolean mRemoteRandom = false;
public HashSet<String> mAllowedAppsVpn = new HashSet<>();
public boolean mAllowedAppsVpnAreDisallowed = true;
-
public String mCrlFilename;
public String mProfileCreator;
-
+ public String mExternalAuthenticator;
public int mAuthRetry = AUTH_RETRY_NONE_FORGET;
public int mTunMtu;
-
-
public boolean mPushPeerInfo = false;
- public static final boolean mIsOpenVPN22 = false;
-
public int mVersion = 0;
-
// timestamp when the profile was last used
public long mLastUsed;
-
-
public String importedProfileHash;
-
/* Options no longer used in new profiles */
public String mServerName = "openvpn.example.com";
public String mServerPort = "1194";
public boolean mUseUdp = true;
-
+ private transient PrivateKey mPrivateKey;
+ // Public attributes, since I got mad with getter/setter
+ // set members to default values
+ private UUID mUuid;
+ private int mProfileVersion;
public VpnProfile(String name) {
@@ -200,6 +188,48 @@ public class VpnProfile implements Serializable, Cloneable {
return '"' + escapedString + '"';
}
+ public static boolean doUseOpenVPN3(Context c) {
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c);
+ boolean useOpenVPN3 = prefs.getBoolean("ovpn3", false);
+ if (!BuildConfig.openvpn3)
+ useOpenVPN3 = false;
+ return useOpenVPN3;
+ }
+
+ //! Put inline data inline and other data as normal escaped filename
+ public static String insertFileData(String cfgentry, String filedata) {
+ if (filedata == null) {
+ return String.format("%s %s\n", cfgentry, "file missing in config profile");
+ } else if (isEmbedded(filedata)) {
+ String dataWithOutHeader = getEmbeddedContent(filedata);
+ return String.format(Locale.ENGLISH, "<%s>\n%s\n</%s>\n", cfgentry, dataWithOutHeader, cfgentry);
+ } else {
+ return String.format(Locale.ENGLISH, "%s %s\n", cfgentry, openVpnEscape(filedata));
+ }
+ }
+
+ public static String getDisplayName(String embeddedFile) {
+ int start = DISPLAYNAME_TAG.length();
+ int end = embeddedFile.indexOf(INLINE_TAG);
+ return embeddedFile.substring(start, end);
+ }
+
+ public static String getEmbeddedContent(String data) {
+ if (!data.contains(INLINE_TAG))
+ return data;
+
+ int start = data.indexOf(INLINE_TAG) + INLINE_TAG.length();
+ return data.substring(start);
+ }
+
+ public static boolean isEmbedded(String data) {
+ if (data == null)
+ return false;
+ if (data.startsWith(INLINE_TAG) || data.startsWith(DISPLAYNAME_TAG))
+ return true;
+ else
+ return false;
+ }
@Override
public boolean equals(Object obj) {
@@ -231,7 +261,7 @@ public class VpnProfile implements Serializable, Cloneable {
}
// Only used for the special case of managed profiles
- public void setUUID(UUID uuid){
+ public void setUUID(UUID uuid) {
mUuid = uuid;
}
@@ -262,7 +292,7 @@ public class VpnProfile implements Serializable, Cloneable {
mUserEditable = true;
}
if (mProfileVersion < 7) {
- for (Connection c: mConnections)
+ for (Connection c : mConnections)
if (c.mProxyType == null)
c.mProxyType = Connection.ProxyType.NONE;
}
@@ -284,15 +314,6 @@ public class VpnProfile implements Serializable, Cloneable {
}
-
- public static boolean doUseOpenVPN3(Context c) {
- SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c);
- boolean useOpenVPN3 = prefs.getBoolean("ovpn3", false);
- if (!BuildConfig.openvpn3)
- useOpenVPN3 = false;
- return useOpenVPN3;
- }
-
public String getConfigFile(Context context, boolean configForOvpn3) {
File cacheDir = context.getCacheDir();
@@ -412,9 +433,10 @@ public class VpnProfile implements Serializable, Cloneable {
case VpnProfile.TYPE_USERPASS_KEYSTORE:
cfg.append("auth-user-pass\n");
case VpnProfile.TYPE_KEYSTORE:
+ case VpnProfile.TYPE_EXTERNAL_APP:
if (!configForOvpn3) {
- String[] ks = getKeyStoreCertificates(context);
- cfg.append("### From Keystore ####\n");
+ String[] ks = getExternalCertificates(context);
+ cfg.append("### From Keystore/ext auth app ####\n");
if (ks != null) {
cfg.append("<ca>\n").append(ks[0]).append("\n</ca>\n");
if (ks[1] != null)
@@ -641,18 +663,6 @@ public class VpnProfile implements Serializable, Cloneable {
}
- //! Put inline data inline and other data as normal escaped filename
- public static String insertFileData(String cfgentry, String filedata) {
- if (filedata == null) {
- return String.format("%s %s\n", cfgentry, "file missing in config profile");
- } else if (isEmbedded(filedata)) {
- String dataWithOutHeader = getEmbeddedContent(filedata);
- return String.format(Locale.ENGLISH, "<%s>\n%s\n</%s>\n", cfgentry, dataWithOutHeader, cfgentry);
- } else {
- return String.format(Locale.ENGLISH, "%s %s\n", cfgentry, openVpnEscape(filedata));
- }
- }
-
@NonNull
private Collection<String> getCustomRoutes(String routes) {
Vector<String> cidrRoutes = new Vector<>();
@@ -714,7 +724,6 @@ public class VpnProfile implements Serializable, Cloneable {
return parts[0] + " " + netmask;
}
-
public Intent prepareStartService(Context context) {
Intent intent = getStartServiceIntent(context);
@@ -744,33 +753,6 @@ public class VpnProfile implements Serializable, Cloneable {
return intent;
}
- public String[] getKeyStoreCertificates(Context context) {
- return getKeyStoreCertificates(context, 5);
- }
-
- public static String getDisplayName(String embeddedFile) {
- int start = DISPLAYNAME_TAG.length();
- int end = embeddedFile.indexOf(INLINE_TAG);
- return embeddedFile.substring(start, end);
- }
-
- public static String getEmbeddedContent(String data) {
- if (!data.contains(INLINE_TAG))
- return data;
-
- int start = data.indexOf(INLINE_TAG) + INLINE_TAG.length();
- return data.substring(start);
- }
-
- public static boolean isEmbedded(String data) {
- if (data == null)
- return false;
- if (data.startsWith(INLINE_TAG) || data.startsWith(DISPLAYNAME_TAG))
- return true;
- else
- return false;
- }
-
public void checkForRestart(final Context context) {
/* This method is called when OpenVPNService is restarted */
@@ -779,7 +761,7 @@ public class VpnProfile implements Serializable, Cloneable {
new Thread(new Runnable() {
@Override
public void run() {
- getKeyStoreCertificates(context);
+ getExternalCertificates(context);
}
}).start();
@@ -815,26 +797,40 @@ public class VpnProfile implements Serializable, Cloneable {
}
+ private X509Certificate[] getKeyStoreCertificates(Context context) throws KeyChainException, InterruptedException {
+ PrivateKey privateKey = KeyChain.getPrivateKey(context, mAlias);
+ mPrivateKey = privateKey;
- class NoCertReturnedException extends Exception {
- public NoCertReturnedException(String msg) {
- super(msg);
- }
+
+ X509Certificate[] caChain = KeyChain.getCertificateChain(context, mAlias);
+ return caChain;
}
- synchronized String[] getKeyStoreCertificates(Context context, int tries) {
+ private X509Certificate[] getExtAppCertificates(Context context) throws KeyChainException {
+ if (mExternalAuthenticator == null || mAlias == null)
+ throw new KeyChainException("Alias or external auth provider name not set");
+ return ExtAuthHelper.getCertificateChain(context, mExternalAuthenticator, mAlias);
+ }
+
+ public String[] getExternalCertificates(Context context) {
+ return getExternalCertificates(context, 5);
+ }
+
+
+ synchronized String[] getExternalCertificates(Context context, int tries) {
// Force application context- KeyChain methods will block long enough that by the time they
// are finished and try to unbind, the original activity context might have been destroyed.
context = context.getApplicationContext();
try {
- PrivateKey privateKey = KeyChain.getPrivateKey(context, mAlias);
- mPrivateKey = privateKey;
-
String keystoreChain = null;
-
- X509Certificate[] caChain = KeyChain.getCertificateChain(context, mAlias);
+ X509Certificate caChain[];
+ if (mAuthenticationType == TYPE_EXTERNAL_APP) {
+ caChain = getExtAppCertificates(context);
+ } else {
+ caChain = getKeyStoreCertificates(context);
+ }
if (caChain == null)
throw new NoCertReturnedException("No certificate returned from Keystore");
@@ -917,19 +913,18 @@ public class VpnProfile implements Serializable, Cloneable {
} catch (InterruptedException e1) {
VpnStatus.logException(e1);
}
- return getKeyStoreCertificates(context, tries - 1);
+ return getExternalCertificates(context, tries - 1);
}
}
- public int checkProfile(Context c)
- {
+ public int checkProfile(Context c) {
return checkProfile(c, doUseOpenVPN3(c));
}
//! Return an error if something is wrong
public int checkProfile(Context context, boolean useOpenVPN3) {
- if (mAuthenticationType == TYPE_KEYSTORE || mAuthenticationType == TYPE_USERPASS_KEYSTORE) {
+ if (mAuthenticationType == TYPE_KEYSTORE || mAuthenticationType == TYPE_USERPASS_KEYSTORE || mAuthenticationType == TYPE_EXTERNAL_APP) {
if (mAlias == null)
return R.string.no_keystore_cert_selected;
} else if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) {
@@ -986,7 +981,7 @@ public class VpnProfile implements Serializable, Cloneable {
return R.string.openvpn3_socksproxy;
}
}
- for (Connection c: mConnections) {
+ for (Connection c : mConnections) {
if (c.mProxyType == Connection.ProxyType.ORBOT) {
if (usesExtraProxyOptions())
return R.string.error_orbot_and_proxy_options;
@@ -1116,11 +1111,35 @@ public class VpnProfile implements Serializable, Cloneable {
return mPrivateKey;
}
- public String getSignedData(String b64data) {
- PrivateKey privkey = getKeystoreKey();
-
+ @Nullable
+ public String getSignedData(Context c, String b64data) {
byte[] data = Base64.decode(b64data, Base64.DEFAULT);
+ byte[] signed_bytes;
+ if (mAuthenticationType == TYPE_EXTERNAL_APP)
+ signed_bytes = getExtAppSignedData(c, data);
+ else
+ signed_bytes = getKeyChainSignedData(data);
+ if (signed_bytes != null)
+ return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
+ else
+ return null;
+ }
+
+ private byte[] getExtAppSignedData(Context c, byte[] data) {
+ if (TextUtils.isEmpty(mExternalAuthenticator))
+ return null;
+ try {
+ return ExtAuthHelper.signData(c, mExternalAuthenticator, mAlias, data);
+ } catch (KeyChainException | InterruptedException e) {
+ VpnStatus.logError(R.string.error_extapp_sign, mExternalAuthenticator, e.getClass().toString(), e.getLocalizedMessage());
+ return null;
+ }
+ }
+
+ private byte[] getKeyChainSignedData(byte[] data) {
+
+ PrivateKey privkey = getKeystoreKey();
// The Jelly Bean *evil* Hack
// 4.2 implements the RSA/ECB/PKCS1PADDING in the OpenSSLprovider
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
@@ -1152,8 +1171,7 @@ public class VpnProfile implements Serializable, Cloneable {
signed_bytes = signer.doFinal(data);
}
- return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
-
+ return signed_bytes;
} catch (NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException
| BadPaddingException | NoSuchPaddingException | SignatureException e) {
VpnStatus.logError(R.string.error_rsa_sign, e.getClass().toString(), e.getLocalizedMessage());
@@ -1161,7 +1179,7 @@ public class VpnProfile implements Serializable, Cloneable {
}
}
- private String processSignJellyBeans(PrivateKey privkey, byte[] data) {
+ private byte[] processSignJellyBeans(PrivateKey privkey, byte[] data) {
try {
Method getKey = privkey.getClass().getSuperclass().getDeclaredMethod("getOpenSSLKey");
getKey.setAccessible(true);
@@ -1179,8 +1197,7 @@ public class VpnProfile implements Serializable, Cloneable {
getPkeyContext.setAccessible(false);
// 112 with TLS 1.2 (172 back with 4.3), 36 with TLS 1.0
- byte[] signed_bytes = NativeUtils.rsasign(data, pkey);
- return Base64.encodeToString(signed_bytes, Base64.NO_WRAP);
+ return NativeUtils.rsasign(data, pkey);
} catch (NoSuchMethodException | InvalidKeyException | InvocationTargetException | IllegalAccessException | IllegalArgumentException e) {
VpnStatus.logError(R.string.error_rsa_sign, e.getClass().toString(), e.getLocalizedMessage());
@@ -1188,17 +1205,22 @@ public class VpnProfile implements Serializable, Cloneable {
}
}
- private boolean usesExtraProxyOptions()
- {
- if (mUseCustomConfig && mCustomConfigOptions !=null && mCustomConfigOptions.contains("http-proxy-option "))
+ private boolean usesExtraProxyOptions() {
+ if (mUseCustomConfig && mCustomConfigOptions != null && mCustomConfigOptions.contains("http-proxy-option "))
return true;
- for (Connection c: mConnections)
+ for (Connection c : mConnections)
if (c.usesExtraProxyOptions())
- return true;
+ return true;
return false;
}
+ class NoCertReturnedException extends Exception {
+ public NoCertReturnedException(String msg) {
+ super(msg);
+ }
+ }
+
}
diff --git a/main/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java b/main/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java
new file mode 100644
index 00000000..d513d759
--- /dev/null
+++ b/main/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java
@@ -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.core;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.*;
+import android.security.KeyChainException;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
+import de.blinkt.openvpn.api.ExternalCertificateProvider;
+
+import java.io.ByteArrayInputStream;
+import java.io.Closeable;
+import java.io.UnsupportedEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class ExtAuthHelper {
+
+ public static final String ACTION_CERT_CONFIGURATION = "de.blinkt.openvpn.api.ExternalCertificateConfiguration";
+ public static final String ACTION_CERT_PROVIDER = "de.blinkt.openvpn.api.ExternalCertificateProvider";
+
+ public static final String EXTRA_ALIAS = "de.blinkt.openvpn.api.KEY_ALIAS";
+ public static final String EXTRA_DESCRIPTION = "de.blinkt.openvpn.api.KEY_DESCRIPTION";
+
+
+ public static void setExternalAuthProviderSpinnerList(Spinner spinner, String selectedApp) {
+ Context c = spinner.getContext();
+ final PackageManager pm = c.getPackageManager();
+ ArrayList<ExternalAuthProvider> extProviders = getExternalAuthProviderList(c);
+
+ int selectedPos = -1;
+
+
+ for (int i = 0; i < extProviders.size(); i++) {
+ if (extProviders.get(i).packageName.equals(selectedApp))
+ selectedPos = i;
+ }
+ SpinnerAdapter extAppAdapter = new ArrayAdapter<ExternalAuthProvider>(c, android.R.layout.simple_spinner_item, android.R.id.text1, extProviders);
+ spinner.setAdapter(extAppAdapter);
+ if (selectedPos != -1)
+ spinner.setSelection(selectedPos);
+ }
+
+ static ArrayList<ExternalAuthProvider> getExternalAuthProviderList(Context c) {
+ Intent configureExtAuth = new Intent(ACTION_CERT_CONFIGURATION);
+
+ final PackageManager packageManager = c.getPackageManager();
+ List<ResolveInfo> configureList =
+ packageManager.queryIntentActivities(configureExtAuth, 0);
+
+ Intent serviceExtAuth = new Intent(ACTION_CERT_PROVIDER);
+
+ List<ResolveInfo> serviceList =
+ packageManager.queryIntentServices(serviceExtAuth, 0);
+
+
+ // For now only list those who appear in both lists
+
+ ArrayList<ExternalAuthProvider> providers = new ArrayList<ExternalAuthProvider>();
+
+ for (ResolveInfo service : serviceList) {
+ ExternalAuthProvider ext = new ExternalAuthProvider();
+ ext.packageName = service.serviceInfo.packageName;
+
+ ext.label = (String) service.serviceInfo.applicationInfo.loadLabel(packageManager);
+
+ for (ResolveInfo activity : configureList) {
+ if (service.serviceInfo.packageName.equals(activity.activityInfo.packageName)) {
+ ext.configurable = true;
+ }
+ }
+ providers.add(ext);
+
+ }
+ return providers;
+
+ }
+
+ @Nullable
+ @WorkerThread
+ public static byte[] signData(@NonNull Context context,
+ @NonNull String extAuthPackageName,
+ @NonNull String alias,
+ @NonNull byte[] data
+ ) throws KeyChainException, InterruptedException
+
+ {
+
+
+ try (ExternalAuthProviderConnection authProviderConnection = bindToExtAuthProvider(context.getApplicationContext(), extAuthPackageName)) {
+ ExternalCertificateProvider externalAuthProvider = authProviderConnection.getService();
+ return externalAuthProvider.getSignedData(alias, data);
+
+ } catch (RemoteException e) {
+ throw new KeyChainException(e);
+ }
+ }
+
+ @Nullable
+ @WorkerThread
+ public static X509Certificate[] getCertificateChain(@NonNull Context context,
+ @NonNull String extAuthPackageName,
+ @NonNull String alias) throws KeyChainException {
+
+ final byte[] certificateBytes;
+ try (ExternalAuthProviderConnection authProviderConnection = bindToExtAuthProvider(context.getApplicationContext(), extAuthPackageName)) {
+ ExternalCertificateProvider externalAuthProvider = authProviderConnection.getService();
+ certificateBytes = externalAuthProvider.getCertificateChain(alias);
+ if (certificateBytes == null) {
+ return null;
+ }
+ Collection<X509Certificate> chain = toCertificates(certificateBytes);
+ return chain.toArray(new X509Certificate[chain.size()]);
+
+ } catch (RemoteException | RuntimeException | InterruptedException e) {
+ throw new KeyChainException(e);
+ }
+ }
+
+ public static Bundle getCertificateMetaData(@NonNull Context context,
+ @NonNull String extAuthPackageName,
+ String alias) throws KeyChainException
+ {
+ try (ExternalAuthProviderConnection authProviderConnection = bindToExtAuthProvider(context.getApplicationContext(), extAuthPackageName)) {
+ ExternalCertificateProvider externalAuthProvider = authProviderConnection.getService();
+ return externalAuthProvider.getCertificateMetaData(alias);
+
+ } catch (RemoteException | RuntimeException | InterruptedException e) {
+ throw new KeyChainException(e);
+ }
+ }
+
+ public static Collection<X509Certificate> toCertificates(@NonNull byte[] bytes) {
+ final String BEGINCERT = "-----BEGIN CERTIFICATE-----";
+ try {
+ Vector<X509Certificate> retCerts = new Vector<>();
+ // Java library is broken, although the javadoc says it will extract all certificates from a byte array
+ // it only extracts the first one
+ String allcerts = new String(bytes, "iso8859-1");
+ String[] certstrings = allcerts.split(BEGINCERT);
+ for (String certstring: certstrings) {
+ certstring = BEGINCERT + certstring;
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ retCerts.addAll((Collection<? extends X509Certificate>) certFactory.generateCertificates(
+ new ByteArrayInputStream((certstring.getBytes("iso8859-1")))));
+
+ }
+ return retCerts;
+
+ } catch (CertificateException e) {
+ throw new AssertionError(e);
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ // adapted form Keychain
+ @WorkerThread
+ public static ExternalAuthProviderConnection bindToExtAuthProvider(@NonNull Context context, String packagename) throws KeyChainException, InterruptedException {
+ ensureNotOnMainThread(context);
+ final BlockingQueue<ExternalCertificateProvider> q = new LinkedBlockingQueue<>(1);
+ ServiceConnection extAuthServiceConnection = new ServiceConnection() {
+ volatile boolean mConnectedAtLeastOnce = false;
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (!mConnectedAtLeastOnce) {
+ mConnectedAtLeastOnce = true;
+ try {
+ q.put(ExternalCertificateProvider.Stub.asInterface(service));
+ } catch (InterruptedException e) {
+ // will never happen, since the queue starts with one available slot
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+ };
+ Intent intent = new Intent(ACTION_CERT_PROVIDER);
+ intent.setPackage(packagename);
+
+ if (!context.bindService(intent, extAuthServiceConnection, Context.BIND_AUTO_CREATE)) {
+ throw new KeyChainException("could not bind to external authticator app: " + packagename);
+ }
+ return new ExternalAuthProviderConnection(context, extAuthServiceConnection, q.take());
+ }
+
+ private static void ensureNotOnMainThread(@NonNull Context context) {
+ Looper looper = Looper.myLooper();
+ if (looper != null && looper == context.getMainLooper()) {
+ throw new IllegalStateException(
+ "calling this from your main thread can lead to deadlock");
+ }
+ }
+
+ public static class ExternalAuthProvider {
+
+ public String packageName;
+ public boolean configurable = false;
+ private String label;
+
+ @Override
+ public String toString() {
+ return label;
+ }
+ }
+
+ public static class ExternalAuthProviderConnection implements Closeable {
+ private final Context context;
+ private final ServiceConnection serviceConnection;
+ private final ExternalCertificateProvider service;
+
+ protected ExternalAuthProviderConnection(Context context,
+ ServiceConnection serviceConnection,
+ ExternalCertificateProvider service) {
+ this.context = context;
+ this.serviceConnection = serviceConnection;
+ this.service = service;
+ }
+
+ @Override
+ public void close() {
+ context.unbindService(serviceConnection);
+ }
+
+ public ExternalCertificateProvider getService() {
+ return service;
+ }
+ }
+}
diff --git a/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java b/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
index 5238880b..b2d26836 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
@@ -751,7 +751,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
private void processSignCommand(String b64data) {
- String signed_string = mProfile.getSignedData(b64data);
+ String signed_string = mProfile.getSignedData(mOpenVPNService, b64data);
if (signed_string == null) {
managmentCommand("pk-sig\n");
diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java b/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java
index 7ec72f78..d82807fb 100644
--- a/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java
+++ b/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java
@@ -6,71 +6,122 @@
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.Spinner;
import android.widget.TextView;
+import android.widget.Toast;
import de.blinkt.openvpn.R;
import de.blinkt.openvpn.VpnProfile;
-import de.blinkt.openvpn.core.ProfileManager;
+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 setAlias() {
- if(mProfile.mAlias == null) {
+ 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);
- setKeystoreCertficate();
+ setCertificate(false);
}
}
- protected void setKeystoreCertficate()
- {
+ 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(mProfile.mAlias);
+ setCertificate(true);
+ }
+ }
+
+ private void fetchExtCertificateMetaData() {
new Thread() {
+ @Override
public void run() {
- String certstr="";
try {
- X509Certificate cert = KeyChain.getCertificateChain(getActivity().getApplicationContext(), mProfile.mAlias)[0];
+ Bundle b = ExtAuthHelper.getCertificateMetaData(getActivity(), mProfile.mExternalAuthenticator, mProfile.mAlias);
+ mProfile.mAlias = b.getString(ExtAuthHelper.EXTRA_ALIAS);
+ getActivity().runOnUiThread(() -> setAlias());
+ } catch (KeyChainException e) {
+ e.printStackTrace();
+ }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
- {
- if (isInHardwareKeystore())
- certstr+=getString(R.string.hwkeychain);
+ }
+ }.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);
+ }
}
}
- certstr+= X509Utils.getCertificateValidityString(cert, getResources());
- certstr+=X509Utils.getCertificateFriendlyName(cert);
+ if (cert!=null) {
+ certstr += X509Utils.getCertificateValidityString(cert, getResources());
+ certstr += X509Utils.getCertificateFriendlyName(cert);
+ }
+
+
} catch (Exception e) {
- certstr="Could not get certificate from Keystore: " +e.getLocalizedMessage();
+ certstr = "Could not get certificate from Keystore: " + e.getLocalizedMessage();
}
- final String certStringCopy=certstr;
- getActivity().runOnUiThread(new Runnable() {
+ final String certStringCopy = certstr;
+ Bundle finalMetadata = metadata;
+ getActivity().runOnUiThread(() -> {
+ mAliasCertificate.setText(certStringCopy);
+ if (finalMetadata!=null)
+ mExtAliasName.setText(finalMetadata.getString(ExtAuthHelper.EXTRA_DESCRIPTION));
- @Override
- public void run() {
- mAliasCertificate.setText(certStringCopy);
- }
});
}
@@ -85,20 +136,39 @@ abstract class KeyChainSettingsFragment extends Settings_Fragment implements Vie
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);
+ 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();
+ if (!eAuth.configurable) {
+ fetchExtCertificateMetaData();
+ return;
+ }
+ mProfile.mExternalAuthenticator = eAuth.packageName;
+ 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() {
@@ -111,15 +181,15 @@ abstract class KeyChainSettingsFragment extends Settings_Fragment implements Vie
}
@SuppressWarnings("WrongConstant")
- public void showCertDialog () {
- try {
+ public void showCertDialog() {
+ try {
KeyChain.choosePrivateKeyAlias(getActivity(),
alias -> {
// Credential alias selected. Remember the alias selection for future use.
- mProfile.mAlias=alias;
+ mProfile.mAlias = alias;
mHandler.sendEmptyMessage(UPDATE_ALIAS);
},
- new String[] {"RSA"}, // List of acceptable key types. null for any
+ 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
@@ -133,9 +203,16 @@ abstract class KeyChainSettingsFragment extends Settings_Fragment implements Vie
}
}
- protected void loadPreferences()
- {
+ protected void loadPreferences() {
setAlias();
+
+ }
+
+ private void setAlias() {
+ if (mProfile.mAuthenticationType == VpnProfile.TYPE_EXTERNAL_APP)
+ setExtAlias();
+ else
+ setKeyStoreAlias();
}
@Override
@@ -143,4 +220,14 @@ abstract class KeyChainSettingsFragment extends Settings_Fragment implements Vie
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/Settings_Basic.java b/main/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java
index faefcf86..739746b4 100644
--- a/main/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java
+++ b/main/src/main/java/de/blinkt/openvpn/fragments/Settings_Basic.java
@@ -20,206 +20,206 @@ import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.core.ProfileManager;
import de.blinkt.openvpn.views.FileSelectLayout;
-public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSelectedListener, FileSelectLayout.FileSelectCallback {
- private static final int CHOOSE_FILE_OFFSET = 1000;
-
- private FileSelectLayout mClientCert;
- private FileSelectLayout mCaCert;
- private FileSelectLayout mClientKey;
- private CheckBox mUseLzo;
- private Spinner mType;
- private FileSelectLayout mpkcs12;
- private FileSelectLayout mCrlFile;
- private TextView mPKCS12Password;
- private EditText mUserName;
- private EditText mPassword;
- private View mView;
- private EditText mProfileName;
- private EditText mKeyPassword;
-
- private SparseArray<FileSelectLayout> fileselects = new SparseArray<>();
+public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSelectedListener, FileSelectLayout.FileSelectCallback {
+ private static final int CHOOSE_FILE_OFFSET = 1000;
+
+ private FileSelectLayout mClientCert;
+ private FileSelectLayout mCaCert;
+ private FileSelectLayout mClientKey;
+ private CheckBox mUseLzo;
+ private Spinner mType;
+ private FileSelectLayout mpkcs12;
+ private FileSelectLayout mCrlFile;
+ private TextView mPKCS12Password;
+ private EditText mUserName;
+ private EditText mPassword;
+ private View mView;
+ private EditText mProfileName;
+ private EditText mKeyPassword;
+
+ private SparseArray<FileSelectLayout> fileselects = new SparseArray<>();
private Spinner mAuthRetry;
- private void addFileSelectLayout (FileSelectLayout fsl, Utils.FileType type) {
- int i = fileselects.size() + CHOOSE_FILE_OFFSET;
- fileselects.put(i, fsl);
- fsl.setCaller(this, i, type);
- }
+ private void addFileSelectLayout(FileSelectLayout fsl, Utils.FileType type) {
+ int i = fileselects.size() + CHOOSE_FILE_OFFSET;
+ fileselects.put(i, fsl);
+ fsl.setCaller(this, i, type);
+ }
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- }
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-
-
- mView = inflater.inflate(R.layout.basic_settings,container,false);
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- mProfileName = mView.findViewById(id.profilename);
- mClientCert = mView.findViewById(id.certselect);
- mClientKey = mView.findViewById(id.keyselect);
- mCaCert = mView.findViewById(id.caselect);
- mpkcs12 = mView.findViewById(id.pkcs12select);
- mCrlFile = mView.findViewById(id.crlfile);
- mUseLzo = mView.findViewById(id.lzo);
- mType = mView.findViewById(id.type);
- mPKCS12Password = mView.findViewById(id.pkcs12password);
- mUserName = mView.findViewById(id.auth_username);
- mPassword = mView.findViewById(id.auth_password);
- mKeyPassword = mView.findViewById(id.key_password);
- mAuthRetry = mView.findViewById(id.auth_retry);
+ mView = inflater.inflate(R.layout.basic_settings, container, false);
- addFileSelectLayout(mCaCert, Utils.FileType.CA_CERTIFICATE);
- addFileSelectLayout(mClientCert, Utils.FileType.CLIENT_CERTIFICATE);
- addFileSelectLayout(mClientKey, Utils.FileType.KEYFILE);
- addFileSelectLayout(mpkcs12, Utils.FileType.PKCS12);
- addFileSelectLayout(mCrlFile, Utils.FileType.CRL_FILE);
- mCaCert.setShowClear();
- mCrlFile.setShowClear();
+ mProfileName = mView.findViewById(id.profilename);
+ mClientCert = mView.findViewById(id.certselect);
+ mClientKey = mView.findViewById(id.keyselect);
+ mCaCert = mView.findViewById(id.caselect);
+ mpkcs12 = mView.findViewById(id.pkcs12select);
+ mCrlFile = mView.findViewById(id.crlfile);
+ mUseLzo = mView.findViewById(id.lzo);
+ mType = mView.findViewById(id.type);
+ mPKCS12Password = mView.findViewById(id.pkcs12password);
- mType.setOnItemSelectedListener(this);
- mAuthRetry.setOnItemSelectedListener(this);
+ mUserName = mView.findViewById(id.auth_username);
+ mPassword = mView.findViewById(id.auth_password);
+ mKeyPassword = mView.findViewById(id.key_password);
+ mAuthRetry = mView.findViewById(id.auth_retry);
+ addFileSelectLayout(mCaCert, Utils.FileType.CA_CERTIFICATE);
+ addFileSelectLayout(mClientCert, Utils.FileType.CLIENT_CERTIFICATE);
+ addFileSelectLayout(mClientKey, Utils.FileType.KEYFILE);
+ addFileSelectLayout(mpkcs12, Utils.FileType.PKCS12);
+ addFileSelectLayout(mCrlFile, Utils.FileType.CRL_FILE);
+ mCaCert.setShowClear();
+ mCrlFile.setShowClear();
+ mType.setOnItemSelectedListener(this);
+ mAuthRetry.setOnItemSelectedListener(this);
- initKeychainViews(mView);
+ initKeychainViews(mView);
- return mView;
- }
+ return mView;
+ }
-
- @Override
- public void onActivityResult(int request, int result, Intent data) {
- if (result == Activity.RESULT_OK && request >= CHOOSE_FILE_OFFSET) {
-
- FileSelectLayout fsl = fileselects.get(request);
+ @Override
+ public void onActivityResult(int request, int result, Intent data) {
+ super.onActivityResult(request, result, data);
+ if (result == Activity.RESULT_OK && request >= CHOOSE_FILE_OFFSET) {
+ FileSelectLayout fsl = fileselects.get(request);
fsl.parseResponse(data, getActivity());
- savePreferences();
+ savePreferences();
- // Private key files may result in showing/hiding the private key password dialog
- if(fsl==mClientKey) {
- changeType(mType.getSelectedItemPosition());
- }
- }
+ // Private key files may result in showing/hiding the private key password dialog
+ if (fsl == mClientKey) {
+ changeType(mType.getSelectedItemPosition());
+ }
+ }
- }
+ }
@Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- if (parent == mType) {
- changeType(position);
- }
- }
-
-
- private void changeType(int type){
- // hide everything
- mView.findViewById(R.id.pkcs12).setVisibility(View.GONE);
- mView.findViewById(R.id.certs).setVisibility(View.GONE);
- mView.findViewById(R.id.statickeys).setVisibility(View.GONE);
- mView.findViewById(R.id.keystore).setVisibility(View.GONE);
- mView.findViewById(R.id.cacert).setVisibility(View.GONE);
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ if (parent == mType) {
+ changeType(position);
+ }
+ }
+
+
+ private void changeType(int type) {
+ // hide everything
+ mView.findViewById(R.id.pkcs12).setVisibility(View.GONE);
+ mView.findViewById(R.id.certs).setVisibility(View.GONE);
+ mView.findViewById(R.id.statickeys).setVisibility(View.GONE);
+ mView.findViewById(R.id.keystore).setVisibility(View.GONE);
+ mView.findViewById(R.id.cacert).setVisibility(View.GONE);
((FileSelectLayout) mView.findViewById(R.id.caselect)).setClearable(false);
mView.findViewById(R.id.userpassword).setVisibility(View.GONE);
- mView.findViewById(R.id.key_password_layout).setVisibility(View.GONE);
-
- // Fall through are by design
- switch(type) {
- case VpnProfile.TYPE_USERPASS_CERTIFICATES:
- mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE);
- case VpnProfile.TYPE_CERTIFICATES:
- mView.findViewById(R.id.certs).setVisibility(View.VISIBLE);
- mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE);
- if(mProfile.requireTLSKeyPassword())
- mView.findViewById(R.id.key_password_layout).setVisibility(View.VISIBLE);
- break;
-
- case VpnProfile.TYPE_USERPASS_PKCS12:
- mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE);
- case VpnProfile.TYPE_PKCS12:
- mView.findViewById(R.id.pkcs12).setVisibility(View.VISIBLE);
- break;
-
- case VpnProfile.TYPE_STATICKEYS:
- mView.findViewById(R.id.statickeys).setVisibility(View.VISIBLE);
- break;
-
- case VpnProfile.TYPE_USERPASS_KEYSTORE:
- mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE);
- case VpnProfile.TYPE_KEYSTORE:
- mView.findViewById(R.id.keystore).setVisibility(View.VISIBLE);
- mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE);
- ((FileSelectLayout) mView.findViewById(R.id.caselect)).setClearable(true);
- break;
-
- case VpnProfile.TYPE_USERPASS:
- mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE);
- mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE);
- break;
- }
-
-
- }
-
- protected void loadPreferences() {
- super.loadPreferences();
- mProfileName.setText(mProfile.mName);
- mClientCert.setData(mProfile.mClientCertFilename, getActivity());
- mClientKey.setData(mProfile.mClientKeyFilename, getActivity());
- mCaCert.setData(mProfile.mCaFilename, getActivity());
+ mView.findViewById(R.id.key_password_layout).setVisibility(View.GONE);
+ mView.findViewById(R.id.external_auth).setVisibility(View.GONE);
+
+ // Fall through are by design
+ switch (type) {
+ case VpnProfile.TYPE_USERPASS_CERTIFICATES:
+ mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE);
+ case VpnProfile.TYPE_CERTIFICATES:
+ mView.findViewById(R.id.certs).setVisibility(View.VISIBLE);
+ mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE);
+ if (mProfile.requireTLSKeyPassword())
+ mView.findViewById(R.id.key_password_layout).setVisibility(View.VISIBLE);
+ break;
+
+ case VpnProfile.TYPE_USERPASS_PKCS12:
+ mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE);
+ case VpnProfile.TYPE_PKCS12:
+ mView.findViewById(R.id.pkcs12).setVisibility(View.VISIBLE);
+ break;
+
+ case VpnProfile.TYPE_STATICKEYS:
+ mView.findViewById(R.id.statickeys).setVisibility(View.VISIBLE);
+ break;
+
+ case VpnProfile.TYPE_USERPASS_KEYSTORE:
+ mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE);
+ case VpnProfile.TYPE_KEYSTORE:
+ mView.findViewById(R.id.keystore).setVisibility(View.VISIBLE);
+ mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE);
+ ((FileSelectLayout) mView.findViewById(R.id.caselect)).setClearable(true);
+ break;
+
+ case VpnProfile.TYPE_USERPASS:
+ mView.findViewById(R.id.userpassword).setVisibility(View.VISIBLE);
+ mView.findViewById(R.id.cacert).setVisibility(View.VISIBLE);
+ break;
+ case VpnProfile.TYPE_EXTERNAL_APP:
+ mView.findViewById(R.id.external_auth).setVisibility(View.VISIBLE);
+ break;
+ }
+
+
+ }
+
+ protected void loadPreferences() {
+ super.loadPreferences();
+ mProfileName.setText(mProfile.mName);
+ mClientCert.setData(mProfile.mClientCertFilename, getActivity());
+ mClientKey.setData(mProfile.mClientKeyFilename, getActivity());
+ mCaCert.setData(mProfile.mCaFilename, getActivity());
mCrlFile.setData(mProfile.mCrlFilename, getActivity());
- mUseLzo.setChecked(mProfile.mUseLzo);
- mType.setSelection(mProfile.mAuthenticationType);
- mpkcs12.setData(mProfile.mPKCS12Filename, getActivity());
- mPKCS12Password.setText(mProfile.mPKCS12Password);
- mUserName.setText(mProfile.mUsername);
- mPassword.setText(mProfile.mPassword);
- mKeyPassword.setText(mProfile.mKeyPassword);
- mAuthRetry.setSelection(mProfile.mAuthRetry);
- }
-
- protected void savePreferences() {
- super.savePreferences();
- mProfile.mName = mProfileName.getText().toString();
- mProfile.mCaFilename = mCaCert.getData();
- mProfile.mClientCertFilename = mClientCert.getData();
- mProfile.mClientKeyFilename = mClientKey.getData();
+ mUseLzo.setChecked(mProfile.mUseLzo);
+ mType.setSelection(mProfile.mAuthenticationType);
+ mpkcs12.setData(mProfile.mPKCS12Filename, getActivity());
+ mPKCS12Password.setText(mProfile.mPKCS12Password);
+ mUserName.setText(mProfile.mUsername);
+ mPassword.setText(mProfile.mPassword);
+ mKeyPassword.setText(mProfile.mKeyPassword);
+ mAuthRetry.setSelection(mProfile.mAuthRetry);
+ }
+
+ protected void savePreferences() {
+ super.savePreferences();
+ mProfile.mName = mProfileName.getText().toString();
+ mProfile.mCaFilename = mCaCert.getData();
+ mProfile.mClientCertFilename = mClientCert.getData();
+ mProfile.mClientKeyFilename = mClientKey.getData();
mProfile.mCrlFilename = mCrlFile.getData();
- mProfile.mUseLzo = mUseLzo.isChecked();
- mProfile.mAuthenticationType = mType.getSelectedItemPosition();
- mProfile.mPKCS12Filename = mpkcs12.getData();
- mProfile.mPKCS12Password = mPKCS12Password.getText().toString();
-
- mProfile.mPassword = mPassword.getText().toString();
- mProfile.mUsername = mUserName.getText().toString();
- mProfile.mKeyPassword = mKeyPassword.getText().toString();
- mProfile.mAuthRetry = mAuthRetry.getSelectedItemPosition();
-
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- savePreferences();
- if(mProfile!=null) {
- outState.putString(getActivity().getPackageName() + "profileUUID", mProfile.getUUID().toString());
- }
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- }
+ mProfile.mUseLzo = mUseLzo.isChecked();
+ mProfile.mAuthenticationType = mType.getSelectedItemPosition();
+ mProfile.mPKCS12Filename = mpkcs12.getData();
+ mProfile.mPKCS12Password = mPKCS12Password.getText().toString();
+
+ mProfile.mPassword = mPassword.getText().toString();
+ mProfile.mUsername = mUserName.getText().toString();
+ mProfile.mKeyPassword = mKeyPassword.getText().toString();
+ mProfile.mAuthRetry = mAuthRetry.getSelectedItemPosition();
+
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ savePreferences();
+ if (mProfile != null) {
+ outState.putString(getActivity().getPackageName() + "profileUUID", mProfile.getUUID().toString());
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
}
diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/ShowConfigFragment.java b/main/src/main/java/de/blinkt/openvpn/fragments/ShowConfigFragment.java
index f759f0ed..e578aeeb 100644
--- a/main/src/main/java/de/blinkt/openvpn/fragments/ShowConfigFragment.java
+++ b/main/src/main/java/de/blinkt/openvpn/fragments/ShowConfigFragment.java
@@ -51,16 +51,18 @@ public class ShowConfigFragment extends Fragment {
new Thread() {
public void run() {
/* Add a few newlines to make the textview scrollable past the FAB */
- configtext = vp.getConfigFile(getActivity(), VpnProfile.doUseOpenVPN3(getActivity())) + "\n\n\n";
- getActivity().runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- cv.setText(configtext);
- if (mfabButton!=null)
- mfabButton.setVisibility(View.VISIBLE);
- }
- });
+ try {
+
+ configtext = vp.getConfigFile(getActivity(), VpnProfile.doUseOpenVPN3(getActivity())) + "\n\n\n";
+ } catch (Exception e) {
+ e.printStackTrace();
+ configtext = "Error generating config file: " + e.getLocalizedMessage();
+ }
+ getActivity().runOnUiThread(() -> {
+ cv.setText(configtext);
+ if (mfabButton!=null)
+ mfabButton.setVisibility(View.VISIBLE);
+ });
}
@@ -110,7 +112,7 @@ public class ShowConfigFragment extends Fragment {
final VpnProfile vp = ProfileManager.get(getActivity(),profileUUID);
int check=vp.checkProfile(getActivity());
- if(check!= R.string.no_error_found) {
+ if(check != R.string.no_error_found) {
mConfigView.setText(check);
configtext = getString(check);
}
diff --git a/main/src/main/res/layout/basic_settings.xml b/main/src/main/res/layout/basic_settings.xml
index 862f54b1..eda20ddd 100644
--- a/main/src/main/res/layout/basic_settings.xml
+++ b/main/src/main/res/layout/basic_settings.xml
@@ -62,6 +62,16 @@
android:text="@string/extracahint"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
+ <LinearLayout
+ android:id="@+id/external_auth"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:visibility="gone"
+ tools:visibility="gone">
+
+ <include layout="@layout/extauth_provider"/>
+ </LinearLayout>
<LinearLayout
android:id="@+id/cacert"
diff --git a/main/src/main/res/layout/extauth_provider.xml b/main/src/main/res/layout/extauth_provider.xml
new file mode 100644
index 00000000..69d1f3d8
--- /dev/null
+++ b/main/src/main/res/layout/extauth_provider.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- A layout to select a certificate, akin to a file selector on web pages. -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dip"
+ android:gravity="center_vertical">
+ <TextView
+ android:textStyle="bold"
+ android:id="@+id/extauth_title"
+ style="@style/item"
+ android:text="@string/external_authenticator"
+ android:textAppearance="?android:attr/textAppearanceSmall"/>
+
+ <Spinner
+ android:id="@+id/extauth_spinner"
+ style="@style/item"
+ android:layout_below="@id/extauth_title"
+ android:layout_marginBottom="20dp"
+ />
+
+ <Button
+ android:layout_below="@id/extauth_spinner"
+ android:id="@+id/configure_extauth_button"
+ style="@style/accountSetupButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:text="@string/configure"/>
+
+ <TextView
+ android:layout_below="@id/extauth_spinner"
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_toLeftOf="@+id/select_keystore_button"
+ android:text="@string/client_certificate_title"
+ android:textStyle="bold"
+ android:layout_alignParentStart="true"
+ android:layout_toStartOf="@+id/select_keystore_button"/>
+
+ <TextView
+ android:id="@+id/extauth_detail"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_below="@+id/title"
+ android:layout_marginLeft="16dip"
+ android:layout_toLeftOf="@+id/select_keystore_button"
+ android:ellipsize="end"
+ android:singleLine="false"
+ android:text="@string/no_certificate"/>
+</RelativeLayout> \ No newline at end of file
diff --git a/main/src/main/res/values-de/arrays.xml b/main/src/main/res/values-de/arrays.xml
index 402fda49..5386f8c2 100755
--- a/main/src/main/res/values-de/arrays.xml
+++ b/main/src/main/res/values-de/arrays.xml
@@ -15,6 +15,7 @@
<item>Nutzer/PW + Zertifikate</item>
<item>Nutzer/PW + PKCS12 </item>
<item>Nutzer/PW + Android</item>
+ <item>External Auth Provider</item>
</string-array>
<string-array name="tls_directions_entries">
<item translatable="false">0</item>
diff --git a/main/src/main/res/values/arrays.xml b/main/src/main/res/values/arrays.xml
index bbb77fe1..7b8f42db 100644
--- a/main/src/main/res/values/arrays.xml
+++ b/main/src/main/res/values/arrays.xml
@@ -15,6 +15,7 @@
<item>User/PW + Certificates</item>
<item>User/PW + PKCS12 </item>
<item>User/PW + Android</item>
+ <item>External Auth Provider</item>
</string-array>
<string-array name="tls_directions_entries">
<item translatable="false">0</item>
diff --git a/main/src/main/res/values/strings.xml b/main/src/main/res/values/strings.xml
index 10ccaa86..505dc80a 100755
--- a/main/src/main/res/values/strings.xml
+++ b/main/src/main/res/values/strings.xml
@@ -181,6 +181,7 @@
<string name="show_log_window">Show log window</string>
<string name="mobile_info">%10$s %9$s running on %3$s %1$s (%2$s), Android %6$s (%7$s) API %4$d, ABI %5$s, (%8$s)</string>
<string name="error_rsa_sign">Error signing with Android keystore key %1$s: %2$s</string>
+ <string name="error_extapp_sign">Error signing with external authenticator app (%3$s): %1$s: %2$s</string>
<string name="faq_system_dialogs">The VPN connection warning telling you that this app can intercept all traffic is imposed by the system to prevent abuse of the VPNService API.\nThe VPN connection notification (The key symbol) is also imposed by the Android system to signal an ongoing VPN connection. On some images this notification plays a sound.\nAndroid introduced these system dialogs for your own safety and made sure that they cannot be circumvented. (On some images this unfortunately includes a notification sound)</string>
<string name="faq_system_dialogs_title">Connection warning and notification sound</string>
<string name="translationby">English translation by Arne Schwabe&lt;arne@rfc2549.org&gt;</string>
@@ -482,5 +483,8 @@
</string>
<string name="openurl_requested">Open URL to continue VPN authentication</string>
<string name="state_auth_pending">Authentication pending</string>
+ <string name="external_authenticator">External Authenticator</string>
+ <string name="configure">Configure</string>
+ <string name="extauth_not_configured">External Authneticator not configured</string>
</resources>
diff --git a/main/src/ovpn3/java/de/blinkt/openvpn/core/OpenVPNThreadv3.java b/main/src/ovpn3/java/de/blinkt/openvpn/core/OpenVPNThreadv3.java
index 3e7011e7..d132f612 100644
--- a/main/src/ovpn3/java/de/blinkt/openvpn/core/OpenVPNThreadv3.java
+++ b/main/src/ovpn3/java/de/blinkt/openvpn/core/OpenVPNThreadv3.java
@@ -222,7 +222,7 @@ public class OpenVPNThreadv3 extends ClientAPI_OpenVPNClient implements Runnable
@Override
public void external_pki_cert_request(ClientAPI_ExternalPKICertRequest certreq) {
VpnStatus.logDebug("Got external PKI certificate request from OpenVPN core");
- String[] ks = mVp.getKeyStoreCertificates((Context) mService);
+ String[] ks = mVp.getExternalCertificates(mService);
if(ks==null) {
certreq.setError(true);
certreq.setErrorText("Error in pki cert request");
@@ -241,7 +241,7 @@ public class OpenVPNThreadv3 extends ClientAPI_OpenVPNClient implements Runnable
@Override
public void external_pki_sign_request(ClientAPI_ExternalPKISignRequest signreq) {
VpnStatus.logDebug("Got external PKI signing request from OpenVPN core");
- signreq.setSig(mVp.getSignedData(signreq.getData()));
+ signreq.setSig(mVp.getSignedData(mService, signreq.getData()));
}
void setUserPW() {