summaryrefslogtreecommitdiff
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
parent7b7940186fafcdf4bb15ea8e087b8cf345cd53c8 (diff)
Implement certificate authentication via external provider
-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
-rw-r--r--tlsexternalcertprovider/build.gradle9
-rw-r--r--tlsexternalcertprovider/src/main/AndroidManifest.xml10
-rw-r--r--tlsexternalcertprovider/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl25
-rw-r--r--tlsexternalcertprovider/src/main/java/de/blinkt/externalcertprovider/ExternalCertService.java135
-rw-r--r--tlsexternalcertprovider/src/main/java/de/blinkt/externalcertprovider/SelectCertificateActivity.java9
-rw-r--r--tlsexternalcertprovider/src/main/java/de/blinkt/externalcertprovider/SimpleSigner.java147
-rw-r--r--tlsexternalcertprovider/src/main/res/values/strings.xml2
-rw-r--r--tlsexternalcertprovider/src/test/java/de/blinkt/externalcertprovider/SignDataTest.java (renamed from tlsexternalcertprovider/src/test/java/de/blinkt/externalcertprovider/ExampleUnitTest.java)9
22 files changed, 996 insertions, 466 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() {
diff --git a/tlsexternalcertprovider/build.gradle b/tlsexternalcertprovider/build.gradle
index 945bfef5..cbc48ce1 100644
--- a/tlsexternalcertprovider/build.gradle
+++ b/tlsexternalcertprovider/build.gradle
@@ -44,11 +44,12 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation (
- 'org.bouncycastle:bcprov-jdk15on:' + bouncycastleVersion,
- 'org.bouncycastle:bcprov-ext-jdk15on:' + bouncycastleVersion,
- 'org.bouncycastle:bcpkix-jdk15on:' + bouncycastleVersion,
+ //'org.bouncycastle:bcprov-jdk15on:' + bouncycastleVersion,
+ //'org.bouncycastle:bcprov-ext-jdk15on:' + bouncycastleVersion,
+ //'org.bouncycastle:bcpkix-jdk15on:' + bouncycastleVersion,
'org.bouncycastle:bcmail-jdk15on:' + bouncycastleVersion,
- 'org.bouncycastle:bcpg-jdk15on:' + bouncycastleVersion
+ //'org.bouncycastle:bcpg-jdk15on:' + bouncycastleVersion
)
+ testImplementation 'junit:junit:4.12'
}
diff --git a/tlsexternalcertprovider/src/main/AndroidManifest.xml b/tlsexternalcertprovider/src/main/AndroidManifest.xml
index 3ca07874..44af9254 100644
--- a/tlsexternalcertprovider/src/main/AndroidManifest.xml
+++ b/tlsexternalcertprovider/src/main/AndroidManifest.xml
@@ -14,7 +14,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
- >
+ >
<!--
OpenVPN for android will search for all application that have a activity that fulfils this
@@ -24,9 +24,11 @@
the already selected alias will be provided in the extra android.security.extra.KEY_ALIAS
if applicable
-->
- <activity android:name=".SelectCertificateActivity">
+ <activity android:name=".SelectCertificateActivity"
+ android:exported="true">
<intent-filter>
- <category android:name="de.blinkt.openvpn.CertProviderSelect"/>
+ <action android:name="de.blinkt.openvpn.api.ExternalCertificateConfiguration"/>
+ <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
@@ -35,7 +37,7 @@
android:name=".ExternalCertService"
tools:ignore="ExportedService">
<intent-filter>
- <action android:name="de.blinkt.openvpn.api.ExternalCertificateProvider" />
+ <action android:name="de.blinkt.openvpn.api.ExternalCertificateProvider"/>
</intent-filter>
</service>
</application>
diff --git a/tlsexternalcertprovider/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl b/tlsexternalcertprovider/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl
index d1e1a4bf..c6db965b 100644
--- a/tlsexternalcertprovider/src/main/aidl/de/blinkt/openvpn/api/ExternalCertificateProvider.aidl
+++ b/tlsexternalcertprovider/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/tlsexternalcertprovider/src/main/java/de/blinkt/externalcertprovider/ExternalCertService.java b/tlsexternalcertprovider/src/main/java/de/blinkt/externalcertprovider/ExternalCertService.java
index 5948a2b2..caf382dd 100644
--- a/tlsexternalcertprovider/src/main/java/de/blinkt/externalcertprovider/ExternalCertService.java
+++ b/tlsexternalcertprovider/src/main/java/de/blinkt/externalcertprovider/ExternalCertService.java
@@ -7,8 +7,10 @@ package de.blinkt.externalcertprovider;
import android.app.Service;
import android.content.Intent;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.text.TextUtils;
import de.blinkt.openvpn.api.ExternalCertificateProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
@@ -26,6 +28,9 @@ import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
+import static de.blinkt.externalcertprovider.SelectCertificateActivity.EXTRA_ALIAS;
+import static de.blinkt.externalcertprovider.SelectCertificateActivity.EXTRA_DESCRIPTION;
+
/**
* This is a VERY basic implementation.
* It does not even check if the service is even allowed to use the API
@@ -33,128 +38,14 @@ import java.security.spec.PKCS8EncodedKeySpec;
*/
public class ExternalCertService extends Service {
- String pemkey = "-----BEGIN PRIVATE KEY-----\n" +
- "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDsZY/pEsIaW+ZW\n" +
- "KgipgjotRHijADuwn+cnEECT7/HMPqCqBKKAGxOp5v6B1nCQqNjU3jDYNQDSvmLw\n" +
- "SNr8FY3Exm0LmfErgwAK0yojC+XN+TXfQ2EVcq2VmPZzIUFeoN1HJ6DVmtRBqBwd\n" +
- "VyBxF4/3KJ4+B87s1Q5CTx50R45HndIUKCcsFBD10Za1k3SE7/kE3o1Kb993q+rR\n" +
- "WNNE/loEAf8Gepf3/eNXSOHw30ATn2YjWuNVVD1UOe4A+RLx0t90LrrX8I3G3RhY\n" +
- "HJMiC3X6qNbgtS8tudT+uU+G4nVIFmD7P8m0MEIp+zuzK7lZgWpG80WDv/3VGv83\n" +
- "DG9b/WHxAgMBAAECggEBAIOdaCpUD02trOh8LqZxowJhBOl7z7/ex0uweMPk67LT\n" +
- "i5AdVHwOlzwZJ8oSIknoOBEMRBWcLQEojt1JMuL2/R95emzjIKshHHzqZKNulFvB\n" +
- "TIUpdnwChTKtH0mqUkLlPU3Ienty4IpNlpmfUKimfbkWHERdBJBHbtDsTABhdo3X\n" +
- "9pCF/yRKqJS2Fy/Mkl3gv1y/NB1OL4Jhl7vQbf+kmgfQN2qdOVe2BOKQ8NlPUDmE\n" +
- "/1XNIDaE3s6uvUaoFfwowzsCCwN2/8QrRMMKkjvV+lEVtNmQdYxj5Xj5IwS0vkK0\n" +
- "6icsngW87cpZxxc1zsRWcSTloy5ohub4FgKhlolmigECgYEA+cBlxzLvaMzMlBQY\n" +
- "kCac9KQMvVL+DIFHlZA5i5L/9pRVp4JJwj3GUoehFJoFhsxnKr8HZyLwBKlCmUVm\n" +
- "VxnshRWiAU18emUmeAtSGawlAS3QXhikVZDdd/L20YusLT+DXV81wlKR97/r9+17\n" +
- "klQOLkSdPm9wcMDOWMNHX8bUg8kCgYEA8k+hQv6+TR/+Beao2IIctFtw/EauaJiJ\n" +
- "wW5ql1cpCLPMAOQUvjs0Km3zqctfBF8mUjdkcyJ4uhL9FZtfywY22EtRIXOJ/8VR\n" +
- "we65mVo6RLR8YVM54sihanuFOnlyF9LIBWB+9pUfh1/Y7DSebh7W73uxhAxQhi3Y\n" +
- "QwfIQIFd8OkCgYBalH4VXhLYhpaYCiXSej6ot6rrK2N6c5Tb2MAWMA1nh+r84tMP\n" +
- "gMoh+pDgYPAqMI4mQbxUmqZEeoLuBe6VHpDav7rPECRaW781AJ4ZM4cEQ3Jz/inz\n" +
- "4qOAMn10CF081/Ez9ykPPlU0bsYNWHNd4eB2xWnmUBKOwk7UgJatVPaUiQKBgQCI\n" +
- "f18CVGpzG9CHFnaK8FCnMNOm6VIaTcNcGY0mD81nv5Dt943P054BQMsAHTY7SjZW\n" +
- "HioRyZtkhonXAB2oSqnekh7zzxgv4sG5k3ct8evdBCcE1FNJc2eqikZ0uDETRoOy\n" +
- "s7cRxNNr+QxDkyikM+80HOPU1PMPgwfOSrX90GJQ8QKBgEBKohGMV/sNa4t14Iau\n" +
- "qO8aagoqh/68K9GFXljsl3/iCSa964HIEREtW09Qz1w3dotEgp2w8bsDa+OwWrLy\n" +
- "0SY7T5jRViM3cDWRlUBLrGGiL0FiwsfqiRiji60y19erJgrgyGVIb1kIgIBRkgFM\n" +
- "2MMweASzTmZcri4PA/5C0HYb\n" +
- "-----END PRIVATE KEY-----\n";
-
- String[] certchain = new String[]{"-----BEGIN CERTIFICATE-----\n" +
- "MIIFFDCCAvygAwIBAgIBAjANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJLRzEL\n" +
- "MAkGA1UECBMCTkExEDAOBgNVBAcTB0JJU0hLRUsxFTATBgNVBAoTDE9wZW5WUE4t\n" +
- "VEVTVDEhMB8GCSqGSIb3DQEJARYSbWVAbXlob3N0Lm15ZG9tYWluMB4XDTE0MTAy\n" +
- "MjIxNTk1M1oXDTI0MTAxOTIxNTk1M1owajELMAkGA1UEBhMCS0cxCzAJBgNVBAgT\n" +
- "Ak5BMRUwEwYDVQQKEwxPcGVuVlBOLVRFU1QxFDASBgNVBAMTC1Rlc3QtQ2xpZW50\n" +
- "MSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21haW4wggEiMA0GCSqGSIb3\n" +
- "DQEBAQUAA4IBDwAwggEKAoIBAQDsZY/pEsIaW+ZWKgipgjotRHijADuwn+cnEECT\n" +
- "7/HMPqCqBKKAGxOp5v6B1nCQqNjU3jDYNQDSvmLwSNr8FY3Exm0LmfErgwAK0yoj\n" +
- "C+XN+TXfQ2EVcq2VmPZzIUFeoN1HJ6DVmtRBqBwdVyBxF4/3KJ4+B87s1Q5CTx50\n" +
- "R45HndIUKCcsFBD10Za1k3SE7/kE3o1Kb993q+rRWNNE/loEAf8Gepf3/eNXSOHw\n" +
- "30ATn2YjWuNVVD1UOe4A+RLx0t90LrrX8I3G3RhYHJMiC3X6qNbgtS8tudT+uU+G\n" +
- "4nVIFmD7P8m0MEIp+zuzK7lZgWpG80WDv/3VGv83DG9b/WHxAgMBAAGjgcgwgcUw\n" +
- "CQYDVR0TBAIwADAdBgNVHQ4EFgQU0rQ2D7H83aXqKvfHI4n64/p6RB0wgZgGA1Ud\n" +
- "IwSBkDCBjYAUK0DlyX319JY46S/jL9lAZMmOBZuhaqRoMGYxCzAJBgNVBAYTAktH\n" +
- "MQswCQYDVQQIEwJOQTEQMA4GA1UEBxMHQklTSEtFSzEVMBMGA1UEChMMT3BlblZQ\n" +
- "Ti1URVNUMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21haW6CCQChTt76\n" +
- "kPKugTANBgkqhkiG9w0BAQsFAAOCAgEAf+D+hKfs32KlzTzB5kKxMRLwudqnnj+9\n" +
- "llK2/FV0ZD7k/36q9z4GGF9zhfjI4GcbTZfKBdA3BzNkm+Z4dxSaVbsqrMN/yRUI\n" +
- "g1zIwmHTcUwFCyvLo4dtoDLtsLMnl0pVjQEqMFZoq/LaXBBzyaoKnEtMoFtRbgp+\n" +
- "bFOAsADhHppMCjeeIIm8xeV5WLdF/9PEof3ZeD1FFnTfgkQdHYFQWrkyTOJPPw46\n" +
- "ZVpkgzspMcSZiLzFhDnyGRLhZtDq+3Wx0ie+kVmjKwnVXL9GjtZn1gvs2qvwgBmH\n" +
- "ZAepd7FeDOLFHWqsXSPzMHU2TsrDTrBNjCzOUmFj3tX17+8KayMlJjw68sPCFhk/\n" +
- "qTK6aPnJEjw+xh//m070kLBj9dEzADBa6CT6NUSbaoDzpsx7PHNfUMQwcdh0kCcK\n" +
- "AU6lXrH42sJhgRGuKaOP+n5MTmKxAN6S449qLtrZOF1rfA3kAarIxm2LzcDIbuRX\n" +
- "IYr2RjDZrVGhh5amU8kexrvD61X+jNZc1cbzyrBg0tQqH4iU00wa2gyU/sFdDSrb\n" +
- "mSld9t0WxMhNdJ6A2dCq7XvjMORH2PUVwXG4xv3u/J6yX7W3ku3/yjf2x4K0VBOb\n" +
- "g82Hi35k9i5UOiKxxcH0pSVTmk2oD+c1S4nfGYNmZNnb0WErJBsdRET7STCHt0kj\n" +
- "CAKK4CXz9EM=\n" +
- "-----END CERTIFICATE-----\n"
- ,
- "-----BEGIN CERTIFICATE-----\n" +
- "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\n" +
- "-----END CERTIFICATE-----\n"};
private final ExternalCertificateProvider.Stub mBinder = new ExternalCertificateProvider.Stub() {
@Override
public byte[] getSignedData(String alias, byte[] data) throws RemoteException {
try {
-
- // This is more or less code that has been just modified long enough that it works
- // Don't take it as good example how to get a Privatekey
- StringReader keyreader = new StringReader(certchain + pemkey);
- PEMParser pemparser = new PEMParser(keyreader);
-
- PEMKeyPair bcKeyPair = (PEMKeyPair) pemparser.readObject();
-
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bcKeyPair.getPrivateKeyInfo().getEncoded());
- KeyFactory kf = KeyFactory.getInstance("RSA");
- PrivateKey key = kf.generatePrivate(keySpec);
-
- // The actual signing
-
- Cipher signer;
- signer = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
+ return SimpleSigner.signData(data);
- signer.init(Cipher.ENCRYPT_MODE, key);
-
- byte[] signed_bytes = signer.doFinal(data);
- return signed_bytes;
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
@@ -176,12 +67,22 @@ public class ExternalCertService extends Service {
}
@Override
- public String[] getCertificateChain(String alias) throws RemoteException {
+ public byte[] getCertificateChain(String alias) throws RemoteException {
+
+ return TextUtils.join("\n", SimpleSigner.certchain).getBytes();
+ }
- return certchain;
+ @Override
+ public Bundle getCertificateMetaData(String alias){
+ Bundle b = new Bundle();
+ b.putString(EXTRA_ALIAS, "mynicecert");
+ b.putString(EXTRA_DESCRIPTION, "Super secret example key!");
+ return b;
}
};
+
+
@Override
public void onCreate() {
super.onCreate();
diff --git a/tlsexternalcertprovider/src/main/java/de/blinkt/externalcertprovider/SelectCertificateActivity.java b/tlsexternalcertprovider/src/main/java/de/blinkt/externalcertprovider/SelectCertificateActivity.java
index 8d465aa4..3b59b7ac 100644
--- a/tlsexternalcertprovider/src/main/java/de/blinkt/externalcertprovider/SelectCertificateActivity.java
+++ b/tlsexternalcertprovider/src/main/java/de/blinkt/externalcertprovider/SelectCertificateActivity.java
@@ -11,6 +11,9 @@ import android.security.KeyChain;
import android.os.Bundle;
public class SelectCertificateActivity extends Activity {
+ public static final String EXTRA_ALIAS = "de.blinkt.openvpn.api.KEY_ALIAS";
+ public static final String EXTRA_DESCRIPTION = "de.blinkt.openvpn.api.KEY_DESCRIPTION";
+
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -19,8 +22,10 @@ public class SelectCertificateActivity extends Activity {
findViewById(R.id.nicecert).setOnClickListener((v) ->
{
Intent intent = new Intent();
- intent.putExtra(KeyChain.EXTRA_KEY_ALIAS, "mynicecert");
- setResult(RESULT_OK);
+ intent.putExtra(EXTRA_ALIAS, "mynicecert");
+ intent.putExtra(EXTRA_DESCRIPTION, "Super secret example key!");
+ setResult(RESULT_OK, intent);
+ finish();
});
}
}
diff --git a/tlsexternalcertprovider/src/main/java/de/blinkt/externalcertprovider/SimpleSigner.java b/tlsexternalcertprovider/src/main/java/de/blinkt/externalcertprovider/SimpleSigner.java
new file mode 100644
index 00000000..7d2f6786
--- /dev/null
+++ b/tlsexternalcertprovider/src/main/java/de/blinkt/externalcertprovider/SimpleSigner.java
@@ -0,0 +1,147 @@
+/*
+ * 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.externalcertprovider;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import java.io.IOException;
+import java.io.StringReader;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+
+public class SimpleSigner {
+ final static String pemkey = "-----BEGIN PRIVATE KEY-----\n" +
+ "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDsZY/pEsIaW+ZW\n" +
+ "KgipgjotRHijADuwn+cnEECT7/HMPqCqBKKAGxOp5v6B1nCQqNjU3jDYNQDSvmLw\n" +
+ "SNr8FY3Exm0LmfErgwAK0yojC+XN+TXfQ2EVcq2VmPZzIUFeoN1HJ6DVmtRBqBwd\n" +
+ "VyBxF4/3KJ4+B87s1Q5CTx50R45HndIUKCcsFBD10Za1k3SE7/kE3o1Kb993q+rR\n" +
+ "WNNE/loEAf8Gepf3/eNXSOHw30ATn2YjWuNVVD1UOe4A+RLx0t90LrrX8I3G3RhY\n" +
+ "HJMiC3X6qNbgtS8tudT+uU+G4nVIFmD7P8m0MEIp+zuzK7lZgWpG80WDv/3VGv83\n" +
+ "DG9b/WHxAgMBAAECggEBAIOdaCpUD02trOh8LqZxowJhBOl7z7/ex0uweMPk67LT\n" +
+ "i5AdVHwOlzwZJ8oSIknoOBEMRBWcLQEojt1JMuL2/R95emzjIKshHHzqZKNulFvB\n" +
+ "TIUpdnwChTKtH0mqUkLlPU3Ienty4IpNlpmfUKimfbkWHERdBJBHbtDsTABhdo3X\n" +
+ "9pCF/yRKqJS2Fy/Mkl3gv1y/NB1OL4Jhl7vQbf+kmgfQN2qdOVe2BOKQ8NlPUDmE\n" +
+ "/1XNIDaE3s6uvUaoFfwowzsCCwN2/8QrRMMKkjvV+lEVtNmQdYxj5Xj5IwS0vkK0\n" +
+ "6icsngW87cpZxxc1zsRWcSTloy5ohub4FgKhlolmigECgYEA+cBlxzLvaMzMlBQY\n" +
+ "kCac9KQMvVL+DIFHlZA5i5L/9pRVp4JJwj3GUoehFJoFhsxnKr8HZyLwBKlCmUVm\n" +
+ "VxnshRWiAU18emUmeAtSGawlAS3QXhikVZDdd/L20YusLT+DXV81wlKR97/r9+17\n" +
+ "klQOLkSdPm9wcMDOWMNHX8bUg8kCgYEA8k+hQv6+TR/+Beao2IIctFtw/EauaJiJ\n" +
+ "wW5ql1cpCLPMAOQUvjs0Km3zqctfBF8mUjdkcyJ4uhL9FZtfywY22EtRIXOJ/8VR\n" +
+ "we65mVo6RLR8YVM54sihanuFOnlyF9LIBWB+9pUfh1/Y7DSebh7W73uxhAxQhi3Y\n" +
+ "QwfIQIFd8OkCgYBalH4VXhLYhpaYCiXSej6ot6rrK2N6c5Tb2MAWMA1nh+r84tMP\n" +
+ "gMoh+pDgYPAqMI4mQbxUmqZEeoLuBe6VHpDav7rPECRaW781AJ4ZM4cEQ3Jz/inz\n" +
+ "4qOAMn10CF081/Ez9ykPPlU0bsYNWHNd4eB2xWnmUBKOwk7UgJatVPaUiQKBgQCI\n" +
+ "f18CVGpzG9CHFnaK8FCnMNOm6VIaTcNcGY0mD81nv5Dt943P054BQMsAHTY7SjZW\n" +
+ "HioRyZtkhonXAB2oSqnekh7zzxgv4sG5k3ct8evdBCcE1FNJc2eqikZ0uDETRoOy\n" +
+ "s7cRxNNr+QxDkyikM+80HOPU1PMPgwfOSrX90GJQ8QKBgEBKohGMV/sNa4t14Iau\n" +
+ "qO8aagoqh/68K9GFXljsl3/iCSa964HIEREtW09Qz1w3dotEgp2w8bsDa+OwWrLy\n" +
+ "0SY7T5jRViM3cDWRlUBLrGGiL0FiwsfqiRiji60y19erJgrgyGVIb1kIgIBRkgFM\n" +
+ "2MMweASzTmZcri4PA/5C0HYb\n" +
+ "-----END PRIVATE KEY-----\n";
+ final static String[] certchain = new String[]{"-----BEGIN CERTIFICATE-----\n" +
+ "MIIFFDCCAvygAwIBAgIBAjANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJLRzEL\n" +
+ "MAkGA1UECBMCTkExEDAOBgNVBAcTB0JJU0hLRUsxFTATBgNVBAoTDE9wZW5WUE4t\n" +
+ "VEVTVDEhMB8GCSqGSIb3DQEJARYSbWVAbXlob3N0Lm15ZG9tYWluMB4XDTE0MTAy\n" +
+ "MjIxNTk1M1oXDTI0MTAxOTIxNTk1M1owajELMAkGA1UEBhMCS0cxCzAJBgNVBAgT\n" +
+ "Ak5BMRUwEwYDVQQKEwxPcGVuVlBOLVRFU1QxFDASBgNVBAMTC1Rlc3QtQ2xpZW50\n" +
+ "MSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21haW4wggEiMA0GCSqGSIb3\n" +
+ "DQEBAQUAA4IBDwAwggEKAoIBAQDsZY/pEsIaW+ZWKgipgjotRHijADuwn+cnEECT\n" +
+ "7/HMPqCqBKKAGxOp5v6B1nCQqNjU3jDYNQDSvmLwSNr8FY3Exm0LmfErgwAK0yoj\n" +
+ "C+XN+TXfQ2EVcq2VmPZzIUFeoN1HJ6DVmtRBqBwdVyBxF4/3KJ4+B87s1Q5CTx50\n" +
+ "R45HndIUKCcsFBD10Za1k3SE7/kE3o1Kb993q+rRWNNE/loEAf8Gepf3/eNXSOHw\n" +
+ "30ATn2YjWuNVVD1UOe4A+RLx0t90LrrX8I3G3RhYHJMiC3X6qNbgtS8tudT+uU+G\n" +
+ "4nVIFmD7P8m0MEIp+zuzK7lZgWpG80WDv/3VGv83DG9b/WHxAgMBAAGjgcgwgcUw\n" +
+ "CQYDVR0TBAIwADAdBgNVHQ4EFgQU0rQ2D7H83aXqKvfHI4n64/p6RB0wgZgGA1Ud\n" +
+ "IwSBkDCBjYAUK0DlyX319JY46S/jL9lAZMmOBZuhaqRoMGYxCzAJBgNVBAYTAktH\n" +
+ "MQswCQYDVQQIEwJOQTEQMA4GA1UEBxMHQklTSEtFSzEVMBMGA1UEChMMT3BlblZQ\n" +
+ "Ti1URVNUMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21haW6CCQChTt76\n" +
+ "kPKugTANBgkqhkiG9w0BAQsFAAOCAgEAf+D+hKfs32KlzTzB5kKxMRLwudqnnj+9\n" +
+ "llK2/FV0ZD7k/36q9z4GGF9zhfjI4GcbTZfKBdA3BzNkm+Z4dxSaVbsqrMN/yRUI\n" +
+ "g1zIwmHTcUwFCyvLo4dtoDLtsLMnl0pVjQEqMFZoq/LaXBBzyaoKnEtMoFtRbgp+\n" +
+ "bFOAsADhHppMCjeeIIm8xeV5WLdF/9PEof3ZeD1FFnTfgkQdHYFQWrkyTOJPPw46\n" +
+ "ZVpkgzspMcSZiLzFhDnyGRLhZtDq+3Wx0ie+kVmjKwnVXL9GjtZn1gvs2qvwgBmH\n" +
+ "ZAepd7FeDOLFHWqsXSPzMHU2TsrDTrBNjCzOUmFj3tX17+8KayMlJjw68sPCFhk/\n" +
+ "qTK6aPnJEjw+xh//m070kLBj9dEzADBa6CT6NUSbaoDzpsx7PHNfUMQwcdh0kCcK\n" +
+ "AU6lXrH42sJhgRGuKaOP+n5MTmKxAN6S449qLtrZOF1rfA3kAarIxm2LzcDIbuRX\n" +
+ "IYr2RjDZrVGhh5amU8kexrvD61X+jNZc1cbzyrBg0tQqH4iU00wa2gyU/sFdDSrb\n" +
+ "mSld9t0WxMhNdJ6A2dCq7XvjMORH2PUVwXG4xv3u/J6yX7W3ku3/yjf2x4K0VBOb\n" +
+ "g82Hi35k9i5UOiKxxcH0pSVTmk2oD+c1S4nfGYNmZNnb0WErJBsdRET7STCHt0kj\n" +
+ "CAKK4CXz9EM=\n" +
+ "-----END CERTIFICATE-----\n"
+ ,
+ "-----BEGIN CERTIFICATE-----\n" +
+ "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\n" +
+ "-----END CERTIFICATE-----\n"};
+
+ public static byte[] signData(byte[] data) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+ // This is more or less code that has been just modified long enough that it works
+ // Don't take it as good example how to get a Privatekey
+ StringReader keyreader = new StringReader(SimpleSigner.certchain[0] + SimpleSigner.pemkey);
+ PEMParser pemparser = new PEMParser(keyreader);
+
+ X509CertificateHolder cert = (X509CertificateHolder) pemparser.readObject();
+ PrivateKeyInfo keyInfo = (PrivateKeyInfo) pemparser.readObject();
+
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyInfo.getEncoded());
+ KeyFactory kf = KeyFactory.getInstance("RSA");
+ PrivateKey key = kf.generatePrivate(keySpec);
+
+ // The actual signing
+
+ Cipher signer;
+ signer = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
+
+
+ signer.init(Cipher.ENCRYPT_MODE, key);
+
+ byte[] signed_bytes = signer.doFinal(data);
+ return signed_bytes;
+ }
+}
diff --git a/tlsexternalcertprovider/src/main/res/values/strings.xml b/tlsexternalcertprovider/src/main/res/values/strings.xml
index dca6fe10..a8324931 100644
--- a/tlsexternalcertprovider/src/main/res/values/strings.xml
+++ b/tlsexternalcertprovider/src/main/res/values/strings.xml
@@ -4,5 +4,5 @@
-->
<resources>
- <string name="app_name">ExternalCertProvider</string>
+ <string name="app_name">Blinkt Demo Provider</string>
</resources>
diff --git a/tlsexternalcertprovider/src/test/java/de/blinkt/externalcertprovider/ExampleUnitTest.java b/tlsexternalcertprovider/src/test/java/de/blinkt/externalcertprovider/SignDataTest.java
index 785f56a9..7b4e0764 100644
--- a/tlsexternalcertprovider/src/test/java/de/blinkt/externalcertprovider/ExampleUnitTest.java
+++ b/tlsexternalcertprovider/src/test/java/de/blinkt/externalcertprovider/SignDataTest.java
@@ -5,8 +5,10 @@
package de.blinkt.externalcertprovider;
+import de.blinkt.openvpn.api.ExternalCertificateProvider;
import org.junit.Test;
+import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.*;
/**
@@ -14,9 +16,10 @@ import static org.junit.Assert.*;
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
-public class ExampleUnitTest {
+public class SignDataTest {
@Test
- public void addition_isCorrect() {
- assertEquals(4, 2 + 2);
+ public void testSignData() throws Exception
+ {
+ SimpleSigner.signData(new byte[]{1,2,3,4,5,6,7,8});
}
} \ No newline at end of file