From b9ac2b15eac3e5e5f9dc89c948ec8278e2e7c1f9 Mon Sep 17 00:00:00 2001 From: Arne Schwabe Date: Mon, 6 Aug 2018 18:19:41 +0200 Subject: Implement certificate authentication via external provider --- .../openvpn/api/ExternalCertificateProvider.aidl | 25 +- .../java/android/support/v4n/view/ViewPager.java | 7 +- .../main/java/de/blinkt/openvpn/VpnProfile.java | 238 ++++++++------- .../java/de/blinkt/openvpn/core/ExtAuthHelper.java | 249 +++++++++++++++ .../openvpn/core/OpenVpnManagementThread.java | 2 +- .../fragments/KeyChainSettingsFragment.java | 143 +++++++-- .../blinkt/openvpn/fragments/Settings_Basic.java | 338 ++++++++++----------- .../openvpn/fragments/ShowConfigFragment.java | 24 +- main/src/main/res/layout/basic_settings.xml | 10 + main/src/main/res/layout/extauth_provider.xml | 70 +++++ main/src/main/res/values-de/arrays.xml | 1 + main/src/main/res/values/arrays.xml | 1 + main/src/main/res/values/strings.xml | 4 + .../de/blinkt/openvpn/core/OpenVPNThreadv3.java | 4 +- 14 files changed, 788 insertions(+), 328 deletions(-) create mode 100644 main/src/main/java/de/blinkt/openvpn/core/ExtAuthHelper.java create mode 100644 main/src/main/res/layout/extauth_provider.xml (limited to 'main/src') 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 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\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("\n").append(ks[0]).append("\n\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\n", cfgentry, dataWithOutHeader, cfgentry); - } else { - return String.format(Locale.ENGLISH, "%s %s\n", cfgentry, openVpnEscape(filedata)); - } - } - @NonNull private Collection getCustomRoutes(String routes) { Vector 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 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(c, android.R.layout.simple_spinner_item, android.R.id.text1, extProviders); + spinner.setAdapter(extAppAdapter); + if (selectedPos != -1) + spinner.setSelection(selectedPos); + } + + static ArrayList getExternalAuthProviderList(Context c) { + Intent configureExtAuth = new Intent(ACTION_CERT_CONFIGURATION); + + final PackageManager packageManager = c.getPackageManager(); + List configureList = + packageManager.queryIntentActivities(configureExtAuth, 0); + + Intent serviceExtAuth = new Intent(ACTION_CERT_PROVIDER); + + List serviceList = + packageManager.queryIntentServices(serviceExtAuth, 0); + + + // For now only list those who appear in both lists + + ArrayList providers = new ArrayList(); + + 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 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 toCertificates(@NonNull byte[] bytes) { + final String BEGINCERT = "-----BEGIN CERTIFICATE-----"; + try { + Vector 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) 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 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 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 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" /> + + + + + + + + + + + + +