From: Markus Pfeiffer Date: Tue, 21 Nov 2023 14:37:21 +0000 (+0100) Subject: android: Add ManagedConfigurationService and related classes X-Git-Tag: android-2.5.0^2~27 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=8796e9bb3186f95a9e017625f751c93c2894a503;p=thirdparty%2Fstrongswan.git android: Add ManagedConfigurationService and related classes Add service that provides access to managed configurations. --- 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 index 0000000000..a429f5fc8b --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedConfiguration.java @@ -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 . + * + * 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 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 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 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 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 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 index 0000000000..ac8e73d9b4 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedConfigurationService.java @@ -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 . + * + * 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 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 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 index 0000000000..90169871c1 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedVpnProfile.java @@ -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 . + * + * 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; + } +}