android: Add ManagedConfigurationService and related classes

Add service that provides access to managed configurations.
This commit is contained in:
Markus Pfeiffer 2023-11-21 15:37:21 +01:00 committed by Tobias Brunner
parent c2007d5b09
commit 8796e9bb31
3 changed files with 403 additions and 0 deletions

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}