]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
android: Add ManagedConfigurationService and related classes
authorMarkus Pfeiffer <markus.pfeiffer@relution.io>
Tue, 21 Nov 2023 14:37:21 +0000 (15:37 +0100)
committerTobias Brunner <tobias@strongswan.org>
Wed, 21 Feb 2024 11:24:53 +0000 (12:24 +0100)
Add service that provides access to managed configurations.

src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedConfiguration.java [new file with mode: 0644]
src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedConfigurationService.java [new file with mode: 0644]
src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedVpnProfile.java [new file with mode: 0644]

diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedConfiguration.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedConfiguration.java
new file mode 100644 (file)
index 0000000..a429f5f
--- /dev/null
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2024 Tobias Brunner
+ * Copyright (C) 2023 Relution GmbH
+ *
+ * Copyright (C) secunet Security Networks AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+package org.strongswan.android.data;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+
+import org.strongswan.android.utils.Constants;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import androidx.annotation.NonNull;
+
+public class ManagedConfiguration
+{
+       private static final String KEY_ALLOW_PROFILE_CREATE = "allow_profile_create";
+       private static final String KEY_ALLOW_PROFILE_IMPORT = "allow_profile_import";
+       private static final String KEY_ALLOW_EXISTING_PROFILES = "allow_existing_profiles";
+       private static final String KEY_ALLOW_CERTIFICATE_IMPORT = "allow_certificate_import";
+       private static final String KEY_ALLOW_SETTINGS_ACCESS = "allow_settings_access";
+       private static final String KEY_MANAGED_PROFILES = "managed_profiles";
+
+       private final boolean mAllowProfileCreation;
+       private final boolean mAllowProfileImport;
+       private final boolean mAllowExistingProfiles;
+       private final boolean mAllowCertificateImport;
+
+       private final boolean mAllowSettingsAccess;
+       private final String mDefaultVpnProfile;
+       private final boolean mIgnoreBatteryOptimizations;
+
+       private final Map<String, ManagedVpnProfile> mManagedVpnProfiles;
+
+       ManagedConfiguration()
+       {
+               mAllowProfileCreation = true;
+               mAllowProfileImport = true;
+               mAllowExistingProfiles = true;
+               mAllowCertificateImport = true;
+
+               mAllowSettingsAccess = true;
+               mDefaultVpnProfile = null;
+               mIgnoreBatteryOptimizations = false;
+
+               mManagedVpnProfiles = Collections.emptyMap();
+       }
+
+       ManagedConfiguration(final Bundle bundle)
+       {
+               mAllowProfileCreation = bundle.getBoolean(KEY_ALLOW_PROFILE_CREATE, true);
+               mAllowProfileImport = bundle.getBoolean(KEY_ALLOW_PROFILE_IMPORT, true);
+               mAllowExistingProfiles = bundle.getBoolean(KEY_ALLOW_EXISTING_PROFILES, true);
+               mAllowCertificateImport = bundle.getBoolean(KEY_ALLOW_CERTIFICATE_IMPORT, true);
+
+               mAllowSettingsAccess = bundle.getBoolean(KEY_ALLOW_SETTINGS_ACCESS, true);
+               mDefaultVpnProfile = bundle.getString(Constants.PREF_DEFAULT_VPN_PROFILE, null);
+               mIgnoreBatteryOptimizations = bundle.getBoolean(Constants.PREF_IGNORE_POWER_WHITELIST, false);
+
+               final List<Bundle> managedProfileBundles = getBundleArrayList(bundle, KEY_MANAGED_PROFILES);
+               mManagedVpnProfiles = new HashMap<>(managedProfileBundles.size());
+
+               for (final Bundle managedProfileBundle : managedProfileBundles)
+               {
+                       addManagedProfile(managedProfileBundle);
+               }
+       }
+
+       private void addManagedProfile(Bundle managedProfileBundle)
+       {
+               UUID uuid;
+               try
+               {
+                       uuid = UUID.fromString(managedProfileBundle.getString(VpnProfileDataSource.KEY_UUID));
+               }
+               catch (IllegalArgumentException e)
+               {
+                       return;
+               }
+               if (mManagedVpnProfiles.containsKey(uuid.toString()))
+               {
+                       return;
+               }
+
+               final ManagedVpnProfile vpnProfile = new ManagedVpnProfile(managedProfileBundle, uuid);
+               mManagedVpnProfiles.put(uuid.toString(), vpnProfile);
+       }
+
+       private List<Bundle> getBundleArrayList(final Bundle bundle, final String key)
+       {
+               if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
+               {
+                       return getBundleArrayListCompat(bundle, key);
+               }
+
+               final Bundle[] bundles = bundle.getParcelableArray(key, Bundle.class);
+               if (bundles == null)
+               {
+                       return Collections.emptyList();
+               }
+               return Arrays.asList(bundles);
+       }
+
+       @NonNull
+       private static List<Bundle> getBundleArrayListCompat(final Bundle bundle, final String key)
+       {
+               final Parcelable[] parcelables = bundle.getParcelableArray(key);
+               if (parcelables == null)
+               {
+                       return Collections.emptyList();
+               }
+               final Bundle[] bundles = Arrays.copyOf(parcelables, parcelables.length, Bundle[].class);
+               return Arrays.asList(bundles);
+       }
+
+       public boolean isAllowProfileCreation()
+       {
+               return mAllowProfileCreation;
+       }
+
+       public boolean isAllowProfileImport()
+       {
+               return mAllowProfileImport;
+       }
+
+       public boolean isAllowExistingProfiles()
+       {
+               return mAllowExistingProfiles;
+       }
+
+       public boolean isAllowCertificateImport()
+       {
+               return mAllowCertificateImport;
+       }
+
+       public boolean isAllowSettingsAccess()
+       {
+               return mAllowSettingsAccess;
+       }
+
+       public String getDefaultVpnProfile()
+       {
+               if (mDefaultVpnProfile != null && mDefaultVpnProfile.equalsIgnoreCase("mru"))
+               {
+                       return Constants.PREF_DEFAULT_VPN_PROFILE_MRU;
+               }
+               return mDefaultVpnProfile;
+       }
+
+       public boolean isIgnoreBatteryOptimizations()
+       {
+               return mIgnoreBatteryOptimizations;
+       }
+
+       public Map<String, ManagedVpnProfile> getVpnProfiles()
+       {
+               return mManagedVpnProfiles;
+       }
+}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedConfigurationService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedConfigurationService.java
new file mode 100644 (file)
index 0000000..ac8e73d
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 Relution GmbH
+ *
+ * Copyright (C) secunet Security Networks AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+package org.strongswan.android.data;
+
+import android.content.Context;
+import android.content.RestrictionsManager;
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.os.Bundle;
+
+import org.strongswan.android.utils.Constants;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.UUID;
+
+import androidx.preference.PreferenceManager;
+
+public class ManagedConfigurationService
+{
+       private final Context mContext;
+
+       private ManagedConfiguration mManagedConfiguration = new ManagedConfiguration();
+       private Map<String, ManagedVpnProfile> mManagedVpnProfiles = Collections.emptyMap();
+
+       public ManagedConfigurationService(final Context context)
+       {
+               this.mContext = context;
+       }
+
+       public void loadConfiguration()
+       {
+               if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
+               {
+                       return;
+               }
+
+               final RestrictionsManager restrictionsService = mContext.getSystemService(RestrictionsManager.class);
+               if (restrictionsService == null)
+               {
+                       return;
+               }
+
+               final Bundle configuration = restrictionsService.getApplicationRestrictions();
+               if (configuration == null)
+               {
+                       return;
+               }
+
+               final ManagedConfiguration managedConfiguration = new ManagedConfiguration(configuration);
+               mManagedConfiguration = managedConfiguration;
+               mManagedVpnProfiles = managedConfiguration.getVpnProfiles();
+       }
+
+       public void updateSettings()
+       {
+               if (!mManagedConfiguration.isAllowSettingsAccess())
+               {
+                       final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(mContext);
+                       final SharedPreferences.Editor editor = pref.edit();
+                       editor.putBoolean(Constants.PREF_IGNORE_POWER_WHITELIST, mManagedConfiguration.isIgnoreBatteryOptimizations());
+                       editor.putString(Constants.PREF_DEFAULT_VPN_PROFILE, mManagedConfiguration.getDefaultVpnProfile());
+                       editor.apply();
+               }
+       }
+
+       public ManagedConfiguration getManagedConfiguration()
+       {
+               return mManagedConfiguration;
+       }
+
+       public Map<String, ManagedVpnProfile> getManagedProfiles()
+       {
+               return mManagedVpnProfiles;
+       }
+}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedVpnProfile.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedVpnProfile.java
new file mode 100644 (file)
index 0000000..9016987
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 Relution GmbH
+ *
+ * Copyright (C) secunet Security Networks AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+package org.strongswan.android.data;
+
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import org.strongswan.android.utils.Constants;
+
+import java.util.UUID;
+
+public class ManagedVpnProfile extends VpnProfile
+{
+       private static final String KEY_REMOTE = "remote";
+       private static final String KEY_LOCAL = "local";
+       private static final String KEY_INCLUDED_APPS = "included_apps";
+       private static final String KEY_EXCLUDED_APPS = "excluded_apps";
+
+       private static final String KEY_TRANSPORT_IPV6_FLAG = "transport_ipv6";
+       private static final String KEY_REMOTE_CERT_REQ_FLAG = "remote_cert_req";
+       private static final String KEY_REMOTE_REVOCATION_CRL_FLAG = "remote_revocation_crl";
+       private static final String KEY_REMOTE_REVOCATION_OCSP_FLAG = "remote_revocation_ocsp";
+       private static final String KEY_REMOTE_REVOCATION_STRICT_FLAG = "remote_revocation_strict";
+       private static final String KEY_LOCAL_RSA_PSS_FLAG = "local_rsa_pss";
+
+       private static final String KEY_SPLIT_TUNNELLING_BLOCK_IPV4_FLAG = "split_tunnelling_block_ipv4";
+       private static final String KEY_SPLIT_TUNNELLING_BLOCK_IPV6_FLAG = "split_tunnelling_block_ipv6";
+
+       ManagedVpnProfile(final Bundle bundle, final UUID uuid)
+       {
+               int flags = 0;
+               int splitFlags = 0;
+
+               setReadOnly(true);
+               setUUID(uuid);
+               setName(bundle.getString(VpnProfileDataSource.KEY_NAME));
+               setVpnType(VpnType.fromIdentifier(bundle.getString(VpnProfileDataSource.KEY_VPN_TYPE)));
+
+               final Bundle remote = bundle.getBundle(KEY_REMOTE);
+               if (remote != null)
+               {
+                       setGateway(remote.getString(VpnProfileDataSource.KEY_GATEWAY));
+                       setPort(getInt(remote, VpnProfileDataSource.KEY_PORT, 1, 65535));
+                       setRemoteId(remote.getString(VpnProfileDataSource.KEY_REMOTE_ID));
+                       setCertificateAlias(remote.getString(VpnProfileDataSource.KEY_CERTIFICATE));
+
+                       flags = addNegativeFlag(flags, remote, KEY_REMOTE_CERT_REQ_FLAG, VpnProfile.FLAGS_SUPPRESS_CERT_REQS);
+                       flags = addNegativeFlag(flags, remote, KEY_REMOTE_REVOCATION_CRL_FLAG, VpnProfile.FLAGS_DISABLE_CRL);
+                       flags = addNegativeFlag(flags, remote, KEY_REMOTE_REVOCATION_OCSP_FLAG, VpnProfile.FLAGS_DISABLE_OCSP);
+                       flags = addPositiveFlag(flags, remote, KEY_REMOTE_REVOCATION_STRICT_FLAG, VpnProfile.FLAGS_STRICT_REVOCATION);
+               }
+
+               final Bundle local = bundle.getBundle(KEY_LOCAL);
+               if (local != null)
+               {
+                       setLocalId(local.getString(VpnProfileDataSource.KEY_LOCAL_ID));
+                       setUsername(local.getString(VpnProfileDataSource.KEY_USERNAME));
+
+                       flags = addPositiveFlag(flags, local, KEY_LOCAL_RSA_PSS_FLAG, VpnProfile.FLAGS_RSA_PSS);
+               }
+
+               final String includedPackageNames = bundle.getString(KEY_INCLUDED_APPS);
+               final String excludedPackageNames = bundle.getString(KEY_EXCLUDED_APPS);
+
+               if (!TextUtils.isEmpty(includedPackageNames))
+               {
+                       setSelectedAppsHandling(VpnProfile.SelectedAppsHandling.SELECTED_APPS_ONLY);
+                       setSelectedApps(includedPackageNames);
+               }
+               else if (!TextUtils.isEmpty(excludedPackageNames))
+               {
+                       setSelectedAppsHandling(VpnProfile.SelectedAppsHandling.SELECTED_APPS_EXCLUDE);
+                       setSelectedApps(excludedPackageNames);
+               }
+
+               setMTU(getInt(bundle, VpnProfileDataSource.KEY_MTU, Constants.MTU_MIN, Constants.MTU_MAX));
+               setNATKeepAlive(getInt(bundle, VpnProfileDataSource.KEY_NAT_KEEPALIVE, Constants.NAT_KEEPALIVE_MIN, Constants.NAT_KEEPALIVE_MAX));
+               setIkeProposal(bundle.getString(VpnProfileDataSource.KEY_IKE_PROPOSAL));
+               setEspProposal(bundle.getString(VpnProfileDataSource.KEY_ESP_PROPOSAL));
+               setDnsServers(bundle.getString(VpnProfileDataSource.KEY_DNS_SERVERS));
+               flags = addPositiveFlag(flags, bundle, KEY_TRANSPORT_IPV6_FLAG, VpnProfile.FLAGS_IPv6_TRANSPORT);
+
+               final Bundle splitTunneling = bundle.getBundle(VpnProfileDataSource.KEY_SPLIT_TUNNELING);
+               if (splitTunneling != null)
+               {
+                       splitFlags = addPositiveFlag(splitFlags, splitTunneling, KEY_SPLIT_TUNNELLING_BLOCK_IPV4_FLAG, VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4);
+                       splitFlags = addPositiveFlag(splitFlags, splitTunneling, KEY_SPLIT_TUNNELLING_BLOCK_IPV6_FLAG, VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6);
+
+                       setExcludedSubnets(splitTunneling.getString(VpnProfileDataSource.KEY_EXCLUDED_SUBNETS));
+                       setIncludedSubnets(splitTunneling.getString(VpnProfileDataSource.KEY_INCLUDED_SUBNETS));
+               }
+
+               setSplitTunneling(splitFlags);
+               setFlags(flags);
+       }
+
+       private static Integer getInt(final Bundle bundle, final String key, final int min, final int max)
+       {
+               final int value = bundle.getInt(key);
+               return value < min || value > max ? null : value;
+       }
+
+       private static int addPositiveFlag(int flags, Bundle bundle, String key, int flag)
+       {
+               if (bundle.getBoolean(key))
+               {
+                       flags |= flag;
+               }
+               return flags;
+       }
+
+       private static int addNegativeFlag(int flags, Bundle bundle, String key, int flag)
+       {
+               if (!bundle.getBoolean(key))
+               {
+                       flags |= flag;
+               }
+               return flags;
+       }
+}