From 270290b7f36038876383673f123b805e31eb50db Mon Sep 17 00:00:00 2001 From: Arne Schwabe Date: Mon, 2 Jul 2018 18:47:24 +0200 Subject: Implement app restrictions for MDM managed profiles --- main/src/main/AndroidManifest.xml | 9 + .../java/android/support/v4n/view/ViewPager.java | 4 +- .../main/java/de/blinkt/openvpn/VpnProfile.java | 11 +- .../de/blinkt/openvpn/activities/BaseActivity.java | 16 ++ .../de/blinkt/openvpn/activities/MainActivity.java | 1 + .../de/blinkt/openvpn/api/AppRestrictions.java | 182 +++++++++++++++++++++ .../de/blinkt/openvpn/core/OpenVPNService.java | 2 + .../fragments/KeyChainSettingsFragment.java | 146 +++++++++++++++++ .../blinkt/openvpn/fragments/Settings_Basic.java | 166 +++---------------- .../openvpn/fragments/Settings_UserEditable.java | 40 +++-- .../blinkt/openvpn/fragments/VPNProfileList.java | 41 ++--- main/src/main/res/layout/settings_usereditable.xml | 31 ++-- main/src/main/res/values/strings.xml | 1 + main/src/main/res/values/untranslatable.xml | 13 ++ main/src/main/res/xml/app_restrictions.xml | 64 ++++++++ 15 files changed, 530 insertions(+), 197 deletions(-) create mode 100644 main/src/main/java/de/blinkt/openvpn/api/AppRestrictions.java create mode 100644 main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java create mode 100644 main/src/main/res/xml/app_restrictions.xml (limited to 'main') diff --git a/main/src/main/AndroidManifest.xml b/main/src/main/AndroidManifest.xml index 94be45c2..22c4c6b9 100644 --- a/main/src/main/AndroidManifest.xml +++ b/main/src/main/AndroidManifest.xml @@ -22,6 +22,8 @@ android:name="android.hardware.touchscreen" android:required="false" /> + + + + + + + @@ -236,6 +244,7 @@ android:name=".api.ConnectVPN" android:targetActivity=".api.RemoteAction" /> + \ No newline at end of file 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 53daa70d..943b1396 100644 --- a/main/src/main/java/android/support/v4n/view/ViewPager.java +++ b/main/src/main/java/android/support/v4n/view/ViewPager.java @@ -2719,7 +2719,7 @@ public class ViewPager extends ViewGroup { @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { // Dispatch scroll events from this ViewPager. - if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) { + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { return super.dispatchPopulateAccessibilityEvent(event); } @@ -2767,7 +2767,7 @@ public class ViewPager extends ViewGroup { event.setClassName(ViewPager.class.getName()); final AccessibilityRecordCompat recordCompat = AccessibilityRecordCompat.obtain(); recordCompat.setScrollable(canScroll()); - if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED && mAdapter != null) { recordCompat.setItemCount(mAdapter.getCount()); recordCompat.setFromIndex(mCurItem); diff --git a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java index 74c0b595..d89bb362 100644 --- a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -165,12 +165,16 @@ public class VpnProfile implements Serializable, Cloneable { // 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; + public VpnProfile(String name) { mUuid = UUID.randomUUID(); mName = name; @@ -226,6 +230,11 @@ public class VpnProfile implements Serializable, Cloneable { } + // Only used for the special case of managed profiles + public void setUUID(UUID uuid){ + mUuid = uuid; + } + public String getName() { if (TextUtils.isEmpty(mName)) return "No profile name"; @@ -1100,7 +1109,7 @@ public class VpnProfile implements Serializable, Cloneable { } public String getUUIDString() { - return mUuid.toString(); + return mUuid.toString().toLowerCase(Locale.ENGLISH); } public PrivateKey getKeystoreKey() { diff --git a/main/src/main/java/de/blinkt/openvpn/activities/BaseActivity.java b/main/src/main/java/de/blinkt/openvpn/activities/BaseActivity.java index 8cdc1e90..08f2787b 100644 --- a/main/src/main/java/de/blinkt/openvpn/activities/BaseActivity.java +++ b/main/src/main/java/de/blinkt/openvpn/activities/BaseActivity.java @@ -5,11 +5,17 @@ package de.blinkt.openvpn.activities; +import android.annotation.TargetApi; import android.app.Activity; import android.app.UiModeManager; +import android.content.Context; +import android.content.RestrictionsManager; import android.content.res.Configuration; +import android.os.Build; import android.os.Bundle; +import android.os.UserManager; import android.view.Window; +import de.blinkt.openvpn.api.AppRestrictions; public class BaseActivity extends Activity { private boolean isAndroidTV() { @@ -24,4 +30,14 @@ public class BaseActivity extends Activity { } super.onCreate(savedInstanceState); } + + @Override + protected void onResume() { + super.onResume(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + AppRestrictions.getInstance(this).checkRestrictions(this); + + } + } + } diff --git a/main/src/main/java/de/blinkt/openvpn/activities/MainActivity.java b/main/src/main/java/de/blinkt/openvpn/activities/MainActivity.java index 5b06c833..f7c46d01 100644 --- a/main/src/main/java/de/blinkt/openvpn/activities/MainActivity.java +++ b/main/src/main/java/de/blinkt/openvpn/activities/MainActivity.java @@ -127,4 +127,5 @@ public class MainActivity extends BaseActivity { } + } diff --git a/main/src/main/java/de/blinkt/openvpn/api/AppRestrictions.java b/main/src/main/java/de/blinkt/openvpn/api/AppRestrictions.java new file mode 100644 index 00000000..311b600d --- /dev/null +++ b/main/src/main/java/de/blinkt/openvpn/api/AppRestrictions.java @@ -0,0 +1,182 @@ +/* + * 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.api; + +import android.annotation.TargetApi; +import android.content.*; +import android.os.Build; +import android.os.Bundle; +import android.os.Parcelable; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.ConfigParser; +import de.blinkt.openvpn.core.ProfileManager; +import de.blinkt.openvpn.core.VpnStatus; + +import java.io.IOException; +import java.io.StringReader; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; + + +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class AppRestrictions { + public static final String PROFILE_CREATOR = "de.blinkt.openvpn.api.AppRestrictions"; + final static int CONFIG_VERSION = 1; + static boolean alreadyChecked = false; + private static AppRestrictions mInstance; + private RestrictionsManager mRestrictionsMgr; + + private AppRestrictions(Context c) { + + } + + public static AppRestrictions getInstance(Context c) { + if (mInstance == null) + mInstance = new AppRestrictions(c); + return mInstance; + } + + private void addChangesListener(Context c) { + IntentFilter restrictionsFilter = + new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED); + BroadcastReceiver restrictionsReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + applyRestrictions(context); + } + }; + c.registerReceiver(restrictionsReceiver, restrictionsFilter); + } + + private String hashConfig(String config) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA1"); + byte utf8_bytes[] = config.getBytes(); + digest.update(utf8_bytes, 0, utf8_bytes.length); + return new BigInteger(1, digest.digest()).toString(16); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return null; + } + } + + private void applyRestrictions(Context c) { + mRestrictionsMgr = (RestrictionsManager) c.getSystemService(Context.RESTRICTIONS_SERVICE); + if (mRestrictionsMgr == null) + return; + Bundle restrictions = mRestrictionsMgr.getApplicationRestrictions(); + if (restrictions == null) + return; + + String configVersion = restrictions.getString("version", "(not set)"); + try { + if (Integer.parseInt(configVersion) != CONFIG_VERSION) + throw new NumberFormatException("Wrong version"); + } catch (NumberFormatException nex) { + VpnStatus.logError(String.format(Locale.US, "App restriction version %s does not match expected version %d", configVersion, CONFIG_VERSION)); + return; + } + Parcelable[] profileList = restrictions.getParcelableArray(("vpn_configuration_list")); + if (profileList == null) { + VpnStatus.logError("App restriction does not contain a profile list (vpn_configuration_list)"); + return; + } + + Set provisionedUuids = new HashSet<>(); + + ProfileManager pm = ProfileManager.getInstance(c); + for (Parcelable profile : profileList) { + if (!(profile instanceof Bundle)) { + VpnStatus.logError("App restriction profile has wrong type"); + continue; + } + Bundle p = (Bundle) profile; + + String uuid = p.getString("uuid"); + String ovpn = p.getString("ovpn"); + String name = p.getString("name"); + + if (uuid == null || ovpn == null || name == null) { + VpnStatus.logError("App restriction profile misses uuid, ovpn or name key"); + continue; + } + + String ovpnHash = hashConfig(ovpn); + + provisionedUuids.add(uuid.toLowerCase(Locale.ENGLISH)); + // Check if the profile already exists + VpnProfile vpnProfile = ProfileManager.get(c, uuid); + + + if (vpnProfile != null) { + // Profile exists, check if need to update it + if (ovpnHash.equals(vpnProfile.importedProfileHash)) + // not modified skip to next profile + continue; + + } + addProfile(c, ovpn, uuid, name, vpnProfile); + } + + Vector profilesToRemove = new Vector<>(); + // get List of all managed profiles + for (VpnProfile vp: pm.getProfiles()) + { + if (PROFILE_CREATOR.equals(vp.mProfileCreator)) { + if (!provisionedUuids.contains(vp.getUUIDString())) + profilesToRemove.add(vp); + } + } + for (VpnProfile vp: profilesToRemove) { + VpnStatus.logInfo("Remove with uuid: %s and name: %s since it is no longer in the list of managed profiles"); + pm.removeProfile(c, vp); + } + + } + + private void addProfile(Context c, String config, String uuid, String name, VpnProfile vpnProfile) { + ConfigParser cp = new ConfigParser(); + try { + cp.parseConfig(new StringReader(config)); + VpnProfile vp = cp.convertProfile(); + vp.mProfileCreator = PROFILE_CREATOR; + + // We don't want provisioned profiles to be editable + vp.mUserEditable = false; + + vp.mName = name; + vp.setUUID(UUID.fromString(uuid)); + vp.importedProfileHash = hashConfig(config); + + ProfileManager pm = ProfileManager.getInstance(c); + + if (vpnProfile != null) { + vp.mVersion = vpnProfile.mVersion + 1; + vp.mAlias = vpnProfile.mAlias; + } + + // The add method will replace any older profiles with the same UUID + pm.addProfile(vp); + pm.saveProfile(c, vp); + pm.saveProfileList(c); + + } catch (ConfigParser.ConfigParseError | IOException | IllegalArgumentException e) { + VpnStatus.logException("Error during import of managed profile", e); + } + } + + public void checkRestrictions(Context c) { + if (alreadyChecked) { + return; + } + alreadyChecked = true; + addChangesListener(c); + applyRestrictions(c); + } +} diff --git a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java index b5afbb0e..264052b4 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -845,6 +845,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac session = getString(R.string.session_ipv6string, session, mLocalIP, mLocalIPv6); else if (mLocalIP != null) session = getString(R.string.session_ipv4string, session, mLocalIP); + else + session = getString(R.string.session_ipv4string, session, mLocalIPv6); builder.setSession(session); diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java b/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java new file mode 100644 index 00000000..7ec72f78 --- /dev/null +++ b/main/src/main/java/de/blinkt/openvpn/fragments/KeyChainSettingsFragment.java @@ -0,0 +1,146 @@ +/* + * 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.AlertDialog; +import android.content.ActivityNotFoundException; +import android.os.Build; +import android.os.Handler; +import android.os.Message; +import android.security.KeyChain; +import android.security.KeyChainException; +import android.view.View; +import android.widget.TextView; +import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.ProfileManager; +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 TextView mAliasCertificate; + private TextView mAliasName; + private Handler mHandler; + + + + private void setAlias() { + 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(); + } + } + + protected void setKeystoreCertficate() + { + new Thread() { + public void run() { + String certstr=""; + try { + X509Certificate 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); + + } catch (Exception e) { + certstr="Could not get certificate from Keystore: " +e.getLocalizedMessage(); + } + + final String certStringCopy=certstr; + getActivity().runOnUiThread(new Runnable() { + + @Override + public void run() { + mAliasCertificate.setText(certStringCopy); + } + }); + + } + }.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); + mAliasCertificate = v.findViewById(R.id.alias_certificate); + mAliasName = v.findViewById(R.id.aliasname); + if (mHandler == null) { + mHandler = new Handler(this); + } + } + + @Override + public void onClick(View v) { + if (v == v.findViewById(R.id.select_keystore_button)) { + showCertDialog(); + } + } + + @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(); + } + + @Override + public boolean handleMessage(Message msg) { + setAlias(); + return true; + } +} 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 b6c6aad2..faefcf86 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 @@ -5,55 +5,32 @@ package de.blinkt.openvpn.fragments; -import android.annotation.TargetApi; import android.app.Activity; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.content.ActivityNotFoundException; import android.content.Intent; -import android.os.Build; import android.os.Bundle; -import android.os.Handler; -import android.os.Handler.Callback; -import android.os.Message; -import android.security.KeyChain; -import android.security.KeyChainAliasCallback; -import android.security.KeyChainException; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; +import android.widget.*; import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.Spinner; -import android.widget.TextView; - -import java.security.cert.X509Certificate; - import de.blinkt.openvpn.R; import de.blinkt.openvpn.R.id; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ProfileManager; -import de.blinkt.openvpn.core.X509Utils; import de.blinkt.openvpn.views.FileSelectLayout; -public class Settings_Basic extends Settings_Fragment implements View.OnClickListener, OnItemSelectedListener, Callback, FileSelectLayout.FileSelectCallback { +public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSelectedListener, FileSelectLayout.FileSelectCallback { private static final int CHOOSE_FILE_OFFSET = 1000; - private static final int UPDATE_ALIAS = 20; private FileSelectLayout mClientCert; private FileSelectLayout mCaCert; private FileSelectLayout mClientKey; - private TextView mAliasName; - private TextView mAliasCertificate; private CheckBox mUseLzo; private Spinner mType; private FileSelectLayout mpkcs12; private FileSelectLayout mCrlFile; private TextView mPKCS12Password; - private Handler mHandler; private EditText mUserName; private EditText mPassword; private View mView; @@ -76,45 +53,6 @@ public class Settings_Basic extends Settings_Fragment implements View.OnClickLis } - private void setKeystoreCertficate() - { - new Thread() { - public void run() { - String certstr=""; - try { - X509Certificate 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); - - } catch (Exception e) { - certstr="Could not get certificate from Keystore: " +e.getLocalizedMessage(); - } - - final String certStringCopy=certstr; - getActivity().runOnUiThread(new Runnable() { - - @Override - public void run() { - mAliasCertificate.setText(certStringCopy); - } - }); - - } - }.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); - } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -122,22 +60,20 @@ public class Settings_Basic extends Settings_Fragment implements View.OnClickLis mView = inflater.inflate(R.layout.basic_settings,container,false); - mProfileName = (EditText) mView.findViewById(R.id.profilename); - mClientCert = (FileSelectLayout) mView.findViewById(R.id.certselect); - mClientKey = (FileSelectLayout) mView.findViewById(R.id.keyselect); - mCaCert = (FileSelectLayout) mView.findViewById(R.id.caselect); - mpkcs12 = (FileSelectLayout) mView.findViewById(R.id.pkcs12select); - mCrlFile = (FileSelectLayout) mView.findViewById(id.crlfile); - mUseLzo = (CheckBox) mView.findViewById(R.id.lzo); - mType = (Spinner) mView.findViewById(R.id.type); - mPKCS12Password = (TextView) mView.findViewById(R.id.pkcs12password); - mAliasName = (TextView) mView.findViewById(R.id.aliasname); - mAliasCertificate = (TextView) mView.findViewById(id.alias_certificate); - - mUserName = (EditText) mView.findViewById(R.id.auth_username); - mPassword = (EditText) mView.findViewById(R.id.auth_password); - mKeyPassword = (EditText) mView.findViewById(R.id.key_password); - mAuthRetry = (Spinner) mView.findViewById(id.auth_retry); + 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); addFileSelectLayout(mCaCert, Utils.FileType.CA_CERTIFICATE); addFileSelectLayout(mClientCert, Utils.FileType.CLIENT_CERTIFICATE); @@ -150,25 +86,14 @@ public class Settings_Basic extends Settings_Fragment implements View.OnClickLis mType.setOnItemSelectedListener(this); mAuthRetry.setOnItemSelectedListener(this); - mView.findViewById(R.id.select_keystore_button).setOnClickListener(this); - if (mHandler == null) { - mHandler = new Handler(this); - } + initKeychainViews(mView); return mView; } - @Override - public void onStart() { - super.onStart(); - String profileUuid =getArguments().getString(getActivity().getPackageName() + ".profileUUID"); - mProfile=ProfileManager.get(getActivity(),profileUuid); - loadPreferences(); - - } @Override public void onActivityResult(int request, int result, Intent data) { @@ -245,7 +170,8 @@ public class Settings_Basic extends Settings_Fragment implements View.OnClickLis } - private void loadPreferences() { + protected void loadPreferences() { + super.loadPreferences(); mProfileName.setText(mProfile.mName); mClientCert.setData(mProfile.mClientCertFilename, getActivity()); mClientKey.setData(mProfile.mClientKeyFilename, getActivity()); @@ -260,13 +186,10 @@ public class Settings_Basic extends Settings_Fragment implements View.OnClickLis mPassword.setText(mProfile.mPassword); mKeyPassword.setText(mProfile.mKeyPassword); mAuthRetry.setSelection(mProfile.mAuthRetry); - - setAlias(); - } protected void savePreferences() { - + super.savePreferences(); mProfile.mName = mProfileName.getText().toString(); mProfile.mCaFilename = mCaCert.getData(); mProfile.mClientCertFilename = mClientCert.getData(); @@ -285,48 +208,6 @@ public class Settings_Basic extends Settings_Fragment implements View.OnClickLis } - - private void setAlias() { - 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(); - } - } - - @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) { - 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(); - } - } - - @Override - public void onClick(View v) { - if (v == mView.findViewById(R.id.select_keystore_button)) { - showCertDialog(); - } - } - @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -341,11 +222,4 @@ public class Settings_Basic extends Settings_Fragment implements View.OnClickLis } - @Override - public boolean handleMessage(Message msg) { - setAlias(); - return true; - } - - } diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/Settings_UserEditable.java b/main/src/main/java/de/blinkt/openvpn/fragments/Settings_UserEditable.java index f9477a85..98ebb55b 100644 --- a/main/src/main/java/de/blinkt/openvpn/fragments/Settings_UserEditable.java +++ b/main/src/main/java/de/blinkt/openvpn/fragments/Settings_UserEditable.java @@ -14,36 +14,50 @@ import android.view.ViewGroup; import android.widget.TextView; import de.blinkt.openvpn.R; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.api.AppRestrictions; -public class Settings_UserEditable extends OpenVpnPreferencesFragment { - @Override - protected void loadSettings() { - - } - - @Override - protected void saveSettings() { +public class Settings_UserEditable extends KeyChainSettingsFragment implements View.OnClickListener { - } + private View mView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.settings_usereditable, container, false); - TextView messageView = (TextView) v.findViewById(R.id.messageUserEdit); + mView = inflater.inflate(R.layout.settings_usereditable, container, false); + TextView messageView = (TextView) mView.findViewById(R.id.messageUserEdit); messageView.setText(getString(R.string.message_no_user_edit, getPackageString(mProfile.mProfileCreator))); - return v; + initKeychainViews(this.mView); + return mView; } private String getPackageString(String packageName) { + + if (AppRestrictions.PROFILE_CREATOR.equals(packageName)) + return "Android Enterprise Management"; + final PackageManager pm = getActivity().getPackageManager(); ApplicationInfo ai; try { - ai = pm.getApplicationInfo( packageName, 0); + ai = pm.getApplicationInfo(packageName, 0); } catch (final PackageManager.NameNotFoundException e) { ai = null; } final String applicationName = (String) (ai != null ? pm.getApplicationLabel(ai) : "(unknown)"); return String.format("%s (%s)", applicationName, packageName); } + + @Override + protected void savePreferences() { + + } + + @Override + public void onResume() { + super.onResume(); + mView.findViewById(R.id.keystore).setVisibility(View.GONE); + if (mProfile.mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE || + mProfile.mAuthenticationType == VpnProfile.TYPE_KEYSTORE) + mView.findViewById(R.id.keystore).setVisibility(View.VISIBLE); + } } diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java b/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java index a0aac73d..7ad13aaf 100644 --- a/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java +++ b/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java @@ -526,32 +526,25 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn dialog.setView(entry); dialog.setNeutralButton(R.string.menu_import_short, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - startImportConfigFilePicker(); - } - }); + (dialog1, which) -> startImportConfigFilePicker()); dialog.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String name = entry.getText().toString(); - if (getPM().getProfileByName(name) == null) { - VpnProfile profile; - if (mCopyProfile != null) - profile = mCopyProfile.copy(name); - else - profile = new VpnProfile(name); - - addProfile(profile); - editVPN(profile); - } else { - Toast.makeText(getActivity(), R.string.duplicate_profile_name, Toast.LENGTH_LONG).show(); - } + (dialog12, which) -> { + String name = entry.getText().toString(); + if (getPM().getProfileByName(name) == null) { + VpnProfile profile; + if (mCopyProfile != null) { + profile = mCopyProfile.copy(name); + // Remove restrictions on copy profile + profile.mProfileCreator = null; + profile.mUserEditable = true; + } else + profile = new VpnProfile(name); + + addProfile(profile); + editVPN(profile); + } else { + Toast.makeText(getActivity(), R.string.duplicate_profile_name, Toast.LENGTH_LONG).show(); } - - }); dialog.setNegativeButton(android.R.string.cancel, null); dialog.create().show(); diff --git a/main/src/main/res/layout/settings_usereditable.xml b/main/src/main/res/layout/settings_usereditable.xml index cc7f1ff5..6e954116 100644 --- a/main/src/main/res/layout/settings_usereditable.xml +++ b/main/src/main/res/layout/settings_usereditable.xml @@ -5,17 +5,26 @@ --> + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:padding="@dimen/stdpadding" + android:layout_width="match_parent" + android:gravity="center" + android:layout_height="match_parent"> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:text="@string/message_no_user_edit" + android:id="@+id/messageUserEdit" + android:layout_gravity="center_horizontal"/> + + + \ No newline at end of file diff --git a/main/src/main/res/values/strings.xml b/main/src/main/res/values/strings.xml index 5590acf3..10ccaa86 100755 --- a/main/src/main/res/values/strings.xml +++ b/main/src/main/res/values/strings.xml @@ -482,4 +482,5 @@ Open URL to continue VPN authentication Authentication pending + diff --git a/main/src/main/res/values/untranslatable.xml b/main/src/main/res/values/untranslatable.xml index f8f0cc96..94012670 100644 --- a/main/src/main/res/values/untranslatable.xml +++ b/main/src/main/res/values/untranslatable.xml @@ -50,4 +50,17 @@ VPN API permission dialog cancelled aes-256-gcm bf-cbc sha1 + + Unique UUID that identifies the profile (example: + 0E910C15–9A85-4DD9-AE0D-E6862392E638). Generate using uuidgen or similar tools + UUID + Content of the OpenVPN configuration file + Config + Name of the VPN profile + Name + List of VPN configurations + VPN configuration + Version of the managed configuration schema + + diff --git a/main/src/main/res/xml/app_restrictions.xml b/main/src/main/res/xml/app_restrictions.xml new file mode 100644 index 00000000..6387712c --- /dev/null +++ b/main/src/main/res/xml/app_restrictions.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- cgit v1.2.3