From 03d93d1fb7281da7d57af9a21f9b16dd580b276b Mon Sep 17 00:00:00 2001 From: Arne Schwabe Date: Fri, 5 Apr 2019 17:15:21 +0200 Subject: Fix KeyChain not selecting EC and fix manifest Also convert another file to Kotlin --- main/src/main/AndroidManifest.xml | 3 +- .../blinkt/openvpn/activities/ConfigConverter.java | 2 +- .../fragments/KeyChainSettingsFragment.java | 249 --------------------- .../openvpn/fragments/KeyChainSettingsFragment.kt | 249 +++++++++++++++++++++ main/src/main/res/layout/keystore_selector.xml | 25 ++- main/src/main/res/values/strings.xml | 1 + 6 files changed, 273 insertions(+), 256 deletions(-) delete mode 100644 main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java create mode 100644 main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.kt diff --git a/main/src/main/AndroidManifest.xml b/main/src/main/AndroidManifest.xml index 92120282..9cf728f3 100644 --- a/main/src/main/AndroidManifest.xml +++ b/main/src/main/AndroidManifest.xml @@ -171,7 +171,7 @@ - // https://commonsware.com/blog/2019/03/27/death-external-storage-stay-away-files.html + - /> diff --git a/main/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java b/main/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java index 8d0c5433..38b47b5a 100644 --- a/main/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java +++ b/main/src/main/java/de/blinkt/openvpn/activities/ConfigConverter.java @@ -263,7 +263,7 @@ public class ConfigConverter extends BaseActivity implements FileSelectCallback, }, - new String[]{"RSA"}, // List of acceptable key types. null for any + new String[]{"RSA", "EC"}, // List of acceptable key types. null for any null, // issuer, null for any mResult.mServerName, // host name of server requesting the cert, null if unavailable -1, // port of server requesting the cert, -1 if unavailable diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java b/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java deleted file mode 100644 index 16f0438d..00000000 --- a/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (c) 2012-2018 Arne Schwabe - * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt - */ - -package de.blinkt.openvpn.fragments; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.app.AlertDialog; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.security.KeyChain; -import android.security.KeyChainException; -import android.text.TextUtils; -import android.view.View; -import android.widget.AdapterView; -import android.widget.Spinner; -import android.widget.TextView; -import android.widget.Toast; -import de.blinkt.openvpn.R; -import de.blinkt.openvpn.VpnProfile; -import de.blinkt.openvpn.api.ExternalCertificateProvider; -import de.blinkt.openvpn.core.ExtAuthHelper; -import de.blinkt.openvpn.core.X509Utils; - -import java.security.cert.X509Certificate; - -abstract class KeyChainSettingsFragment extends Settings_Fragment implements View.OnClickListener, Handler.Callback { - private static final int UPDATE_ALIAS = 20; - private static final int UPDATEE_EXT_ALIAS = 210; - - - private TextView mAliasCertificate; - private TextView mAliasName; - private Handler mHandler; - private TextView mExtAliasName; - private Spinner mExtAuthSpinner; - - - private void setKeyStoreAlias() { - if (mProfile.mAlias == null) { - mAliasName.setText(R.string.client_no_certificate); - mAliasCertificate.setText(""); - } else { - mAliasCertificate.setText("Loading certificate from Keystore..."); - mAliasName.setText(mProfile.mAlias); - setCertificate(false); - } - } - - private void setExtAlias() { - if (mProfile.mAlias == null) { - mExtAliasName.setText(R.string.extauth_not_configured); - mAliasCertificate.setText(""); - } else { - mAliasCertificate.setText("Querying certificate from external provider..."); - mExtAliasName.setText(""); - setCertificate(true); - } - } - - private void fetchExtCertificateMetaData() { - new Thread() { - @Override - public void run() { - try { - Bundle b = ExtAuthHelper.getCertificateMetaData(getActivity(), mProfile.mExternalAuthenticator, mProfile.mAlias); - mProfile.mAlias = b.getString(ExtAuthHelper.EXTRA_ALIAS); - getActivity().runOnUiThread(() -> setAlias()); - } catch (KeyChainException e) { - e.printStackTrace(); - } - - } - }.start(); - } - - - protected void setCertificate(boolean external) { - new Thread() { - public void run() { - String certstr = ""; - Bundle metadata= null; - try { - X509Certificate cert; - - if (external) { - if (!TextUtils.isEmpty(mProfile.mExternalAuthenticator) && !TextUtils.isEmpty(mProfile.mAlias)) { - cert = ExtAuthHelper.getCertificateChain(getActivity(), mProfile.mExternalAuthenticator, mProfile.mAlias)[0]; - metadata = ExtAuthHelper.getCertificateMetaData(getActivity(), mProfile.mExternalAuthenticator, mProfile.mAlias); - } else { - cert = null; - certstr = getString(R.string.extauth_not_configured); - } - } else { - cert = KeyChain.getCertificateChain(getActivity().getApplicationContext(), mProfile.mAlias)[0]; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - { - if (isInHardwareKeystore()) - certstr += getString(R.string.hwkeychain); - } - } - } - if (cert!=null) { - certstr += X509Utils.getCertificateValidityString(cert, getResources()); - certstr += X509Utils.getCertificateFriendlyName(cert); - } - - - - } catch (Exception e) { - certstr = "Could not get certificate from Keystore: " + e.getLocalizedMessage(); - } - - final String certStringCopy = certstr; - Bundle finalMetadata = metadata; - getActivity().runOnUiThread(() -> { - mAliasCertificate.setText(certStringCopy); - if (finalMetadata!=null) - mExtAliasName.setText(finalMetadata.getString(ExtAuthHelper.EXTRA_DESCRIPTION)); - - }); - - } - }.start(); - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) - private boolean isInHardwareKeystore() throws KeyChainException, InterruptedException { - String algorithm = KeyChain.getPrivateKey(getActivity().getApplicationContext(), mProfile.mAlias).getAlgorithm(); - return KeyChain.isBoundKeyAlgorithm(algorithm); - } - - protected void initKeychainViews(View v) { - v.findViewById(R.id.select_keystore_button).setOnClickListener(this); - v.findViewById(R.id.configure_extauth_button).setOnClickListener(this); - mAliasCertificate = v.findViewById(R.id.alias_certificate); - mExtAuthSpinner = v.findViewById(R.id.extauth_spinner); - mExtAuthSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - ExtAuthHelper.ExternalAuthProvider selectedProvider = (ExtAuthHelper.ExternalAuthProvider) parent.getItemAtPosition(position); - if (!selectedProvider.packageName.equals(mProfile.mExternalAuthenticator)) { - mProfile.mAlias = ""; - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - - } - }); - mExtAliasName = v.findViewById(R.id.extauth_detail); - mAliasName = v.findViewById(R.id.aliasname); - if (mHandler == null) { - mHandler = new Handler(this); - } - ExtAuthHelper.setExternalAuthProviderSpinnerList(mExtAuthSpinner, mProfile.mExternalAuthenticator); - } - - @Override - public void onClick(View v) { - if (v == v.findViewById(R.id.select_keystore_button)) { - showCertDialog(); - } else if (v == v.findViewById(R.id.configure_extauth_button)) { - startExternalAuthConfig(); - } - } - - private void startExternalAuthConfig() { - ExtAuthHelper.ExternalAuthProvider eAuth = (ExtAuthHelper.ExternalAuthProvider) mExtAuthSpinner.getSelectedItem(); - mProfile.mExternalAuthenticator = eAuth.packageName; - if (!eAuth.configurable) { - fetchExtCertificateMetaData(); - return; - } - Intent extauth = new Intent(ExtAuthHelper.ACTION_CERT_CONFIGURATION); - extauth.setPackage(eAuth.packageName); - extauth.putExtra(ExtAuthHelper.EXTRA_ALIAS, mProfile.mAlias); - startActivityForResult(extauth, UPDATEE_EXT_ALIAS); - } - - @Override - protected void savePreferences() { - - } - - @Override - public void onStart() { - super.onStart(); - loadPreferences(); - } - - @SuppressWarnings("WrongConstant") - public void showCertDialog() { - try { - KeyChain.choosePrivateKeyAlias(getActivity(), - alias -> { - // Credential alias selected. Remember the alias selection for future use. - mProfile.mAlias = alias; - mHandler.sendEmptyMessage(UPDATE_ALIAS); - }, - new String[]{"RSA"}, // List of acceptable key types. null for any - null, // issuer, null for any - mProfile.mServerName, // host name of server requesting the cert, null if unavailable - -1, // port of server requesting the cert, -1 if unavailable - mProfile.mAlias); // alias to preselect, null if unavailable - } catch (ActivityNotFoundException anf) { - AlertDialog.Builder ab = new AlertDialog.Builder(getActivity()); - ab.setTitle(R.string.broken_image_cert_title); - ab.setMessage(R.string.broken_image_cert); - ab.setPositiveButton(android.R.string.ok, null); - ab.show(); - } - } - - protected void loadPreferences() { - setAlias(); - - } - - private void setAlias() { - if (mProfile.mAuthenticationType == VpnProfile.TYPE_EXTERNAL_APP) - setExtAlias(); - else - setKeyStoreAlias(); - } - - @Override - public boolean handleMessage(Message msg) { - setAlias(); - return true; - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (requestCode == UPDATEE_EXT_ALIAS && resultCode == Activity.RESULT_OK) { - mProfile.mAlias = data.getStringExtra(ExtAuthHelper.EXTRA_ALIAS); - mExtAliasName.setText(data.getStringExtra(ExtAuthHelper.EXTRA_DESCRIPTION)); - } - } -} diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.kt b/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.kt new file mode 100644 index 00000000..fe074c63 --- /dev/null +++ b/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.kt @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2012-2018 Arne Schwabe + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + */ + +package de.blinkt.openvpn.fragments + +import android.annotation.TargetApi +import android.app.Activity +import android.app.AlertDialog +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.os.Message +import android.security.KeyChain +import android.security.KeyChainException +import android.text.TextUtils +import android.view.View +import android.widget.AdapterView +import android.widget.Spinner +import android.widget.TextView +import android.widget.Toast +import de.blinkt.openvpn.R +import de.blinkt.openvpn.VpnProfile +import de.blinkt.openvpn.api.ExternalCertificateProvider +import de.blinkt.openvpn.core.ExtAuthHelper +import de.blinkt.openvpn.core.X509Utils + +import java.security.cert.X509Certificate + +internal abstract class KeyChainSettingsFragment : Settings_Fragment(), View.OnClickListener, Handler.Callback { + + + private lateinit var mAliasCertificate: TextView + private lateinit var mAliasName: TextView + private var mHandler: Handler? = null + private lateinit var mExtAliasName: TextView + private lateinit var mExtAuthSpinner: Spinner + + private val isInHardwareKeystore: Boolean + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + @Throws(KeyChainException::class, InterruptedException::class) + get() { + val algorithm = KeyChain.getPrivateKey(activity.applicationContext, mProfile.mAlias)!!.algorithm + return KeyChain.isBoundKeyAlgorithm(algorithm) + } + + + private fun setKeyStoreAlias() { + if (mProfile.mAlias == null) { + mAliasName.setText(R.string.client_no_certificate) + mAliasCertificate.text = "" + } else { + mAliasCertificate.text = "Loading certificate from Keystore..." + mAliasName.text = mProfile.mAlias + setCertificate(false) + } + } + + private fun setExtAlias() { + if (mProfile.mAlias == null) { + mExtAliasName.setText(R.string.extauth_not_configured) + mAliasCertificate.text = "" + } else { + mAliasCertificate.text = "Querying certificate from external provider..." + mExtAliasName.text = "" + setCertificate(true) + } + } + + private fun fetchExtCertificateMetaData() { + object : Thread() { + override fun run() { + try { + val b = ExtAuthHelper.getCertificateMetaData(activity, mProfile.mExternalAuthenticator, mProfile.mAlias) + mProfile.mAlias = b.getString(ExtAuthHelper.EXTRA_ALIAS) + activity.runOnUiThread { setAlias() } + } catch (e: KeyChainException) { + e.printStackTrace() + } + + } + }.start() + } + + + protected fun setCertificate(external: Boolean) { + object : Thread() { + override fun run() { + var certstr = "" + var metadata: Bundle? = null + try { + val cert: X509Certificate? + + if (external) { + if (!TextUtils.isEmpty(mProfile.mExternalAuthenticator) && !TextUtils.isEmpty(mProfile.mAlias)) { + cert = ExtAuthHelper.getCertificateChain(activity, mProfile.mExternalAuthenticator, mProfile.mAlias)!![0] + metadata = ExtAuthHelper.getCertificateMetaData(activity, mProfile.mExternalAuthenticator, mProfile.mAlias) + } else { + cert = null + certstr = getString(R.string.extauth_not_configured) + } + } else { + cert = KeyChain.getCertificateChain(activity.applicationContext, mProfile.mAlias)!![0] + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + run { + if (isInHardwareKeystore) + certstr += getString(R.string.hwkeychain) + } + } + } + if (cert != null) { + certstr += X509Utils.getCertificateValidityString(cert, resources) + certstr += X509Utils.getCertificateFriendlyName(cert) + } + + + } catch (e: Exception) { + certstr = "Could not get certificate from Keystore: " + e.localizedMessage!! + } + + val certStringCopy = certstr + val finalMetadata = metadata + activity.runOnUiThread { + mAliasCertificate.text = certStringCopy + if (finalMetadata != null) + mExtAliasName.text = finalMetadata.getString(ExtAuthHelper.EXTRA_DESCRIPTION) + + } + + } + }.start() + } + + protected fun initKeychainViews(v: View) { + v.findViewById(R.id.select_keystore_button).setOnClickListener(this) + v.findViewById(R.id.configure_extauth_button).setOnClickListener(this) + v.findViewById(R.id.install_keystore_button).setOnClickListener(this) + mAliasCertificate = v.findViewById(R.id.alias_certificate) + mExtAuthSpinner = v.findViewById(R.id.extauth_spinner) + mExtAuthSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { + val selectedProvider = parent.getItemAtPosition(position) as ExtAuthHelper.ExternalAuthProvider + if (selectedProvider.packageName != mProfile.mExternalAuthenticator) { + mProfile.mAlias = "" + } + } + + override fun onNothingSelected(parent: AdapterView<*>) { + + } + } + mExtAliasName = v.findViewById(R.id.extauth_detail) + mAliasName = v.findViewById(R.id.aliasname) + if (mHandler == null) { + mHandler = Handler(this) + } + ExtAuthHelper.setExternalAuthProviderSpinnerList(mExtAuthSpinner, mProfile.mExternalAuthenticator) + + v.findViewById(R.id.install_keystore_button).setOnClickListener { + startActivity(KeyChain.createInstallIntent()) }; + } + + override fun onClick(v: View) { + if (v === v.findViewById(R.id.select_keystore_button)) { + showCertDialog() + } else if (v === v.findViewById(R.id.configure_extauth_button)) { + startExternalAuthConfig() + } + } + + private fun startExternalAuthConfig() { + val eAuth = mExtAuthSpinner!!.selectedItem as ExtAuthHelper.ExternalAuthProvider + mProfile.mExternalAuthenticator = eAuth.packageName + if (!eAuth.configurable) { + fetchExtCertificateMetaData() + return + } + val extauth = Intent(ExtAuthHelper.ACTION_CERT_CONFIGURATION) + extauth.setPackage(eAuth.packageName) + extauth.putExtra(ExtAuthHelper.EXTRA_ALIAS, mProfile.mAlias) + startActivityForResult(extauth, UPDATEE_EXT_ALIAS) + } + + override fun savePreferences() { + + } + + override fun onStart() { + super.onStart() + loadPreferences() + } + + fun showCertDialog() { + try { + KeyChain.choosePrivateKeyAlias(activity, + { alias -> + // Credential alias selected. Remember the alias selection for future use. + mProfile.mAlias = alias + mHandler!!.sendEmptyMessage(UPDATE_ALIAS) + }, + arrayOf("RSA", "EC"), null, // issuer, null for any + mProfile.mServerName, // host name of server requesting the cert, null if unavailable + -1, // port of server requesting the cert, -1 if unavailable + mProfile.mAlias)// List of acceptable key types. null for any + // alias to preselect, null if unavailable + } catch (anf: ActivityNotFoundException) { + val ab = AlertDialog.Builder(activity) + ab.setTitle(R.string.broken_image_cert_title) + ab.setMessage(R.string.broken_image_cert) + ab.setPositiveButton(android.R.string.ok, null) + ab.show() + } + + } + + protected open fun loadPreferences() { + setAlias() + + } + + private fun setAlias() { + if (mProfile.mAuthenticationType == VpnProfile.TYPE_EXTERNAL_APP) + setExtAlias() + else + setKeyStoreAlias() + } + + override fun handleMessage(msg: Message): Boolean { + setAlias() + return true + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) { + super.onActivityResult(requestCode, resultCode, data) + + if (requestCode == UPDATEE_EXT_ALIAS && resultCode == Activity.RESULT_OK) { + mProfile.mAlias = data.getStringExtra(ExtAuthHelper.EXTRA_ALIAS) + mExtAliasName.text = data.getStringExtra(ExtAuthHelper.EXTRA_DESCRIPTION) + } + } + + companion object { + private val UPDATE_ALIAS = 20 + private val UPDATEE_EXT_ALIAS = 210 + } +} diff --git a/main/src/main/res/layout/keystore_selector.xml b/main/src/main/res/layout/keystore_selector.xml index 28a51836..d0b24892 100644 --- a/main/src/main/res/layout/keystore_selector.xml +++ b/main/src/main/res/layout/keystore_selector.xml @@ -28,7 +28,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" - android:text="@string/select"/> + android:text="@string/select" + android:layout_alignParentEnd="true" /> + android:textStyle="bold" + android:layout_alignParentStart="true" + android:layout_toStartOf="@+id/select_keystore_button" /> + android:text="@string/no_certificate" + android:layout_alignParentStart="true" + android:layout_marginStart="16dip" + android:layout_toStartOf="@+id/select_keystore_button" /> + android:layout_alignParentStart="true" + android:layout_marginStart="16dip" + android:layout_toStartOf="@+id/select_keystore_button" /> +