From ff6b6b5b491d034ad13e3cfa6a4efc2fb9041275 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:20 +0100
Subject: [PATCH 01/45] Add ._.DS_Store to .gitignore
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index fc41250c4..067431411 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,7 @@ fuzzing-corpora/
*.tar.bz2
*.tar.gz
.DS_Store
+._.DS_Store
coverage/
*.gcno
*.gcda
From 5d192246e89858c433152b4009e17fa9ae13fc3a Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:21 +0100
Subject: [PATCH 02/45] android: Remove AndroidX legacy support
---
src/frontends/android/app/build.gradle | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle
index 7aa4d8454..195422791 100644
--- a/src/frontends/android/app/build.gradle
+++ b/src/frontends/android/app/build.gradle
@@ -46,7 +46,6 @@ android {
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.preference:preference:1.2.1'
- implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.10.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:5.8.0'
From a3e895b4d8b9128e509ec2d41bf9b0504ea9b410 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:21 +0100
Subject: [PATCH 03/45] android: Remove unnecessary API checks
The minSdkVersion is 21, remove unnecessary checks and code that target
older API versions.
---
.../android/logic/CharonVpnService.java | 37 +++--
.../android/logic/StrongSwanApplication.java | 37 ++---
.../strongswan/android/ui/MainActivity.java | 43 +++---
.../ui/TrustedCertificateImportActivity.java | 3 +-
.../ui/TrustedCertificatesActivity.java | 17 +--
.../android/ui/VpnProfileDetailActivity.java | 130 +++++++++---------
.../android/ui/VpnProfileImportActivity.java | 50 +++----
7 files changed, 142 insertions(+), 175 deletions(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
index 165f479dc..03102502d 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
@@ -101,11 +101,12 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
private volatile boolean mTerminate;
private volatile boolean mIsDisconnecting;
private volatile boolean mShowNotification;
- private BuilderAdapter mBuilderAdapter = new BuilderAdapter();
+ private final BuilderAdapter mBuilderAdapter = new BuilderAdapter();
private Handler mHandler;
private VpnStateService mService;
private final Object mServiceLock = new Object();
- private final ServiceConnection mServiceConnection = new ServiceConnection() {
+ private final ServiceConnection mServiceConnection = new ServiceConnection()
+ {
@Override
public void onServiceDisconnected(ComponentName name)
{ /* since the service is local this is theoretically only called when the process is terminated */
@@ -346,7 +347,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
{
synchronized (this)
{
- if (mNextProfile != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ if (mNextProfile != null)
{
mBuilderAdapter.setProfile(mNextProfile);
mBuilderAdapter.establishBlocking();
@@ -437,10 +438,10 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
name = profile.getName();
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL)
- .setSmallIcon(R.drawable.ic_notification)
- .setCategory(NotificationCompat.CATEGORY_SERVICE)
- .setVisibility(publicVersion ? NotificationCompat.VISIBILITY_PUBLIC
- : NotificationCompat.VISIBILITY_PRIVATE);
+ .setSmallIcon(R.drawable.ic_notification)
+ .setCategory(NotificationCompat.CATEGORY_SERVICE)
+ .setVisibility(publicVersion ? NotificationCompat.VISIBILITY_PUBLIC
+ : NotificationCompat.VISIBILITY_PRIVATE);
int s = R.string.state_disabled;
if (error != ErrorState.NO_ERROR)
{
@@ -527,10 +528,11 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
}
@Override
- public void stateChanged() {
+ public void stateChanged()
+ {
if (mShowNotification)
{
- NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(VPN_STATE_NOTIFICATION_ID, buildNotification(false));
}
}
@@ -813,7 +815,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
private VpnService.Builder mBuilder;
private BuilderCache mCache;
private BuilderCache mEstablishedCache;
- private PacketDropper mDropper = new PacketDropper();
+ private final PacketDropper mDropper = new PacketDropper();
public synchronized void setProfile(VpnProfile profile)
{
@@ -1071,7 +1073,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
}
}
}
- catch (ClosedByInterruptException|InterruptedException e)
+ catch (ClosedByInterruptException | InterruptedException e)
{
/* regular interruption */
}
@@ -1277,7 +1279,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
}
}
}
- else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ else
{ /* allow traffic that would otherwise be blocked to bypass the VPN */
builder.allowFamily(OsConstants.AF_INET);
}
@@ -1317,7 +1319,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
}
}
}
- else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ else
{
builder.allowFamily(OsConstants.AF_INET6);
}
@@ -1327,8 +1329,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
builder.addRoute("::", 0);
}
/* apply selected applications */
- if (mSelectedApps.size() > 0 &&
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ if (mSelectedApps.size() > 0)
{
switch (mAppHandling)
{
@@ -1372,11 +1373,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
{
return false;
}
- else if (addr instanceof Inet6Address)
- {
- return true;
- }
- return false;
+ return addr instanceof Inet6Address;
}
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
index 92a112a6d..ac9866155 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
@@ -16,20 +16,18 @@
package org.strongswan.android.logic;
+import android.app.Application;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import org.strongswan.android.security.LocalCertificateKeyStoreProvider;
+
import java.security.Security;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import org.strongswan.android.security.LocalCertificateKeyStoreProvider;
-import org.strongswan.android.ui.MainActivity;
-
-import android.app.Application;
-import android.content.Context;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-
import androidx.core.os.HandlerCompat;
public class StrongSwanApplication extends Application
@@ -38,7 +36,8 @@ public class StrongSwanApplication extends Application
private final ExecutorService mExecutorService = Executors.newFixedThreadPool(4);
private final Handler mMainHandler = HandlerCompat.createAsync(Looper.getMainLooper());
- static {
+ static
+ {
Security.addProvider(new LocalCertificateKeyStoreProvider());
}
@@ -51,6 +50,7 @@ public class StrongSwanApplication extends Application
/**
* Returns the current application context
+ *
* @return context
*/
public static Context getContext()
@@ -60,6 +60,7 @@ public class StrongSwanApplication extends Application
/**
* Returns a thread pool to run tasks in separate threads
+ *
* @return thread pool
*/
public Executor getExecutor()
@@ -69,6 +70,7 @@ public class StrongSwanApplication extends Application
/**
* Returns a handler to execute stuff by the main thread.
+ *
* @return handler
*/
public Handler getHandler()
@@ -82,21 +84,6 @@ public class StrongSwanApplication extends Application
*/
static
{
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2)
- {
- System.loadLibrary("strongswan");
-
- if (MainActivity.USE_BYOD)
- {
- System.loadLibrary("tpmtss");
- System.loadLibrary("tncif");
- System.loadLibrary("tnccs");
- System.loadLibrary("imcv");
- }
-
- System.loadLibrary("charon");
- System.loadLibrary("ipsec");
- }
System.loadLibrary("androidbridge");
}
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
index f2d8939ec..a48a0a886 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
@@ -21,7 +21,6 @@ package org.strongswan.android.ui;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
-import android.os.Build;
import android.os.Bundle;
import android.text.format.Formatter;
import android.view.Menu;
@@ -82,16 +81,6 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
return true;
}
- @Override
- public boolean onPrepareOptionsMenu(Menu menu)
- {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
- {
- menu.removeItem(R.id.menu_import_profile);
- }
- return true;
- }
-
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
@@ -195,26 +184,26 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
size = Formatter.formatFileSize(getActivity(), s);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
- .setTitle(R.string.clear_crl_cache_title)
- .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
+ .setTitle(R.string.clear_crl_cache_title)
+ .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
{
- @Override
- public void onClick(DialogInterface dialog, int which)
- {
- dismiss();
- }
- })
- .setPositiveButton(R.string.clear, new DialogInterface.OnClickListener()
+ dismiss();
+ }
+ })
+ .setPositiveButton(R.string.clear, new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton)
{
- @Override
- public void onClick(DialogInterface dialog, int whichButton)
+ for (String file : list)
{
- for (String file : list)
- {
- getActivity().deleteFile(file);
- }
+ getActivity().deleteFile(file);
}
- });
+ }
+ });
builder.setMessage(getActivity().getResources().getQuantityString(R.plurals.clear_crl_cache_msg, list.size(), list.size(), size));
return builder.create();
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java
index def0b88a5..4e1e39e2d 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java
@@ -78,7 +78,7 @@ public class TrustedCertificateImportActivity extends AppCompatActivity
{
importCertificate(intent.getData());
}
- else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
+ else
{
Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
openIntent.setType("*/*");
@@ -89,7 +89,6 @@ public class TrustedCertificateImportActivity extends AppCompatActivity
catch (ActivityNotFoundException e)
{ /* some devices are unable to browse for files */
finish();
- return;
}
}
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java
index c941a49e9..c32ec5d82 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java
@@ -17,7 +17,6 @@
package org.strongswan.android.ui;
import android.content.Intent;
-import android.os.Build;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
@@ -73,10 +72,10 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr
mAdapter = new TrustedCertificatesPagerAdapter(this);
- mPager = (ViewPager2)findViewById(R.id.viewpager);
+ mPager = findViewById(R.id.viewpager);
mPager.setAdapter(mAdapter);
- TabLayout tabs = (TabLayout)findViewById(R.id.tabs);
+ TabLayout tabs = findViewById(R.id.tabs);
new TabLayoutMediator(tabs, mPager, (tab, position) -> {
tab.setText(mAdapter.getTitle(position));
}).attach();
@@ -91,16 +90,6 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr
return true;
}
- @Override
- public boolean onPrepareOptionsMenu(Menu menu)
- {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
- {
- menu.removeItem(R.id.menu_import_certificate);
- }
- return true;
- }
-
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
@@ -164,7 +153,7 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr
public static class TrustedCertificatesPagerAdapter extends FragmentStateAdapter
{
- private TrustedCertificatesTab mTabs[];
+ private final TrustedCertificatesTab[] mTabs;
public TrustedCertificatesPagerAdapter(@NonNull FragmentActivity fragmentActivity)
{
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
index dc3bc1cc7..85d178e52 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
@@ -22,7 +22,6 @@ import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.security.KeyChain;
@@ -44,7 +43,6 @@ import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
-import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
@@ -195,64 +193,64 @@ public class VpnProfileDetailActivity extends AppCompatActivity
setContentView(R.layout.profile_detail_view);
- mName = (MultiAutoCompleteTextView)findViewById(R.id.name);
- mNameWrap = (TextInputLayoutHelper)findViewById(R.id.name_wrap);
- mGateway = (EditText)findViewById(R.id.gateway);
- mGatewayWrap = (TextInputLayoutHelper) findViewById(R.id.gateway_wrap);
- mSelectVpnType = (Spinner)findViewById(R.id.vpn_type);
- mTncNotice = (RelativeLayout)findViewById(R.id.tnc_notice);
+ mName = findViewById(R.id.name);
+ mNameWrap = findViewById(R.id.name_wrap);
+ mGateway = findViewById(R.id.gateway);
+ mGatewayWrap = findViewById(R.id.gateway_wrap);
+ mSelectVpnType = findViewById(R.id.vpn_type);
+ mTncNotice = findViewById(R.id.tnc_notice);
- mUsernamePassword = (ViewGroup)findViewById(R.id.username_password_group);
- mUsername = (EditText)findViewById(R.id.username);
- mUsernameWrap = (TextInputLayoutHelper) findViewById(R.id.username_wrap);
- mPassword = (EditText)findViewById(R.id.password);
+ mUsernamePassword = findViewById(R.id.username_password_group);
+ mUsername = findViewById(R.id.username);
+ mUsernameWrap = findViewById(R.id.username_wrap);
+ mPassword = findViewById(R.id.password);
- mUserCertificate = (ViewGroup)findViewById(R.id.user_certificate_group);
- mSelectUserCert = (RelativeLayout)findViewById(R.id.select_user_certificate);
+ mUserCertificate = findViewById(R.id.user_certificate_group);
+ mSelectUserCert = findViewById(R.id.select_user_certificate);
- mCheckAuto = (CheckBox)findViewById(R.id.ca_auto);
- mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate);
+ mCheckAuto = findViewById(R.id.ca_auto);
+ mSelectCert = findViewById(R.id.select_certificate);
- mShowAdvanced = (CheckBox)findViewById(R.id.show_advanced);
- mAdvancedSettings = (ViewGroup)findViewById(R.id.advanced_settings);
+ mShowAdvanced = findViewById(R.id.show_advanced);
+ mAdvancedSettings = findViewById(R.id.advanced_settings);
- mRemoteId = (MultiAutoCompleteTextView)findViewById(R.id.remote_id);
- mRemoteIdWrap = (TextInputLayoutHelper) findViewById(R.id.remote_id_wrap);
+ mRemoteId = findViewById(R.id.remote_id);
+ mRemoteIdWrap = findViewById(R.id.remote_id_wrap);
mLocalId = findViewById(R.id.local_id);
mLocalIdWrap = findViewById(R.id.local_id_wrap);
mDnsServers = findViewById(R.id.dns_servers);
mDnsServersWrap = findViewById(R.id.dns_servers_wrap);
- mMTU = (EditText)findViewById(R.id.mtu);
- mMTUWrap = (TextInputLayoutHelper) findViewById(R.id.mtu_wrap);
- mPort = (EditText)findViewById(R.id.port);
- mPortWrap = (TextInputLayoutHelper) findViewById(R.id.port_wrap);
- mNATKeepalive = (EditText)findViewById(R.id.nat_keepalive);
- mNATKeepaliveWrap = (TextInputLayoutHelper) findViewById(R.id.nat_keepalive_wrap);
+ mMTU = findViewById(R.id.mtu);
+ mMTUWrap = findViewById(R.id.mtu_wrap);
+ mPort = findViewById(R.id.port);
+ mPortWrap = findViewById(R.id.port_wrap);
+ mNATKeepalive = findViewById(R.id.nat_keepalive);
+ mNATKeepaliveWrap = findViewById(R.id.nat_keepalive_wrap);
mCertReq = findViewById(R.id.cert_req);
mUseCrl = findViewById(R.id.use_crl);
mUseOcsp = findViewById(R.id.use_ocsp);
- mStrictRevocation= findViewById(R.id.strict_revocation);
- mRsaPss= findViewById(R.id.rsa_pss);
- mIPv6Transport= findViewById(R.id.ipv6_transport);
- mIncludedSubnets = (EditText)findViewById(R.id.included_subnets);
- mIncludedSubnetsWrap = (TextInputLayoutHelper)findViewById(R.id.included_subnets_wrap);
- mExcludedSubnets = (EditText)findViewById(R.id.excluded_subnets);
- mExcludedSubnetsWrap = (TextInputLayoutHelper)findViewById(R.id.excluded_subnets_wrap);
- mBlockIPv4 = (CheckBox)findViewById(R.id.split_tunneling_v4);
- mBlockIPv6 = (CheckBox)findViewById(R.id.split_tunneling_v6);
+ mStrictRevocation = findViewById(R.id.strict_revocation);
+ mRsaPss = findViewById(R.id.rsa_pss);
+ mIPv6Transport = findViewById(R.id.ipv6_transport);
+ mIncludedSubnets = findViewById(R.id.included_subnets);
+ mIncludedSubnetsWrap = findViewById(R.id.included_subnets_wrap);
+ mExcludedSubnets = findViewById(R.id.excluded_subnets);
+ mExcludedSubnetsWrap = findViewById(R.id.excluded_subnets_wrap);
+ mBlockIPv4 = findViewById(R.id.split_tunneling_v4);
+ mBlockIPv6 = findViewById(R.id.split_tunneling_v6);
- mSelectSelectedAppsHandling = (Spinner)findViewById(R.id.apps_handling);
- mSelectApps = (RelativeLayout)findViewById(R.id.select_applications);
+ mSelectSelectedAppsHandling = findViewById(R.id.apps_handling);
+ mSelectApps = findViewById(R.id.select_applications);
- mIkeProposal = (EditText)findViewById(R.id.ike_proposal);
- mIkeProposalWrap = (TextInputLayoutHelper)findViewById(R.id.ike_proposal_wrap);
- mEspProposal = (EditText)findViewById(R.id.esp_proposal);
- mEspProposalWrap = (TextInputLayoutHelper)findViewById(R.id.esp_proposal_wrap);
+ mIkeProposal = findViewById(R.id.ike_proposal);
+ mIkeProposalWrap = findViewById(R.id.ike_proposal_wrap);
+ mEspProposal = findViewById(R.id.esp_proposal);
+ mEspProposalWrap = findViewById(R.id.esp_proposal_wrap);
/* make the link clickable */
((TextView)findViewById(R.id.proposal_intro)).setMovementMethod(LinkMovementMethod.getInstance());
- mProfileIdLabel = (TextView)findViewById(R.id.profile_id_label);
- mProfileId = (TextView)findViewById(R.id.profile_id);
+ mProfileIdLabel = findViewById(R.id.profile_id_label);
+ mProfileId = findViewById(R.id.profile_id);
final SpaceTokenizer spaceTokenizer = new SpaceTokenizer();
mName.setTokenizer(spaceTokenizer);
@@ -262,14 +260,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mName.setAdapter(gatewayAdapter);
mRemoteId.setAdapter(gatewayAdapter);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
+ mGateway.addTextChangedListener(new TextWatcher()
{
- findViewById(R.id.apps).setVisibility(View.GONE);
- mSelectSelectedAppsHandling.setVisibility(View.GONE);
- mSelectApps.setVisibility(View.GONE);
- }
-
- mGateway.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@@ -294,7 +286,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
}
});
- mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() {
+ mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener()
+ {
@Override
public void onItemSelected(AdapterView> parent, View view, int position, long id)
{
@@ -312,7 +305,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
((TextView)mTncNotice.findViewById(android.R.id.text1)).setText(R.string.tnc_notice_title);
((TextView)mTncNotice.findViewById(android.R.id.text2)).setText(R.string.tnc_notice_subtitle);
- mTncNotice.setOnClickListener(new OnClickListener() {
+ mTncNotice.setOnClickListener(new OnClickListener()
+ {
@Override
public void onClick(View v)
{
@@ -321,14 +315,15 @@ public class VpnProfileDetailActivity extends AppCompatActivity
});
mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener());
- ((Button)findViewById(R.id.install_user_certificate)).setOnClickListener(v -> {
+ findViewById(R.id.install_user_certificate).setOnClickListener(v -> {
Intent intent = KeyChain.createInstallIntent();
mInstallPKCS12.launch(intent);
});
mSelectUserIdAdapter = new CertificateIdentitiesAdapter(this);
mLocalId.setAdapter(mSelectUserIdAdapter);
- mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener()
+ {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
{
@@ -336,7 +331,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
}
});
- mSelectCert.setOnClickListener(new OnClickListener() {
+ mSelectCert.setOnClickListener(new OnClickListener()
+ {
@Override
public void onClick(View v)
{
@@ -346,7 +342,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
}
});
- mShowAdvanced.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ mShowAdvanced.setOnCheckedChangeListener(new OnCheckedChangeListener()
+ {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
{
@@ -354,7 +351,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
}
});
- mSelectSelectedAppsHandling.setOnItemSelectedListener(new OnItemSelectedListener() {
+ mSelectSelectedAppsHandling.setOnItemSelectedListener(new OnItemSelectedListener()
+ {
@Override
public void onItemSelected(AdapterView> parent, View view, int position, long id)
{
@@ -370,7 +368,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
}
});
- mSelectApps.setOnClickListener(new OnClickListener() {
+ mSelectApps.setOnClickListener(new OnClickListener()
+ {
@Override
public void onClick(View v)
{
@@ -489,7 +488,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
AlertDialog.Builder adb = new AlertDialog.Builder(VpnProfileDetailActivity.this);
adb.setTitle(R.string.alert_text_nocertfound_title);
adb.setMessage(R.string.alert_text_nocertfound);
- adb.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ adb.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
+ {
@Override
public void onClick(DialogInterface dialog, int id)
{
@@ -624,6 +624,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
/**
* Verify the user input and display error messages.
+ *
* @return true if the input is valid
*/
private boolean verifyInput()
@@ -969,7 +970,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
{
final X509Certificate[] chain = KeyChain.getCertificateChain(VpnProfileDetailActivity.this, alias);
/* alias() is not called from our main thread */
- runOnUiThread(new Runnable() {
+ runOnUiThread(new Runnable()
+ {
@Override
public void run()
{
@@ -992,7 +994,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
/**
* Callback interface for the user certificate loader.
*/
- private interface UserCertificateLoaderCallback {
+ private interface UserCertificateLoaderCallback
+ {
void onComplete(X509Certificate result);
}
@@ -1050,7 +1053,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.tnc_notice_title)
.setMessage(HtmlCompat.fromHtml(getString(R.string.tnc_notice_details), HtmlCompat.FROM_HTML_MODE_LEGACY))
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
+ {
@Override
public void onClick(DialogInterface dialog, int id)
{
@@ -1111,7 +1115,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
if (text instanceof Spanned)
{
SpannableString sp = new SpannableString(text + " ");
- TextUtils.copySpansFrom((Spanned) text, 0, text.length(), Object.class, sp, 0);
+ TextUtils.copySpansFrom((Spanned)text, 0, text.length(), Object.class, sp, 0);
return sp;
}
else
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java
index d7a069d0b..c58365d9c 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java
@@ -21,7 +21,6 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
@@ -135,12 +134,12 @@ public class VpnProfileImportActivity extends AppCompatActivity
}
);
- private LoaderManager.LoaderCallbacks mProfileLoaderCallbacks = new LoaderManager.LoaderCallbacks()
+ private final LoaderManager.LoaderCallbacks mProfileLoaderCallbacks = new LoaderManager.LoaderCallbacks()
{
@Override
public Loader onCreateLoader(int id, Bundle args)
{
- return new ProfileLoader(VpnProfileImportActivity.this, (Uri)args.getParcelable(PROFILE_URI));
+ return new ProfileLoader(VpnProfileImportActivity.this, args.getParcelable(PROFILE_URI));
}
@Override
@@ -156,7 +155,7 @@ public class VpnProfileImportActivity extends AppCompatActivity
}
};
- private LoaderManager.LoaderCallbacks mUserCertificateLoaderCallbacks = new LoaderManager.LoaderCallbacks()
+ private final LoaderManager.LoaderCallbacks mUserCertificateLoaderCallbacks = new LoaderManager.LoaderCallbacks()
{
@Override
public Loader onCreateLoader(int id, Bundle args)
@@ -191,23 +190,23 @@ public class VpnProfileImportActivity extends AppCompatActivity
setContentView(R.layout.profile_import_view);
mProgressBar = findViewById(R.id.progress_bar);
- mExistsWarning = (TextView)findViewById(R.id.exists_warning);
- mBasicDataGroup = (ViewGroup)findViewById(R.id.basic_data_group);
- mName = (TextView)findViewById(R.id.name);
- mGateway = (TextView)findViewById(R.id.gateway);
- mSelectVpnType = (TextView)findViewById(R.id.vpn_type);
+ mExistsWarning = findViewById(R.id.exists_warning);
+ mBasicDataGroup = findViewById(R.id.basic_data_group);
+ mName = findViewById(R.id.name);
+ mGateway = findViewById(R.id.gateway);
+ mSelectVpnType = findViewById(R.id.vpn_type);
- mUsernamePassword = (ViewGroup)findViewById(R.id.username_password_group);
- mUsername = (EditText)findViewById(R.id.username);
- mUsernameWrap = (TextInputLayoutHelper) findViewById(R.id.username_wrap);
- mPassword = (EditText)findViewById(R.id.password);
+ mUsernamePassword = findViewById(R.id.username_password_group);
+ mUsername = findViewById(R.id.username);
+ mUsernameWrap = findViewById(R.id.username_wrap);
+ mPassword = findViewById(R.id.password);
- mUserCertificate = (ViewGroup)findViewById(R.id.user_certificate_group);
- mSelectUserCert = (RelativeLayout)findViewById(R.id.select_user_certificate);
- mImportUserCert = (Button)findViewById(R.id.import_user_certificate);
+ mUserCertificate = findViewById(R.id.user_certificate_group);
+ mSelectUserCert = findViewById(R.id.select_user_certificate);
+ mImportUserCert = findViewById(R.id.import_user_certificate);
- mRemoteCertificate = (ViewGroup)findViewById(R.id.remote_certificate_group);
- mRemoteCert = (RelativeLayout)findViewById(R.id.remote_certificate);
+ mRemoteCertificate = findViewById(R.id.remote_certificate_group);
+ mRemoteCert = findViewById(R.id.remote_certificate);
mExistsWarning.setVisibility(View.GONE);
mBasicDataGroup.setVisibility(View.GONE);
@@ -216,7 +215,8 @@ public class VpnProfileImportActivity extends AppCompatActivity
mRemoteCertificate.setVisibility(View.GONE);
mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener());
- mImportUserCert.setOnClickListener(new View.OnClickListener() {
+ mImportUserCert.setOnClickListener(new View.OnClickListener()
+ {
@Override
public void onClick(View v)
{
@@ -233,7 +233,7 @@ public class VpnProfileImportActivity extends AppCompatActivity
{
loadProfile(getIntent().getData());
}
- else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
+ else
{
Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
openIntent.setType("*/*");
@@ -535,9 +535,9 @@ public class VpnProfileImportActivity extends AppCompatActivity
if (split != null)
{
String included = getSubnets(split, "subnets");
- profile.setIncludedSubnets(included != null ? included : null);
+ profile.setIncludedSubnets(included);
String excluded = getSubnets(split, "excluded");
- profile.setExcludedSubnets(excluded != null ? excluded : null);
+ profile.setExcludedSubnets(excluded);
int st = 0;
st |= split.optBoolean("block-ipv4") ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 : 0;
st |= split.optBoolean("block-ipv6") ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 : 0;
@@ -710,6 +710,7 @@ public class VpnProfileImportActivity extends AppCompatActivity
/**
* Verify the user input and display error messages.
+ *
* @return true if the input is valid
*/
private boolean verifyInput()
@@ -899,14 +900,15 @@ public class VpnProfileImportActivity extends AppCompatActivity
public void alias(final String alias)
{
/* alias() is not called from our main thread */
- runOnUiThread(new Runnable() {
+ runOnUiThread(new Runnable()
+ {
@Override
public void run()
{
mUserCertLoading = alias;
updateUserCertView();
if (alias != null)
- { /* otherwise the dialog was canceled, the request denied */
+ { /* otherwise the dialog was canceled, the request denied */
LoaderManager.getInstance(VpnProfileImportActivity.this).restartLoader(USER_CERT_LOADER, null, mUserCertificateLoaderCallbacks);
}
}
From 73af77709a07252dcc15ed8b414ea896ed18af7f Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:21 +0100
Subject: [PATCH 04/45] android: Remove unnecessary @TargetApi
The minSdkVersion is 21, remove unnecessary @TargetApi annotations.
---
.../java/org/strongswan/android/logic/CharonVpnService.java | 1 -
.../android/ui/TrustedCertificateImportActivity.java | 3 ---
2 files changed, 4 deletions(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
index 03102502d..6bd5e6bc4 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
@@ -1236,7 +1236,6 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
}
}
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void applyData(VpnService.Builder builder)
{
for (IPRange address : mAddresses)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java
index 4e1e39e2d..60e57b03b 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java
@@ -16,13 +16,11 @@
package org.strongswan.android.ui;
-import android.annotation.TargetApi;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
import android.widget.Toast;
@@ -61,7 +59,6 @@ public class TrustedCertificateImportActivity extends AppCompatActivity
}
);
- @TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public void onCreate(Bundle savedInstanceState)
{
From b687f0c22fefffc987675d562af1acf5d0236b09 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:21 +0100
Subject: [PATCH 05/45] android: Use try-with-resources for IO
---
.../java/org/strongswan/android/logic/CharonVpnService.java | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
index 6bd5e6bc4..5f5b2a187 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
@@ -1037,9 +1037,8 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
@Override
public synchronized void run()
{
- try
+ try (FileInputStream plain = new FileInputStream(mFd.getFileDescriptor()))
{
- FileInputStream plain = new FileInputStream(mFd.getFileDescriptor());
ByteBuffer packet = ByteBuffer.allocate(mCache.mMtu);
while (true)
{
@@ -1073,7 +1072,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
}
}
}
- catch (ClosedByInterruptException | InterruptedException e)
+ catch (final ClosedByInterruptException | InterruptedException e)
{
/* regular interruption */
}
From 7c8773dea51f36c999eea9c7d3a1a34d238abeae Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:21 +0100
Subject: [PATCH 06/45] android: Add interface for VPN data source
Change VPN profile source to an interface. Preparation to allow managed
configurations as a second source.
---
.../android/data/VpnProfileDataSource.java | 454 ++----------------
.../android/data/VpnProfileSource.java | 114 +++++
.../android/data/VpnProfileSqlDataSource.java | 433 +++++++++++++++++
.../android/logic/CharonVpnService.java | 3 +-
.../android/ui/SettingsFragment.java | 16 +-
.../android/ui/VpnProfileControlActivity.java | 12 +-
.../android/ui/VpnProfileDetailActivity.java | 3 +-
.../android/ui/VpnProfileImportActivity.java | 3 +-
.../android/ui/VpnProfileListFragment.java | 34 +-
.../strongswan/android/ui/VpnTileService.java | 3 +-
10 files changed, 631 insertions(+), 444 deletions(-)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
index 5604f67a1..07308956c 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 Relution GmbH
* Copyright (C) 2012-2019 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
@@ -18,298 +19,50 @@
package org.strongswan.android.data;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
import android.database.SQLException;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.util.Log;
-import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
-public class VpnProfileDataSource
+public interface VpnProfileDataSource
{
- private static final String TAG = VpnProfileDataSource.class.getSimpleName();
- public static final String KEY_ID = "_id";
- public static final String KEY_UUID = "_uuid";
- public static final String KEY_NAME = "name";
- public static final String KEY_GATEWAY = "gateway";
- public static final String KEY_VPN_TYPE = "vpn_type";
- public static final String KEY_USERNAME = "username";
- public static final String KEY_PASSWORD = "password";
- public static final String KEY_CERTIFICATE = "certificate";
- public static final String KEY_USER_CERTIFICATE = "user_certificate";
- public static final String KEY_MTU = "mtu";
- public static final String KEY_PORT = "port";
- public static final String KEY_SPLIT_TUNNELING = "split_tunneling";
- public static final String KEY_LOCAL_ID = "local_id";
- public static final String KEY_REMOTE_ID = "remote_id";
- public static final String KEY_EXCLUDED_SUBNETS = "excluded_subnets";
- public static final String KEY_INCLUDED_SUBNETS = "included_subnets";
- public static final String KEY_SELECTED_APPS = "selected_apps";
- public static final String KEY_SELECTED_APPS_LIST = "selected_apps_list";
- public static final String KEY_NAT_KEEPALIVE = "nat_keepalive";
- public static final String KEY_FLAGS = "flags";
- public static final String KEY_IKE_PROPOSAL = "ike_proposal";
- public static final String KEY_ESP_PROPOSAL = "esp_proposal";
- public static final String KEY_DNS_SERVERS = "dns_servers";
-
- private DatabaseHelper mDbHelper;
- private SQLiteDatabase mDatabase;
- private final Context mContext;
-
- private static final String DATABASE_NAME = "strongswan.db";
- private static final String TABLE_VPNPROFILE = "vpnprofile";
-
- private static final int DATABASE_VERSION = 17;
-
- public static final DbColumn[] COLUMNS = new DbColumn[] {
- new DbColumn(KEY_ID, "INTEGER PRIMARY KEY AUTOINCREMENT", 1),
- new DbColumn(KEY_UUID, "TEXT UNIQUE", 9),
- new DbColumn(KEY_NAME, "TEXT NOT NULL", 1),
- new DbColumn(KEY_GATEWAY, "TEXT NOT NULL", 1),
- new DbColumn(KEY_VPN_TYPE, "TEXT NOT NULL", 3),
- new DbColumn(KEY_USERNAME, "TEXT", 1),
- new DbColumn(KEY_PASSWORD, "TEXT", 1),
- new DbColumn(KEY_CERTIFICATE, "TEXT", 1),
- new DbColumn(KEY_USER_CERTIFICATE, "TEXT", 2),
- new DbColumn(KEY_MTU, "INTEGER", 5),
- new DbColumn(KEY_PORT, "INTEGER", 5),
- new DbColumn(KEY_SPLIT_TUNNELING, "INTEGER", 7),
- new DbColumn(KEY_LOCAL_ID, "TEXT", 8),
- new DbColumn(KEY_REMOTE_ID, "TEXT", 8),
- new DbColumn(KEY_EXCLUDED_SUBNETS, "TEXT", 10),
- new DbColumn(KEY_INCLUDED_SUBNETS, "TEXT", 11),
- new DbColumn(KEY_SELECTED_APPS, "INTEGER", 12),
- new DbColumn(KEY_SELECTED_APPS_LIST, "TEXT", 12),
- new DbColumn(KEY_NAT_KEEPALIVE, "INTEGER", 13),
- new DbColumn(KEY_FLAGS, "INTEGER", 14),
- new DbColumn(KEY_IKE_PROPOSAL, "TEXT", 15),
- new DbColumn(KEY_ESP_PROPOSAL, "TEXT", 15),
- new DbColumn(KEY_DNS_SERVERS, "TEXT", 17),
- };
-
- private static final String[] ALL_COLUMNS = getColumns(DATABASE_VERSION);
-
- private static String getDatabaseCreate(int version)
- {
- boolean first = true;
- StringBuilder create = new StringBuilder("CREATE TABLE ");
- create.append(TABLE_VPNPROFILE);
- create.append(" (");
- for (DbColumn column : COLUMNS)
- {
- if (column.Since <= version)
- {
- if (!first)
- {
- create.append(",");
- }
- first = false;
- create.append(column.Name);
- create.append(" ");
- create.append(column.Type);
- }
- }
- create.append(");");
- return create.toString();
- }
-
- private static String[] getColumns(int version)
- {
- ArrayList columns = new ArrayList<>();
- for (DbColumn column : COLUMNS)
- {
- if (column.Since <= version)
- {
- columns.add(column.Name);
- }
- }
- return columns.toArray(new String[0]);
- }
-
- private static class DatabaseHelper extends SQLiteOpenHelper
- {
- public DatabaseHelper(Context context)
- {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- }
-
- @Override
- public void onCreate(SQLiteDatabase database)
- {
- database.execSQL(getDatabaseCreate(DATABASE_VERSION));
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
- {
- Log.w(TAG, "Upgrading database from version " + oldVersion +
- " to " + newVersion);
- if (oldVersion < 2)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_USER_CERTIFICATE +
- " TEXT;");
- }
- if (oldVersion < 3)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_VPN_TYPE +
- " TEXT DEFAULT '';");
- }
- if (oldVersion < 4)
- { /* remove NOT NULL constraint from username column */
- updateColumns(db, 4);
- }
- if (oldVersion < 5)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_MTU +
- " INTEGER;");
- }
- if (oldVersion < 6)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_PORT +
- " INTEGER;");
- }
- if (oldVersion < 7)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SPLIT_TUNNELING +
- " INTEGER;");
- }
- if (oldVersion < 8)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_LOCAL_ID +
- " TEXT;");
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_REMOTE_ID +
- " TEXT;");
- }
- if (oldVersion < 9)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_UUID +
- " TEXT;");
- updateColumns(db, 9);
- }
- if (oldVersion < 10)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_EXCLUDED_SUBNETS +
- " TEXT;");
- }
- if (oldVersion < 11)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_INCLUDED_SUBNETS +
- " TEXT;");
- }
- if (oldVersion < 12)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SELECTED_APPS +
- " INTEGER;");
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SELECTED_APPS_LIST +
- " TEXT;");
- }
- if (oldVersion < 13)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_NAT_KEEPALIVE +
- " INTEGER;");
- }
- if (oldVersion < 14)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_FLAGS +
- " INTEGER;");
- }
- if (oldVersion < 15)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_IKE_PROPOSAL +
- " TEXT;");
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_ESP_PROPOSAL +
- " TEXT;");
- }
- if (oldVersion < 16)
- { /* add a UUID to all entries that haven't one yet */
- db.beginTransaction();
- try
- {
- Cursor cursor = db.query(TABLE_VPNPROFILE, getColumns(16), KEY_UUID + " is NULL", null, null, null, null);
- for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext())
- {
- ContentValues values = new ContentValues();
- values.put(KEY_UUID, UUID.randomUUID().toString());
- db.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + cursor.getLong(cursor.getColumnIndexOrThrow(KEY_ID)), null);
- }
- cursor.close();
- db.setTransactionSuccessful();
- }
- finally
- {
- db.endTransaction();
- }
- }
- if (oldVersion < 17)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_DNS_SERVERS +
- " TEXT;");
- }
- }
-
- private void updateColumns(SQLiteDatabase db, int version)
- {
- db.beginTransaction();
- try
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " RENAME TO tmp_" + TABLE_VPNPROFILE + ";");
- db.execSQL(getDatabaseCreate(version));
- StringBuilder insert = new StringBuilder("INSERT INTO " + TABLE_VPNPROFILE + " SELECT ");
- SQLiteQueryBuilder.appendColumns(insert, getColumns(version));
- db.execSQL(insert.append(" FROM tmp_" + TABLE_VPNPROFILE + ";").toString());
- db.execSQL("DROP TABLE tmp_" + TABLE_VPNPROFILE + ";");
- db.setTransactionSuccessful();
- }
- finally
- {
- db.endTransaction();
- }
- }
- }
-
- /**
- * Construct a new VPN profile data source. The context is used to
- * open/create the database.
- * @param context context used to access the database
- */
- public VpnProfileDataSource(Context context)
- {
- this.mContext = context;
- }
+ String KEY_ID = "_id";
+ String KEY_UUID = "_uuid";
+ String KEY_NAME = "name";
+ String KEY_GATEWAY = "gateway";
+ String KEY_VPN_TYPE = "vpn_type";
+ String KEY_USERNAME = "username";
+ String KEY_PASSWORD = "password";
+ String KEY_CERTIFICATE = "certificate";
+ String KEY_USER_CERTIFICATE = "user_certificate";
+ String KEY_MTU = "mtu";
+ String KEY_PORT = "port";
+ String KEY_SPLIT_TUNNELING = "split_tunneling";
+ String KEY_LOCAL_ID = "local_id";
+ String KEY_REMOTE_ID = "remote_id";
+ String KEY_EXCLUDED_SUBNETS = "excluded_subnets";
+ String KEY_INCLUDED_SUBNETS = "included_subnets";
+ String KEY_SELECTED_APPS = "selected_apps";
+ String KEY_SELECTED_APPS_LIST = "selected_apps_list";
+ String KEY_NAT_KEEPALIVE = "nat_keepalive";
+ String KEY_FLAGS = "flags";
+ String KEY_IKE_PROPOSAL = "ike_proposal";
+ String KEY_ESP_PROPOSAL = "esp_proposal";
+ String KEY_DNS_SERVERS = "dns_servers";
/**
* Open the VPN profile data source. The database is automatically created
* if it does not yet exist. If that fails an exception is thrown.
+ *
* @return itself (allows to chain initialization calls)
* @throws SQLException if the database could not be opened or created
*/
- public VpnProfileDataSource open() throws SQLException
- {
- if (mDbHelper == null)
- {
- mDbHelper = new DatabaseHelper(mContext);
- mDatabase = mDbHelper.getWritableDatabase();
- }
- return this;
- }
+ VpnProfileDataSource open() throws SQLException;
/**
* Close the data source.
*/
- public void close()
- {
- if (mDbHelper != null)
- {
- mDbHelper.close();
- mDbHelper = null;
- }
- }
+ void close();
/**
* Insert the given VPN profile into the database. On success the Id of
@@ -318,83 +71,47 @@ public class VpnProfileDataSource
* @param profile the profile to add
* @return the added VPN profile or null, if failed
*/
- public VpnProfile insertProfile(VpnProfile profile)
- {
- ContentValues values = ContentValuesFromVpnProfile(profile);
- long insertId = mDatabase.insert(TABLE_VPNPROFILE, null, values);
- if (insertId == -1)
- {
- return null;
- }
- profile.setId(insertId);
- return profile;
- }
+ VpnProfile insertProfile(VpnProfile profile);
/**
* Updates the given VPN profile in the database.
+ *
* @param profile the profile to update
* @return true if update succeeded, false otherwise
*/
- public boolean updateVpnProfile(VpnProfile profile)
- {
- long id = profile.getId();
- ContentValues values = ContentValuesFromVpnProfile(profile);
- return mDatabase.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + id, null) > 0;
- }
+ boolean updateVpnProfile(VpnProfile profile);
/**
* Delete the given VPN profile from the database.
+ *
* @param profile the profile to delete
* @return true if deleted, false otherwise
*/
- public boolean deleteVpnProfile(VpnProfile profile)
- {
- long id = profile.getId();
- return mDatabase.delete(TABLE_VPNPROFILE, KEY_ID + " = " + id, null) > 0;
- }
+ boolean deleteVpnProfile(VpnProfile profile);
/**
* Get a single VPN profile from the database.
+ *
* @param id the ID of the VPN profile
* @return the profile or null, if not found
*/
- public VpnProfile getVpnProfile(long id)
- {
- VpnProfile profile = null;
- Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS,
- KEY_ID + "=" + id, null, null, null, null);
- if (cursor.moveToFirst())
- {
- profile = VpnProfileFromCursor(cursor);
- }
- cursor.close();
- return profile;
- }
+ VpnProfile getVpnProfile(long id);
/**
* Get a single VPN profile from the database by its UUID.
+ *
* @param uuid the UUID of the VPN profile
* @return the profile or null, if not found
*/
- public VpnProfile getVpnProfile(UUID uuid)
- {
- VpnProfile profile = null;
- Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS,
- KEY_UUID + "='" + uuid.toString() + "'", null, null, null, null);
- if (cursor.moveToFirst())
- {
- profile = VpnProfileFromCursor(cursor);
- }
- cursor.close();
- return profile;
- }
+ VpnProfile getVpnProfile(UUID uuid);
/**
* Get a single VPN profile from the database by its UUID as String.
+ *
* @param uuid the UUID of the VPN profile as String
* @return the profile or null, if not found
*/
- public VpnProfile getVpnProfile(String uuid)
+ default VpnProfile getVpnProfile(String uuid)
{
try
{
@@ -413,97 +130,8 @@ public class VpnProfileDataSource
/**
* Get a list of all VPN profiles stored in the database.
+ *
* @return list of VPN profiles
*/
- public List getAllVpnProfiles()
- {
- List vpnProfiles = new ArrayList();
-
- Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, null, null, null, null, null);
- cursor.moveToFirst();
- while (!cursor.isAfterLast())
- {
- VpnProfile vpnProfile = VpnProfileFromCursor(cursor);
- vpnProfiles.add(vpnProfile);
- cursor.moveToNext();
- }
- cursor.close();
- return vpnProfiles;
- }
-
- private VpnProfile VpnProfileFromCursor(Cursor cursor)
- {
- VpnProfile profile = new VpnProfile();
- profile.setId(cursor.getLong(cursor.getColumnIndexOrThrow(KEY_ID)));
- profile.setUUID(UUID.fromString(cursor.getString(cursor.getColumnIndexOrThrow(KEY_UUID))));
- profile.setName(cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME)));
- profile.setGateway(cursor.getString(cursor.getColumnIndexOrThrow(KEY_GATEWAY)));
- profile.setVpnType(VpnType.fromIdentifier(cursor.getString(cursor.getColumnIndexOrThrow(KEY_VPN_TYPE))));
- profile.setUsername(cursor.getString(cursor.getColumnIndexOrThrow(KEY_USERNAME)));
- profile.setPassword(cursor.getString(cursor.getColumnIndexOrThrow(KEY_PASSWORD)));
- profile.setCertificateAlias(cursor.getString(cursor.getColumnIndexOrThrow(KEY_CERTIFICATE)));
- profile.setUserCertificateAlias(cursor.getString(cursor.getColumnIndexOrThrow(KEY_USER_CERTIFICATE)));
- profile.setMTU(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_MTU)));
- profile.setPort(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_PORT)));
- profile.setSplitTunneling(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_SPLIT_TUNNELING)));
- profile.setLocalId(cursor.getString(cursor.getColumnIndexOrThrow(KEY_LOCAL_ID)));
- profile.setRemoteId(cursor.getString(cursor.getColumnIndexOrThrow(KEY_REMOTE_ID)));
- profile.setExcludedSubnets(cursor.getString(cursor.getColumnIndexOrThrow(KEY_EXCLUDED_SUBNETS)));
- profile.setIncludedSubnets(cursor.getString(cursor.getColumnIndexOrThrow(KEY_INCLUDED_SUBNETS)));
- profile.setSelectedAppsHandling(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_SELECTED_APPS)));
- profile.setSelectedApps(cursor.getString(cursor.getColumnIndexOrThrow(KEY_SELECTED_APPS_LIST)));
- profile.setNATKeepAlive(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_NAT_KEEPALIVE)));
- profile.setFlags(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_FLAGS)));
- profile.setIkeProposal(cursor.getString(cursor.getColumnIndexOrThrow(KEY_IKE_PROPOSAL)));
- profile.setEspProposal(cursor.getString(cursor.getColumnIndexOrThrow(KEY_ESP_PROPOSAL)));
- profile.setDnsServers(cursor.getString(cursor.getColumnIndexOrThrow(KEY_DNS_SERVERS)));
- return profile;
- }
-
- private ContentValues ContentValuesFromVpnProfile(VpnProfile profile)
- {
- ContentValues values = new ContentValues();
- values.put(KEY_UUID, profile.getUUID().toString());
- values.put(KEY_NAME, profile.getName());
- values.put(KEY_GATEWAY, profile.getGateway());
- values.put(KEY_VPN_TYPE, profile.getVpnType().getIdentifier());
- values.put(KEY_USERNAME, profile.getUsername());
- values.put(KEY_PASSWORD, profile.getPassword());
- values.put(KEY_CERTIFICATE, profile.getCertificateAlias());
- values.put(KEY_USER_CERTIFICATE, profile.getUserCertificateAlias());
- values.put(KEY_MTU, profile.getMTU());
- values.put(KEY_PORT, profile.getPort());
- values.put(KEY_SPLIT_TUNNELING, profile.getSplitTunneling());
- values.put(KEY_LOCAL_ID, profile.getLocalId());
- values.put(KEY_REMOTE_ID, profile.getRemoteId());
- values.put(KEY_EXCLUDED_SUBNETS, profile.getExcludedSubnets());
- values.put(KEY_INCLUDED_SUBNETS, profile.getIncludedSubnets());
- values.put(KEY_SELECTED_APPS, profile.getSelectedAppsHandling().getValue());
- values.put(KEY_SELECTED_APPS_LIST, profile.getSelectedApps());
- values.put(KEY_NAT_KEEPALIVE, profile.getNATKeepAlive());
- values.put(KEY_FLAGS, profile.getFlags());
- values.put(KEY_IKE_PROPOSAL, profile.getIkeProposal());
- values.put(KEY_ESP_PROPOSAL, profile.getEspProposal());
- values.put(KEY_DNS_SERVERS, profile.getDnsServers());
- return values;
- }
-
- private Integer getInt(Cursor cursor, int columnIndex)
- {
- return cursor.isNull(columnIndex) ? null : cursor.getInt(columnIndex);
- }
-
- private static class DbColumn
- {
- public final String Name;
- public final String Type;
- public final Integer Since;
-
- public DbColumn(String name, String type, Integer since)
- {
- Name = name;
- Type = type;
- Since = since;
- }
- }
+ List getAllVpnProfiles();
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
new file mode 100644
index 000000000..98e879452
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
@@ -0,0 +1,114 @@
+/*
+ * 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.database.SQLException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class VpnProfileSource implements VpnProfileDataSource
+{
+ private final List dataSources = new ArrayList<>();
+ private final VpnProfileSqlDataSource vpnProfileSqlDataSource;
+
+ public VpnProfileSource(Context context)
+ {
+ vpnProfileSqlDataSource = new VpnProfileSqlDataSource(context);
+ dataSources.add(vpnProfileSqlDataSource);
+ }
+
+ @Override
+ public VpnProfileDataSource open() throws SQLException
+ {
+ for (final VpnProfileDataSource source : dataSources)
+ {
+ source.open();
+ }
+ return this;
+ }
+
+ @Override
+ public void close()
+ {
+ for (final VpnProfileDataSource source : dataSources)
+ {
+ source.close();
+ }
+ }
+
+ @Override
+ public VpnProfile insertProfile(VpnProfile profile)
+ {
+ return vpnProfileSqlDataSource.insertProfile(profile);
+ }
+
+ @Override
+ public boolean updateVpnProfile(VpnProfile profile)
+ {
+ return vpnProfileSqlDataSource.updateVpnProfile(profile);
+ }
+
+ @Override
+ public boolean deleteVpnProfile(VpnProfile profile)
+ {
+ return vpnProfileSqlDataSource.deleteVpnProfile(profile);
+ }
+
+ @Override
+ public VpnProfile getVpnProfile(long id)
+ {
+ for (final VpnProfileDataSource source : dataSources)
+ {
+ final VpnProfile profile = source.getVpnProfile(id);
+ if (profile != null)
+ {
+ return profile;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public VpnProfile getVpnProfile(UUID uuid)
+ {
+ for (final VpnProfileDataSource source : dataSources)
+ {
+ final VpnProfile profile = source.getVpnProfile(uuid);
+ if (profile != null)
+ {
+ return profile;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public List getAllVpnProfiles()
+ {
+ final List profiles = new ArrayList<>();
+
+ for (final VpnProfileDataSource source : dataSources)
+ {
+ profiles.addAll(source.getAllVpnProfiles());
+ }
+
+ return profiles;
+ }
+}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
new file mode 100644
index 000000000..3b5339e91
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2012-2019 Tobias Brunner
+ * Copyright (C) 2012 Giuliano Grassi
+ * Copyright (C) 2012 Ralf Sager
+ *
+ * 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.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class VpnProfileSqlDataSource implements VpnProfileDataSource
+{
+ private static final String TAG = VpnProfileSqlDataSource.class.getSimpleName();
+
+ private static final DbColumn[] COLUMNS = new VpnProfileSqlDataSource.DbColumn[]{
+ new VpnProfileSqlDataSource.DbColumn(KEY_ID, "INTEGER PRIMARY KEY AUTOINCREMENT", 1),
+ new VpnProfileSqlDataSource.DbColumn(KEY_UUID, "TEXT UNIQUE", 9),
+ new VpnProfileSqlDataSource.DbColumn(KEY_NAME, "TEXT NOT NULL", 1),
+ new VpnProfileSqlDataSource.DbColumn(KEY_GATEWAY, "TEXT NOT NULL", 1),
+ new VpnProfileSqlDataSource.DbColumn(KEY_VPN_TYPE, "TEXT NOT NULL", 3),
+ new VpnProfileSqlDataSource.DbColumn(KEY_USERNAME, "TEXT", 1),
+ new VpnProfileSqlDataSource.DbColumn(KEY_PASSWORD, "TEXT", 1),
+ new VpnProfileSqlDataSource.DbColumn(KEY_CERTIFICATE, "TEXT", 1),
+ new VpnProfileSqlDataSource.DbColumn(KEY_USER_CERTIFICATE, "TEXT", 2),
+ new VpnProfileSqlDataSource.DbColumn(KEY_MTU, "INTEGER", 5),
+ new VpnProfileSqlDataSource.DbColumn(KEY_PORT, "INTEGER", 5),
+ new VpnProfileSqlDataSource.DbColumn(KEY_SPLIT_TUNNELING, "INTEGER", 7),
+ new VpnProfileSqlDataSource.DbColumn(KEY_LOCAL_ID, "TEXT", 8),
+ new VpnProfileSqlDataSource.DbColumn(KEY_REMOTE_ID, "TEXT", 8),
+ new VpnProfileSqlDataSource.DbColumn(KEY_EXCLUDED_SUBNETS, "TEXT", 10),
+ new VpnProfileSqlDataSource.DbColumn(KEY_INCLUDED_SUBNETS, "TEXT", 11),
+ new VpnProfileSqlDataSource.DbColumn(KEY_SELECTED_APPS, "INTEGER", 12),
+ new VpnProfileSqlDataSource.DbColumn(KEY_SELECTED_APPS_LIST, "TEXT", 12),
+ new VpnProfileSqlDataSource.DbColumn(KEY_NAT_KEEPALIVE, "INTEGER", 13),
+ new VpnProfileSqlDataSource.DbColumn(KEY_FLAGS, "INTEGER", 14),
+ new VpnProfileSqlDataSource.DbColumn(KEY_IKE_PROPOSAL, "TEXT", 15),
+ new VpnProfileSqlDataSource.DbColumn(KEY_ESP_PROPOSAL, "TEXT", 15),
+ new VpnProfileSqlDataSource.DbColumn(KEY_DNS_SERVERS, "TEXT", 17),
+ };
+
+ private DatabaseHelper mDbHelper;
+ private SQLiteDatabase mDatabase;
+ private final Context mContext;
+
+ private static final String DATABASE_NAME = "strongswan.db";
+ private static final String TABLE_VPNPROFILE = "vpnprofile";
+
+ private static final int DATABASE_VERSION = 17;
+
+ private static final String[] ALL_COLUMNS = getColumns(DATABASE_VERSION);
+
+ private static String getDatabaseCreate(int version)
+ {
+ boolean first = true;
+ StringBuilder create = new StringBuilder("CREATE TABLE ");
+ create.append(TABLE_VPNPROFILE);
+ create.append(" (");
+ for (DbColumn column : COLUMNS)
+ {
+ if (column.Since <= version)
+ {
+ if (!first)
+ {
+ create.append(",");
+ }
+ first = false;
+ create.append(column.Name);
+ create.append(" ");
+ create.append(column.Type);
+ }
+ }
+ create.append(");");
+ return create.toString();
+ }
+
+ private static String[] getColumns(int version)
+ {
+ ArrayList columns = new ArrayList<>();
+ for (DbColumn column : COLUMNS)
+ {
+ if (column.Since <= version)
+ {
+ columns.add(column.Name);
+ }
+ }
+ return columns.toArray(new String[0]);
+ }
+
+ private static class DatabaseHelper extends SQLiteOpenHelper
+ {
+ public DatabaseHelper(Context context)
+ {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase database)
+ {
+ database.execSQL(getDatabaseCreate(DATABASE_VERSION));
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
+ {
+ Log.w(TAG, "Upgrading database from version " + oldVersion +
+ " to " + newVersion);
+ if (oldVersion < 2)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_USER_CERTIFICATE +
+ " TEXT;");
+ }
+ if (oldVersion < 3)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_VPN_TYPE +
+ " TEXT DEFAULT '';");
+ }
+ if (oldVersion < 4)
+ { /* remove NOT NULL constraint from username column */
+ updateColumns(db, 4);
+ }
+ if (oldVersion < 5)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_MTU +
+ " INTEGER;");
+ }
+ if (oldVersion < 6)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_PORT +
+ " INTEGER;");
+ }
+ if (oldVersion < 7)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SPLIT_TUNNELING +
+ " INTEGER;");
+ }
+ if (oldVersion < 8)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_LOCAL_ID +
+ " TEXT;");
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_REMOTE_ID +
+ " TEXT;");
+ }
+ if (oldVersion < 9)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_UUID +
+ " TEXT;");
+ updateColumns(db, 9);
+ }
+ if (oldVersion < 10)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_EXCLUDED_SUBNETS +
+ " TEXT;");
+ }
+ if (oldVersion < 11)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_INCLUDED_SUBNETS +
+ " TEXT;");
+ }
+ if (oldVersion < 12)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SELECTED_APPS +
+ " INTEGER;");
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SELECTED_APPS_LIST +
+ " TEXT;");
+ }
+ if (oldVersion < 13)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_NAT_KEEPALIVE +
+ " INTEGER;");
+ }
+ if (oldVersion < 14)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_FLAGS +
+ " INTEGER;");
+ }
+ if (oldVersion < 15)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_IKE_PROPOSAL +
+ " TEXT;");
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_ESP_PROPOSAL +
+ " TEXT;");
+ }
+ if (oldVersion < 16)
+ { /* add a UUID to all entries that haven't one yet */
+ db.beginTransaction();
+ try
+ {
+ Cursor cursor = db.query(TABLE_VPNPROFILE, getColumns(16), KEY_UUID + " is NULL", null, null, null, null);
+ for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext())
+ {
+ ContentValues values = new ContentValues();
+ values.put(KEY_UUID, UUID.randomUUID().toString());
+ db.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + cursor.getLong(cursor.getColumnIndexOrThrow(KEY_ID)), null);
+ }
+ cursor.close();
+ db.setTransactionSuccessful();
+ }
+ finally
+ {
+ db.endTransaction();
+ }
+ }
+ if (oldVersion < 17)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_DNS_SERVERS +
+ " TEXT;");
+ }
+ }
+
+ private void updateColumns(SQLiteDatabase db, int version)
+ {
+ db.beginTransaction();
+ try
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " RENAME TO tmp_" + TABLE_VPNPROFILE + ";");
+ db.execSQL(getDatabaseCreate(version));
+ StringBuilder insert = new StringBuilder("INSERT INTO " + TABLE_VPNPROFILE + " SELECT ");
+ SQLiteQueryBuilder.appendColumns(insert, getColumns(version));
+ db.execSQL(insert.append(" FROM tmp_" + TABLE_VPNPROFILE + ";").toString());
+ db.execSQL("DROP TABLE tmp_" + TABLE_VPNPROFILE + ";");
+ db.setTransactionSuccessful();
+ }
+ finally
+ {
+ db.endTransaction();
+ }
+ }
+ }
+
+ /**
+ * Construct a new VPN profile data source. The context is used to
+ * open/create the database.
+ *
+ * @param context context used to access the database
+ */
+ public VpnProfileSqlDataSource(Context context)
+ {
+ this.mContext = context;
+ }
+
+ @Override
+ public VpnProfileDataSource open() throws SQLException
+ {
+ if (mDbHelper == null)
+ {
+ mDbHelper = new DatabaseHelper(mContext);
+ mDatabase = mDbHelper.getWritableDatabase();
+ }
+ return this;
+ }
+
+ @Override
+ public void close()
+ {
+ if (mDbHelper != null)
+ {
+ mDbHelper.close();
+ mDbHelper = null;
+ }
+ }
+
+ @Override
+ public VpnProfile insertProfile(VpnProfile profile)
+ {
+ ContentValues values = ContentValuesFromVpnProfile(profile);
+ long insertId = mDatabase.insert(TABLE_VPNPROFILE, null, values);
+ if (insertId == -1)
+ {
+ return null;
+ }
+ profile.setId(insertId);
+ return profile;
+ }
+
+ @Override
+ public boolean updateVpnProfile(VpnProfile profile)
+ {
+ long id = profile.getId();
+ ContentValues values = ContentValuesFromVpnProfile(profile);
+ return mDatabase.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + id, null) > 0;
+ }
+
+ @Override
+ public boolean deleteVpnProfile(VpnProfile profile)
+ {
+ long id = profile.getId();
+ return mDatabase.delete(TABLE_VPNPROFILE, KEY_ID + " = " + id, null) > 0;
+ }
+
+ @Override
+ public VpnProfile getVpnProfile(long id)
+ {
+ VpnProfile profile = null;
+ Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS,
+ KEY_ID + "=" + id, null, null, null, null);
+ if (cursor.moveToFirst())
+ {
+ profile = VpnProfileFromCursor(cursor);
+ }
+ cursor.close();
+ return profile;
+ }
+
+ @Override
+ public VpnProfile getVpnProfile(UUID uuid)
+ {
+ VpnProfile profile = null;
+ Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS,
+ KEY_UUID + "='" + uuid.toString() + "'", null, null, null, null);
+ if (cursor.moveToFirst())
+ {
+ profile = VpnProfileFromCursor(cursor);
+ }
+ cursor.close();
+ return profile;
+ }
+
+ @Override
+ public List getAllVpnProfiles()
+ {
+ List vpnProfiles = new ArrayList();
+
+ Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, null, null, null, null, null);
+ cursor.moveToFirst();
+ while (!cursor.isAfterLast())
+ {
+ VpnProfile vpnProfile = VpnProfileFromCursor(cursor);
+ vpnProfiles.add(vpnProfile);
+ cursor.moveToNext();
+ }
+ cursor.close();
+ return vpnProfiles;
+ }
+
+ private VpnProfile VpnProfileFromCursor(Cursor cursor)
+ {
+ VpnProfile profile = new VpnProfile();
+ profile.setId(cursor.getLong(cursor.getColumnIndexOrThrow(KEY_ID)));
+ profile.setUUID(UUID.fromString(cursor.getString(cursor.getColumnIndexOrThrow(KEY_UUID))));
+ profile.setName(cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME)));
+ profile.setGateway(cursor.getString(cursor.getColumnIndexOrThrow(KEY_GATEWAY)));
+ profile.setVpnType(VpnType.fromIdentifier(cursor.getString(cursor.getColumnIndexOrThrow(KEY_VPN_TYPE))));
+ profile.setUsername(cursor.getString(cursor.getColumnIndexOrThrow(KEY_USERNAME)));
+ profile.setPassword(cursor.getString(cursor.getColumnIndexOrThrow(KEY_PASSWORD)));
+ profile.setCertificateAlias(cursor.getString(cursor.getColumnIndexOrThrow(KEY_CERTIFICATE)));
+ profile.setUserCertificateAlias(cursor.getString(cursor.getColumnIndexOrThrow(KEY_USER_CERTIFICATE)));
+ profile.setMTU(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_MTU)));
+ profile.setPort(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_PORT)));
+ profile.setSplitTunneling(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_SPLIT_TUNNELING)));
+ profile.setLocalId(cursor.getString(cursor.getColumnIndexOrThrow(KEY_LOCAL_ID)));
+ profile.setRemoteId(cursor.getString(cursor.getColumnIndexOrThrow(KEY_REMOTE_ID)));
+ profile.setExcludedSubnets(cursor.getString(cursor.getColumnIndexOrThrow(KEY_EXCLUDED_SUBNETS)));
+ profile.setIncludedSubnets(cursor.getString(cursor.getColumnIndexOrThrow(KEY_INCLUDED_SUBNETS)));
+ profile.setSelectedAppsHandling(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_SELECTED_APPS)));
+ profile.setSelectedApps(cursor.getString(cursor.getColumnIndexOrThrow(KEY_SELECTED_APPS_LIST)));
+ profile.setNATKeepAlive(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_NAT_KEEPALIVE)));
+ profile.setFlags(getInt(cursor, cursor.getColumnIndexOrThrow(KEY_FLAGS)));
+ profile.setIkeProposal(cursor.getString(cursor.getColumnIndexOrThrow(KEY_IKE_PROPOSAL)));
+ profile.setEspProposal(cursor.getString(cursor.getColumnIndexOrThrow(KEY_ESP_PROPOSAL)));
+ profile.setDnsServers(cursor.getString(cursor.getColumnIndexOrThrow(KEY_DNS_SERVERS)));
+ return profile;
+ }
+
+ private ContentValues ContentValuesFromVpnProfile(VpnProfile profile)
+ {
+ ContentValues values = new ContentValues();
+ values.put(KEY_UUID, profile.getUUID().toString());
+ values.put(KEY_NAME, profile.getName());
+ values.put(KEY_GATEWAY, profile.getGateway());
+ values.put(KEY_VPN_TYPE, profile.getVpnType().getIdentifier());
+ values.put(KEY_USERNAME, profile.getUsername());
+ values.put(KEY_PASSWORD, profile.getPassword());
+ values.put(KEY_CERTIFICATE, profile.getCertificateAlias());
+ values.put(KEY_USER_CERTIFICATE, profile.getUserCertificateAlias());
+ values.put(KEY_MTU, profile.getMTU());
+ values.put(KEY_PORT, profile.getPort());
+ values.put(KEY_SPLIT_TUNNELING, profile.getSplitTunneling());
+ values.put(KEY_LOCAL_ID, profile.getLocalId());
+ values.put(KEY_REMOTE_ID, profile.getRemoteId());
+ values.put(KEY_EXCLUDED_SUBNETS, profile.getExcludedSubnets());
+ values.put(KEY_INCLUDED_SUBNETS, profile.getIncludedSubnets());
+ values.put(KEY_SELECTED_APPS, profile.getSelectedAppsHandling().getValue());
+ values.put(KEY_SELECTED_APPS_LIST, profile.getSelectedApps());
+ values.put(KEY_NAT_KEEPALIVE, profile.getNATKeepAlive());
+ values.put(KEY_FLAGS, profile.getFlags());
+ values.put(KEY_IKE_PROPOSAL, profile.getIkeProposal());
+ values.put(KEY_ESP_PROPOSAL, profile.getEspProposal());
+ values.put(KEY_DNS_SERVERS, profile.getDnsServers());
+ return values;
+ }
+
+ private Integer getInt(Cursor cursor, int columnIndex)
+ {
+ return cursor.isNull(columnIndex) ? null : cursor.getInt(columnIndex);
+ }
+
+ private static class DbColumn
+ {
+ public final String Name;
+ public final String Type;
+ public final Integer Since;
+
+ public DbColumn(String name, String type, Integer since)
+ {
+ Name = name;
+ Type = type;
+ Since = since;
+ }
+ }
+}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
index 5f5b2a187..115ff7cec 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
@@ -45,6 +45,7 @@ import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfile.SelectedAppsHandling;
import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.data.VpnProfileSource;
import org.strongswan.android.data.VpnType.VpnTypeFeature;
import org.strongswan.android.logic.VpnStateService.ErrorState;
import org.strongswan.android.logic.VpnStateService.State;
@@ -196,7 +197,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
/* handler used to do changes in the main UI thread */
mHandler = new Handler(getMainLooper());
- mDataSource = new VpnProfileDataSource(this);
+ mDataSource = new VpnProfileSource(this);
mDataSource.open();
/* use a separate thread as main thread for charon */
mConnectionHandler = new Thread(this);
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java
index 98e399256..7f047ce6b 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java
@@ -16,6 +16,9 @@
package org.strongswan.android.ui;
+import static org.strongswan.android.utils.Constants.PREF_DEFAULT_VPN_PROFILE;
+import static org.strongswan.android.utils.Constants.PREF_DEFAULT_VPN_PROFILE_MRU;
+
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
@@ -23,6 +26,7 @@ import android.os.Bundle;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.data.VpnProfileSource;
import java.util.ArrayList;
import java.util.Collections;
@@ -34,9 +38,6 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
-import static org.strongswan.android.utils.Constants.PREF_DEFAULT_VPN_PROFILE;
-import static org.strongswan.android.utils.Constants.PREF_DEFAULT_VPN_PROFILE_MRU;
-
public class SettingsFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener
{
private ListPreference mDefaultVPNProfile;
@@ -46,7 +47,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Prefer
{
setPreferencesFromResource(R.xml.settings, s);
- mDefaultVPNProfile = (ListPreference)findPreference(PREF_DEFAULT_VPN_PROFILE);
+ mDefaultVPNProfile = findPreference(PREF_DEFAULT_VPN_PROFILE);
mDefaultVPNProfile.setOnPreferenceChangeListener(this);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
{
@@ -59,11 +60,12 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Prefer
{
super.onResume();
- VpnProfileDataSource profiles = new VpnProfileDataSource(getActivity());
+ VpnProfileDataSource profiles = new VpnProfileSource(getActivity());
profiles.open();
List all = profiles.getAllVpnProfiles();
- Collections.sort(all, new Comparator() {
+ Collections.sort(all, new Comparator()
+ {
@Override
public int compare(VpnProfile lhs, VpnProfile rhs)
{
@@ -111,7 +113,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Prefer
private void setCurrentProfileName(String uuid)
{
- VpnProfileDataSource profiles = new VpnProfileDataSource(getActivity());
+ VpnProfileDataSource profiles = new VpnProfileSource(getActivity());
profiles.open();
if (!uuid.equals(PREF_DEFAULT_VPN_PROFILE_MRU))
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java
index 1913bbb5f..fb83c4e10 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java
@@ -42,12 +42,12 @@ import android.widget.Toast;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.data.VpnProfileSource;
import org.strongswan.android.data.VpnType.VpnTypeFeature;
import org.strongswan.android.logic.VpnStateService;
import org.strongswan.android.logic.VpnStateService.State;
import org.strongswan.android.utils.Constants;
-import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
@@ -257,6 +257,7 @@ public class VpnProfileControlActivity extends AppCompatActivity
/**
* Check if we have permission to display notifications to the user, if necessary,
* ask the user to allow this.
+ *
* @return true if profile can be initiated immediately
*/
private boolean checkNotificationPermission()
@@ -274,6 +275,7 @@ public class VpnProfileControlActivity extends AppCompatActivity
/**
* Check if we are on the system's power whitelist, if necessary, or ask the user
* to add us.
+ *
* @return true if profile can be initiated immediately
*/
private boolean checkPowerWhitelist()
@@ -373,7 +375,7 @@ public class VpnProfileControlActivity extends AppCompatActivity
{
VpnProfile profile = null;
- VpnProfileDataSource dataSource = new VpnProfileDataSource(this);
+ VpnProfileDataSource dataSource = new VpnProfileSource(this);
dataSource.open();
String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_ID);
if (profileUUID != null)
@@ -415,7 +417,7 @@ public class VpnProfileControlActivity extends AppCompatActivity
String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_ID);
if (profileUUID != null)
{
- VpnProfileDataSource dataSource = new VpnProfileDataSource(this);
+ VpnProfileDataSource dataSource = new VpnProfileSource(this);
dataSource.open();
profile = dataSource.getVpnProfile(profileUUID);
dataSource.close();
@@ -583,9 +585,9 @@ public class VpnProfileControlActivity extends AppCompatActivity
final Bundle profileInfo = getArguments();
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.login_dialog, null);
- EditText username = (EditText)view.findViewById(R.id.username);
+ EditText username = view.findViewById(R.id.username);
username.setText(profileInfo.getString(VpnProfileDataSource.KEY_USERNAME));
- final EditText password = (EditText)view.findViewById(R.id.password);
+ final EditText password = view.findViewById(R.id.password);
AlertDialog.Builder adb = new AlertDialog.Builder(getActivity());
adb.setView(view);
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
index 85d178e52..8fd12338f 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
@@ -56,6 +56,7 @@ import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfile.SelectedAppsHandling;
import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.data.VpnProfileSource;
import org.strongswan.android.data.VpnType;
import org.strongswan.android.data.VpnType.VpnTypeFeature;
import org.strongswan.android.logic.StrongSwanApplication;
@@ -188,7 +189,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
/* the title is set when we load the profile, if any */
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- mDataSource = new VpnProfileDataSource(this);
+ mDataSource = new VpnProfileSource(this);
mDataSource.open();
setContentView(R.layout.profile_detail_view);
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java
index c58365d9c..3f7c51c19 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java
@@ -45,6 +45,7 @@ import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfile.SelectedAppsHandling;
import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.data.VpnProfileSource;
import org.strongswan.android.data.VpnType;
import org.strongswan.android.data.VpnType.VpnTypeFeature;
import org.strongswan.android.logic.TrustedCertificateManager;
@@ -184,7 +185,7 @@ public class VpnProfileImportActivity extends AppCompatActivity
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- mDataSource = new VpnProfileDataSource(this);
+ mDataSource = new VpnProfileSource(this);
mDataSource.open();
setContentView(R.layout.profile_import_view);
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
index 291a4fcea..a4ed19308 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
@@ -41,6 +41,7 @@ import android.widget.Toast;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.data.VpnProfileSource;
import org.strongswan.android.ui.adapter.VpnProfileAdapter;
import org.strongswan.android.utils.Constants;
@@ -65,12 +66,14 @@ public class VpnProfileListFragment extends Fragment
private HashSet mSelected;
private boolean mReadOnly;
- private BroadcastReceiver mProfilesChanged = new BroadcastReceiver()
+ private final BroadcastReceiver mProfilesChanged = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
- long id, ids[];
+ long id;
+ long[] ids;
+
if ((id = intent.getLongExtra(Constants.VPN_PROFILES_SINGLE, 0)) > 0)
{
VpnProfile profile = mDataSource.getVpnProfile(id);
@@ -104,7 +107,8 @@ public class VpnProfileListFragment extends Fragment
/**
* The activity containing this fragment should implement this interface
*/
- public interface OnVpnProfileSelectedListener {
+ public interface OnVpnProfileSelectedListener
+ {
void onVpnProfileSelected(VpnProfile profile);
}
@@ -159,7 +163,7 @@ public class VpnProfileListFragment extends Fragment
mSelected = selected != null ? new HashSet<>(selected) : new HashSet<>();
}
- mDataSource = new VpnProfileDataSource(this.getActivity());
+ mDataSource = new VpnProfileSource(this.getActivity());
mDataSource.open();
/* cached list of profiles used as backend for the ListView */
@@ -206,19 +210,18 @@ public class VpnProfileListFragment extends Fragment
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
- switch (item.getItemId())
+ if (item.getItemId() == R.id.add_profile)
{
- case R.id.add_profile:
- Intent connectionIntent = new Intent(getActivity(),
- VpnProfileDetailActivity.class);
- startActivity(connectionIntent);
- return true;
- default:
- return super.onOptionsItemSelected(item);
+ Intent connectionIntent = new Intent(getActivity(),
+ VpnProfileDetailActivity.class);
+ startActivity(connectionIntent);
+ return true;
}
+ return super.onOptionsItemSelected(item);
}
- private final OnItemClickListener mVpnProfileClicked = new OnItemClickListener() {
+ private final OnItemClickListener mVpnProfileClicked = new OnItemClickListener()
+ {
@Override
public void onItemClick(AdapterView> a, View v, int position, long id)
{
@@ -229,7 +232,8 @@ public class VpnProfileListFragment extends Fragment
}
};
- private final MultiChoiceModeListener mVpnProfileSelected = new MultiChoiceModeListener() {
+ private final MultiChoiceModeListener mVpnProfileSelected = new MultiChoiceModeListener()
+ {
private MenuItem mEditProfile;
private MenuItem mCopyProfile;
@@ -297,7 +301,7 @@ public class VpnProfileListFragment extends Fragment
{
profiles.add((VpnProfile)mListView.getItemAtPosition(position));
}
- long ids[] = new long[profiles.size()];
+ long[] ids = new long[profiles.size()];
for (int i = 0; i < profiles.size(); i++)
{
VpnProfile profile = profiles.get(i);
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java
index 2e128962d..6178c59ba 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java
@@ -32,6 +32,7 @@ import android.service.quicksettings.TileService;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.data.VpnProfileSource;
import org.strongswan.android.data.VpnType;
import org.strongswan.android.logic.VpnStateService;
import org.strongswan.android.utils.Constants;
@@ -73,7 +74,7 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt
context.bindService(new Intent(context, VpnStateService.class),
mServiceConnection, Service.BIND_AUTO_CREATE);
- mDataSource = new VpnProfileDataSource(this);
+ mDataSource = new VpnProfileSource(this);
mDataSource.open();
}
From d629e1d358e773d4edfdbe059d7aebfc9b8c6f40 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:22 +0100
Subject: [PATCH 07/45] android: Fix version number on port column
The onUpgrade method creates this column for database version 6. Update
the DbColumn definition to match that version number.
---
.../org/strongswan/android/data/VpnProfileSqlDataSource.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
index 3b5339e91..7032009ca 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
@@ -46,7 +46,7 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource
new VpnProfileSqlDataSource.DbColumn(KEY_CERTIFICATE, "TEXT", 1),
new VpnProfileSqlDataSource.DbColumn(KEY_USER_CERTIFICATE, "TEXT", 2),
new VpnProfileSqlDataSource.DbColumn(KEY_MTU, "INTEGER", 5),
- new VpnProfileSqlDataSource.DbColumn(KEY_PORT, "INTEGER", 5),
+ new VpnProfileSqlDataSource.DbColumn(KEY_PORT, "INTEGER", 6),
new VpnProfileSqlDataSource.DbColumn(KEY_SPLIT_TUNNELING, "INTEGER", 7),
new VpnProfileSqlDataSource.DbColumn(KEY_LOCAL_ID, "TEXT", 8),
new VpnProfileSqlDataSource.DbColumn(KEY_REMOTE_ID, "TEXT", 8),
From 8e3b921abed7eefcd55e91a42981260f3345a68c Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:22 +0100
Subject: [PATCH 08/45] android: Always use UUID to access profiles
Use the UUID rather than the ID to ensure there are no conflicts between
profiles from the database and managed profiles.
---
.../strongswan/android/data/VpnProfile.java | 23 +++++++----
.../android/data/VpnProfileDataSource.java | 8 ----
.../android/data/VpnProfileSource.java | 14 -------
.../android/data/VpnProfileSqlDataSource.java | 23 ++---------
.../android/logic/CharonVpnService.java | 2 +-
.../android/logic/VpnStateService.java | 39 ++++++++++++-------
.../strongswan/android/ui/MainActivity.java | 2 +-
.../android/ui/VpnProfileControlActivity.java | 14 ++-----
.../android/ui/VpnProfileDetailActivity.java | 22 +++++------
.../android/ui/VpnProfileImportActivity.java | 6 +--
.../android/ui/VpnProfileListFragment.java | 27 ++++++-------
.../android/ui/VpnProfileSelectActivity.java | 2 +-
.../strongswan/android/ui/VpnTileService.java | 4 +-
13 files changed, 80 insertions(+), 106 deletions(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java
index f95082711..1d3c2bbca 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java
@@ -22,6 +22,7 @@ package org.strongswan.android.data;
import android.text.TextUtils;
import java.util.Arrays;
+import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
@@ -339,16 +340,22 @@ public class VpnProfile implements Cloneable
@Override
public boolean equals(Object o)
{
- if (o != null && o instanceof VpnProfile)
+ if (o == this)
{
- VpnProfile other = (VpnProfile)o;
- if (this.mUUID != null && other.getUUID() != null)
- {
- return this.mUUID.equals(other.getUUID());
- }
- return this.mId == other.getId();
+ return true;
}
- return false;
+ if (o == null || getClass() != o.getClass())
+ {
+ return false;
+ }
+ VpnProfile that = (VpnProfile)o;
+ return Objects.equals(mUUID, that.mUUID);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(mUUID);
}
@Override
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
index 07308956c..f5bc692cc 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
@@ -89,14 +89,6 @@ public interface VpnProfileDataSource
*/
boolean deleteVpnProfile(VpnProfile profile);
- /**
- * Get a single VPN profile from the database.
- *
- * @param id the ID of the VPN profile
- * @return the profile or null, if not found
- */
- VpnProfile getVpnProfile(long id);
-
/**
* Get a single VPN profile from the database by its UUID.
*
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
index 98e879452..4a709989a 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
@@ -71,20 +71,6 @@ public class VpnProfileSource implements VpnProfileDataSource
return vpnProfileSqlDataSource.deleteVpnProfile(profile);
}
- @Override
- public VpnProfile getVpnProfile(long id)
- {
- for (final VpnProfileDataSource source : dataSources)
- {
- final VpnProfile profile = source.getVpnProfile(id);
- if (profile != null)
- {
- return profile;
- }
- }
- return null;
- }
-
@Override
public VpnProfile getVpnProfile(UUID uuid)
{
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
index 7032009ca..0934edc39 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
@@ -298,30 +298,16 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource
@Override
public boolean updateVpnProfile(VpnProfile profile)
{
- long id = profile.getId();
+ final UUID uuid = profile.getUUID();
ContentValues values = ContentValuesFromVpnProfile(profile);
- return mDatabase.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + id, null) > 0;
+ return mDatabase.update(TABLE_VPNPROFILE, values, KEY_UUID + " = ?", new String[]{uuid.toString()}) > 0;
}
@Override
public boolean deleteVpnProfile(VpnProfile profile)
{
- long id = profile.getId();
- return mDatabase.delete(TABLE_VPNPROFILE, KEY_ID + " = " + id, null) > 0;
- }
-
- @Override
- public VpnProfile getVpnProfile(long id)
- {
- VpnProfile profile = null;
- Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS,
- KEY_ID + "=" + id, null, null, null, null);
- if (cursor.moveToFirst())
- {
- profile = VpnProfileFromCursor(cursor);
- }
- cursor.close();
- return profile;
+ final UUID uuid = profile.getUUID();
+ return mDatabase.delete(TABLE_VPNPROFILE, KEY_UUID + " = ?", new String[]{uuid.toString()}) > 0;
}
@Override
@@ -358,7 +344,6 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource
private VpnProfile VpnProfileFromCursor(Cursor cursor)
{
VpnProfile profile = new VpnProfile();
- profile.setId(cursor.getLong(cursor.getColumnIndexOrThrow(KEY_ID)));
profile.setUUID(UUID.fromString(cursor.getString(cursor.getColumnIndexOrThrow(KEY_UUID))));
profile.setName(cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME)));
profile.setGateway(cursor.getString(cursor.getColumnIndexOrThrow(KEY_GATEWAY)));
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
index 115ff7cec..03e59f7ee 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
@@ -462,7 +462,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
Intent intent = new Intent(getApplicationContext(), VpnProfileControlActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(VpnProfileControlActivity.START_PROFILE);
- intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString());
+ intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_UUID, profile.getUUID().toString());
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java
index 53c22d45a..ccce06e4c 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java
@@ -55,11 +55,11 @@ public class VpnStateService extends Service
private ErrorState mError = ErrorState.NO_ERROR;
private ImcState mImcState = ImcState.UNKNOWN;
private final LinkedList mRemediationInstructions = new LinkedList();
- private static long RETRY_INTERVAL = 1000;
+ private static final long RETRY_INTERVAL = 1000;
/* cap the retry interval at 2 minutes */
- private static long MAX_RETRY_INTERVAL = 120000;
- private static int RETRY_MSG = 1;
- private RetryTimeoutProvider mTimeoutProvider = new RetryTimeoutProvider();
+ private static final long MAX_RETRY_INTERVAL = 120000;
+ private static final int RETRY_MSG = 1;
+ private final RetryTimeoutProvider mTimeoutProvider = new RetryTimeoutProvider();
private long mRetryTimeout;
private long mRetryIn;
@@ -89,7 +89,7 @@ public class VpnStateService extends Service
*/
public interface VpnStateListener
{
- public void stateChanged();
+ void stateChanged();
}
/**
@@ -169,6 +169,7 @@ public class VpnStateService extends Service
/**
* Get the total number of seconds until there is an automatic retry to reconnect.
+ *
* @return total number of seconds until the retry
*/
public int getRetryTimeout()
@@ -178,6 +179,7 @@ public class VpnStateService extends Service
/**
* Get the number of seconds until there is an automatic retry to reconnect.
+ *
* @return number of seconds until the retry
*/
public int getRetryIn()
@@ -283,8 +285,9 @@ public class VpnStateService extends Service
/**
* Connect (or reconnect) a profile
+ *
* @param profileInfo optional profile info (basically the UUID and password), taken from the
- * previous profile if null
+ * previous profile if null
* @param fromScratch true if this is a manual retry/reconnect or a completely new connection
*/
public void connect(Bundle profileInfo, boolean fromScratch)
@@ -330,7 +333,7 @@ public class VpnStateService extends Service
Intent intent = new Intent(this, VpnProfileControlActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(VpnProfileControlActivity.START_PROFILE);
- intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, mProfile.getUUID().toString());
+ intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_UUID, mProfile.getUUID().toString());
startActivity(intent);
/* reset the retry timer immediately in case the user needs more time to enter the password */
notifyListeners(() -> {
@@ -353,7 +356,8 @@ public class VpnStateService extends Service
*/
private void notifyListeners(final Callable change)
{
- mHandler.post(new Runnable() {
+ mHandler.post(new Runnable()
+ {
@Override
public void run()
{
@@ -386,7 +390,8 @@ public class VpnStateService extends Service
*/
public void startConnection(final VpnProfile profile)
{
- notifyListeners(new Callable() {
+ notifyListeners(new Callable()
+ {
@Override
public Boolean call() throws Exception
{
@@ -411,7 +416,8 @@ public class VpnStateService extends Service
*/
public void setState(final State state)
{
- notifyListeners(new Callable() {
+ notifyListeners(new Callable()
+ {
@Override
public Boolean call() throws Exception
{
@@ -438,7 +444,8 @@ public class VpnStateService extends Service
*/
public void setError(final ErrorState error)
{
- notifyListeners(new Callable() {
+ notifyListeners(new Callable()
+ {
@Override
public Boolean call() throws Exception
{
@@ -471,7 +478,8 @@ public class VpnStateService extends Service
*/
public void setImcState(final ImcState state)
{
- notifyListeners(new Callable() {
+ notifyListeners(new Callable()
+ {
@Override
public Boolean call() throws Exception
{
@@ -501,7 +509,8 @@ public class VpnStateService extends Service
*/
public void addRemediationInstruction(final RemediationInstruction instruction)
{
- mHandler.post(new Runnable() {
+ mHandler.post(new Runnable()
+ {
@Override
public void run()
{
@@ -535,7 +544,8 @@ public class VpnStateService extends Service
/**
* Special Handler subclass that handles the retry countdown (more accurate than CountDownTimer)
*/
- private static class RetryHandler extends Handler {
+ private static class RetryHandler extends Handler
+ {
WeakReference mService;
public RetryHandler(Looper looper, VpnStateService service)
@@ -604,6 +614,7 @@ public class VpnStateService extends Service
/**
* Called each time a new retry timeout is started. The timeout increases until reset() is
* called and the base timeout is returned again.
+ *
* @param error Error state
*/
public long getTimeout(ErrorState error)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
index a48a0a886..a836ffbcc 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
@@ -115,7 +115,7 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
{
Intent intent = new Intent(this, VpnProfileControlActivity.class);
intent.setAction(VpnProfileControlActivity.START_PROFILE);
- intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString());
+ intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_UUID, profile.getUUID().toString());
startActivity(intent);
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java
index fb83c4e10..d9ef939ba 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java
@@ -64,7 +64,7 @@ public class VpnProfileControlActivity extends AppCompatActivity
{
public static final String START_PROFILE = "org.strongswan.android.action.START_PROFILE";
public static final String DISCONNECT = "org.strongswan.android.action.DISCONNECT";
- public static final String EXTRA_VPN_PROFILE_ID = "org.strongswan.android.VPN_PROFILE_ID";
+ public static final String EXTRA_VPN_PROFILE_UUID = "org.strongswan.android.VPN_PROFILE_UUID";
private static final String WAITING_FOR_RESULT = "WAITING_FOR_RESULT";
private static final String PROFILE_NAME = "PROFILE_NAME";
@@ -377,19 +377,11 @@ public class VpnProfileControlActivity extends AppCompatActivity
VpnProfileDataSource dataSource = new VpnProfileSource(this);
dataSource.open();
- String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_ID);
+ String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_UUID);
if (profileUUID != null)
{
profile = dataSource.getVpnProfile(profileUUID);
}
- else
- {
- long profileId = intent.getLongExtra(EXTRA_VPN_PROFILE_ID, 0);
- if (profileId > 0)
- {
- profile = dataSource.getVpnProfile(profileId);
- }
- }
dataSource.close();
if (profile != null)
@@ -414,7 +406,7 @@ public class VpnProfileControlActivity extends AppCompatActivity
removeFragmentByTag(DIALOG_TAG);
- String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_ID);
+ String profileUUID = intent.getStringExtra(EXTRA_VPN_PROFILE_UUID);
if (profileUUID != null)
{
VpnProfileDataSource dataSource = new VpnProfileSource(this);
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
index 8fd12338f..30787e1fd 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
@@ -88,7 +88,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
public class VpnProfileDetailActivity extends AppCompatActivity
{
private VpnProfileDataSource mDataSource;
- private Long mId;
+ private String mUuid;
private TrustedCertificateEntry mCertEntry;
private String mUserCertLoading;
private CertificateIdentitiesAdapter mSelectUserIdAdapter;
@@ -380,11 +380,11 @@ public class VpnProfileDetailActivity extends AppCompatActivity
}
});
- mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID);
- if (mId == null)
+ mUuid = savedInstanceState == null ? null : savedInstanceState.getString(VpnProfileDataSource.KEY_UUID);
+ if (mUuid == null)
{
Bundle extras = getIntent().getExtras();
- mId = extras == null ? null : extras.getLong(VpnProfileDataSource.KEY_ID);
+ mUuid = extras == null ? null : extras.getString(VpnProfileDataSource.KEY_UUID);
}
loadProfileData(savedInstanceState);
@@ -406,9 +406,9 @@ public class VpnProfileDetailActivity extends AppCompatActivity
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
- if (mId != null)
+ if (mUuid != null)
{
- outState.putLong(VpnProfileDataSource.KEY_ID, mId);
+ outState.putString(VpnProfileDataSource.KEY_UUID, mUuid);
}
if (mUserCertEntry != null)
{
@@ -615,10 +615,10 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mDataSource.insertProfile(mProfile);
}
Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED);
- intent.putExtra(Constants.VPN_PROFILES_SINGLE, mProfile.getId());
+ intent.putExtra(Constants.VPN_PROFILES_SINGLE, mProfile.getUUID().toString());
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
- setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_ID, mProfile.getId()));
+ setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_UUID, mProfile.getUUID().toString()));
finish();
}
}
@@ -757,9 +757,9 @@ public class VpnProfileDetailActivity extends AppCompatActivity
Integer flags = null;
getSupportActionBar().setTitle(R.string.add_profile);
- if (mId != null && mId != 0)
+ if (mUuid != null)
{
- mProfile = mDataSource.getVpnProfile(mId);
+ mProfile = mDataSource.getVpnProfile(mUuid);
if (mProfile != null)
{
mName.setText(mProfile.getName());
@@ -791,7 +791,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
else
{
Log.e(VpnProfileDetailActivity.class.getSimpleName(),
- "VPN profile with id " + mId + " not found");
+ "VPN profile with UUID " + mUuid + " not found");
finish();
}
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java
index 3f7c51c19..c62383ec0 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java
@@ -675,7 +675,7 @@ public class VpnProfileImportActivity extends AppCompatActivity
updateProfileData();
if (mExisting != null)
{
- mProfile.setId(mExisting.getId());
+ mProfile.setUUID(mExisting.getUUID());
mDataSource.updateVpnProfile(mProfile);
}
else
@@ -697,14 +697,14 @@ public class VpnProfileImportActivity extends AppCompatActivity
}
}
Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED);
- intent.putExtra(Constants.VPN_PROFILES_SINGLE, mProfile.getId());
+ intent.putExtra(Constants.VPN_PROFILES_SINGLE, mProfile.getUUID().toString());
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
- setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_ID, mProfile.getId()));
+ setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_UUID, mProfile.getUUID().toString()));
finish();
}
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
index a4ed19308..7c3572325 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
@@ -49,6 +49,7 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
import java.util.UUID;
import androidx.fragment.app.Fragment;
@@ -71,12 +72,12 @@ public class VpnProfileListFragment extends Fragment
@Override
public void onReceive(Context context, Intent intent)
{
- long id;
- long[] ids;
+ String uuid;
+ String[] uuids;
- if ((id = intent.getLongExtra(Constants.VPN_PROFILES_SINGLE, 0)) > 0)
+ if ((uuid = intent.getStringExtra(Constants.VPN_PROFILES_SINGLE)) != null)
{
- VpnProfile profile = mDataSource.getVpnProfile(id);
+ VpnProfile profile = mDataSource.getVpnProfile(uuid);
if (profile != null)
{ /* in case this was an edit, we remove it first */
mVpnProfiles.remove(profile);
@@ -84,15 +85,15 @@ public class VpnProfileListFragment extends Fragment
mListAdapter.notifyDataSetChanged();
}
}
- else if ((ids = intent.getLongArrayExtra(Constants.VPN_PROFILES_MULTIPLE)) != null)
+ else if ((uuids = intent.getStringArrayExtra(Constants.VPN_PROFILES_MULTIPLE)) != null)
{
- for (long i : ids)
+ for (String id : uuids)
{
Iterator profiles = mVpnProfiles.iterator();
while (profiles.hasNext())
{
VpnProfile profile = profiles.next();
- if (profile.getId() == i)
+ if (Objects.equals(profile.getUUID().toString(), id))
{
profiles.remove();
break;
@@ -272,7 +273,7 @@ public class VpnProfileListFragment extends Fragment
int position = mSelected.iterator().next();
VpnProfile profile = (VpnProfile)mListView.getItemAtPosition(position);
Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class);
- connectionIntent.putExtra(VpnProfileDataSource.KEY_ID, profile.getId());
+ connectionIntent.putExtra(VpnProfileDataSource.KEY_UUID, profile.getUUID().toString());
startActivity(connectionIntent);
break;
}
@@ -286,11 +287,11 @@ public class VpnProfileListFragment extends Fragment
mDataSource.insertProfile(profile);
Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED);
- intent.putExtra(Constants.VPN_PROFILES_SINGLE, profile.getId());
+ intent.putExtra(Constants.VPN_PROFILES_SINGLE, profile.getUUID().toString());
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class);
- connectionIntent.putExtra(VpnProfileDataSource.KEY_ID, profile.getId());
+ connectionIntent.putExtra(VpnProfileDataSource.KEY_UUID, profile.getUUID().toString());
startActivity(connectionIntent);
break;
}
@@ -301,15 +302,15 @@ public class VpnProfileListFragment extends Fragment
{
profiles.add((VpnProfile)mListView.getItemAtPosition(position));
}
- long[] ids = new long[profiles.size()];
+ String[] uuids = new String[profiles.size()];
for (int i = 0; i < profiles.size(); i++)
{
VpnProfile profile = profiles.get(i);
- ids[i] = profile.getId();
+ uuids[i] = profile.getUUID().toString();
mDataSource.deleteVpnProfile(profile);
}
Intent intent = new Intent(Constants.VPN_PROFILES_CHANGED);
- intent.putExtra(Constants.VPN_PROFILES_MULTIPLE, ids);
+ intent.putExtra(Constants.VPN_PROFILES_MULTIPLE, uuids);
LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
Toast.makeText(VpnProfileListFragment.this.getActivity(),
R.string.profiles_deleted, Toast.LENGTH_SHORT).show();
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java
index 78d8a92db..dd64d0c75 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java
@@ -45,7 +45,7 @@ public class VpnProfileSelectActivity extends AppCompatActivity implements OnVpn
public void onVpnProfileSelected(VpnProfile profile)
{
Intent shortcut = new Intent(VpnProfileControlActivity.START_PROFILE);
- shortcut.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString());
+ shortcut.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_UUID, profile.getUUID().toString());
ShortcutInfoCompat.Builder builder = new ShortcutInfoCompat.Builder(this, profile.getUUID().toString());
builder.setIntent(shortcut);
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java
index 6178c59ba..c49f4ba75 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnTileService.java
@@ -140,7 +140,7 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt
}
else if (mDataSource != null)
{ /* always get the plain profile without cached password */
- profile = mDataSource.getVpnProfile(profile.getId());
+ profile = mDataSource.getVpnProfile(profile.getUUID());
}
/* reconnect the profile in case of an error */
if (mService.getErrorState() == VpnStateService.ErrorState.NO_ERROR)
@@ -173,7 +173,7 @@ public class VpnTileService extends TileService implements VpnStateService.VpnSt
Intent intent = new Intent(this, VpnProfileControlActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(VpnProfileControlActivity.START_PROFILE);
- intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString());
+ intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_UUID, profile.getUUID().toString());
if (profile.getVpnType().has(VpnType.VpnTypeFeature.USER_PASS) &&
profile.getPassword() == null)
{ /* the user will have to enter the password, so collapse the drawer */
From 9618c83c03abdb398c95aaf76dd46619a44d40e5 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:21 +0100
Subject: [PATCH 09/45] android: Add read-only flag to VpnProfile
---
.../org/strongswan/android/data/VpnProfile.java | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java
index 1d3c2bbca..9219b1001 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java
@@ -48,6 +48,7 @@ public class VpnProfile implements Cloneable
private VpnType mVpnType;
private UUID mUUID;
private long mId = -1;
+ private boolean mReadOnly;
public enum SelectedAppsHandling
{
@@ -55,7 +56,7 @@ public class VpnProfile implements Cloneable
SELECTED_APPS_EXCLUDE(1),
SELECTED_APPS_ONLY(2);
- private Integer mValue;
+ private final Integer mValue;
SelectedAppsHandling(int value)
{
@@ -331,6 +332,16 @@ public class VpnProfile implements Cloneable
this.mFlags = flags;
}
+ public boolean isReadOnly()
+ {
+ return mReadOnly;
+ }
+
+ public void setReadOnly(boolean readOnly)
+ {
+ this.mReadOnly = readOnly;
+ }
+
@Override
public String toString()
{
From 3391f7a465f24e41c1ca36cfe2d1ae33a7bab7c9 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:21 +0100
Subject: [PATCH 10/45] android: Prevent editing of read-only profiles
Do not allow users to edit read-only VPN profiles, with the exception of
the profile's password.
---
.../android/ui/VpnProfileDetailActivity.java | 41 +++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
index 30787e1fd..6746711a5 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 Relution GmbH
* Copyright (C) 2012-2020 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
@@ -787,6 +788,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
local_id = mProfile.getLocalId();
alias = mProfile.getCertificateAlias();
getSupportActionBar().setTitle(mProfile.getName());
+
+ setReadOnly(mProfile.isReadOnly());
}
else
{
@@ -851,6 +854,44 @@ public class VpnProfileDetailActivity extends AppCompatActivity
}
}
+ private void setReadOnly(final boolean readOnly)
+ {
+ mName.setEnabled(!readOnly);
+ mGateway.setEnabled(!readOnly);
+ mUsername.setEnabled(!readOnly);
+ mRemoteId.setEnabled(!readOnly);
+ mLocalId.setEnabled(!readOnly);
+ mMTU.setEnabled(!readOnly);
+ mPort.setEnabled(!readOnly);
+ mNATKeepalive.setEnabled(!readOnly);
+ mIncludedSubnets.setEnabled(!readOnly);
+ mExcludedSubnets.setEnabled(!readOnly);
+ mBlockIPv4.setEnabled(!readOnly);
+ mBlockIPv6.setEnabled(!readOnly);
+ mIkeProposal.setEnabled(!readOnly);
+ mEspProposal.setEnabled(!readOnly);
+ mDnsServers.setEnabled(!readOnly);
+
+ mSelectVpnType.setEnabled(!readOnly);
+ mCertReq.setEnabled(!readOnly);
+ mUseCrl.setEnabled(!readOnly);
+ mUseOcsp.setEnabled(!readOnly);
+ mStrictRevocation.setEnabled(!readOnly);
+ mRsaPss.setEnabled(!readOnly);
+ mIPv6Transport.setEnabled(!readOnly);
+
+ mCheckAuto.setEnabled(!readOnly);
+ mSelectSelectedAppsHandling.setEnabled(!readOnly);
+
+ findViewById(R.id.install_user_certificate).setEnabled(!readOnly);
+
+ if (readOnly)
+ {
+ mSelectCert.setOnClickListener(null);
+ mSelectUserCert.setOnClickListener(null);
+ }
+ }
+
/**
* Get the string value in the given text box or null if empty
*
From 150dc5ab6401062797a7dc936e35cb7c91e38d8f Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:22 +0100
Subject: [PATCH 11/45] android: Make selected apps read-only
Also prevent users from changing selected apps in read-only VPN profiles.
---
.../android/data/VpnProfileDataSource.java | 1 +
.../ui/SelectedApplicationsListFragment.java | 9 ++++++++-
.../android/ui/VpnProfileDetailActivity.java | 1 +
.../adapter/SelectedApplicationsAdapter.java | 20 +++++++++++++++----
4 files changed, 26 insertions(+), 5 deletions(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
index f5bc692cc..48aa58c35 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
@@ -49,6 +49,7 @@ public interface VpnProfileDataSource
String KEY_IKE_PROPOSAL = "ike_proposal";
String KEY_ESP_PROPOSAL = "esp_proposal";
String KEY_DNS_SERVERS = "dns_servers";
+ String KEY_READ_ONLY = "read_only";
/**
* Open the VPN profile data source. The database is automatically created
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java
index e2d949a68..5ef78b295 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java
@@ -60,9 +60,11 @@ public class SelectedApplicationsListFragment extends ListFragment implements Lo
super.onViewCreated(view, savedInstanceState);
setHasOptionsMenu(true);
- getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+ final boolean readOnly = getActivity().getIntent().getBooleanExtra(VpnProfileDataSource.KEY_READ_ONLY, false);
+ getListView().setChoiceMode(readOnly ? ListView.CHOICE_MODE_NONE : ListView.CHOICE_MODE_MULTIPLE);
mAdapter = new SelectedApplicationsAdapter(getActivity());
+ mAdapter.setReadOnly(readOnly);
setListAdapter(mAdapter);
setListShown(false);
@@ -101,6 +103,11 @@ public class SelectedApplicationsListFragment extends ListFragment implements Lo
@Override
public void onListItemClick(ListView l, View v, int position, long id)
{
+ if (mAdapter.isReadOnly())
+ {
+ return;
+ }
+
super.onListItemClick(l, v, position, id);
SelectedApplicationEntry item = (SelectedApplicationEntry)getListView().getItemAtPosition(position);
item.setSelected(!item.isSelected());
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
index 6746711a5..b01e67f88 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
@@ -377,6 +377,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
{
Intent intent = new Intent(VpnProfileDetailActivity.this, SelectedApplicationsActivity.class);
intent.putExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelectedApps));
+ intent.putExtra(VpnProfileDataSource.KEY_READ_ONLY, mProfile.isReadOnly());
mSelectApplications.launch(intent);
}
});
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationsAdapter.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationsAdapter.java
index c98e4ab83..9ac720d3b 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationsAdapter.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/SelectedApplicationsAdapter.java
@@ -35,11 +35,12 @@ import java.util.List;
public class SelectedApplicationsAdapter extends BaseAdapter implements Filterable
{
- private Context mContext;
+ private final Context mContext;
private final Object mLock = new Object();
- private List mData;
+ private final List mData;
private List mDataFiltered;
private SelectedApplicationsFilter mFilter;
+ private boolean mReadOnly;
public SelectedApplicationsAdapter(Context context)
{
@@ -100,9 +101,10 @@ public class SelectedApplicationsAdapter extends BaseAdapter implements Filterab
SelectedApplicationEntry item = getItem(position);
CheckableLinearLayout checkable = (CheckableLinearLayout)view;
checkable.setChecked(item.isSelected());
- ImageView icon = (ImageView)view.findViewById(R.id.app_icon);
+ checkable.setEnabled(!mReadOnly);
+ ImageView icon = view.findViewById(R.id.app_icon);
icon.setImageDrawable(item.getIcon());
- TextView text = (TextView)view.findViewById(R.id.app_name);
+ TextView text = view.findViewById(R.id.app_name);
text.setText(item.toString());
return view;
}
@@ -117,6 +119,16 @@ public class SelectedApplicationsAdapter extends BaseAdapter implements Filterab
return mFilter;
}
+ public boolean isReadOnly()
+ {
+ return mReadOnly;
+ }
+
+ public void setReadOnly(final boolean readOnly)
+ {
+ this.mReadOnly = readOnly;
+ }
+
private class SelectedApplicationsFilter extends Filter
{
From d3f5c3a760539de24b98ecdbc0f615b0f748fbaf Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:21 +0100
Subject: [PATCH 12/45] android: Disable copy/delete for read-only profiles
If a profile is marked as read-only, do not allow users to copy or
delete the profile.
---
.../android/ui/VpnProfileListFragment.java | 27 ++++++++++++++++---
1 file changed, 24 insertions(+), 3 deletions(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
index 7c3572325..c56962d72 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 Relution GmbH
* Copyright (C) 2012-2019 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
@@ -50,6 +51,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.UUID;
import androidx.fragment.app.Fragment;
@@ -64,7 +66,7 @@ public class VpnProfileListFragment extends Fragment
private VpnProfileAdapter mListAdapter;
private ListView mListView;
private OnVpnProfileSelectedListener mListener;
- private HashSet mSelected;
+ private Set mSelected;
private boolean mReadOnly;
private final BroadcastReceiver mProfilesChanged = new BroadcastReceiver()
@@ -237,18 +239,27 @@ public class VpnProfileListFragment extends Fragment
{
private MenuItem mEditProfile;
private MenuItem mCopyProfile;
+ private MenuItem mDeleteProfile;
+
+ private boolean mCanEdit;
+ private boolean mCanCopy;
+ private boolean mCanDelete;
+
+ private int mReadOnlyCount;
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu)
{
- mEditProfile.setEnabled(mSelected.size() == 1);
- mCopyProfile.setEnabled(mEditProfile.isEnabled());
+ mEditProfile.setEnabled(mCanEdit);
+ mCopyProfile.setEnabled(mCanCopy);
+ mDeleteProfile.setEnabled(mCanDelete);
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode)
{
+ mReadOnlyCount = 0;
mSelected.clear();
}
@@ -259,6 +270,7 @@ public class VpnProfileListFragment extends Fragment
inflater.inflate(R.menu.profile_list_context, menu);
mEditProfile = menu.findItem(R.id.edit_profile);
mCopyProfile = menu.findItem(R.id.copy_profile);
+ mDeleteProfile = menu.findItem(R.id.delete_profile);
mode.setTitle(R.string.select_profiles);
return true;
}
@@ -327,13 +339,17 @@ public class VpnProfileListFragment extends Fragment
public void onItemCheckedStateChanged(ActionMode mode, int position,
long id, boolean checked)
{
+ VpnProfile profile = (VpnProfile)mListView.getItemAtPosition(position);
+
if (checked)
{
mSelected.add(position);
+ mReadOnlyCount += profile.isReadOnly() ? 1 : 0;
}
else
{
mSelected.remove(position);
+ mReadOnlyCount -= profile.isReadOnly() ? 1 : 0;
}
final int checkedCount = mSelected.size();
switch (checkedCount)
@@ -348,6 +364,11 @@ public class VpnProfileListFragment extends Fragment
mode.setSubtitle(String.format(getString(R.string.x_profiles_selected), checkedCount));
break;
}
+
+ mCanEdit = checkedCount == 1;
+ mCanCopy = checkedCount == 1 && mReadOnlyCount == 0;
+ mCanDelete = checkedCount > 0 && mReadOnlyCount == 0;
+
mode.invalidate();
}
};
From c9c65a94c974ad8d64666c81e8d50057936a356b Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:22 +0100
Subject: [PATCH 13/45] android: Add label to read-only profiles in list
Show "Managed profile" in the list of VPN profiles, to make it
immediately obvious that a profile is managed/read-only.
---
.../android/ui/adapter/VpnProfileAdapter.java | 13 +++---
.../src/main/res/layout/profile_list_item.xml | 40 +++++++++++++------
.../app/src/main/res/values-de/strings.xml | 1 +
.../app/src/main/res/values-pl/strings.xml | 1 +
.../app/src/main/res/values-ru/strings.xml | 1 +
.../app/src/main/res/values-uk/strings.xml | 1 +
.../src/main/res/values-zh-rCN/strings.xml | 1 +
.../src/main/res/values-zh-rTW/strings.xml | 1 +
.../app/src/main/res/values/strings.xml | 1 +
9 files changed, 43 insertions(+), 17 deletions(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java
index 05a15464d..c2942b8a1 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java
@@ -61,11 +61,13 @@ public class VpnProfileAdapter extends ArrayAdapter
vpnProfileView = inflater.inflate(resource, null);
}
VpnProfile profile = getItem(position);
- TextView tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_name);
+ TextView tv = vpnProfileView.findViewById(R.id.profile_item_name);
tv.setText(profile.getName());
- tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_gateway);
+ tv = vpnProfileView.findViewById(R.id.profile_item_managed);
+ tv.setVisibility(profile.isReadOnly() ? View.VISIBLE : View.GONE);
+ tv = vpnProfileView.findViewById(R.id.profile_item_gateway);
tv.setText(getContext().getString(R.string.profile_gateway_label) + ": " + profile.getGateway());
- tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_username);
+ tv = vpnProfileView.findViewById(R.id.profile_item_username);
if (profile.getVpnType().has(VpnTypeFeature.USER_PASS))
{ /* if the view is reused we make sure it is visible */
tv.setVisibility(View.VISIBLE);
@@ -81,7 +83,7 @@ public class VpnProfileAdapter extends ArrayAdapter
{
tv.setVisibility(View.GONE);
}
- tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_certificate);
+ tv = vpnProfileView.findViewById(R.id.profile_item_certificate);
if (profile.getVpnType().has(VpnTypeFeature.CERTIFICATE))
{
tv.setText(getContext().getString(R.string.profile_user_certificate_label) + ": " + profile.getUserCertificateAlias());
@@ -103,7 +105,8 @@ public class VpnProfileAdapter extends ArrayAdapter
private void sortItems()
{
- Collections.sort(this.items, new Comparator() {
+ Collections.sort(this.items, new Comparator()
+ {
@Override
public int compare(VpnProfile lhs, VpnProfile rhs)
{
diff --git a/src/frontends/android/app/src/main/res/layout/profile_list_item.xml b/src/frontends/android/app/src/main/res/layout/profile_list_item.xml
index caf548858..d4d9b651e 100644
--- a/src/frontends/android/app/src/main/res/layout/profile_list_item.xml
+++ b/src/frontends/android/app/src/main/res/layout/profile_list_item.xml
@@ -17,44 +17,60 @@
for more details.
-->
+ android:paddingTop="4dip">
+ android:layout_marginStart="15dp"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ tools:text="Profile name" />
+
+
+ android:textColor="?android:textColorSecondary"
+ tools:text="Server: vpn.example.com" />
+ android:textColor="?android:textColorSecondary"
+ tools:text="Username" />
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:textColorSecondary"
+ tools:text="Certificate" />
diff --git a/src/frontends/android/app/src/main/res/values-de/strings.xml b/src/frontends/android/app/src/main/res/values-de/strings.xml
index 0b21fc747..19d5a1a54 100644
--- a/src/frontends/android/app/src/main/res/values-de/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-de/strings.xml
@@ -139,6 +139,7 @@
Zertifikat aus VPN Profil importierenZertifikat für \"%1$s\"Profil-ID
+ Verwaltetes ProfilEin Wert wird benötigt, um die Verbindung aufbauen zu könnenBitte geben Sie Ihren Benutzernamen ein
diff --git a/src/frontends/android/app/src/main/res/values-pl/strings.xml b/src/frontends/android/app/src/main/res/values-pl/strings.xml
index ec6ba216b..737eddf86 100644
--- a/src/frontends/android/app/src/main/res/values-pl/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-pl/strings.xml
@@ -141,6 +141,7 @@
Import certificate from VPN profileCertificate for \"%1$s\"Profile ID
+ Managed profileA value is required to initiate the connectionWprowadź swoją nazwę użytkownika
diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml
index 0150016b7..2bbe2b4dc 100644
--- a/src/frontends/android/app/src/main/res/values-ru/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml
@@ -135,6 +135,7 @@
Import certificate from VPN profileCertificate for \"%1$s\"Profile ID
+ Managed profileA value is required to initiate the connectionПожалуйста введите имя пользователя
diff --git a/src/frontends/android/app/src/main/res/values-uk/strings.xml b/src/frontends/android/app/src/main/res/values-uk/strings.xml
index e02a640a3..3868fc475 100644
--- a/src/frontends/android/app/src/main/res/values-uk/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-uk/strings.xml
@@ -136,6 +136,7 @@
Import certificate from VPN profileCertificate for \"%1$s\"Profile ID
+ Managed profileA value is required to initiate the connectionВведіть ім\'я користувача
diff --git a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml
index 99693c509..69b9f4d26 100644
--- a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml
@@ -135,6 +135,7 @@
从VPN配置导入证书\"%1$s\" 所对应的证书配置文件ID
+ Managed profile必填信息以初始化连接请输入您的用户名
diff --git a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml
index 79d3e41ba..33473b63e 100644
--- a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml
@@ -135,6 +135,7 @@
從VPN設定檔匯入憑證\"%1$s\" 對應的憑證Profile ID
+ Managed profile請填寫必要訊息才能初始化連線請輸入您的用戶名稱
diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml
index 59f24e7e2..d6d2ce854 100644
--- a/src/frontends/android/app/src/main/res/values/strings.xml
+++ b/src/frontends/android/app/src/main/res/values/strings.xml
@@ -139,6 +139,7 @@
Import certificate from VPN profileCertificate for \"%1$s\"Profile ID
+ Managed profileA value is required to initiate the connectionPlease enter your username
From 5f9f279a33b8cf8f1efdfbb636815206f9a676ce Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:22 +0100
Subject: [PATCH 14/45] android: Show warning message for read-only profiles in
detail view
Show a message explaining that a managed profile can't be edited in
its detail view.
---
.../android/ui/VpnProfileDetailActivity.java | 5 +
.../main/res/layout/profile_detail_view.xml | 998 +++++++++---------
.../app/src/main/res/values-de/strings.xml | 1 +
.../app/src/main/res/values-pl/strings.xml | 1 +
.../app/src/main/res/values-ru/strings.xml | 1 +
.../app/src/main/res/values-uk/strings.xml | 1 +
.../src/main/res/values-zh-rCN/strings.xml | 1 +
.../src/main/res/values-zh-rTW/strings.xml | 1 +
.../app/src/main/res/values/strings.xml | 1 +
9 files changed, 524 insertions(+), 486 deletions(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
index b01e67f88..4a6aac45f 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
@@ -98,6 +98,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
private SelectedAppsHandling mSelectedAppsHandling = SelectedAppsHandling.SELECTED_APPS_DISABLE;
private SortedSet mSelectedApps = new TreeSet<>();
private VpnProfile mProfile;
+ private View mManagedProfile;
private MultiAutoCompleteTextView mName;
private TextInputLayoutHelper mNameWrap;
private EditText mGateway;
@@ -195,6 +196,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
setContentView(R.layout.profile_detail_view);
+ mManagedProfile = findViewById(R.id.managed_profile);
+
mName = findViewById(R.id.name);
mNameWrap = findViewById(R.id.name_wrap);
mGateway = findViewById(R.id.gateway);
@@ -857,6 +860,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity
private void setReadOnly(final boolean readOnly)
{
+ mManagedProfile.setVisibility(readOnly ? View.VISIBLE : View.GONE);
+
mName.setEnabled(!readOnly);
mGateway.setEnabled(!readOnly);
mUsername.setEnabled(!readOnly);
diff --git a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml
index bdded4337..5197c63f7 100644
--- a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml
+++ b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml
@@ -16,541 +16,567 @@
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
-->
-
+
-
+ android:background="@drawable/state_background"
+ android:drawableStart="@android:drawable/ic_dialog_alert"
+ android:drawablePadding="8dp"
+ android:padding="8dp"
+ android:text="@string/alert_text_vpn_profile_read_only"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textStyle="bold"
+ android:visibility="gone"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:visibility="visible" />
-
-
-
-
-
-
-
-
-
-
-
+
+ android:animateLayoutChanges="true"
+ android:orientation="vertical"
+ android:padding="10dp">
+ android:layout_marginTop="6dp"
+ android:hint="@string/profile_gateway_label"
+ app:helper_text="@string/profile_gateway_hint">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:singleLine="true" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:text="@string/profile_vpn_type_label"
+ android:textSize="12sp" />
+ android:entries="@array/vpn_types"
+ android:spinnerMode="dropdown" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+ android:layout_marginTop="8dp"
+ android:hint="@string/profile_name_label"
+ app:helper_text="@string/profile_name_hint">
-
+ android:completionThreshold="1"
+ android:inputType="textNoSuggestions"
+ android:singleLine="true" />
-
+ android:text="@string/profile_show_advanced_label" />
-
+
+
+ android:layout_marginLeft="4dp"
+ android:layout_marginTop="10dp"
+ android:text="@string/profile_advanced_label"
+ android:textSize="20sp" />
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
diff --git a/src/frontends/android/app/src/main/res/values-de/strings.xml b/src/frontends/android/app/src/main/res/values-de/strings.xml
index 19d5a1a54..4e16f5d8d 100644
--- a/src/frontends/android/app/src/main/res/values-de/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-de/strings.xml
@@ -149,6 +149,7 @@
Bitte geben Sie mit Leerzeichen getrennte, gültige Subnetzte und/oder IP-Adressen einBitte geben Sie mit Leerzeichen getrennte, gültige IP-Adressen einBitte geben Sie eine mit Bindestrichen getrennte, gültige Liste von Algorithmen ein
+ Dieses Profil wird von Ihrem Administrator verwaltet und kann nicht bearbeitet werden. Nur das Passwort kann geändert werdenEAP-TNC kann Ihre Privatsphäre beeinträchtigenGerätedaten werden an den Server-Betreiber gesendetTrusted Network Connect (TNC) erlaubt Server-Betreibern den Gesundheitszustand von Endgeräten zu prüfen.
Dazu kann der Betreiber Daten verlangen, wie etwa eine eindeutige Identifikationsnummer, eine Liste der installierten Pakete, Systemeinstellungen oder kryptografische Prüfsummen von Dateien.
Solche Daten werden nur übermittelt nachdem die Identität des Servers geprüft wurde.]]>
diff --git a/src/frontends/android/app/src/main/res/values-pl/strings.xml b/src/frontends/android/app/src/main/res/values-pl/strings.xml
index 737eddf86..68089da93 100644
--- a/src/frontends/android/app/src/main/res/values-pl/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-pl/strings.xml
@@ -151,6 +151,7 @@
Please enter valid subnets and/or IP addresses, separated by spacesPlease enter valid IP addresses, separated by spacesPlease enter a valid list of algorithms, separated by hyphens
+ This VPN profile is managed by your administrator and can\'t be modified. You can only change the passwordEAP-TNC may affect your privacyDevice data is sent to the server operatorTrusted Network Connect (TNC) allows server operators to assess the health of a client device.
For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.
Any data will be sent only after verifying the server\'s identity.]]>
diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml
index 2bbe2b4dc..ab35f29e7 100644
--- a/src/frontends/android/app/src/main/res/values-ru/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml
@@ -145,6 +145,7 @@
Please enter valid subnets and/or IP addresses, separated by spacesPlease enter valid IP addresses, separated by spacesPlease enter a valid list of algorithms, separated by hyphens
+ This VPN profile is managed by your administrator and can\'t be modified. You can only change the passwordEAP-TNC may affect your privacyDevice data is sent to the server operatorTrusted Network Connect (TNC) allows server operators to assess the health of a client device.
For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.
Any data will be sent only after verifying the server\'s identity.]]>
diff --git a/src/frontends/android/app/src/main/res/values-uk/strings.xml b/src/frontends/android/app/src/main/res/values-uk/strings.xml
index 3868fc475..677d7c95d 100644
--- a/src/frontends/android/app/src/main/res/values-uk/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-uk/strings.xml
@@ -146,6 +146,7 @@
Please enter valid subnets and/or IP addresses, separated by spacesPlease enter valid IP addresses, separated by spacesPlease enter a valid list of algorithms, separated by hyphens
+ This VPN profile is managed by your administrator and can\'t be modified. You can only change the passwordEAP-TNC may affect your privacyDevice data is sent to the server operatorTrusted Network Connect (TNC) allows server operators to assess the health of a client device.
For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.
Any data will be sent only after verifying the server\'s identity.]]>
diff --git a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml
index 69b9f4d26..43d3134d9 100644
--- a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml
@@ -145,6 +145,7 @@
请输入有效的子网和/或IP地址,用空格分隔请输入有效的IP地址,以空格分隔请输入用连字符分隔的有效算法列表
+ This VPN profile is managed by your administrator and can\'t be modified. You can only change the passwordEAP-TNC可能会影响您的隐私设备数据已被发送至服务器管理员可信网络连接t (TNC) 允许服务器管理员评定一个用户设备的状况。
任何数据都仅将在验证过服务器的身份ID之后被发出。]]>
diff --git a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml
index 33473b63e..731682f66 100644
--- a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml
+++ b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml
@@ -145,6 +145,7 @@
Please enter valid subnets and/or IP addresses, separated by spacesPlease enter valid IP addresses, separated by spacesPlease enter a valid list of algorithms, separated by hyphens
+ This VPN profile is managed by your administrator and can\'t be modified. You can only change the passwordEAP-TNC可能會影響您的隱私安全裝置資料已經發送給伺服器管理者Trusted Network Connect (TNC) 可以讓伺服器管理者評估用戶裝置的狀況。
任何資料都只有在驗證伺服器的身分ID之後才會被送出。]]>
diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml
index d6d2ce854..71dc6e851 100644
--- a/src/frontends/android/app/src/main/res/values/strings.xml
+++ b/src/frontends/android/app/src/main/res/values/strings.xml
@@ -149,6 +149,7 @@
Please enter valid subnets and/or IP addresses, separated by spacesPlease enter valid IP addresses, separated by spacesPlease enter a valid list of algorithms, separated by hyphens
+ This VPN profile is managed by your administrator and can\'t be modified. You can only change the passwordEAP-TNC may affect your privacyDevice data is sent to the server operatorTrusted Network Connect (TNC) allows server operators to assess the health of a client device.
For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.
Any data will be sent only after verifying the server\'s identity.]]>
From a5167a69e05392d4bbe7c1ab3895fce1c0c5899c Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:21 +0100
Subject: [PATCH 15/45] android: Add data source to VpnProfile
---
.../java/org/strongswan/android/data/VpnProfile.java | 11 +++++++++++
.../org/strongswan/android/data/VpnProfileSource.java | 4 ++--
.../android/data/VpnProfileSqlDataSource.java | 3 +++
3 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java
index 9219b1001..e3ce9d4b8 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java
@@ -49,6 +49,7 @@ public class VpnProfile implements Cloneable
private UUID mUUID;
private long mId = -1;
private boolean mReadOnly;
+ private VpnProfileDataSource mDataSource;
public enum SelectedAppsHandling
{
@@ -342,6 +343,16 @@ public class VpnProfile implements Cloneable
this.mReadOnly = readOnly;
}
+ public VpnProfileDataSource getDataSource()
+ {
+ return mDataSource;
+ }
+
+ public void setDataSource(VpnProfileDataSource mDataSource)
+ {
+ this.mDataSource = mDataSource;
+ }
+
@Override
public String toString()
{
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
index 4a709989a..2ae0872cc 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
@@ -62,13 +62,13 @@ public class VpnProfileSource implements VpnProfileDataSource
@Override
public boolean updateVpnProfile(VpnProfile profile)
{
- return vpnProfileSqlDataSource.updateVpnProfile(profile);
+ return profile.getDataSource().updateVpnProfile(profile);
}
@Override
public boolean deleteVpnProfile(VpnProfile profile)
{
- return vpnProfileSqlDataSource.deleteVpnProfile(profile);
+ return profile.getDataSource().deleteVpnProfile(profile);
}
@Override
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
index 0934edc39..5b1f9ad26 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
@@ -291,6 +291,7 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource
{
return null;
}
+ profile.setDataSource(this);
profile.setId(insertId);
return profile;
}
@@ -319,6 +320,7 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource
if (cursor.moveToFirst())
{
profile = VpnProfileFromCursor(cursor);
+ profile.setDataSource(this);
}
cursor.close();
return profile;
@@ -334,6 +336,7 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource
while (!cursor.isAfterLast())
{
VpnProfile vpnProfile = VpnProfileFromCursor(cursor);
+ vpnProfile.setDataSource(this);
vpnProfiles.add(vpnProfile);
cursor.moveToNext();
}
From 01ea7b92bd153572b70b564f81f98987951558e6 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:24 +0100
Subject: [PATCH 16/45] android: Make VpnType#fromIdentifier null-safe
---
.../org/strongswan/android/data/VpnType.java | 25 ++++++++++++++-----
1 file changed, 19 insertions(+), 6 deletions(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnType.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnType.java
index 1a666734b..0552a645f 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnType.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnType.java
@@ -17,6 +17,10 @@
package org.strongswan.android.data;
import java.util.EnumSet;
+import java.util.Objects;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
public enum VpnType
{
@@ -32,11 +36,19 @@ public enum VpnType
*/
public enum VpnTypeFeature
{
- /** client certificate is required */
+ /**
+ * Client certificate is required
+ */
CERTIFICATE,
- /** username and password are required */
+
+ /**
+ * Username and password are required
+ */
USER_PASS,
- /** enable BYOD features */
+
+ /**
+ * Enable BYOD features
+ */
BYOD;
}
@@ -48,7 +60,6 @@ public enum VpnType
*
* @param id identifier used to store and transmit this specific type
* @param features of the given VPN type
- * @param certificate true if a client certificate is required
*/
VpnType(String id, EnumSet features)
{
@@ -58,6 +69,7 @@ public enum VpnType
/**
* The identifier used to store this value in the database
+ *
* @return identifier
*/
public String getIdentifier()
@@ -81,11 +93,12 @@ public enum VpnType
* @param identifier get the enum entry with this identifier
* @return the enum entry, or the default if not found
*/
- public static VpnType fromIdentifier(String identifier)
+ @NonNull
+ public static VpnType fromIdentifier(@Nullable String identifier)
{
for (VpnType type : VpnType.values())
{
- if (identifier.equals(type.mIdentifier))
+ if (Objects.equals(identifier, type.mIdentifier))
{
return type;
}
From c2007d5b09a3d6f18d1c5e0495555735a631a71d Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:21 +0100
Subject: [PATCH 17/45] android: Add managed_configuration.xml
Add managed configuration and associated English strings.
---
.../android/app/src/main/AndroidManifest.xml | 10 +-
.../app/src/main/res/values/arrays.xml | 9 +
.../values/strings_managed_configuration.xml | 107 +++++++
.../main/res/xml/managed_configuration.xml | 296 ++++++++++++++++++
4 files changed, 421 insertions(+), 1 deletion(-)
create mode 100644 src/frontends/android/app/src/main/res/values/strings_managed_configuration.xml
create mode 100644 src/frontends/android/app/src/main/res/xml/managed_configuration.xml
diff --git a/src/frontends/android/app/src/main/AndroidManifest.xml b/src/frontends/android/app/src/main/AndroidManifest.xml
index 89e3fb110..ec4bf65cf 100644
--- a/src/frontends/android/app/src/main/AndroidManifest.xml
+++ b/src/frontends/android/app/src/main/AndroidManifest.xml
@@ -161,9 +161,17 @@
+
+
+
+ android:exported="false">
IKEv2 EAP-TNC (Username/Password)
+
+ ikev2-eap
+ ikev2-cert
+ ikev2-cert-eap
+ ikev2-eap-tls
+ ikev2-byod-eap
+
+ ikev2-eap
+
All applications use the VPN
diff --git a/src/frontends/android/app/src/main/res/values/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values/strings_managed_configuration.xml
new file mode 100644
index 000000000..573a50706
--- /dev/null
+++ b/src/frontends/android/app/src/main/res/values/strings_managed_configuration.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+ Allow profile creation
+ Specifies whether users are allowed to add their own profiles
+ Allow profile import
+ Specifies whether users are allowed to import their own profiles
+ Show existing profiles
+ Specifies whether users can continue to see and use their previously created profiles
+ Allow certificate import
+ Specifies whether users are allowed to import certificates
+ Allow modifying settings
+ Specifies whether users are allowed change global app settings
+ @string/pref_default_vpn_profile
+ Unique identifier of the VPN profile to use by default, use the value \"mru\" for the most recently used profile
+ @string/pref_power_whitelist_title
+ @string/pref_power_whitelist_summary
+ VPN profiles
+ Collection of managed VPN profiles
+ VPN profile
+ A managed VPN profile
+
+
+ Unique identifier
+ Unique identifier of the VPN profile. Version 4 UUIDs (random-generated) are recommended
+ @string/profile_name_label
+ @string/profile_name_hint
+ @string/profile_vpn_type_label
+ The type of client authentication used by the VPN profile
+ Apps allowed to use the VPN (Optional)
+ Space-separated list of package names; all other apps will not see/use the VPN
+ Apps excluded from using the VPN (Optional)
+ Space-separated list of package names of apps excluded from using the VPN; only used of allow list is empty
+ @string/profile_proposals_ike_label
+ @string/profile_proposals_ike_hint
+ @string/profile_proposals_esp_label
+ @string/profile_proposals_esp_hint
+ @string/profile_mtu_label
+ @string/profile_mtu_hint
+ @string/profile_nat_keepalive_label
+ @string/profile_nat_keepalive_hint
+ @string/profile_dns_servers_label
+ @string/profile_dns_servers_hint
+ @string/profile_ipv6_transport_label
+ @string/profile_ipv6_transport_hint
+
+
+ Remote
+ Specifies information about the server
+ @string/profile_gateway_label
+ @string/profile_gateway_hint
+ @string/profile_port_label
+ @string/profile_port_hint
+ @string/profile_remote_id_label
+ @string/profile_remote_id_hint
+ CA or server certificate (Optional)
+ Base64-encoded CA or server certificate. Is imported into the app, not the system keystore. If not set, automatic CA certificate selection is enabled
+ Send certificate requests
+ Specifies whether to send certificate requests for all installed or selected CA certificates. Disabling this may reduce the size of the IKE_AUTH message if the server does not support fragmentation. But it only works if the server doesn\'t require certificate requests to send back the server certificate
+ @string/profile_use_ocsp_label
+ @string/profile_use_ocsp_hint
+ @string/profile_use_crl_label
+ @string/profile_use_crl_hint
+ @string/profile_strict_revocation_label
+ @string/profile_strict_revocation_hint
+
+
+ Local
+ Specifies information about the client
+ Identity/username for EAP authentication (Optional)
+ If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it. If it is set, the user is not able to change it. In both cases the user may optionally enter the password
+ @string/profile_local_id_label
+ @string/profile_local_id_hint_user
+ @string/profile_user_certificate_label
+ Base64-encoded PKCS#12-container with the client certificate and private key and optional certificate chain (the latter might cause warnings on older Android releases, see Android VPN client configuration for details). Not necessary for username/password-based EAP authentication or if the user already has the certificate/key installed as it may be selected while importing the profile
+ @string/profile_rsa_pss_label
+ @string/profile_rsa_pss_hint
+
+
+ @string/profile_split_tunneling_label
+ @string/profile_split_tunneling_intro
+ @string/profile_included_subnets_label
+ @string/profile_included_subnets_hint
+ @string/profile_excluded_subnets_label
+ @string/profile_excluded_subnets_hint
+ @string/profile_split_tunnelingv4_title
+ Specifies whether to block IPv4 traffic that\'s not destined for the VPN. Forces all IPv4 traffic via VPN (traffic that does not match the negotiated traffic selector is then just dropped). Thus this is basically equivalent to including 0.0.0.0/0 in subnets
+ @string/profile_split_tunnelingv6_title
+ Specifies whether to block IPv6 traffic that\'s not destined for the VPN. Forces all IPv6 traffic via VPN (traffic that does not match the negotiated traffic selector is then just dropped). Thus this is basically equivalent to including ::/0 in subnets
+
+
diff --git a/src/frontends/android/app/src/main/res/xml/managed_configuration.xml b/src/frontends/android/app/src/main/res/xml/managed_configuration.xml
new file mode 100644
index 000000000..425758b20
--- /dev/null
+++ b/src/frontends/android/app/src/main/res/xml/managed_configuration.xml
@@ -0,0 +1,296 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 8796e9bb3186f95a9e017625f751c93c2894a503 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:21 +0100
Subject: [PATCH 18/45] android: Add ManagedConfigurationService and related
classes
Add service that provides access to managed configurations.
---
.../android/data/ManagedConfiguration.java | 179 ++++++++++++++++++
.../data/ManagedConfigurationService.java | 90 +++++++++
.../android/data/ManagedVpnProfile.java | 134 +++++++++++++
3 files changed, 403 insertions(+)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedConfiguration.java
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedConfigurationService.java
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedVpnProfile.java
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 000000000..a429f5fc8
--- /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 000000000..ac8e73d9b
--- /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 000000000..90169871c
--- /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;
+ }
+}
From 36f62585bbd8d7ec5781711269b9184bc5193e5b Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:22 +0100
Subject: [PATCH 19/45] android: Expose managed configuration globally and
notify listeners on changes
Triggers a broadcast if the configuration changed and updates the
profile list accordingly (previously only handled removal of multiple
profiles).
If the app resumes, the configuration is also loaded and listeners are
notified in case the config was updated while the app was in the
background.
---
src/frontends/android/app/build.gradle | 1 +
.../android/logic/StrongSwanApplication.java | 76 ++++++++++++++++++-
.../android/ui/VpnProfileListFragment.java | 14 +++-
3 files changed, 85 insertions(+), 6 deletions(-)
diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle
index 195422791..237134771 100644
--- a/src/frontends/android/app/build.gradle
+++ b/src/frontends/android/app/build.gradle
@@ -45,6 +45,7 @@ android {
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'androidx.lifecycle:lifecycle-process:2.7.0'
implementation 'androidx.preference:preference:1.2.1'
implementation 'com.google.android.material:material:1.10.0'
testImplementation 'junit:junit:4.13.2'
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
index ac9866155..d3805a284 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
@@ -1,5 +1,6 @@
/*
- * Copyright (C) 2014 Tobias Brunner
+ * Copyright (C) 2023 Relution GmbH
+ * Copyright (C) 2014-2024 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
@@ -17,25 +18,53 @@
package org.strongswan.android.logic;
import android.app.Application;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Handler;
import android.os.Looper;
+import android.util.Log;
+import org.strongswan.android.data.ManagedConfigurationService;
import org.strongswan.android.security.LocalCertificateKeyStoreProvider;
+import org.strongswan.android.utils.Constants;
import java.security.Security;
+import java.util.HashSet;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import androidx.annotation.NonNull;
import androidx.core.os.HandlerCompat;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.ProcessLifecycleOwner;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-public class StrongSwanApplication extends Application
+public class StrongSwanApplication extends Application implements DefaultLifecycleObserver
{
+ private static final String TAG = StrongSwanApplication.class.getSimpleName();
+
private static Context mContext;
+
private final ExecutorService mExecutorService = Executors.newFixedThreadPool(4);
private final Handler mMainHandler = HandlerCompat.createAsync(Looper.getMainLooper());
+ private ManagedConfigurationService mManagedConfigurationService;
+
+ private final BroadcastReceiver mRestrictionsReceiver = new BroadcastReceiver()
+ {
+ @Override
+ public void onReceive(Context context, Intent intent)
+ {
+ Log.d(TAG, "Managed configuration changed");
+ reloadManagedConfigurationAndNotifyListeners();
+ }
+ };
+
static
{
Security.addProvider(new LocalCertificateKeyStoreProvider());
@@ -46,6 +75,39 @@ public class StrongSwanApplication extends Application
{
super.onCreate();
StrongSwanApplication.mContext = getApplicationContext();
+
+ mManagedConfigurationService = new ManagedConfigurationService(mContext);
+ ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
+ }
+
+ @Override
+ public void onResume(@NonNull LifecycleOwner owner)
+ {
+ reloadManagedConfigurationAndNotifyListeners();
+
+ final IntentFilter restrictionsFilter = new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
+ registerReceiver(mRestrictionsReceiver, restrictionsFilter);
+ }
+
+ @Override
+ public void onPause(@NonNull LifecycleOwner owner)
+ {
+ unregisterReceiver(mRestrictionsReceiver);
+ }
+
+ private void reloadManagedConfigurationAndNotifyListeners()
+ {
+ final Set uuids = new HashSet<>(mManagedConfigurationService.getManagedProfiles().keySet());
+
+ mManagedConfigurationService.loadConfiguration();
+ mManagedConfigurationService.updateSettings();
+
+ uuids.addAll(mManagedConfigurationService.getManagedProfiles().keySet());
+
+ Log.d(TAG, "Send profiles changed broadcast");
+ Intent profilesChanged = new Intent(Constants.VPN_PROFILES_CHANGED);
+ profilesChanged.putExtra(Constants.VPN_PROFILES_MULTIPLE, uuids.toArray(new String[0]));
+ LocalBroadcastManager.getInstance(mContext).sendBroadcast(profilesChanged);
}
/**
@@ -78,6 +140,16 @@ public class StrongSwanApplication extends Application
return mMainHandler;
}
+ /**
+ * Returns a service providing access to the app's managed configuration.
+ *
+ * @return managed configuration
+ */
+ public ManagedConfigurationService getManagedConfigurationService()
+ {
+ return mManagedConfigurationService;
+ }
+
/*
* The libraries are extracted to /data/data/org.strongswan.android/...
* during installation. On newer releases most are loaded in JNI_OnLoad.
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
index c56962d72..051604f3e 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
@@ -89,18 +89,24 @@ public class VpnProfileListFragment extends Fragment
}
else if ((uuids = intent.getStringArrayExtra(Constants.VPN_PROFILES_MULTIPLE)) != null)
{
- for (String id : uuids)
+ for (final String id : uuids)
{
- Iterator profiles = mVpnProfiles.iterator();
+ final Iterator profiles = mVpnProfiles.iterator();
while (profiles.hasNext())
{
- VpnProfile profile = profiles.next();
+ final VpnProfile profile = profiles.next();
if (Objects.equals(profile.getUUID().toString(), id))
- {
+ { /* in case this was an edit, we remove it first */
profiles.remove();
break;
}
}
+
+ VpnProfile profile = mDataSource.getVpnProfile(id);
+ if (profile != null)
+ {
+ mVpnProfiles.add(profile);
+ }
}
mListAdapter.notifyDataSetChanged();
}
From 8f04d15dfd60af5a8d73d4f8bc7b1242d444eb61 Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Fri, 19 Jan 2024 18:29:20 +0100
Subject: [PATCH 20/45] android: Expose static instance for Application object
While it seems to be possible to cast Context.getApplicationContext()
to the application class, there really is no documented reason why that
should actually be the same object.
---
.../android/logic/StrongSwanApplication.java | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
index d3805a284..8d653624e 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
@@ -49,6 +49,7 @@ public class StrongSwanApplication extends Application implements DefaultLifecyc
private static final String TAG = StrongSwanApplication.class.getSimpleName();
private static Context mContext;
+ private static StrongSwanApplication mInstance;
private final ExecutorService mExecutorService = Executors.newFixedThreadPool(4);
private final Handler mMainHandler = HandlerCompat.createAsync(Looper.getMainLooper());
@@ -75,6 +76,7 @@ public class StrongSwanApplication extends Application implements DefaultLifecyc
{
super.onCreate();
StrongSwanApplication.mContext = getApplicationContext();
+ StrongSwanApplication.mInstance = this;
mManagedConfigurationService = new ManagedConfigurationService(mContext);
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
@@ -120,6 +122,16 @@ public class StrongSwanApplication extends Application implements DefaultLifecyc
return StrongSwanApplication.mContext;
}
+ /**
+ * Returns the current application object
+ *
+ * @return application
+ */
+ public static StrongSwanApplication getInstance()
+ {
+ return StrongSwanApplication.mInstance;
+ }
+
/**
* Returns a thread pool to run tasks in separate threads
*
From 4bfeb3b000315d8a821e5ab5b109ecc969c58241 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:21 +0100
Subject: [PATCH 21/45] android: Add data source for managed VPN profiles
Include the managed VPN profile data source in the profile source,
to show profiles from both sources in the UI.
---
.../data/VpnProfileManagedDataSource.java | 118 ++++++++++++++++++
.../android/data/VpnProfileSource.java | 2 +
2 files changed, 120 insertions(+)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileManagedDataSource.java
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileManagedDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileManagedDataSource.java
new file mode 100644
index 000000000..414d5bc4e
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileManagedDataSource.java
@@ -0,0 +1,118 @@
+/*
+ * 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.SharedPreferences;
+import android.database.SQLException;
+
+import org.strongswan.android.logic.StrongSwanApplication;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+public class VpnProfileManagedDataSource implements VpnProfileDataSource
+{
+ private static final String NAME_MANAGED_VPN_PROFILES = "org.strongswan.android.data.VpnProfileManagedDataSource.preferences";
+
+ private final ManagedConfigurationService mManagedConfigurationService;
+ private final SharedPreferences mSharedPreferences;
+
+ public VpnProfileManagedDataSource(final Context context)
+ {
+ this.mManagedConfigurationService = StrongSwanApplication.getInstance().getManagedConfigurationService();
+ this.mSharedPreferences = context.getSharedPreferences(NAME_MANAGED_VPN_PROFILES, Context.MODE_PRIVATE);
+ }
+
+ @Override
+ public VpnProfileDataSource open() throws SQLException
+ {
+ return this;
+ }
+
+ @Override
+ public void close()
+ {
+ /* remove passwords that are no longer referenced by a VPN profile */
+ final Set actualKeys = mManagedConfigurationService.getManagedProfiles().keySet();
+
+ final Set storedKeys = new HashSet<>(mSharedPreferences.getAll().keySet());
+ storedKeys.removeAll(actualKeys);
+
+ final SharedPreferences.Editor editor = mSharedPreferences.edit();
+ for (String key : storedKeys)
+ {
+ editor.remove(key);
+ }
+
+ editor.apply();
+ }
+
+ @Override
+ public VpnProfile insertProfile(VpnProfile profile)
+ {
+ return null;
+ }
+
+ @Override
+ public boolean updateVpnProfile(VpnProfile profile)
+ {
+ final VpnProfile existingProfile = getVpnProfile(profile.getUUID());
+ if (existingProfile == null)
+ {
+ return false;
+ }
+
+ final String password = profile.getPassword();
+ existingProfile.setPassword(password);
+
+ final SharedPreferences.Editor editor = mSharedPreferences.edit();
+ editor.putString(profile.getUUID().toString(), password);
+ return editor.commit();
+ }
+
+ @Override
+ public boolean deleteVpnProfile(VpnProfile profile)
+ {
+ return false;
+ }
+
+ @Override
+ public VpnProfile getVpnProfile(UUID uuid)
+ {
+ return mManagedConfigurationService.getManagedProfiles().get(uuid.toString());
+ }
+
+ @Override
+ public List getAllVpnProfiles()
+ {
+ final Map managedVpnProfiles = mManagedConfigurationService.getManagedProfiles();
+ final List vpnProfiles = new ArrayList<>();
+ for (final VpnProfile vpnProfile : managedVpnProfiles.values())
+ {
+ final String password = mSharedPreferences.getString(vpnProfile.getUUID().toString(), vpnProfile.getPassword());
+ vpnProfile.setPassword(password);
+ vpnProfile.setDataSource(this);
+ vpnProfiles.add(vpnProfile);
+ }
+ return vpnProfiles;
+ }
+}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
index 2ae0872cc..fd66308e6 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
@@ -31,7 +31,9 @@ public class VpnProfileSource implements VpnProfileDataSource
public VpnProfileSource(Context context)
{
vpnProfileSqlDataSource = new VpnProfileSqlDataSource(context);
+
dataSources.add(vpnProfileSqlDataSource);
+ dataSources.add(new VpnProfileManagedDataSource(context));
}
@Override
From fe13782e3c58d2f4b47472a347040243ca416857 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:22 +0100
Subject: [PATCH 22/45] android: Hide menu items depending on managed
configuration
Hide and disable menu items when disabled by the managed configuration.
---
.../strongswan/android/ui/MainActivity.java | 19 +++++++++++++++++++
.../ui/TrustedCertificatesActivity.java | 19 +++++++++++++++++++
.../android/ui/VpnProfileListFragment.java | 19 +++++++++++++++++++
3 files changed, 57 insertions(+)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
index a836ffbcc..603dedef9 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
@@ -28,6 +28,8 @@ import android.view.MenuItem;
import android.widget.Toast;
import org.strongswan.android.R;
+import org.strongswan.android.data.ManagedConfiguration;
+import org.strongswan.android.data.ManagedConfigurationService;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.logic.StrongSwanApplication;
import org.strongswan.android.logic.TrustedCertificateManager;
@@ -57,6 +59,8 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
private static final String DIALOG_TAG = "Dialog";
+ private ManagedConfigurationService mManagedConfigurationService;
+
@Override
public void onCreate(Bundle savedInstanceState)
{
@@ -72,6 +76,8 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
((StrongSwanApplication)getApplication()).getExecutor().execute(() -> {
TrustedCertificateManager.getInstance().load();
});
+
+ mManagedConfigurationService = StrongSwanApplication.getInstance().getManagedConfigurationService();
}
@Override
@@ -81,6 +87,19 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
return true;
}
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu)
+ {
+ final MenuItem importProfile = menu.findItem(R.id.menu_import_profile);
+ if (importProfile != null)
+ {
+ final ManagedConfiguration managedConfiguration = mManagedConfigurationService.getManagedConfiguration();
+ importProfile.setVisible(managedConfiguration.isAllowProfileImport());
+ importProfile.setEnabled(managedConfiguration.isAllowProfileImport());
+ }
+ return true;
+ }
+
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java
index c32ec5d82..0a0b26a8a 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java
@@ -25,7 +25,10 @@ import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import org.strongswan.android.R;
+import org.strongswan.android.data.ManagedConfiguration;
+import org.strongswan.android.data.ManagedConfigurationService;
import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.logic.StrongSwanApplication;
import org.strongswan.android.logic.TrustedCertificateManager;
import org.strongswan.android.logic.TrustedCertificateManager.TrustedCertificateSource;
import org.strongswan.android.security.TrustedCertificateEntry;
@@ -51,6 +54,8 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr
private ViewPager2 mPager;
private boolean mSelect;
+ private ManagedConfigurationService mManagedConfigurationService;
+
private final ActivityResultLauncher mImportCertificate = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
@@ -81,6 +86,7 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr
}).attach();
mSelect = SELECT_CERTIFICATE.equals(getIntent().getAction());
+ mManagedConfigurationService = StrongSwanApplication.getInstance().getManagedConfigurationService();
}
@Override
@@ -90,6 +96,19 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr
return true;
}
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu)
+ {
+ final MenuItem importCertificate = menu.findItem(R.id.menu_import_certificate);
+ if (importCertificate != null)
+ {
+ final ManagedConfiguration managedConfiguration = mManagedConfigurationService.getManagedConfiguration();
+ importCertificate.setVisible(managedConfiguration.isAllowCertificateImport());
+ importCertificate.setEnabled(managedConfiguration.isAllowCertificateImport());
+ }
+ return true;
+ }
+
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
index 051604f3e..6a22fdf5b 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
@@ -40,9 +40,12 @@ import android.widget.ListView;
import android.widget.Toast;
import org.strongswan.android.R;
+import org.strongswan.android.data.ManagedConfiguration;
+import org.strongswan.android.data.ManagedConfigurationService;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfileDataSource;
import org.strongswan.android.data.VpnProfileSource;
+import org.strongswan.android.logic.StrongSwanApplication;
import org.strongswan.android.ui.adapter.VpnProfileAdapter;
import org.strongswan.android.utils.Constants;
@@ -69,6 +72,8 @@ public class VpnProfileListFragment extends Fragment
private Set mSelected;
private boolean mReadOnly;
+ private ManagedConfigurationService mManagedConfigurationService;
+
private final BroadcastReceiver mProfilesChanged = new BroadcastReceiver()
{
@Override
@@ -175,6 +180,8 @@ public class VpnProfileListFragment extends Fragment
mDataSource = new VpnProfileSource(this.getActivity());
mDataSource.open();
+ mManagedConfigurationService = StrongSwanApplication.getInstance().getManagedConfigurationService();
+
/* cached list of profiles used as backend for the ListView */
mVpnProfiles = mDataSource.getAllVpnProfiles();
@@ -216,6 +223,18 @@ public class VpnProfileListFragment extends Fragment
inflater.inflate(R.menu.profile_list, menu);
}
+ @Override
+ public void onPrepareOptionsMenu(Menu menu)
+ {
+ final MenuItem addProfile = menu.findItem(R.id.add_profile);
+ if (addProfile != null)
+ {
+ final ManagedConfiguration managedConfiguration = mManagedConfigurationService.getManagedConfiguration();
+ addProfile.setVisible(managedConfiguration.isAllowProfileCreation());
+ addProfile.setEnabled(managedConfiguration.isAllowProfileCreation());
+ }
+ }
+
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
From 0af501ef266e40e3e1bedd1c7ad340d117084542 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:22 +0100
Subject: [PATCH 23/45] android: Disable access to settings depending on
managed configuration
---
.../android/ui/SettingsFragment.java | 20 ++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java
index 7f047ce6b..771e5a20b 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/SettingsFragment.java
@@ -18,15 +18,19 @@ package org.strongswan.android.ui;
import static org.strongswan.android.utils.Constants.PREF_DEFAULT_VPN_PROFILE;
import static org.strongswan.android.utils.Constants.PREF_DEFAULT_VPN_PROFILE_MRU;
+import static org.strongswan.android.utils.Constants.PREF_IGNORE_POWER_WHITELIST;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import org.strongswan.android.R;
+import org.strongswan.android.data.ManagedConfiguration;
+import org.strongswan.android.data.ManagedConfigurationService;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.data.VpnProfileDataSource;
import org.strongswan.android.data.VpnProfileSource;
+import org.strongswan.android.logic.StrongSwanApplication;
import java.util.ArrayList;
import java.util.Collections;
@@ -37,14 +41,21 @@ import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
+import androidx.preference.SwitchPreference;
public class SettingsFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener
{
+ private ManagedConfigurationService mManagedConfigurationService;
+
private ListPreference mDefaultVPNProfile;
+ private SwitchPreference mIgnorePowerWhitelist;
@Override
public void onCreatePreferences(Bundle bundle, String s)
{
+ mManagedConfigurationService = StrongSwanApplication.getInstance().getManagedConfigurationService();
+ mManagedConfigurationService.updateSettings();
+
setPreferencesFromResource(R.xml.settings, s);
mDefaultVPNProfile = findPreference(PREF_DEFAULT_VPN_PROFILE);
@@ -53,6 +64,8 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Prefer
{
mDefaultVPNProfile.setEnabled(false);
}
+
+ mIgnorePowerWhitelist = findPreference(PREF_IGNORE_POWER_WHITELIST);
}
@Override
@@ -86,7 +99,8 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Prefer
}
profiles.close();
- if (entries.size() <= 1)
+ final ManagedConfiguration managedConfiguration = mManagedConfigurationService.getManagedConfiguration();
+ if (entries.size() <= 1 || !managedConfiguration.isAllowSettingsAccess())
{
mDefaultVPNProfile.setEnabled(false);
}
@@ -96,6 +110,10 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Prefer
mDefaultVPNProfile.setEntries(entries.toArray(new CharSequence[0]));
mDefaultVPNProfile.setEntryValues(entryvalues.toArray(new CharSequence[0]));
}
+ if (!managedConfiguration.isAllowSettingsAccess())
+ {
+ mIgnorePowerWhitelist.setEnabled(false);
+ }
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getActivity());
setCurrentProfileName(pref.getString(PREF_DEFAULT_VPN_PROFILE, PREF_DEFAULT_VPN_PROFILE_MRU));
From 42626c1dd8dd4578ad834f3a8efdd6b46a7056f5 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:22 +0100
Subject: [PATCH 24/45] android: Hide unmanaged profiles by default
Such profiles could exist if a user already had strongSwan installed.
---
.../android/data/VpnProfileSource.java | 20 +++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
index fd66308e6..77cfa8551 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
@@ -19,17 +19,22 @@ package org.strongswan.android.data;
import android.content.Context;
import android.database.SQLException;
+import org.strongswan.android.logic.StrongSwanApplication;
+
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class VpnProfileSource implements VpnProfileDataSource
{
+ private final ManagedConfigurationService mManagedConfigurationService;
private final List dataSources = new ArrayList<>();
private final VpnProfileSqlDataSource vpnProfileSqlDataSource;
public VpnProfileSource(Context context)
{
+ mManagedConfigurationService = StrongSwanApplication.getInstance().getManagedConfigurationService();
+
vpnProfileSqlDataSource = new VpnProfileSqlDataSource(context);
dataSources.add(vpnProfileSqlDataSource);
@@ -73,10 +78,21 @@ public class VpnProfileSource implements VpnProfileDataSource
return profile.getDataSource().deleteVpnProfile(profile);
}
+ private List getAccessibleSources()
+ {
+ final ManagedConfiguration managedConfiguration = mManagedConfigurationService.getManagedConfiguration();
+ ArrayList sources = new ArrayList<>(dataSources);
+ if (!managedConfiguration.isAllowExistingProfiles())
+ {
+ sources.remove(vpnProfileSqlDataSource);
+ }
+ return sources;
+ }
+
@Override
public VpnProfile getVpnProfile(UUID uuid)
{
- for (final VpnProfileDataSource source : dataSources)
+ for (final VpnProfileDataSource source : getAccessibleSources())
{
final VpnProfile profile = source.getVpnProfile(uuid);
if (profile != null)
@@ -92,7 +108,7 @@ public class VpnProfileSource implements VpnProfileDataSource
{
final List profiles = new ArrayList<>();
- for (final VpnProfileDataSource source : dataSources)
+ for (final VpnProfileDataSource source : getAccessibleSources())
{
profiles.addAll(source.getAllVpnProfiles());
}
From 802047cae8f66b59f806a574671a4dad18f25ed3 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:22 +0100
Subject: [PATCH 25/45] android: Move database helper into separate class
Reduce strong coupling between database helper and VPN profiles, to
prepare for the addition of other tables.
---
.../android/data/DatabaseHelper.java | 261 ++++++++++++++++++
.../android/data/VpnProfileSqlDataSource.java | 244 +---------------
2 files changed, 268 insertions(+), 237 deletions(-)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java
new file mode 100644
index 000000000..c0416d8ff
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2023 Relution GmbH
+ * Copyright (C) 2012-2019 Tobias Brunner
+ * Copyright (C) 2012 Giuliano Grassi
+ * Copyright (C) 2012 Ralf Sager
+ *
+ * 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.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+public class DatabaseHelper extends SQLiteOpenHelper
+{
+ private static final String TAG = DatabaseHelper.class.getSimpleName();
+
+ private static final String DATABASE_NAME = "strongswan.db";
+ static final String TABLE_VPNPROFILE = "vpnprofile";
+
+ private static final int DATABASE_VERSION = 17;
+
+ private static final DbColumn[] COLUMNS = new DbColumn[]{
+ new DbColumn(VpnProfileDataSource.KEY_ID, "INTEGER PRIMARY KEY AUTOINCREMENT", 1),
+ new DbColumn(VpnProfileDataSource.KEY_UUID, "TEXT UNIQUE", 9),
+ new DbColumn(VpnProfileDataSource.KEY_NAME, "TEXT NOT NULL", 1),
+ new DbColumn(VpnProfileDataSource.KEY_GATEWAY, "TEXT NOT NULL", 1),
+ new DbColumn(VpnProfileDataSource.KEY_VPN_TYPE, "TEXT NOT NULL", 3),
+ new DbColumn(VpnProfileDataSource.KEY_USERNAME, "TEXT", 1),
+ new DbColumn(VpnProfileDataSource.KEY_PASSWORD, "TEXT", 1),
+ new DbColumn(VpnProfileDataSource.KEY_CERTIFICATE, "TEXT", 1),
+ new DbColumn(VpnProfileDataSource.KEY_USER_CERTIFICATE, "TEXT", 2),
+ new DbColumn(VpnProfileDataSource.KEY_MTU, "INTEGER", 5),
+ new DbColumn(VpnProfileDataSource.KEY_PORT, "INTEGER", 6),
+ new DbColumn(VpnProfileDataSource.KEY_SPLIT_TUNNELING, "INTEGER", 7),
+ new DbColumn(VpnProfileDataSource.KEY_LOCAL_ID, "TEXT", 8),
+ new DbColumn(VpnProfileDataSource.KEY_REMOTE_ID, "TEXT", 8),
+ new DbColumn(VpnProfileDataSource.KEY_EXCLUDED_SUBNETS, "TEXT", 10),
+ new DbColumn(VpnProfileDataSource.KEY_INCLUDED_SUBNETS, "TEXT", 11),
+ new DbColumn(VpnProfileDataSource.KEY_SELECTED_APPS, "INTEGER", 12),
+ new DbColumn(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, "TEXT", 12),
+ new DbColumn(VpnProfileDataSource.KEY_NAT_KEEPALIVE, "INTEGER", 13),
+ new DbColumn(VpnProfileDataSource.KEY_FLAGS, "INTEGER", 14),
+ new DbColumn(VpnProfileDataSource.KEY_IKE_PROPOSAL, "TEXT", 15),
+ new DbColumn(VpnProfileDataSource.KEY_ESP_PROPOSAL, "TEXT", 15),
+ new DbColumn(VpnProfileDataSource.KEY_DNS_SERVERS, "TEXT", 17),
+ };
+
+ DatabaseHelper(Context context)
+ {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase database)
+ {
+ database.execSQL(getDatabaseCreate(DATABASE_VERSION));
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
+ {
+ Log.w(TAG, "Upgrading database from version " + oldVersion +
+ " to " + newVersion);
+ if (oldVersion < 2)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_USER_CERTIFICATE +
+ " TEXT;");
+ }
+ if (oldVersion < 3)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_VPN_TYPE +
+ " TEXT DEFAULT '';");
+ }
+ if (oldVersion < 4)
+ { /* remove NOT NULL constraint from username column */
+ updateColumns(db, 4);
+ }
+ if (oldVersion < 5)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_MTU +
+ " INTEGER;");
+ }
+ if (oldVersion < 6)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_PORT +
+ " INTEGER;");
+ }
+ if (oldVersion < 7)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_SPLIT_TUNNELING +
+ " INTEGER;");
+ }
+ if (oldVersion < 8)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_LOCAL_ID +
+ " TEXT;");
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_REMOTE_ID +
+ " TEXT;");
+ }
+ if (oldVersion < 9)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_UUID +
+ " TEXT;");
+ updateColumns(db, 9);
+ }
+ if (oldVersion < 10)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_EXCLUDED_SUBNETS +
+ " TEXT;");
+ }
+ if (oldVersion < 11)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_INCLUDED_SUBNETS +
+ " TEXT;");
+ }
+ if (oldVersion < 12)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_SELECTED_APPS +
+ " INTEGER;");
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_SELECTED_APPS_LIST +
+ " TEXT;");
+ }
+ if (oldVersion < 13)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_NAT_KEEPALIVE +
+ " INTEGER;");
+ }
+ if (oldVersion < 14)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_FLAGS +
+ " INTEGER;");
+ }
+ if (oldVersion < 15)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_IKE_PROPOSAL +
+ " TEXT;");
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_ESP_PROPOSAL +
+ " TEXT;");
+ }
+ if (oldVersion < 16)
+ { /* add a UUID to all entries that haven't one yet */
+ db.beginTransaction();
+ try
+ {
+ Cursor cursor = db.query(TABLE_VPNPROFILE, getColumns(16), VpnProfileDataSource.KEY_UUID + " is NULL", null, null, null, null);
+ for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext())
+ {
+ ContentValues values = new ContentValues();
+ values.put(VpnProfileDataSource.KEY_UUID, UUID.randomUUID().toString());
+ db.update(TABLE_VPNPROFILE, values, VpnProfileDataSource.KEY_ID + " = " + cursor.getLong(cursor.getColumnIndexOrThrow(VpnProfileDataSource.KEY_ID)), null);
+ }
+ cursor.close();
+ db.setTransactionSuccessful();
+ }
+ finally
+ {
+ db.endTransaction();
+ }
+ }
+ if (oldVersion < 17)
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_DNS_SERVERS +
+ " TEXT;");
+ }
+ }
+
+ public String[] getAllColumns()
+ {
+ return getColumns(DATABASE_VERSION);
+ }
+
+ private void updateColumns(SQLiteDatabase db, int version)
+ {
+ db.beginTransaction();
+ try
+ {
+ db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " RENAME TO tmp_" + TABLE_VPNPROFILE + ";");
+ db.execSQL(getDatabaseCreate(version));
+ StringBuilder insert = new StringBuilder("INSERT INTO " + TABLE_VPNPROFILE + " SELECT ");
+ SQLiteQueryBuilder.appendColumns(insert, getColumns(version));
+ db.execSQL(insert.append(" FROM tmp_" + TABLE_VPNPROFILE + ";").toString());
+ db.execSQL("DROP TABLE tmp_" + TABLE_VPNPROFILE + ";");
+ db.setTransactionSuccessful();
+ }
+ finally
+ {
+ db.endTransaction();
+ }
+ }
+
+ private String getDatabaseCreate(int version)
+ {
+ boolean first = true;
+ StringBuilder create = new StringBuilder("CREATE TABLE ");
+ create.append(TABLE_VPNPROFILE);
+ create.append(" (");
+ for (DbColumn column : COLUMNS)
+ {
+ if (column.Since <= version)
+ {
+ if (!first)
+ {
+ create.append(",");
+ }
+ first = false;
+ create.append(column.Name);
+ create.append(" ");
+ create.append(column.Type);
+ }
+ }
+ create.append(");");
+ return create.toString();
+ }
+
+ private String[] getColumns(int version)
+ {
+ ArrayList columns = new ArrayList<>();
+ for (DbColumn column : COLUMNS)
+ {
+ if (column.Since <= version)
+ {
+ columns.add(column.Name);
+ }
+ }
+ return columns.toArray(new String[0]);
+ }
+
+ private static class DbColumn
+ {
+ public final String Name;
+ public final String Type;
+ public final Integer Since;
+
+ public DbColumn(String name, String type, Integer since)
+ {
+ Name = name;
+ Type = type;
+ Since = since;
+ }
+ }
+}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
index 5b1f9ad26..008e7dd55 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
@@ -23,9 +23,6 @@ import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.util.Log;
import java.util.ArrayList;
import java.util.List;
@@ -33,223 +30,10 @@ import java.util.UUID;
public class VpnProfileSqlDataSource implements VpnProfileDataSource
{
- private static final String TAG = VpnProfileSqlDataSource.class.getSimpleName();
-
- private static final DbColumn[] COLUMNS = new VpnProfileSqlDataSource.DbColumn[]{
- new VpnProfileSqlDataSource.DbColumn(KEY_ID, "INTEGER PRIMARY KEY AUTOINCREMENT", 1),
- new VpnProfileSqlDataSource.DbColumn(KEY_UUID, "TEXT UNIQUE", 9),
- new VpnProfileSqlDataSource.DbColumn(KEY_NAME, "TEXT NOT NULL", 1),
- new VpnProfileSqlDataSource.DbColumn(KEY_GATEWAY, "TEXT NOT NULL", 1),
- new VpnProfileSqlDataSource.DbColumn(KEY_VPN_TYPE, "TEXT NOT NULL", 3),
- new VpnProfileSqlDataSource.DbColumn(KEY_USERNAME, "TEXT", 1),
- new VpnProfileSqlDataSource.DbColumn(KEY_PASSWORD, "TEXT", 1),
- new VpnProfileSqlDataSource.DbColumn(KEY_CERTIFICATE, "TEXT", 1),
- new VpnProfileSqlDataSource.DbColumn(KEY_USER_CERTIFICATE, "TEXT", 2),
- new VpnProfileSqlDataSource.DbColumn(KEY_MTU, "INTEGER", 5),
- new VpnProfileSqlDataSource.DbColumn(KEY_PORT, "INTEGER", 6),
- new VpnProfileSqlDataSource.DbColumn(KEY_SPLIT_TUNNELING, "INTEGER", 7),
- new VpnProfileSqlDataSource.DbColumn(KEY_LOCAL_ID, "TEXT", 8),
- new VpnProfileSqlDataSource.DbColumn(KEY_REMOTE_ID, "TEXT", 8),
- new VpnProfileSqlDataSource.DbColumn(KEY_EXCLUDED_SUBNETS, "TEXT", 10),
- new VpnProfileSqlDataSource.DbColumn(KEY_INCLUDED_SUBNETS, "TEXT", 11),
- new VpnProfileSqlDataSource.DbColumn(KEY_SELECTED_APPS, "INTEGER", 12),
- new VpnProfileSqlDataSource.DbColumn(KEY_SELECTED_APPS_LIST, "TEXT", 12),
- new VpnProfileSqlDataSource.DbColumn(KEY_NAT_KEEPALIVE, "INTEGER", 13),
- new VpnProfileSqlDataSource.DbColumn(KEY_FLAGS, "INTEGER", 14),
- new VpnProfileSqlDataSource.DbColumn(KEY_IKE_PROPOSAL, "TEXT", 15),
- new VpnProfileSqlDataSource.DbColumn(KEY_ESP_PROPOSAL, "TEXT", 15),
- new VpnProfileSqlDataSource.DbColumn(KEY_DNS_SERVERS, "TEXT", 17),
- };
-
private DatabaseHelper mDbHelper;
private SQLiteDatabase mDatabase;
private final Context mContext;
- private static final String DATABASE_NAME = "strongswan.db";
- private static final String TABLE_VPNPROFILE = "vpnprofile";
-
- private static final int DATABASE_VERSION = 17;
-
- private static final String[] ALL_COLUMNS = getColumns(DATABASE_VERSION);
-
- private static String getDatabaseCreate(int version)
- {
- boolean first = true;
- StringBuilder create = new StringBuilder("CREATE TABLE ");
- create.append(TABLE_VPNPROFILE);
- create.append(" (");
- for (DbColumn column : COLUMNS)
- {
- if (column.Since <= version)
- {
- if (!first)
- {
- create.append(",");
- }
- first = false;
- create.append(column.Name);
- create.append(" ");
- create.append(column.Type);
- }
- }
- create.append(");");
- return create.toString();
- }
-
- private static String[] getColumns(int version)
- {
- ArrayList columns = new ArrayList<>();
- for (DbColumn column : COLUMNS)
- {
- if (column.Since <= version)
- {
- columns.add(column.Name);
- }
- }
- return columns.toArray(new String[0]);
- }
-
- private static class DatabaseHelper extends SQLiteOpenHelper
- {
- public DatabaseHelper(Context context)
- {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- }
-
- @Override
- public void onCreate(SQLiteDatabase database)
- {
- database.execSQL(getDatabaseCreate(DATABASE_VERSION));
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
- {
- Log.w(TAG, "Upgrading database from version " + oldVersion +
- " to " + newVersion);
- if (oldVersion < 2)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_USER_CERTIFICATE +
- " TEXT;");
- }
- if (oldVersion < 3)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_VPN_TYPE +
- " TEXT DEFAULT '';");
- }
- if (oldVersion < 4)
- { /* remove NOT NULL constraint from username column */
- updateColumns(db, 4);
- }
- if (oldVersion < 5)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_MTU +
- " INTEGER;");
- }
- if (oldVersion < 6)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_PORT +
- " INTEGER;");
- }
- if (oldVersion < 7)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SPLIT_TUNNELING +
- " INTEGER;");
- }
- if (oldVersion < 8)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_LOCAL_ID +
- " TEXT;");
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_REMOTE_ID +
- " TEXT;");
- }
- if (oldVersion < 9)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_UUID +
- " TEXT;");
- updateColumns(db, 9);
- }
- if (oldVersion < 10)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_EXCLUDED_SUBNETS +
- " TEXT;");
- }
- if (oldVersion < 11)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_INCLUDED_SUBNETS +
- " TEXT;");
- }
- if (oldVersion < 12)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SELECTED_APPS +
- " INTEGER;");
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SELECTED_APPS_LIST +
- " TEXT;");
- }
- if (oldVersion < 13)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_NAT_KEEPALIVE +
- " INTEGER;");
- }
- if (oldVersion < 14)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_FLAGS +
- " INTEGER;");
- }
- if (oldVersion < 15)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_IKE_PROPOSAL +
- " TEXT;");
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_ESP_PROPOSAL +
- " TEXT;");
- }
- if (oldVersion < 16)
- { /* add a UUID to all entries that haven't one yet */
- db.beginTransaction();
- try
- {
- Cursor cursor = db.query(TABLE_VPNPROFILE, getColumns(16), KEY_UUID + " is NULL", null, null, null, null);
- for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext())
- {
- ContentValues values = new ContentValues();
- values.put(KEY_UUID, UUID.randomUUID().toString());
- db.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + cursor.getLong(cursor.getColumnIndexOrThrow(KEY_ID)), null);
- }
- cursor.close();
- db.setTransactionSuccessful();
- }
- finally
- {
- db.endTransaction();
- }
- }
- if (oldVersion < 17)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_DNS_SERVERS +
- " TEXT;");
- }
- }
-
- private void updateColumns(SQLiteDatabase db, int version)
- {
- db.beginTransaction();
- try
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " RENAME TO tmp_" + TABLE_VPNPROFILE + ";");
- db.execSQL(getDatabaseCreate(version));
- StringBuilder insert = new StringBuilder("INSERT INTO " + TABLE_VPNPROFILE + " SELECT ");
- SQLiteQueryBuilder.appendColumns(insert, getColumns(version));
- db.execSQL(insert.append(" FROM tmp_" + TABLE_VPNPROFILE + ";").toString());
- db.execSQL("DROP TABLE tmp_" + TABLE_VPNPROFILE + ";");
- db.setTransactionSuccessful();
- }
- finally
- {
- db.endTransaction();
- }
- }
- }
-
/**
* Construct a new VPN profile data source. The context is used to
* open/create the database.
@@ -286,7 +70,7 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource
public VpnProfile insertProfile(VpnProfile profile)
{
ContentValues values = ContentValuesFromVpnProfile(profile);
- long insertId = mDatabase.insert(TABLE_VPNPROFILE, null, values);
+ long insertId = mDatabase.insert(DatabaseHelper.TABLE_VPNPROFILE, null, values);
if (insertId == -1)
{
return null;
@@ -301,22 +85,22 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource
{
final UUID uuid = profile.getUUID();
ContentValues values = ContentValuesFromVpnProfile(profile);
- return mDatabase.update(TABLE_VPNPROFILE, values, KEY_UUID + " = ?", new String[]{uuid.toString()}) > 0;
+ return mDatabase.update(DatabaseHelper.TABLE_VPNPROFILE, values, KEY_UUID + " = ?", new String[]{uuid.toString()}) > 0;
}
@Override
public boolean deleteVpnProfile(VpnProfile profile)
{
final UUID uuid = profile.getUUID();
- return mDatabase.delete(TABLE_VPNPROFILE, KEY_UUID + " = ?", new String[]{uuid.toString()}) > 0;
+ return mDatabase.delete(DatabaseHelper.TABLE_VPNPROFILE, KEY_UUID + " = ?", new String[]{uuid.toString()}) > 0;
}
@Override
public VpnProfile getVpnProfile(UUID uuid)
{
VpnProfile profile = null;
- Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS,
- KEY_UUID + "='" + uuid.toString() + "'", null, null, null, null);
+ Cursor cursor = mDatabase.query(DatabaseHelper.TABLE_VPNPROFILE, mDbHelper.getAllColumns(),
+ KEY_UUID + " = ?", new String[]{uuid.toString()}, null, null, null);
if (cursor.moveToFirst())
{
profile = VpnProfileFromCursor(cursor);
@@ -329,9 +113,9 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource
@Override
public List getAllVpnProfiles()
{
- List vpnProfiles = new ArrayList();
+ List vpnProfiles = new ArrayList<>();
- Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, null, null, null, null, null);
+ Cursor cursor = mDatabase.query(DatabaseHelper.TABLE_VPNPROFILE, mDbHelper.getAllColumns(), null, null, null, null, null);
cursor.moveToFirst();
while (!cursor.isAfterLast())
{
@@ -404,18 +188,4 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource
{
return cursor.isNull(columnIndex) ? null : cursor.getInt(columnIndex);
}
-
- private static class DbColumn
- {
- public final String Name;
- public final String Type;
- public final Integer Since;
-
- public DbColumn(String name, String type, Integer since)
- {
- Name = name;
- Type = type;
- Since = since;
- }
- }
}
From 861ac0109a9495c92de32d04a654c60f04253297 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:23 +0100
Subject: [PATCH 26/45] android: Extend database helper with table definition
This simplifies database migration.
---
.../android/data/DatabaseHelper.java | 249 +++++++++---------
.../android/data/VpnProfileSqlDataSource.java | 13 +-
2 files changed, 131 insertions(+), 131 deletions(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java
index c0416d8ff..95efd7cbb 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2023 Relution GmbH
- * Copyright (C) 2012-2019 Tobias Brunner
+ * Copyright (C) 2012-2024 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
*
@@ -28,6 +28,9 @@ import android.database.sqlite.SQLiteQueryBuilder;
import android.util.Log;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
import java.util.UUID;
public class DatabaseHelper extends SQLiteOpenHelper
@@ -35,16 +38,15 @@ public class DatabaseHelper extends SQLiteOpenHelper
private static final String TAG = DatabaseHelper.class.getSimpleName();
private static final String DATABASE_NAME = "strongswan.db";
- static final String TABLE_VPNPROFILE = "vpnprofile";
- private static final int DATABASE_VERSION = 17;
+ private static final String TABLE_NAME_VPN_PROFILE = "vpnprofile";
- private static final DbColumn[] COLUMNS = new DbColumn[]{
+ static final DbTable TABLE_VPN_PROFILE = new DbTable(TABLE_NAME_VPN_PROFILE, 1, new DbColumn[]{
new DbColumn(VpnProfileDataSource.KEY_ID, "INTEGER PRIMARY KEY AUTOINCREMENT", 1),
new DbColumn(VpnProfileDataSource.KEY_UUID, "TEXT UNIQUE", 9),
new DbColumn(VpnProfileDataSource.KEY_NAME, "TEXT NOT NULL", 1),
new DbColumn(VpnProfileDataSource.KEY_GATEWAY, "TEXT NOT NULL", 1),
- new DbColumn(VpnProfileDataSource.KEY_VPN_TYPE, "TEXT NOT NULL", 3),
+ new DbColumn(VpnProfileDataSource.KEY_VPN_TYPE, "TEXT NOT NULL DEFAULT ''", 3),
new DbColumn(VpnProfileDataSource.KEY_USERNAME, "TEXT", 1),
new DbColumn(VpnProfileDataSource.KEY_PASSWORD, "TEXT", 1),
new DbColumn(VpnProfileDataSource.KEY_CERTIFICATE, "TEXT", 1),
@@ -63,7 +65,17 @@ public class DatabaseHelper extends SQLiteOpenHelper
new DbColumn(VpnProfileDataSource.KEY_IKE_PROPOSAL, "TEXT", 15),
new DbColumn(VpnProfileDataSource.KEY_ESP_PROPOSAL, "TEXT", 15),
new DbColumn(VpnProfileDataSource.KEY_DNS_SERVERS, "TEXT", 17),
- };
+ });
+
+ private static final int DATABASE_VERSION = 17;
+
+ private static final Set TABLES;
+
+ static
+ {
+ TABLES = new HashSet<>();
+ TABLES.add(TABLE_VPN_PROFILE);
+ }
DatabaseHelper(Context context)
{
@@ -73,101 +85,31 @@ public class DatabaseHelper extends SQLiteOpenHelper
@Override
public void onCreate(SQLiteDatabase database)
{
- database.execSQL(getDatabaseCreate(DATABASE_VERSION));
+ addNewTables(database, 0);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
- Log.w(TAG, "Upgrading database from version " + oldVersion +
- " to " + newVersion);
- if (oldVersion < 2)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_USER_CERTIFICATE +
- " TEXT;");
- }
- if (oldVersion < 3)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_VPN_TYPE +
- " TEXT DEFAULT '';");
- }
- if (oldVersion < 4)
- { /* remove NOT NULL constraint from username column */
- updateColumns(db, 4);
- }
- if (oldVersion < 5)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_MTU +
- " INTEGER;");
- }
- if (oldVersion < 6)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_PORT +
- " INTEGER;");
- }
- if (oldVersion < 7)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_SPLIT_TUNNELING +
- " INTEGER;");
- }
- if (oldVersion < 8)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_LOCAL_ID +
- " TEXT;");
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_REMOTE_ID +
- " TEXT;");
- }
+ Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion);
+ addNewTables(db, oldVersion);
+ addNewColumns(db, oldVersion);
+
if (oldVersion < 9)
{
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_UUID +
- " TEXT;");
- updateColumns(db, 9);
- }
- if (oldVersion < 10)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_EXCLUDED_SUBNETS +
- " TEXT;");
- }
- if (oldVersion < 11)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_INCLUDED_SUBNETS +
- " TEXT;");
- }
- if (oldVersion < 12)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_SELECTED_APPS +
- " INTEGER;");
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_SELECTED_APPS_LIST +
- " TEXT;");
- }
- if (oldVersion < 13)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_NAT_KEEPALIVE +
- " INTEGER;");
- }
- if (oldVersion < 14)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_FLAGS +
- " INTEGER;");
- }
- if (oldVersion < 15)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_IKE_PROPOSAL +
- " TEXT;");
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_ESP_PROPOSAL +
- " TEXT;");
+ updateColumns(db, TABLE_VPN_PROFILE);
}
if (oldVersion < 16)
{ /* add a UUID to all entries that haven't one yet */
db.beginTransaction();
try
{
- Cursor cursor = db.query(TABLE_VPNPROFILE, getColumns(16), VpnProfileDataSource.KEY_UUID + " is NULL", null, null, null, null);
+ Cursor cursor = db.query(TABLE_VPN_PROFILE.Name, TABLE_VPN_PROFILE.columnNames(), VpnProfileDataSource.KEY_UUID + " is NULL", null, null, null, null);
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext())
{
ContentValues values = new ContentValues();
values.put(VpnProfileDataSource.KEY_UUID, UUID.randomUUID().toString());
- db.update(TABLE_VPNPROFILE, values, VpnProfileDataSource.KEY_ID + " = " + cursor.getLong(cursor.getColumnIndexOrThrow(VpnProfileDataSource.KEY_ID)), null);
+ db.update(TABLE_VPN_PROFILE.Name, values, VpnProfileDataSource.KEY_ID + " = " + cursor.getLong(cursor.getColumnIndexOrThrow(VpnProfileDataSource.KEY_ID)), null);
}
cursor.close();
db.setTransactionSuccessful();
@@ -177,29 +119,19 @@ public class DatabaseHelper extends SQLiteOpenHelper
db.endTransaction();
}
}
- if (oldVersion < 17)
- {
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + VpnProfileDataSource.KEY_DNS_SERVERS +
- " TEXT;");
- }
}
- public String[] getAllColumns()
- {
- return getColumns(DATABASE_VERSION);
- }
-
- private void updateColumns(SQLiteDatabase db, int version)
+ private void updateColumns(SQLiteDatabase db, DbTable table)
{
db.beginTransaction();
try
{
- db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " RENAME TO tmp_" + TABLE_VPNPROFILE + ";");
- db.execSQL(getDatabaseCreate(version));
- StringBuilder insert = new StringBuilder("INSERT INTO " + TABLE_VPNPROFILE + " SELECT ");
- SQLiteQueryBuilder.appendColumns(insert, getColumns(version));
- db.execSQL(insert.append(" FROM tmp_" + TABLE_VPNPROFILE + ";").toString());
- db.execSQL("DROP TABLE tmp_" + TABLE_VPNPROFILE + ";");
+ db.execSQL("ALTER TABLE " + table.Name + " RENAME TO tmp_" + table.Name + ";");
+ db.execSQL(getTableCreate(table));
+ StringBuilder insert = new StringBuilder("INSERT INTO " + table.Name + " SELECT ");
+ SQLiteQueryBuilder.appendColumns(insert, table.columnNames());
+ db.execSQL(insert.append(" FROM tmp_" + table.Name + ";").toString());
+ db.execSQL("DROP TABLE tmp_" + table.Name + ";");
db.setTransactionSuccessful();
}
finally
@@ -208,50 +140,117 @@ public class DatabaseHelper extends SQLiteOpenHelper
}
}
- private String getDatabaseCreate(int version)
+ private static String getTableCreate(DbTable table)
{
boolean first = true;
- StringBuilder create = new StringBuilder("CREATE TABLE ");
- create.append(TABLE_VPNPROFILE);
+ StringBuilder create = new StringBuilder("CREATE TABLE IF NOT EXISTS ");
+ create.append(table.Name);
create.append(" (");
- for (DbColumn column : COLUMNS)
+
+ for (final DbColumn column : table.getColumns())
{
- if (column.Since <= version)
+ if (!first)
{
- if (!first)
- {
- create.append(",");
- }
- first = false;
- create.append(column.Name);
- create.append(" ");
- create.append(column.Type);
+ create.append(",");
}
+ first = false;
+ create.append(column.Name);
+ create.append(" ");
+ create.append(column.Type);
}
create.append(");");
return create.toString();
}
- private String[] getColumns(int version)
+ private void addNewTables(final SQLiteDatabase database, final int oldVersion)
{
- ArrayList columns = new ArrayList<>();
- for (DbColumn column : COLUMNS)
+ for (final String sql : getTableCreates(oldVersion))
{
- if (column.Since <= version)
- {
- columns.add(column.Name);
- }
+ database.execSQL(sql);
}
- return columns.toArray(new String[0]);
}
- private static class DbColumn
+ private List getTableCreates(final int oldVersion)
+ {
+ List statements = new ArrayList<>(TABLES.size());
+ for (final DbTable table : TABLES)
+ {
+ if (table.Since > oldVersion)
+ {
+ statements.add(getTableCreate(table));
+ }
+ }
+ return statements;
+ }
+
+ private void addNewColumns(final SQLiteDatabase database, final int oldVersion)
+ {
+ for (final String sql : getAlterTables(oldVersion))
+ {
+ database.execSQL(sql);
+ }
+ }
+
+ private List getAlterTables(final int oldVersion)
+ {
+ List statements = new ArrayList<>(TABLES.size());
+ for (final DbTable table : TABLES)
+ {
+ statements.addAll(getAlterTables(table, oldVersion));
+ }
+ return statements;
+ }
+
+ private static List getAlterTables(DbTable table, final int oldVersion)
+ {
+ final List sql = new ArrayList<>();
+
+ for (final DbColumn column : table.getColumns())
+ {
+ if (column.Since > table.Since && column.Since > oldVersion)
+ {
+ sql.add("ALTER TABLE " + table.Name + " ADD " + column.Name + " " + column.Type + ";");
+ }
+ }
+ return sql;
+ }
+
+ public static class DbTable
+ {
+ public final String Name;
+ public final int Since;
+ public final DbColumn[] Columns;
+
+ private DbTable(final String name, final int since, final DbColumn[] columns)
+ {
+ Name = name;
+ Since = since;
+ Columns = columns;
+ }
+
+ private DbColumn[] getColumns()
+ {
+ return Columns;
+ }
+
+ public String[] columnNames()
+ {
+ final List columnNames = new ArrayList<>(Columns.length);
+ for (DbColumn column : Columns)
+ {
+ columnNames.add(column.Name);
+ }
+ return columnNames.toArray(new String[0]);
+ }
+ }
+
+ public static class DbColumn
{
public final String Name;
public final String Type;
- public final Integer Since;
+ public final int Since;
- public DbColumn(String name, String type, Integer since)
+ private DbColumn(String name, String type, int since)
{
Name = name;
Type = type;
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
index 008e7dd55..e14fccae6 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
@@ -70,7 +70,7 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource
public VpnProfile insertProfile(VpnProfile profile)
{
ContentValues values = ContentValuesFromVpnProfile(profile);
- long insertId = mDatabase.insert(DatabaseHelper.TABLE_VPNPROFILE, null, values);
+ long insertId = mDatabase.insert(DatabaseHelper.TABLE_VPN_PROFILE.Name, null, values);
if (insertId == -1)
{
return null;
@@ -85,22 +85,22 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource
{
final UUID uuid = profile.getUUID();
ContentValues values = ContentValuesFromVpnProfile(profile);
- return mDatabase.update(DatabaseHelper.TABLE_VPNPROFILE, values, KEY_UUID + " = ?", new String[]{uuid.toString()}) > 0;
+ return mDatabase.update(DatabaseHelper.TABLE_VPN_PROFILE.Name, values, KEY_UUID + " = ?", new String[]{uuid.toString()}) > 0;
}
@Override
public boolean deleteVpnProfile(VpnProfile profile)
{
final UUID uuid = profile.getUUID();
- return mDatabase.delete(DatabaseHelper.TABLE_VPNPROFILE, KEY_UUID + " = ?", new String[]{uuid.toString()}) > 0;
+ return mDatabase.delete(DatabaseHelper.TABLE_VPN_PROFILE.Name, KEY_UUID + " = ?", new String[]{uuid.toString()}) > 0;
}
@Override
public VpnProfile getVpnProfile(UUID uuid)
{
VpnProfile profile = null;
- Cursor cursor = mDatabase.query(DatabaseHelper.TABLE_VPNPROFILE, mDbHelper.getAllColumns(),
- KEY_UUID + " = ?", new String[]{uuid.toString()}, null, null, null);
+ DatabaseHelper.DbTable table = DatabaseHelper.TABLE_VPN_PROFILE;
+ Cursor cursor = mDatabase.query(table.Name, table.columnNames(), KEY_UUID + " = ?", new String[]{uuid.toString()}, null, null, null);
if (cursor.moveToFirst())
{
profile = VpnProfileFromCursor(cursor);
@@ -115,7 +115,8 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource
{
List vpnProfiles = new ArrayList<>();
- Cursor cursor = mDatabase.query(DatabaseHelper.TABLE_VPNPROFILE, mDbHelper.getAllColumns(), null, null, null, null, null);
+ DatabaseHelper.DbTable table = DatabaseHelper.TABLE_VPN_PROFILE;
+ Cursor cursor = mDatabase.query(table.Name, table.columnNames(), null, null, null, null, null);
cursor.moveToFirst();
while (!cursor.isAfterLast())
{
From 9a917252e2c1b52d5bdd2586b05282e39059103d Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:23 +0100
Subject: [PATCH 27/45] android: Provide global database helper instance
---
.../android/data/DatabaseHelper.java | 2 +-
.../android/data/VpnProfileSource.java | 2 +-
.../android/data/VpnProfileSqlDataSource.java | 21 ++++++++-----------
.../android/logic/StrongSwanApplication.java | 13 ++++++++++++
4 files changed, 24 insertions(+), 14 deletions(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java
index 95efd7cbb..b978603d1 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java
@@ -77,7 +77,7 @@ public class DatabaseHelper extends SQLiteOpenHelper
TABLES.add(TABLE_VPN_PROFILE);
}
- DatabaseHelper(Context context)
+ public DatabaseHelper(Context context)
{
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
index 77cfa8551..299d0a51a 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSource.java
@@ -35,7 +35,7 @@ public class VpnProfileSource implements VpnProfileDataSource
{
mManagedConfigurationService = StrongSwanApplication.getInstance().getManagedConfigurationService();
- vpnProfileSqlDataSource = new VpnProfileSqlDataSource(context);
+ vpnProfileSqlDataSource = new VpnProfileSqlDataSource();
dataSources.add(vpnProfileSqlDataSource);
dataSources.add(new VpnProfileManagedDataSource(context));
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
index e14fccae6..50ffc6297 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileSqlDataSource.java
@@ -19,38 +19,36 @@
package org.strongswan.android.data;
import android.content.ContentValues;
-import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
+import org.strongswan.android.logic.StrongSwanApplication;
+
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class VpnProfileSqlDataSource implements VpnProfileDataSource
{
- private DatabaseHelper mDbHelper;
+ private final DatabaseHelper mDbHelper;
+
private SQLiteDatabase mDatabase;
- private final Context mContext;
/**
* Construct a new VPN profile data source. The context is used to
* open/create the database.
- *
- * @param context context used to access the database
*/
- public VpnProfileSqlDataSource(Context context)
+ public VpnProfileSqlDataSource()
{
- this.mContext = context;
+ mDbHelper = StrongSwanApplication.getInstance().getDatabaseHelper();
}
@Override
public VpnProfileDataSource open() throws SQLException
{
- if (mDbHelper == null)
+ if (mDatabase == null)
{
- mDbHelper = new DatabaseHelper(mContext);
mDatabase = mDbHelper.getWritableDatabase();
}
return this;
@@ -59,10 +57,9 @@ public class VpnProfileSqlDataSource implements VpnProfileDataSource
@Override
public void close()
{
- if (mDbHelper != null)
+ if (mDatabase != null)
{
- mDbHelper.close();
- mDbHelper = null;
+ mDatabase = null;
}
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
index 8d653624e..58b55e033 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
@@ -26,6 +26,7 @@ import android.os.Handler;
import android.os.Looper;
import android.util.Log;
+import org.strongswan.android.data.DatabaseHelper;
import org.strongswan.android.data.ManagedConfigurationService;
import org.strongswan.android.security.LocalCertificateKeyStoreProvider;
import org.strongswan.android.utils.Constants;
@@ -56,6 +57,8 @@ public class StrongSwanApplication extends Application implements DefaultLifecyc
private ManagedConfigurationService mManagedConfigurationService;
+ private DatabaseHelper mDatabaseHelper;
+
private final BroadcastReceiver mRestrictionsReceiver = new BroadcastReceiver()
{
@Override
@@ -78,6 +81,8 @@ public class StrongSwanApplication extends Application implements DefaultLifecyc
StrongSwanApplication.mContext = getApplicationContext();
StrongSwanApplication.mInstance = this;
+ mDatabaseHelper = new DatabaseHelper(mContext);
+
mManagedConfigurationService = new ManagedConfigurationService(mContext);
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
}
@@ -162,6 +167,14 @@ public class StrongSwanApplication extends Application implements DefaultLifecyc
return mManagedConfigurationService;
}
+ /**
+ * @return the application's database helper used to access its SQLite database
+ */
+ public DatabaseHelper getDatabaseHelper()
+ {
+ return mDatabaseHelper;
+ }
+
/*
* The libraries are extracted to /data/data/org.strongswan.android/...
* during installation. On newer releases most are loaded in JNI_OnLoad.
From 8a50651212f4682d9725f515ffd61d47132e2dbf Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:23 +0100
Subject: [PATCH 28/45] android: Add password for client certificate to managed
config
---
.../org/strongswan/android/data/VpnProfileDataSource.java | 1 +
.../src/main/res/values/strings_managed_configuration.xml | 2 ++
.../android/app/src/main/res/xml/managed_configuration.xml | 7 +++++++
3 files changed, 10 insertions(+)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
index 48aa58c35..f95ce4734 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java
@@ -35,6 +35,7 @@ public interface VpnProfileDataSource
String KEY_PASSWORD = "password";
String KEY_CERTIFICATE = "certificate";
String KEY_USER_CERTIFICATE = "user_certificate";
+ String KEY_USER_CERTIFICATE_PASSWORD = "user_certificate_password";
String KEY_MTU = "mtu";
String KEY_PORT = "port";
String KEY_SPLIT_TUNNELING = "split_tunneling";
diff --git a/src/frontends/android/app/src/main/res/values/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values/strings_managed_configuration.xml
index 573a50706..6a58f62ad 100644
--- a/src/frontends/android/app/src/main/res/values/strings_managed_configuration.xml
+++ b/src/frontends/android/app/src/main/res/values/strings_managed_configuration.xml
@@ -89,6 +89,8 @@
@string/profile_local_id_hint_user@string/profile_user_certificate_labelBase64-encoded PKCS#12-container with the client certificate and private key and optional certificate chain (the latter might cause warnings on older Android releases, see Android VPN client configuration for details). Not necessary for username/password-based EAP authentication or if the user already has the certificate/key installed as it may be selected while importing the profile
+ User certificate password (Optional)
+ Password required to extract the private key of the PKCS#12-container for installation@string/profile_rsa_pss_label@string/profile_rsa_pss_hint
diff --git a/src/frontends/android/app/src/main/res/xml/managed_configuration.xml b/src/frontends/android/app/src/main/res/xml/managed_configuration.xml
index 425758b20..105b3f839 100644
--- a/src/frontends/android/app/src/main/res/xml/managed_configuration.xml
+++ b/src/frontends/android/app/src/main/res/xml/managed_configuration.xml
@@ -190,6 +190,13 @@
android:restrictionType="string"
android:title="@string/managed_config_local_p12_title" />
+
+
Date: Tue, 21 Nov 2023 15:37:23 +0100
Subject: [PATCH 29/45] android: Add utility class that pairs a certificate
with a private key
---
.../org/strongswan/android/utils/KeyPair.java | 76 +++++++++++++++++++
1 file changed, 76 insertions(+)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/utils/KeyPair.java
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/KeyPair.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/KeyPair.java
new file mode 100644
index 000000000..52d4d1dcd
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/KeyPair.java
@@ -0,0 +1,76 @@
+/*
+ * 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.utils;
+
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.util.Objects;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Represents a key pair, which consists of a certificate (i.e. public key) and its corresponding
+ * private key.
+ */
+public class KeyPair
+{
+ @NonNull
+ public final Certificate certificate;
+ @NonNull
+ public final PrivateKey privateKey;
+
+ /**
+ * Constructor for a {@link KeyPair}.
+ *
+ * @param certificate the certificate of the key pair.
+ * @param privateKey the private key of the key pair.
+ */
+ public KeyPair(@NonNull Certificate certificate, @NonNull PrivateKey privateKey)
+ {
+ this.certificate = certificate;
+ this.privateKey = privateKey;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass())
+ {
+ return false;
+ }
+ final KeyPair that = (KeyPair)o;
+ return Objects.equals(certificate, that.certificate) &&
+ Objects.equals(privateKey, that.privateKey);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(certificate, privateKey);
+ }
+
+ @NonNull
+ @Override
+ public String toString()
+ {
+ return "KeyPair{" + certificate + ", " + privateKey + "}";
+ }
+}
From 4ac9fc327e1261951f20dc08a9e6a12379e4208b Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:23 +0100
Subject: [PATCH 30/45] android: Add utility that converts a Base64 string to a
X509Certificate
---
.../android/utils/Certificates.java | 42 +++++++++++++++++++
1 file changed, 42 insertions(+)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/utils/Certificates.java
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Certificates.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Certificates.java
new file mode 100644
index 000000000..acadfd56f
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Certificates.java
@@ -0,0 +1,42 @@
+/*
+ * 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.utils;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import androidx.annotation.NonNull;
+
+public class Certificates
+{
+ @NonNull
+ public static X509Certificate from(@NonNull final String certificateData) throws IOException, CertificateException
+ {
+ final byte[] bytes = android.util.Base64.decode(certificateData, android.util.Base64.DEFAULT);
+
+ try (final ByteArrayInputStream stream = new ByteArrayInputStream(bytes))
+ {
+ final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+ return (X509Certificate)certificateFactory.generateCertificate(stream);
+ }
+ }
+
+ private Certificates() {}
+}
From 22bce57e4c4b1f3d5d9b294f160c0cc78c5566f3 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:23 +0100
Subject: [PATCH 31/45] android: Add utility that parses a PKCS#12 container
and extracts a KeyPair
---
.../strongswan/android/utils/KeyPairs.java | 101 ++++++++++++++++++
1 file changed, 101 insertions(+)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/utils/KeyPairs.java
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/KeyPairs.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/KeyPairs.java
new file mode 100644
index 000000000..43c896683
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/KeyPairs.java
@@ -0,0 +1,101 @@
+/*
+ * 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.utils;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class KeyPairs
+{
+ private static final String KEYSTORE_INSTANCE = "PKCS12";
+
+ @NonNull
+ private static KeyStore toKeyStore(@NonNull byte[] bytes, @NonNull char[] password)
+ throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException
+ {
+ try (final ByteArrayInputStream stream = new ByteArrayInputStream(bytes))
+ {
+ final KeyStore keyStore = KeyStore.getInstance(KEYSTORE_INSTANCE);
+ keyStore.load(stream, password);
+ return keyStore;
+ }
+ }
+
+ @Nullable
+ private static KeyPair getKeyPair(
+ @NonNull final KeyStore keyStore,
+ @NonNull final String alias,
+ @NonNull final char[] passwordChars)
+ throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException
+ {
+ final Certificate certificate = keyStore.getCertificate(alias);
+ if (!(certificate instanceof X509Certificate))
+ {
+ return null;
+ }
+
+ final Key key = keyStore.getKey(alias, passwordChars);
+ if (key == null)
+ {
+ return null;
+ }
+ return new KeyPair(certificate, (PrivateKey)key);
+ }
+
+ @Nullable
+ private static KeyPair getKeyPair(@NonNull KeyStore keyStore, @NonNull char[] passwordChars)
+ throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException
+ {
+ final Enumeration aliases = keyStore.aliases();
+ while (aliases.hasMoreElements())
+ {
+ final String alias = aliases.nextElement();
+ final KeyPair keyPair = getKeyPair(keyStore, alias, passwordChars);
+ if (keyPair != null)
+ {
+ return keyPair;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ public static KeyPair from(@NonNull final String userCertificate, @NonNull final String password)
+ throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException
+ {
+ final byte[] bytes = android.util.Base64.decode(userCertificate, android.util.Base64.DEFAULT);
+ final char[] passwordChars = password.toCharArray();
+
+ final KeyStore keyStore = toKeyStore(bytes, passwordChars);
+ return getKeyPair(keyStore, passwordChars);
+ }
+
+ private KeyPairs() {}
+}
From 9cbc03e84f80828068e6f2986eb8fc0a2662547f Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:23 +0100
Subject: [PATCH 32/45] android: Add entities for CA/server and user
certificates
---
.../android/data/ManagedCertificate.java | 97 +++++++++++++++++++
.../data/ManagedTrustedCertificate.java | 89 +++++++++++++++++
.../android/data/ManagedUserCertificate.java | 92 ++++++++++++++++++
3 files changed, 278 insertions(+)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedCertificate.java
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedTrustedCertificate.java
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedUserCertificate.java
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedCertificate.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedCertificate.java
new file mode 100644
index 000000000..df1b4eac1
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedCertificate.java
@@ -0,0 +1,97 @@
+/*
+ * 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.ContentValues;
+import android.database.Cursor;
+
+import androidx.annotation.NonNull;
+
+public abstract class ManagedCertificate
+{
+ public static final String KEY_ID = "_id";
+ public static final String KEY_VPN_PROFILE_UUID = "vpn_profile_uuid";
+ public static final String KEY_ALIAS = "alias";
+ public static final String KEY_DATA = "data";
+
+ long id = -1;
+
+ @NonNull
+ final String vpnProfileUuid;
+
+ @NonNull
+ String alias;
+
+ @NonNull
+ final String data;
+
+ ManagedCertificate(
+ @NonNull final String vpnProfileUuid,
+ @NonNull final String alias,
+ @NonNull final String data)
+ {
+ this.vpnProfileUuid = vpnProfileUuid;
+ this.alias = alias;
+ this.data = data;
+ }
+
+ ManagedCertificate(@NonNull final Cursor cursor)
+ {
+ id = cursor.getLong(cursor.getColumnIndexOrThrow(KEY_ID));
+ vpnProfileUuid = cursor.getString(cursor.getColumnIndexOrThrow(KEY_VPN_PROFILE_UUID));
+ alias = cursor.getString(cursor.getColumnIndexOrThrow(KEY_ALIAS));
+ data = cursor.getString(cursor.getColumnIndexOrThrow(KEY_DATA));
+ }
+
+ @NonNull
+ public ContentValues asContentValues()
+ {
+ final ContentValues values = new ContentValues();
+ values.put(KEY_VPN_PROFILE_UUID, vpnProfileUuid);
+ values.put(KEY_ALIAS, alias);
+ values.put(KEY_DATA, data);
+ return values;
+ }
+
+ public long getId()
+ {
+ return id;
+ }
+
+ public void setId(long id)
+ {
+ this.id = id;
+ }
+
+ @NonNull
+ public String getVpnProfileUuid()
+ {
+ return vpnProfileUuid;
+ }
+
+ @NonNull
+ public String getAlias()
+ {
+ return alias;
+ }
+
+ @NonNull
+ public String getData()
+ {
+ return data;
+ }
+}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedTrustedCertificate.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedTrustedCertificate.java
new file mode 100644
index 000000000..8ccd4021d
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedTrustedCertificate.java
@@ -0,0 +1,89 @@
+/*
+ * 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.database.Cursor;
+
+import org.strongswan.android.utils.Certificates;
+
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.Objects;
+
+import androidx.annotation.NonNull;
+
+public class ManagedTrustedCertificate extends ManagedCertificate
+{
+ public ManagedTrustedCertificate(
+ @NonNull final String vpnProfileUuid,
+ @NonNull final String data)
+ {
+ super(vpnProfileUuid, determineAlias(vpnProfileUuid, data), data);
+ }
+
+ public ManagedTrustedCertificate(@NonNull final Cursor cursor)
+ {
+ super(cursor);
+ }
+
+ private static String determineAlias(String vpnProfileUuid, String data)
+ {
+ /* fallback in case the certificate is invalid */
+ String certAlias = "trusted:" + vpnProfileUuid;
+ try
+ {
+ X509Certificate cert = Certificates.from(data);
+ KeyStore store = KeyStore.getInstance("LocalCertificateStore");
+ store.load(null, null);
+ certAlias = store.getCertificateAlias(cert);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ return certAlias;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass())
+ {
+ return false;
+ }
+ ManagedTrustedCertificate that = (ManagedTrustedCertificate)o;
+ return Objects.equals(vpnProfileUuid, that.vpnProfileUuid) &&
+ Objects.equals(data, that.data);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(vpnProfileUuid, data);
+ }
+
+ @NonNull
+ @Override
+ public String toString()
+ {
+ return "ManagedTrustedCertificate {" + vpnProfileUuid + ", " + alias + "}";
+ }
+}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedUserCertificate.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedUserCertificate.java
new file mode 100644
index 000000000..cad28e884
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedUserCertificate.java
@@ -0,0 +1,92 @@
+/*
+ * 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.ContentValues;
+import android.database.Cursor;
+
+import java.util.Objects;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class ManagedUserCertificate extends ManagedCertificate
+{
+ public static final String KEY_PASSWORD = "password";
+
+ private final String privateKeyPassword;
+
+ public ManagedUserCertificate(
+ @NonNull final String vpnProfileUuid,
+ @NonNull final String data,
+ @Nullable final String password)
+ {
+ super(vpnProfileUuid, "user:" + vpnProfileUuid, data);
+ privateKeyPassword = password;
+ }
+
+ public ManagedUserCertificate(@NonNull final Cursor cursor)
+ {
+ super(cursor);
+ privateKeyPassword = cursor.getString(cursor.getColumnIndexOrThrow(KEY_PASSWORD));
+ }
+
+ @NonNull
+ @Override
+ public ContentValues asContentValues()
+ {
+ final ContentValues values = super.asContentValues();
+ values.put(KEY_PASSWORD, privateKeyPassword);
+ return values;
+ }
+
+ @Nullable
+ public String getPrivateKeyPassword()
+ {
+ return privateKeyPassword;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass())
+ {
+ return false;
+ }
+ ManagedUserCertificate that = (ManagedUserCertificate)o;
+ return Objects.equals(vpnProfileUuid, that.vpnProfileUuid) &&
+ Objects.equals(data, that.data) &&
+ Objects.equals(privateKeyPassword, that.privateKeyPassword);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(vpnProfileUuid, data);
+ }
+
+ @NonNull
+ @Override
+ public String toString()
+ {
+ return "ManagedUserCertificate {" + vpnProfileUuid + ", " + alias + "}";
+ }
+}
From 6882f177410f5601ae5aca10e515335bdcdc02d7 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:23 +0100
Subject: [PATCH 33/45] android: Add trusted and user certificates to
ManagedVpnProfile
---
.../android/data/ManagedVpnProfile.java | 131 ++++++++++++++----
1 file changed, 101 insertions(+), 30 deletions(-)
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
index 90169871c..054dde19f 100644
--- 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
@@ -21,8 +21,11 @@ import android.text.TextUtils;
import org.strongswan.android.utils.Constants;
+import java.util.Objects;
import java.util.UUID;
+import androidx.annotation.Nullable;
+
public class ManagedVpnProfile extends VpnProfile
{
private static final String KEY_REMOTE = "remote";
@@ -40,6 +43,9 @@ public class ManagedVpnProfile extends VpnProfile
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";
+ private ManagedTrustedCertificate trustedCertificate;
+ private ManagedUserCertificate userCertificate;
+
ManagedVpnProfile(final Bundle bundle, final UUID uuid)
{
int flags = 0;
@@ -51,41 +57,14 @@ public class ManagedVpnProfile extends VpnProfile
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);
- }
+ flags = configureRemote(uuid, remote, flags);
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);
- }
+ flags = configureLocal(uuid, local, flags);
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);
- }
+ configureSelectedApps(includedPackageNames, 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));
@@ -108,6 +87,67 @@ public class ManagedVpnProfile extends VpnProfile
setFlags(flags);
}
+ private void configureSelectedApps(String includedPackageNames, String excludedPackageNames)
+ {
+ if (!TextUtils.isEmpty(includedPackageNames))
+ {
+ setSelectedAppsHandling(SelectedAppsHandling.SELECTED_APPS_ONLY);
+ setSelectedApps(includedPackageNames);
+ }
+ else if (!TextUtils.isEmpty(excludedPackageNames))
+ {
+ setSelectedAppsHandling(SelectedAppsHandling.SELECTED_APPS_EXCLUDE);
+ setSelectedApps(excludedPackageNames);
+ }
+ }
+
+ private int configureRemote(final UUID uuid, @Nullable Bundle remote, int flags)
+ {
+ if (remote == null)
+ {
+ return flags;
+ }
+
+ setGateway(remote.getString(VpnProfileDataSource.KEY_GATEWAY));
+ setPort(getInt(remote, VpnProfileDataSource.KEY_PORT, 1, 65_535));
+ setRemoteId(remote.getString(VpnProfileDataSource.KEY_REMOTE_ID));
+
+ final String certificateData = remote.getString(VpnProfileDataSource.KEY_CERTIFICATE);
+ if (!TextUtils.isEmpty(certificateData))
+ {
+ trustedCertificate = new ManagedTrustedCertificate(uuid.toString(), certificateData);
+ setCertificateAlias(trustedCertificate.getAlias());
+ }
+
+ 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);
+ return flags;
+ }
+
+ private int configureLocal(final UUID uuid, @Nullable Bundle local, int flags)
+ {
+ if (local == null)
+ {
+ return flags;
+ }
+
+ setLocalId(local.getString(VpnProfileDataSource.KEY_LOCAL_ID));
+ setUsername(local.getString(VpnProfileDataSource.KEY_USERNAME));
+
+ final String userCertificateData = local.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE);
+ final String userCertificatePassword = local.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE_PASSWORD, "");
+ if (!TextUtils.isEmpty(userCertificateData))
+ {
+ userCertificate = new ManagedUserCertificate(uuid.toString(), userCertificateData, userCertificatePassword);
+ setUserCertificateAlias(userCertificate.getAlias());
+ }
+
+ flags = addPositiveFlag(flags, local, KEY_LOCAL_RSA_PSS_FLAG, VpnProfile.FLAGS_RSA_PSS);
+ return flags;
+ }
+
private static Integer getInt(final Bundle bundle, final String key, final int min, final int max)
{
final int value = bundle.getInt(key);
@@ -131,4 +171,35 @@ public class ManagedVpnProfile extends VpnProfile
}
return flags;
}
+
+ public ManagedTrustedCertificate getTrustedCertificate()
+ {
+ return trustedCertificate;
+ }
+
+ public ManagedUserCertificate getUserCertificate()
+ {
+ return userCertificate;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o == this)
+ {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass())
+ {
+ return false;
+ }
+ ManagedVpnProfile that = (ManagedVpnProfile)o;
+ return Objects.equals(getUUID(), that.getUUID());
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(getUUID());
+ }
}
From e2f505350e88c436695bae71f1ce7fa7327c98ec Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:23 +0100
Subject: [PATCH 34/45] android: Add database migration for managed
certificates
---
.../android/data/DatabaseHelper.java | 21 ++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java
index b978603d1..1a6b7e174 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/DatabaseHelper.java
@@ -40,6 +40,8 @@ public class DatabaseHelper extends SQLiteOpenHelper
private static final String DATABASE_NAME = "strongswan.db";
private static final String TABLE_NAME_VPN_PROFILE = "vpnprofile";
+ private static final String TABLE_NAME_TRUSTED_CERTIFICATE = "trustedcertificate";
+ private static final String TABLE_NAME_USER_CERTIFICATE = "usercertificate";
static final DbTable TABLE_VPN_PROFILE = new DbTable(TABLE_NAME_VPN_PROFILE, 1, new DbColumn[]{
new DbColumn(VpnProfileDataSource.KEY_ID, "INTEGER PRIMARY KEY AUTOINCREMENT", 1),
@@ -67,7 +69,22 @@ public class DatabaseHelper extends SQLiteOpenHelper
new DbColumn(VpnProfileDataSource.KEY_DNS_SERVERS, "TEXT", 17),
});
- private static final int DATABASE_VERSION = 17;
+ public static final DbTable TABLE_TRUSTED_CERTIFICATE = new DbTable(TABLE_NAME_TRUSTED_CERTIFICATE, 18, new DbColumn[]{
+ new DbColumn(ManagedCertificate.KEY_ID, "INTEGER PRIMARY KEY AUTOINCREMENT", 18),
+ new DbColumn(ManagedCertificate.KEY_VPN_PROFILE_UUID, "TEXT UNIQUE", 18),
+ new DbColumn(ManagedCertificate.KEY_ALIAS, "TEXT NOT NULL", 18),
+ new DbColumn(ManagedCertificate.KEY_DATA, "TEXT NOT NULL", 18),
+ });
+
+ public static final DbTable TABLE_USER_CERTIFICATE = new DbTable(TABLE_NAME_USER_CERTIFICATE, 18, new DbColumn[]{
+ new DbColumn(ManagedCertificate.KEY_ID, "INTEGER PRIMARY KEY AUTOINCREMENT", 18),
+ new DbColumn(ManagedCertificate.KEY_VPN_PROFILE_UUID, "TEXT UNIQUE", 18),
+ new DbColumn(ManagedCertificate.KEY_ALIAS, "TEXT NOT NULL", 18),
+ new DbColumn(ManagedCertificate.KEY_DATA, "TEXT NOT NULL", 18),
+ new DbColumn(ManagedUserCertificate.KEY_PASSWORD, "TEXT", 18),
+ });
+
+ private static final int DATABASE_VERSION = 18;
private static final Set TABLES;
@@ -75,6 +92,8 @@ public class DatabaseHelper extends SQLiteOpenHelper
{
TABLES = new HashSet<>();
TABLES.add(TABLE_VPN_PROFILE);
+ TABLES.add(TABLE_TRUSTED_CERTIFICATE);
+ TABLES.add(TABLE_USER_CERTIFICATE);
}
public DatabaseHelper(Context context)
From fb302d967cc0032288d7237aa30d115e1390600f Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:23 +0100
Subject: [PATCH 35/45] android: Add installer for managed trusted certificates
This installs a configured CA or server certificate into the app's local
key store.
---
.../ManagedTrustedCertificateInstaller.java | 87 +++++++++++++++++++
1 file changed, 87 insertions(+)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedTrustedCertificateInstaller.java
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedTrustedCertificateInstaller.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedTrustedCertificateInstaller.java
new file mode 100644
index 000000000..ed774133f
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedTrustedCertificateInstaller.java
@@ -0,0 +1,87 @@
+/*
+ * 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.logic;
+
+import android.content.Context;
+import android.util.Log;
+
+import org.strongswan.android.data.ManagedTrustedCertificate;
+import org.strongswan.android.utils.Certificates;
+
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import androidx.annotation.NonNull;
+
+public class ManagedTrustedCertificateInstaller
+{
+ private static final String TAG = ManagedTrustedCertificateInstaller.class.getSimpleName();
+
+ public ManagedTrustedCertificateInstaller(@NonNull final Context context)
+ {
+ }
+
+ private boolean installTrustedCert(@NonNull ManagedTrustedCertificate trustedCertificate)
+ throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException
+ {
+ Log.d(TAG, "Install trusted certificate " + trustedCertificate);
+ final X509Certificate certificate = Certificates.from(trustedCertificate.getData());
+
+ KeyStore store = KeyStore.getInstance("LocalCertificateStore");
+ store.load(null, null);
+ store.setCertificateEntry(trustedCertificate.getAlias(), certificate);
+ return true;
+ }
+
+ private void uninstallTrustedCert(@NonNull ManagedTrustedCertificate trustedCertificate)
+ throws CertificateException, IOException, NoSuchAlgorithmException, KeyStoreException
+ {
+ Log.d(TAG, "Remove trusted certificate " + trustedCertificate);
+ KeyStore store = KeyStore.getInstance("LocalCertificateStore");
+ store.load(null, null);
+ store.deleteEntry(trustedCertificate.getAlias());
+ }
+
+ public synchronized boolean tryInstall(@NonNull ManagedTrustedCertificate trustedCertificate)
+ {
+ try
+ {
+ return installTrustedCert(trustedCertificate);
+ }
+ catch (final Exception e)
+ {
+ Log.e(TAG, "Could not install trusted certificate " + trustedCertificate, e);
+ return false;
+ }
+ }
+
+ public synchronized void tryRemove(@NonNull ManagedTrustedCertificate trustedCertificate)
+ {
+ try
+ {
+ uninstallTrustedCert(trustedCertificate);
+ }
+ catch (final Exception e)
+ {
+ Log.e(TAG, "Could not remove trusted certificate " + trustedCertificate, e);
+ }
+ }
+}
From cd67c30fd11fc198232e6616fc9959ac216b607f Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:23 +0100
Subject: [PATCH 36/45] android: Add installer for managed user
certificates/keys
This installs the configured user certificate into Android's key store
using the DevicePolicyManager.
This is only accessible if the app is installed on an enrolled device and
has been granted the CERT_INSTALL delegate scope.
---
.../ManagedUserCertificateInstaller.java | 128 ++++++++++++++++++
1 file changed, 128 insertions(+)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedUserCertificateInstaller.java
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedUserCertificateInstaller.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedUserCertificateInstaller.java
new file mode 100644
index 000000000..249cb9310
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedUserCertificateInstaller.java
@@ -0,0 +1,128 @@
+/*
+ * 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.logic;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+import org.strongswan.android.data.ManagedUserCertificate;
+import org.strongswan.android.utils.KeyPair;
+import org.strongswan.android.utils.KeyPairs;
+
+import java.io.IOException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+
+import androidx.annotation.NonNull;
+
+public class ManagedUserCertificateInstaller
+{
+ private static final String TAG = ManagedUserCertificateInstaller.class.getSimpleName();
+
+ private final DevicePolicyManager policyManager;
+
+ public ManagedUserCertificateInstaller(final Context context)
+ {
+ this.policyManager = (DevicePolicyManager)context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ }
+
+ private boolean installKeyPair(@NonNull ManagedUserCertificate userCertificate, @NonNull KeyPair keyPair)
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
+ {
+ int flags = DevicePolicyManager.INSTALLKEY_REQUEST_CREDENTIALS_ACCESS | DevicePolicyManager.INSTALLKEY_SET_USER_SELECTABLE;
+ return policyManager.installKeyPair(
+ null,
+ keyPair.privateKey,
+ new Certificate[]{keyPair.certificate},
+ userCertificate.getAlias(),
+ flags);
+ }
+ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
+ {
+ return policyManager.installKeyPair(
+ null,
+ keyPair.privateKey,
+ new Certificate[]{keyPair.certificate},
+ userCertificate.getAlias(),
+ true);
+ }
+
+ /* This effectively prevents the app from using its own certificate, so certificate based
+ * authentication can only really work on Android 6+. The certificate chooser is currently
+ * never shown on devices that are enrolled */
+ return policyManager.installKeyPair(
+ null,
+ keyPair.privateKey,
+ keyPair.certificate,
+ userCertificate.getAlias());
+ }
+
+ private boolean installKeyPair(@NonNull ManagedUserCertificate userCertificate)
+ throws UnrecoverableKeyException, CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException
+ {
+ final KeyPair keyPair = KeyPairs.from(userCertificate.getData(), userCertificate.getPrivateKeyPassword());
+ if (keyPair == null)
+ {
+ return false;
+ }
+ Log.d(TAG, "Install key pair " + userCertificate);
+ return installKeyPair(userCertificate, keyPair);
+ }
+
+ private void removeKeyPair(@NonNull ManagedUserCertificate userCertificate)
+ {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
+ {
+ Log.w(TAG, "Cannot remove key pair, unsupported on API level " + Build.VERSION.SDK_INT);
+ return;
+ }
+
+ Log.d(TAG, "Remove key pair " + userCertificate);
+ policyManager.removeKeyPair(null, userCertificate.getAlias());
+ }
+
+ public synchronized boolean tryInstall(@NonNull ManagedUserCertificate userCertificate)
+ {
+ try
+ {
+ return installKeyPair(userCertificate);
+ }
+ catch (final Exception e)
+ {
+ Log.e(TAG, "Could not install key pair " + userCertificate, e);
+ return false;
+ }
+ }
+
+ public synchronized void tryRemove(@NonNull ManagedUserCertificate userCertificate)
+ {
+ try
+ {
+ removeKeyPair(userCertificate);
+ }
+ catch (final Exception e)
+ {
+ Log.e(TAG, "Could not remove key pair " + userCertificate, e);
+ }
+ }
+}
From a04798a796b9cce1960ff3cc4bd2439e304e4970 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:23 +0100
Subject: [PATCH 37/45] android: Add base repository for installed managed
certificates
---
.../data/ManagedCertificateRepository.java | 181 ++++++++++++++++++
1 file changed, 181 insertions(+)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedCertificateRepository.java
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedCertificateRepository.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedCertificateRepository.java
new file mode 100644
index 000000000..00f30b460
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedCertificateRepository.java
@@ -0,0 +1,181 @@
+/*
+ * 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.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public abstract class ManagedCertificateRepository
+{
+ @NonNull
+ private final ManagedConfigurationService managedConfigurationService;
+
+ @NonNull
+ private final SQLiteDatabase database;
+ @NonNull
+ private final DatabaseHelper.DbTable table;
+
+ protected ManagedCertificateRepository(
+ @NonNull final ManagedConfigurationService managedConfigurationService,
+ @NonNull final DatabaseHelper databaseHelper,
+ @NonNull final DatabaseHelper.DbTable table)
+ {
+ this.managedConfigurationService = managedConfigurationService;
+
+ this.database = databaseHelper.getReadableDatabase();
+ this.table = table;
+ }
+
+ @Nullable
+ protected abstract T getCertificate(@NonNull final ManagedVpnProfile vpnProfile);
+
+ @NonNull
+ protected abstract T createCertificate(@NonNull Cursor cursor);
+
+ protected abstract boolean isInstalled(@NonNull T certificate);
+
+ private boolean exists(@NonNull T certificate)
+ {
+ final String vpnProfileUuid = certificate.getVpnProfileUuid();
+ try (final Cursor cursor = database.query(table.Name, table.columnNames(), ManagedCertificate.KEY_VPN_PROFILE_UUID + " = ?", new String[]{vpnProfileUuid}, null, null, null))
+ {
+ cursor.moveToFirst();
+ if (!cursor.isAfterLast())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @NonNull
+ public List getConfiguredCertificates()
+ {
+ managedConfigurationService.loadConfiguration();
+
+ final Map managedVpnProfiles = managedConfigurationService.getManagedProfiles();
+ final List certificates = new ArrayList<>(managedVpnProfiles.size());
+
+ for (final ManagedVpnProfile vpnProfile : managedVpnProfiles.values())
+ {
+ final T certificate = getCertificate(vpnProfile);
+ if (certificate != null)
+ {
+ certificates.add(certificate);
+ }
+ }
+
+ return certificates;
+ }
+
+ /**
+ * @return the collection of certificates that were previously installed.
+ * @see #addInstalledCertificate(ManagedCertificate)
+ */
+ @NonNull
+ private List getCertificates()
+ {
+ try (final Cursor cursor = database.query(table.Name, table.columnNames(), null, null, null, null, null))
+ {
+ final List certificates = new ArrayList<>();
+
+ cursor.moveToFirst();
+ while (!cursor.isAfterLast())
+ {
+ final T certificate = createCertificate(cursor);
+ certificates.add(certificate);
+ cursor.moveToNext();
+ }
+ return certificates;
+ }
+ }
+
+ /**
+ * Returns the collection of certificates that were previously marked as installed and are still
+ * reported as installed by the OS.
+ *
+ * @return the collection of installed certificates.
+ * @see #addInstalledCertificate(ManagedCertificate)
+ */
+ @NonNull
+ public List getInstalledCertificates()
+ {
+ final List certificates = getCertificates();
+ final List installed = new ArrayList<>(certificates.size());
+
+ for (final T certificate : certificates)
+ {
+ if (isInstalled(certificate))
+ {
+ installed.add(certificate);
+ }
+ }
+
+ return installed;
+ }
+
+ /**
+ * Returns a map containing certificates previously marked as installed, indexed by the
+ * unique identifier of the VPN profile they are associated with.
+ *
+ * @return a map containing installed certificates, index by the VPN profile's unique
+ * identifier.
+ */
+ @NonNull
+ public Map getCertificateMap()
+ {
+ final List certificates = getCertificates();
+ final Map map = new HashMap<>(certificates.size());
+
+ for (final T certificate : certificates)
+ {
+ map.put(certificate.getVpnProfileUuid(), certificate);
+ }
+
+ return map;
+ }
+
+ public void addInstalledCertificate(@NonNull final T certificate)
+ {
+ final ContentValues values = certificate.asContentValues();
+
+ if (exists(certificate))
+ {
+ final String vpnProfileUuid = certificate.getVpnProfileUuid();
+ database.update(table.Name, values, ManagedCertificate.KEY_VPN_PROFILE_UUID + " = ?", new String[]{vpnProfileUuid});
+ }
+ else
+ {
+ database.insert(table.Name, null, values);
+ }
+ }
+
+ public void removeInstalledCertificate(@NonNull final T certificate)
+ {
+ final String vpnProfileUuid = certificate.getVpnProfileUuid();
+ database.delete(table.Name, ManagedCertificate.KEY_VPN_PROFILE_UUID + " = ?", new String[]{vpnProfileUuid});
+ }
+}
From 99dfa8cb0e2142888791fba5b4f931e805fd086b Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:23 +0100
Subject: [PATCH 38/45] android: Add repository for managed trusted
certificates
---
.../ManagedTrustedCertificateRepository.java | 61 +++++++++++++++++++
1 file changed, 61 insertions(+)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedTrustedCertificateRepository.java
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedTrustedCertificateRepository.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedTrustedCertificateRepository.java
new file mode 100644
index 000000000..e261c4284
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedTrustedCertificateRepository.java
@@ -0,0 +1,61 @@
+/*
+ * 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.database.Cursor;
+
+import org.strongswan.android.logic.TrustedCertificateManager;
+
+import java.security.cert.X509Certificate;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class ManagedTrustedCertificateRepository extends ManagedCertificateRepository
+{
+ private static final DatabaseHelper.DbTable TABLE = DatabaseHelper.TABLE_TRUSTED_CERTIFICATE;
+
+ public ManagedTrustedCertificateRepository(
+ @NonNull final ManagedConfigurationService managedConfigurationService,
+ @NonNull final DatabaseHelper databaseHelper)
+ {
+ super(managedConfigurationService, databaseHelper, TABLE);
+ }
+
+ @Nullable
+ @Override
+ protected ManagedTrustedCertificate getCertificate(@NonNull ManagedVpnProfile vpnProfile)
+ {
+ return vpnProfile.getTrustedCertificate();
+ }
+
+ @NonNull
+ @Override
+ protected ManagedTrustedCertificate createCertificate(@NonNull Cursor cursor)
+ {
+ return new ManagedTrustedCertificate(cursor);
+ }
+
+ @Override
+ protected boolean isInstalled(@NonNull ManagedTrustedCertificate certificate)
+ {
+ TrustedCertificateManager certificateManager = TrustedCertificateManager.getInstance();
+ final X509Certificate x509Certificate = certificateManager.getCACertificateFromAlias(certificate.getAlias());
+
+ return x509Certificate != null;
+ }
+}
From 97cb35afe5443795fbe1bf94a41271f62f1fa67e Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:23 +0100
Subject: [PATCH 39/45] android: Add repository for managed user certificates
---
.../ManagedUserCertificateRepository.java | 67 +++++++++++++++++++
1 file changed, 67 insertions(+)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedUserCertificateRepository.java
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedUserCertificateRepository.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedUserCertificateRepository.java
new file mode 100644
index 000000000..55804f1e2
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/ManagedUserCertificateRepository.java
@@ -0,0 +1,67 @@
+/*
+ * 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.app.admin.DevicePolicyManager;
+import android.database.Cursor;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class ManagedUserCertificateRepository extends ManagedCertificateRepository
+{
+ private static final DatabaseHelper.DbTable TABLE = DatabaseHelper.TABLE_USER_CERTIFICATE;
+
+ @NonNull
+ private final DevicePolicyManager devicePolicyManager;
+
+ public ManagedUserCertificateRepository(
+ @NonNull final ManagedConfigurationService managedConfigurationService,
+ @NonNull final DevicePolicyManager devicePolicyManager,
+ @NonNull final DatabaseHelper databaseHelper)
+ {
+ super(managedConfigurationService, databaseHelper, TABLE);
+ this.devicePolicyManager = devicePolicyManager;
+ }
+
+ @Nullable
+ @Override
+ protected ManagedUserCertificate getCertificate(@NonNull ManagedVpnProfile vpnProfile)
+ {
+ return vpnProfile.getUserCertificate();
+ }
+
+ @NonNull
+ @Override
+ protected ManagedUserCertificate createCertificate(@NonNull Cursor cursor)
+ {
+ return new ManagedUserCertificate(cursor);
+ }
+
+ @Override
+ protected boolean isInstalled(@NonNull ManagedUserCertificate certificate)
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
+ {
+ return devicePolicyManager.hasKeyPair(certificate.getAlias());
+ }
+ /* We don't know, so we assume a certificate we installed may have been removed by the
+ * user, so we install it again to make sure it's still there */
+ return false;
+ }
+}
From 9cb23f650a6587f5632971e96bde9b448f8fc709 Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:24 +0100
Subject: [PATCH 40/45] android: Add utility class to determine differences in
two lists of objects
This allows determining the difference between two lists in the form of
inserts, updates and deletes (and unchanged elements).
---
src/frontends/android/app/build.gradle | 1 +
.../strongswan/android/utils/Difference.java | 166 ++++++++++++++++++
.../android/utils/DifferenceTest.java | 151 ++++++++++++++++
3 files changed, 318 insertions(+)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/utils/Difference.java
create mode 100644 src/frontends/android/app/src/test/java/org/strongswan/android/utils/DifferenceTest.java
diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle
index 237134771..43135e444 100644
--- a/src/frontends/android/app/build.gradle
+++ b/src/frontends/android/app/build.gradle
@@ -49,5 +49,6 @@ dependencies {
implementation 'androidx.preference:preference:1.2.1'
implementation 'com.google.android.material:material:1.10.0'
testImplementation 'junit:junit:4.13.2'
+ testImplementation 'org.assertj:assertj-core:3.24.2'
testImplementation 'org.mockito:mockito-core:5.8.0'
}
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Difference.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Difference.java
new file mode 100644
index 000000000..e3d2fe990
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Difference.java
@@ -0,0 +1,166 @@
+/*
+ * 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.utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import androidx.annotation.NonNull;
+import androidx.arch.core.util.Function;
+import androidx.core.util.Pair;
+
+public class Difference
+{
+ @NonNull
+ private final List inserts;
+ @NonNull
+ private final List> updates;
+ @NonNull
+ private final List unchanged;
+ @NonNull
+ private final List deletes;
+
+ @NonNull
+ public static Difference between(
+ @NonNull final List existing,
+ @NonNull final List modified,
+ @NonNull final Function getKey)
+ {
+ final Map existingMap = mapOf(existing, getKey);
+ final Map modifiedMap = mapOf(modified, getKey);
+
+ final List inserts = notIn(existingMap, getKey, modified);
+ final List deletes = notIn(modifiedMap, getKey, existing);
+ final List> updates = new ArrayList<>(modifiedMap.size());
+ final List unchanged = new ArrayList<>(existingMap.size());
+ changeBetween(existingMap, modifiedMap, updates, unchanged);
+
+ return new Difference<>(inserts, updates, unchanged, deletes);
+ }
+
+ @NonNull
+ private static Map mapOf(
+ @NonNull final List list,
+ @NonNull final Function getKey)
+ {
+ final Map map = new HashMap<>(list.size());
+
+ for (final V entry : list)
+ {
+ final K key = getKey.apply(entry);
+ map.put(key, entry);
+ }
+
+ return map;
+ }
+
+ @NonNull
+ private static List notIn(
+ @NonNull final Map map,
+ @NonNull final Function getKey,
+ @NonNull final List list)
+ {
+ final List filtered = new ArrayList<>(list.size());
+
+ for (final V value : list)
+ {
+ final K key = getKey.apply(value);
+ if (!map.containsKey(key))
+ {
+ filtered.add(value);
+ }
+ }
+
+ return filtered;
+ }
+
+ @NonNull
+ private static void changeBetween(
+ @NonNull final Map existingMap,
+ @NonNull final Map modifiedMap,
+ @NonNull List> updates,
+ @NonNull List unchanged)
+ {
+ for (final Map.Entry entry : modifiedMap.entrySet())
+ {
+ final V existingValue = existingMap.get(entry.getKey());
+ final V modifiedValue = entry.getValue();
+
+ if (existingValue != null && !Objects.equals(existingValue, modifiedValue))
+ {
+ final Pair change = Pair.create(existingValue, modifiedValue);
+ updates.add(change);
+ }
+ else if (existingValue != null)
+ {
+ unchanged.add(existingValue);
+ }
+ }
+
+ }
+
+ public Difference(
+ @NonNull List inserts,
+ @NonNull List> updates,
+ @NonNull List unchanged,
+ @NonNull List deletes)
+ {
+ this.inserts = inserts;
+ this.updates = updates;
+ this.unchanged = unchanged;
+ this.deletes = deletes;
+ }
+
+ @NonNull
+ public List getInserts()
+ {
+ return inserts;
+ }
+
+ @NonNull
+ public List> getUpdates()
+ {
+ return updates;
+ }
+
+ @NonNull
+ public List getUnchanged()
+ {
+ return unchanged;
+ }
+
+ @NonNull
+ public List getDeletes()
+ {
+ return deletes;
+ }
+
+ public boolean isEmpty()
+ {
+ return inserts.isEmpty() && updates.isEmpty() && deletes.isEmpty();
+ }
+
+ @NonNull
+ @Override
+ public String toString()
+ {
+ return "Difference {" + inserts + ", " + updates + ", " + deletes + "}";
+ }
+}
diff --git a/src/frontends/android/app/src/test/java/org/strongswan/android/utils/DifferenceTest.java b/src/frontends/android/app/src/test/java/org/strongswan/android/utils/DifferenceTest.java
new file mode 100644
index 000000000..01fcd719e
--- /dev/null
+++ b/src/frontends/android/app/src/test/java/org/strongswan/android/utils/DifferenceTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.utils;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Objects;
+
+import androidx.core.util.Pair;
+
+public class DifferenceTest
+{
+ @Test
+ public void testElementAdded()
+ {
+ final Element element = new Element("a", 0);
+ final List existing = List.of();
+ final List modified = List.of(element);
+
+ final Difference diff = Difference.between(existing, modified, Element::getKey);
+
+ assertThat(diff.getInserts()).containsExactly(element);
+ assertThat(diff.getUpdates()).isEmpty();
+ assertThat(diff.getUnchanged()).isEmpty();
+ assertThat(diff.getDeletes()).isEmpty();
+ }
+
+ @Test
+ public void testElementRemoved()
+ {
+ final Element element = new Element("a", 0);
+ final List existing = List.of(element);
+ final List modified = List.of();
+
+ final Difference diff = Difference.between(existing, modified, Element::getKey);
+
+ assertThat(diff.getInserts()).isEmpty();
+ assertThat(diff.getUpdates()).isEmpty();
+ assertThat(diff.getUnchanged()).isEmpty();
+ assertThat(diff.getDeletes()).containsExactly(element);
+ }
+
+ @Test
+ public void testElementIdentical()
+ {
+ final Element element0 = new Element("a", 0);
+ final Element element1 = new Element("a", 0);
+ final List existing = List.of(element0);
+ final List modified = List.of(element1);
+
+ final Difference diff = Difference.between(existing, modified, Element::getKey);
+
+ assertThat(diff.getInserts()).isEmpty();
+ assertThat(diff.getUpdates()).isEmpty();
+ assertThat(diff.getUnchanged()).containsExactly(element0);
+ assertThat(diff.getDeletes()).isEmpty();
+ }
+
+ @Test
+ public void testElementSwap()
+ {
+ final Element elementA = new Element("a", 0);
+ final Element elementB = new Element("b", 0);
+ final List existing = List.of(elementA);
+ final List modified = List.of(elementB);
+
+ final Difference diff = Difference.between(existing, modified, Element::getKey);
+
+ assertThat(diff.getInserts()).containsExactly(elementB);
+ assertThat(diff.getUpdates()).isEmpty();
+ assertThat(diff.getUnchanged()).isEmpty();
+ assertThat(diff.getDeletes()).containsExactly(elementA);
+ }
+
+ @Test
+ public void testElementUpdate()
+ {
+ final Element elementA0 = new Element("a", 0);
+ final Element elementA1 = new Element("a", 1);
+ final List existing = List.of(elementA0);
+ final List modified = List.of(elementA1);
+
+ final Difference diff = Difference.between(existing, modified, Element::getKey);
+
+ assertThat(diff.getInserts()).isEmpty();
+ assertThat(diff.getUpdates()).containsExactly(Pair.create(elementA0, elementA1));
+ assertThat(diff.getUnchanged()).isEmpty();
+ assertThat(diff.getDeletes()).isEmpty();
+ }
+
+ private static class Element
+ {
+ private final String key;
+ private final int value;
+
+ public Element(final String key, final int value)
+ {
+ this.key = key;
+ this.value = value;
+ }
+
+ public String getKey()
+ {
+ return key;
+ }
+
+ public int getValue()
+ {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass())
+ {
+ return false;
+ }
+ Element element = (Element)o;
+ return value == element.value && Objects.equals(key, element.key);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(key, value);
+ }
+ }
+}
From aa06d7549178cfdabcae865cbefd6a9fc491060d Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:24 +0100
Subject: [PATCH 41/45] android: Add manager for managed trusted certificates
This is used to install, replace or delete currently installed trusted
certificates based on the app's current managed configuration.
Certificates that are shared between multiple profiles are protected
and not uninstalled if a profile that uses it remains.
---
.../ManagedTrustedCertificateManager.java | 125 ++++++++++++++++++
1 file changed, 125 insertions(+)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedTrustedCertificateManager.java
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedTrustedCertificateManager.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedTrustedCertificateManager.java
new file mode 100644
index 000000000..395949a3d
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedTrustedCertificateManager.java
@@ -0,0 +1,125 @@
+/*
+ * 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.logic;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import org.strongswan.android.data.DatabaseHelper;
+import org.strongswan.android.data.ManagedConfigurationService;
+import org.strongswan.android.data.ManagedTrustedCertificate;
+import org.strongswan.android.data.ManagedTrustedCertificateRepository;
+import org.strongswan.android.utils.Difference;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+
+import androidx.annotation.NonNull;
+import androidx.core.util.Pair;
+
+public class ManagedTrustedCertificateManager
+{
+ private static final String TAG = ManagedTrustedCertificateManager.class.getSimpleName();
+
+ @NonNull
+ private final ExecutorService executorService;
+ @NonNull
+ private final Handler handler;
+
+ @NonNull
+ private final ManagedTrustedCertificateRepository certificateRepository;
+ @NonNull
+ private final ManagedTrustedCertificateInstaller certificateInstaller;
+
+ public ManagedTrustedCertificateManager(
+ @NonNull final Context context,
+ @NonNull final ExecutorService executorService,
+ @NonNull final Handler handler,
+ @NonNull final ManagedConfigurationService managedConfigurationService,
+ @NonNull final DatabaseHelper databaseHelper)
+ {
+ this.executorService = executorService;
+ this.handler = handler;
+
+ this.certificateRepository = new ManagedTrustedCertificateRepository(managedConfigurationService, databaseHelper);
+ this.certificateInstaller = new ManagedTrustedCertificateInstaller(context);
+ }
+
+ public void update(@NonNull final Runnable onUpdateCompleted)
+ {
+ executorService.execute(() -> {
+ final List configured = certificateRepository.getConfiguredCertificates();
+ final List installed = certificateRepository.getInstalledCertificates();
+
+ final Difference diff = Difference.between(installed, configured, ManagedTrustedCertificate::getVpnProfileUuid);
+ if (diff.isEmpty())
+ {
+ Log.d(TAG, "No trusted certificates changed, nothing to do");
+ handler.post(onUpdateCompleted);
+ return;
+ }
+ Log.d(TAG, "Trusted certificates changed " + diff);
+
+ final Set protectedAliases = new HashSet<>();
+ for (final ManagedTrustedCertificate unchanged : diff.getUnchanged())
+ {
+ protectedAliases.add(unchanged.getAlias());
+ }
+
+ for (final ManagedTrustedCertificate delete : diff.getDeletes())
+ {
+ remove(delete, !protectedAliases.contains(delete.getAlias()));
+ }
+
+ for (final Pair update : diff.getUpdates())
+ {
+ remove(update.first, !protectedAliases.contains(update.first.getAlias()));
+ install(update.second);
+ }
+
+ for (final ManagedTrustedCertificate insert : diff.getInserts())
+ {
+ install(insert);
+ }
+
+ TrustedCertificateManager.getInstance().reset();
+ TrustedCertificateManager.getInstance().load();
+ handler.post(onUpdateCompleted);
+ });
+ }
+
+ private void install(@NonNull final ManagedTrustedCertificate trustedCertificate)
+ {
+ if (certificateInstaller.tryInstall(trustedCertificate))
+ {
+ certificateRepository.addInstalledCertificate(trustedCertificate);
+ }
+ }
+
+ private void remove(@NonNull final ManagedTrustedCertificate trustedCertificate, boolean uninstall)
+ {
+ if (uninstall)
+ {
+ certificateInstaller.tryRemove(trustedCertificate);
+ }
+ certificateRepository.removeInstalledCertificate(trustedCertificate);
+ }
+}
From b0ba845e271e1f1d6bd5e016a3b23d402d7e2def Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:24 +0100
Subject: [PATCH 42/45] android: Add manager for managed user certificates
This can be used to install, replace or delete currently installed user
certificates based on the app's current managed configuration.
---
.../logic/ManagedUserCertificateManager.java | 97 +++++++++++++++++++
1 file changed, 97 insertions(+)
create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedUserCertificateManager.java
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedUserCertificateManager.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedUserCertificateManager.java
new file mode 100644
index 000000000..78424e2aa
--- /dev/null
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/ManagedUserCertificateManager.java
@@ -0,0 +1,97 @@
+/*
+ * 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.logic;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.util.Log;
+
+import org.strongswan.android.data.DatabaseHelper;
+import org.strongswan.android.data.ManagedConfigurationService;
+import org.strongswan.android.data.ManagedUserCertificate;
+import org.strongswan.android.data.ManagedUserCertificateRepository;
+import org.strongswan.android.utils.Difference;
+
+import java.util.List;
+
+import androidx.annotation.NonNull;
+import androidx.core.util.Pair;
+
+public class ManagedUserCertificateManager
+{
+ private static final String TAG = ManagedUserCertificateManager.class.getSimpleName();
+
+ @NonNull
+ private final ManagedUserCertificateRepository certificateRepository;
+ @NonNull
+ private final ManagedUserCertificateInstaller certificateInstaller;
+
+ public ManagedUserCertificateManager(
+ @NonNull final Context context,
+ @NonNull final ManagedConfigurationService managedConfigurationService,
+ @NonNull final DatabaseHelper databaseHelper)
+ {
+ final DevicePolicyManager devicePolicyManager = (DevicePolicyManager)context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ this.certificateRepository = new ManagedUserCertificateRepository(managedConfigurationService, devicePolicyManager, databaseHelper);
+ this.certificateInstaller = new ManagedUserCertificateInstaller(context);
+ }
+
+ public void update()
+ {
+ final List configured = certificateRepository.getConfiguredCertificates();
+ final List installed = certificateRepository.getInstalledCertificates();
+
+ final Difference diff = Difference.between(installed, configured, ManagedUserCertificate::getVpnProfileUuid);
+ if (diff.isEmpty())
+ {
+ Log.d(TAG, "No key pairs changed, nothing to do");
+ return;
+ }
+ Log.d(TAG, "Key pairs changed " + diff);
+
+ for (final ManagedUserCertificate delete : diff.getDeletes())
+ {
+ remove(delete);
+ }
+
+ for (final Pair update : diff.getUpdates())
+ {
+ remove(update.first);
+ install(update.second);
+ }
+
+ for (final ManagedUserCertificate insert : diff.getInserts())
+ {
+ install(insert);
+ }
+ }
+
+ private void install(@NonNull final ManagedUserCertificate userCertificate)
+ {
+ if (certificateInstaller.tryInstall(userCertificate))
+ {
+ certificateRepository.addInstalledCertificate(userCertificate);
+ }
+ }
+
+ private void remove(@NonNull final ManagedUserCertificate userCertificate)
+ {
+ certificateInstaller.tryRemove(userCertificate);
+ certificateRepository.removeInstalledCertificate(userCertificate);
+ }
+}
From 8c6b3019a7272ca53939385f71e5c5fe03964b6d Mon Sep 17 00:00:00 2001
From: Markus Pfeiffer
Date: Tue, 21 Nov 2023 15:37:24 +0100
Subject: [PATCH 43/45] android: Update managed certificates if config changes
---
.../android/logic/StrongSwanApplication.java | 21 ++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)
diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
index 58b55e033..7f73dbfb8 100644
--- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
+++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
@@ -56,6 +56,8 @@ public class StrongSwanApplication extends Application implements DefaultLifecyc
private final Handler mMainHandler = HandlerCompat.createAsync(Looper.getMainLooper());
private ManagedConfigurationService mManagedConfigurationService;
+ private ManagedTrustedCertificateManager mTrustedCertificateManager;
+ private ManagedUserCertificateManager mUserCertificateManager;
private DatabaseHelper mDatabaseHelper;
@@ -84,6 +86,12 @@ public class StrongSwanApplication extends Application implements DefaultLifecyc
mDatabaseHelper = new DatabaseHelper(mContext);
mManagedConfigurationService = new ManagedConfigurationService(mContext);
+
+ mTrustedCertificateManager = new ManagedTrustedCertificateManager(mContext, mExecutorService, mMainHandler,
+ mManagedConfigurationService, mDatabaseHelper);
+
+ mUserCertificateManager = new ManagedUserCertificateManager(mContext, mManagedConfigurationService, mDatabaseHelper);
+
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
}
@@ -109,12 +117,15 @@ public class StrongSwanApplication extends Application implements DefaultLifecyc
mManagedConfigurationService.loadConfiguration();
mManagedConfigurationService.updateSettings();
- uuids.addAll(mManagedConfigurationService.getManagedProfiles().keySet());
+ mUserCertificateManager.update();
+ mTrustedCertificateManager.update(() -> {
+ uuids.addAll(mManagedConfigurationService.getManagedProfiles().keySet());
- Log.d(TAG, "Send profiles changed broadcast");
- Intent profilesChanged = new Intent(Constants.VPN_PROFILES_CHANGED);
- profilesChanged.putExtra(Constants.VPN_PROFILES_MULTIPLE, uuids.toArray(new String[0]));
- LocalBroadcastManager.getInstance(mContext).sendBroadcast(profilesChanged);
+ Log.d(TAG, "Send profiles changed broadcast");
+ Intent profilesChanged = new Intent(Constants.VPN_PROFILES_CHANGED);
+ profilesChanged.putExtra(Constants.VPN_PROFILES_MULTIPLE, uuids.toArray(new String[0]));
+ LocalBroadcastManager.getInstance(mContext).sendBroadcast(profilesChanged);
+ });
}
/**
From 51a5d96b360c953a26d1285232f5040d06633837 Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Tue, 30 Jan 2024 18:57:43 +0100
Subject: [PATCH 44/45] android: Add translations for managed configuration
strings
Not actually translating anything, but making the linter happy.
---
.../strings_managed_configuration.xml | 109 ++++++++++++++++++
.../strings_managed_configuration.xml | 109 ++++++++++++++++++
.../strings_managed_configuration.xml | 109 ++++++++++++++++++
.../strings_managed_configuration.xml | 109 ++++++++++++++++++
.../strings_managed_configuration.xml | 109 ++++++++++++++++++
.../strings_managed_configuration.xml | 109 ++++++++++++++++++
6 files changed, 654 insertions(+)
create mode 100644 src/frontends/android/app/src/main/res/values-de/strings_managed_configuration.xml
create mode 100644 src/frontends/android/app/src/main/res/values-pl/strings_managed_configuration.xml
create mode 100644 src/frontends/android/app/src/main/res/values-ru/strings_managed_configuration.xml
create mode 100644 src/frontends/android/app/src/main/res/values-uk/strings_managed_configuration.xml
create mode 100644 src/frontends/android/app/src/main/res/values-zh-rCN/strings_managed_configuration.xml
create mode 100644 src/frontends/android/app/src/main/res/values-zh-rTW/strings_managed_configuration.xml
diff --git a/src/frontends/android/app/src/main/res/values-de/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values-de/strings_managed_configuration.xml
new file mode 100644
index 000000000..6a58f62ad
--- /dev/null
+++ b/src/frontends/android/app/src/main/res/values-de/strings_managed_configuration.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+ Allow profile creation
+ Specifies whether users are allowed to add their own profiles
+ Allow profile import
+ Specifies whether users are allowed to import their own profiles
+ Show existing profiles
+ Specifies whether users can continue to see and use their previously created profiles
+ Allow certificate import
+ Specifies whether users are allowed to import certificates
+ Allow modifying settings
+ Specifies whether users are allowed change global app settings
+ @string/pref_default_vpn_profile
+ Unique identifier of the VPN profile to use by default, use the value \"mru\" for the most recently used profile
+ @string/pref_power_whitelist_title
+ @string/pref_power_whitelist_summary
+ VPN profiles
+ Collection of managed VPN profiles
+ VPN profile
+ A managed VPN profile
+
+
+ Unique identifier
+ Unique identifier of the VPN profile. Version 4 UUIDs (random-generated) are recommended
+ @string/profile_name_label
+ @string/profile_name_hint
+ @string/profile_vpn_type_label
+ The type of client authentication used by the VPN profile
+ Apps allowed to use the VPN (Optional)
+ Space-separated list of package names; all other apps will not see/use the VPN
+ Apps excluded from using the VPN (Optional)
+ Space-separated list of package names of apps excluded from using the VPN; only used of allow list is empty
+ @string/profile_proposals_ike_label
+ @string/profile_proposals_ike_hint
+ @string/profile_proposals_esp_label
+ @string/profile_proposals_esp_hint
+ @string/profile_mtu_label
+ @string/profile_mtu_hint
+ @string/profile_nat_keepalive_label
+ @string/profile_nat_keepalive_hint
+ @string/profile_dns_servers_label
+ @string/profile_dns_servers_hint
+ @string/profile_ipv6_transport_label
+ @string/profile_ipv6_transport_hint
+
+
+ Remote
+ Specifies information about the server
+ @string/profile_gateway_label
+ @string/profile_gateway_hint
+ @string/profile_port_label
+ @string/profile_port_hint
+ @string/profile_remote_id_label
+ @string/profile_remote_id_hint
+ CA or server certificate (Optional)
+ Base64-encoded CA or server certificate. Is imported into the app, not the system keystore. If not set, automatic CA certificate selection is enabled
+ Send certificate requests
+ Specifies whether to send certificate requests for all installed or selected CA certificates. Disabling this may reduce the size of the IKE_AUTH message if the server does not support fragmentation. But it only works if the server doesn\'t require certificate requests to send back the server certificate
+ @string/profile_use_ocsp_label
+ @string/profile_use_ocsp_hint
+ @string/profile_use_crl_label
+ @string/profile_use_crl_hint
+ @string/profile_strict_revocation_label
+ @string/profile_strict_revocation_hint
+
+
+ Local
+ Specifies information about the client
+ Identity/username for EAP authentication (Optional)
+ If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it. If it is set, the user is not able to change it. In both cases the user may optionally enter the password
+ @string/profile_local_id_label
+ @string/profile_local_id_hint_user
+ @string/profile_user_certificate_label
+ Base64-encoded PKCS#12-container with the client certificate and private key and optional certificate chain (the latter might cause warnings on older Android releases, see Android VPN client configuration for details). Not necessary for username/password-based EAP authentication or if the user already has the certificate/key installed as it may be selected while importing the profile
+ User certificate password (Optional)
+ Password required to extract the private key of the PKCS#12-container for installation
+ @string/profile_rsa_pss_label
+ @string/profile_rsa_pss_hint
+
+
+ @string/profile_split_tunneling_label
+ @string/profile_split_tunneling_intro
+ @string/profile_included_subnets_label
+ @string/profile_included_subnets_hint
+ @string/profile_excluded_subnets_label
+ @string/profile_excluded_subnets_hint
+ @string/profile_split_tunnelingv4_title
+ Specifies whether to block IPv4 traffic that\'s not destined for the VPN. Forces all IPv4 traffic via VPN (traffic that does not match the negotiated traffic selector is then just dropped). Thus this is basically equivalent to including 0.0.0.0/0 in subnets
+ @string/profile_split_tunnelingv6_title
+ Specifies whether to block IPv6 traffic that\'s not destined for the VPN. Forces all IPv6 traffic via VPN (traffic that does not match the negotiated traffic selector is then just dropped). Thus this is basically equivalent to including ::/0 in subnets
+
+
diff --git a/src/frontends/android/app/src/main/res/values-pl/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values-pl/strings_managed_configuration.xml
new file mode 100644
index 000000000..6a58f62ad
--- /dev/null
+++ b/src/frontends/android/app/src/main/res/values-pl/strings_managed_configuration.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+ Allow profile creation
+ Specifies whether users are allowed to add their own profiles
+ Allow profile import
+ Specifies whether users are allowed to import their own profiles
+ Show existing profiles
+ Specifies whether users can continue to see and use their previously created profiles
+ Allow certificate import
+ Specifies whether users are allowed to import certificates
+ Allow modifying settings
+ Specifies whether users are allowed change global app settings
+ @string/pref_default_vpn_profile
+ Unique identifier of the VPN profile to use by default, use the value \"mru\" for the most recently used profile
+ @string/pref_power_whitelist_title
+ @string/pref_power_whitelist_summary
+ VPN profiles
+ Collection of managed VPN profiles
+ VPN profile
+ A managed VPN profile
+
+
+ Unique identifier
+ Unique identifier of the VPN profile. Version 4 UUIDs (random-generated) are recommended
+ @string/profile_name_label
+ @string/profile_name_hint
+ @string/profile_vpn_type_label
+ The type of client authentication used by the VPN profile
+ Apps allowed to use the VPN (Optional)
+ Space-separated list of package names; all other apps will not see/use the VPN
+ Apps excluded from using the VPN (Optional)
+ Space-separated list of package names of apps excluded from using the VPN; only used of allow list is empty
+ @string/profile_proposals_ike_label
+ @string/profile_proposals_ike_hint
+ @string/profile_proposals_esp_label
+ @string/profile_proposals_esp_hint
+ @string/profile_mtu_label
+ @string/profile_mtu_hint
+ @string/profile_nat_keepalive_label
+ @string/profile_nat_keepalive_hint
+ @string/profile_dns_servers_label
+ @string/profile_dns_servers_hint
+ @string/profile_ipv6_transport_label
+ @string/profile_ipv6_transport_hint
+
+
+ Remote
+ Specifies information about the server
+ @string/profile_gateway_label
+ @string/profile_gateway_hint
+ @string/profile_port_label
+ @string/profile_port_hint
+ @string/profile_remote_id_label
+ @string/profile_remote_id_hint
+ CA or server certificate (Optional)
+ Base64-encoded CA or server certificate. Is imported into the app, not the system keystore. If not set, automatic CA certificate selection is enabled
+ Send certificate requests
+ Specifies whether to send certificate requests for all installed or selected CA certificates. Disabling this may reduce the size of the IKE_AUTH message if the server does not support fragmentation. But it only works if the server doesn\'t require certificate requests to send back the server certificate
+ @string/profile_use_ocsp_label
+ @string/profile_use_ocsp_hint
+ @string/profile_use_crl_label
+ @string/profile_use_crl_hint
+ @string/profile_strict_revocation_label
+ @string/profile_strict_revocation_hint
+
+
+ Local
+ Specifies information about the client
+ Identity/username for EAP authentication (Optional)
+ If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it. If it is set, the user is not able to change it. In both cases the user may optionally enter the password
+ @string/profile_local_id_label
+ @string/profile_local_id_hint_user
+ @string/profile_user_certificate_label
+ Base64-encoded PKCS#12-container with the client certificate and private key and optional certificate chain (the latter might cause warnings on older Android releases, see Android VPN client configuration for details). Not necessary for username/password-based EAP authentication or if the user already has the certificate/key installed as it may be selected while importing the profile
+ User certificate password (Optional)
+ Password required to extract the private key of the PKCS#12-container for installation
+ @string/profile_rsa_pss_label
+ @string/profile_rsa_pss_hint
+
+
+ @string/profile_split_tunneling_label
+ @string/profile_split_tunneling_intro
+ @string/profile_included_subnets_label
+ @string/profile_included_subnets_hint
+ @string/profile_excluded_subnets_label
+ @string/profile_excluded_subnets_hint
+ @string/profile_split_tunnelingv4_title
+ Specifies whether to block IPv4 traffic that\'s not destined for the VPN. Forces all IPv4 traffic via VPN (traffic that does not match the negotiated traffic selector is then just dropped). Thus this is basically equivalent to including 0.0.0.0/0 in subnets
+ @string/profile_split_tunnelingv6_title
+ Specifies whether to block IPv6 traffic that\'s not destined for the VPN. Forces all IPv6 traffic via VPN (traffic that does not match the negotiated traffic selector is then just dropped). Thus this is basically equivalent to including ::/0 in subnets
+
+
diff --git a/src/frontends/android/app/src/main/res/values-ru/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values-ru/strings_managed_configuration.xml
new file mode 100644
index 000000000..6a58f62ad
--- /dev/null
+++ b/src/frontends/android/app/src/main/res/values-ru/strings_managed_configuration.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+ Allow profile creation
+ Specifies whether users are allowed to add their own profiles
+ Allow profile import
+ Specifies whether users are allowed to import their own profiles
+ Show existing profiles
+ Specifies whether users can continue to see and use their previously created profiles
+ Allow certificate import
+ Specifies whether users are allowed to import certificates
+ Allow modifying settings
+ Specifies whether users are allowed change global app settings
+ @string/pref_default_vpn_profile
+ Unique identifier of the VPN profile to use by default, use the value \"mru\" for the most recently used profile
+ @string/pref_power_whitelist_title
+ @string/pref_power_whitelist_summary
+ VPN profiles
+ Collection of managed VPN profiles
+ VPN profile
+ A managed VPN profile
+
+
+ Unique identifier
+ Unique identifier of the VPN profile. Version 4 UUIDs (random-generated) are recommended
+ @string/profile_name_label
+ @string/profile_name_hint
+ @string/profile_vpn_type_label
+ The type of client authentication used by the VPN profile
+ Apps allowed to use the VPN (Optional)
+ Space-separated list of package names; all other apps will not see/use the VPN
+ Apps excluded from using the VPN (Optional)
+ Space-separated list of package names of apps excluded from using the VPN; only used of allow list is empty
+ @string/profile_proposals_ike_label
+ @string/profile_proposals_ike_hint
+ @string/profile_proposals_esp_label
+ @string/profile_proposals_esp_hint
+ @string/profile_mtu_label
+ @string/profile_mtu_hint
+ @string/profile_nat_keepalive_label
+ @string/profile_nat_keepalive_hint
+ @string/profile_dns_servers_label
+ @string/profile_dns_servers_hint
+ @string/profile_ipv6_transport_label
+ @string/profile_ipv6_transport_hint
+
+
+ Remote
+ Specifies information about the server
+ @string/profile_gateway_label
+ @string/profile_gateway_hint
+ @string/profile_port_label
+ @string/profile_port_hint
+ @string/profile_remote_id_label
+ @string/profile_remote_id_hint
+ CA or server certificate (Optional)
+ Base64-encoded CA or server certificate. Is imported into the app, not the system keystore. If not set, automatic CA certificate selection is enabled
+ Send certificate requests
+ Specifies whether to send certificate requests for all installed or selected CA certificates. Disabling this may reduce the size of the IKE_AUTH message if the server does not support fragmentation. But it only works if the server doesn\'t require certificate requests to send back the server certificate
+ @string/profile_use_ocsp_label
+ @string/profile_use_ocsp_hint
+ @string/profile_use_crl_label
+ @string/profile_use_crl_hint
+ @string/profile_strict_revocation_label
+ @string/profile_strict_revocation_hint
+
+
+ Local
+ Specifies information about the client
+ Identity/username for EAP authentication (Optional)
+ If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it. If it is set, the user is not able to change it. In both cases the user may optionally enter the password
+ @string/profile_local_id_label
+ @string/profile_local_id_hint_user
+ @string/profile_user_certificate_label
+ Base64-encoded PKCS#12-container with the client certificate and private key and optional certificate chain (the latter might cause warnings on older Android releases, see Android VPN client configuration for details). Not necessary for username/password-based EAP authentication or if the user already has the certificate/key installed as it may be selected while importing the profile
+ User certificate password (Optional)
+ Password required to extract the private key of the PKCS#12-container for installation
+ @string/profile_rsa_pss_label
+ @string/profile_rsa_pss_hint
+
+
+ @string/profile_split_tunneling_label
+ @string/profile_split_tunneling_intro
+ @string/profile_included_subnets_label
+ @string/profile_included_subnets_hint
+ @string/profile_excluded_subnets_label
+ @string/profile_excluded_subnets_hint
+ @string/profile_split_tunnelingv4_title
+ Specifies whether to block IPv4 traffic that\'s not destined for the VPN. Forces all IPv4 traffic via VPN (traffic that does not match the negotiated traffic selector is then just dropped). Thus this is basically equivalent to including 0.0.0.0/0 in subnets
+ @string/profile_split_tunnelingv6_title
+ Specifies whether to block IPv6 traffic that\'s not destined for the VPN. Forces all IPv6 traffic via VPN (traffic that does not match the negotiated traffic selector is then just dropped). Thus this is basically equivalent to including ::/0 in subnets
+
+
diff --git a/src/frontends/android/app/src/main/res/values-uk/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values-uk/strings_managed_configuration.xml
new file mode 100644
index 000000000..6a58f62ad
--- /dev/null
+++ b/src/frontends/android/app/src/main/res/values-uk/strings_managed_configuration.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+ Allow profile creation
+ Specifies whether users are allowed to add their own profiles
+ Allow profile import
+ Specifies whether users are allowed to import their own profiles
+ Show existing profiles
+ Specifies whether users can continue to see and use their previously created profiles
+ Allow certificate import
+ Specifies whether users are allowed to import certificates
+ Allow modifying settings
+ Specifies whether users are allowed change global app settings
+ @string/pref_default_vpn_profile
+ Unique identifier of the VPN profile to use by default, use the value \"mru\" for the most recently used profile
+ @string/pref_power_whitelist_title
+ @string/pref_power_whitelist_summary
+ VPN profiles
+ Collection of managed VPN profiles
+ VPN profile
+ A managed VPN profile
+
+
+ Unique identifier
+ Unique identifier of the VPN profile. Version 4 UUIDs (random-generated) are recommended
+ @string/profile_name_label
+ @string/profile_name_hint
+ @string/profile_vpn_type_label
+ The type of client authentication used by the VPN profile
+ Apps allowed to use the VPN (Optional)
+ Space-separated list of package names; all other apps will not see/use the VPN
+ Apps excluded from using the VPN (Optional)
+ Space-separated list of package names of apps excluded from using the VPN; only used of allow list is empty
+ @string/profile_proposals_ike_label
+ @string/profile_proposals_ike_hint
+ @string/profile_proposals_esp_label
+ @string/profile_proposals_esp_hint
+ @string/profile_mtu_label
+ @string/profile_mtu_hint
+ @string/profile_nat_keepalive_label
+ @string/profile_nat_keepalive_hint
+ @string/profile_dns_servers_label
+ @string/profile_dns_servers_hint
+ @string/profile_ipv6_transport_label
+ @string/profile_ipv6_transport_hint
+
+
+ Remote
+ Specifies information about the server
+ @string/profile_gateway_label
+ @string/profile_gateway_hint
+ @string/profile_port_label
+ @string/profile_port_hint
+ @string/profile_remote_id_label
+ @string/profile_remote_id_hint
+ CA or server certificate (Optional)
+ Base64-encoded CA or server certificate. Is imported into the app, not the system keystore. If not set, automatic CA certificate selection is enabled
+ Send certificate requests
+ Specifies whether to send certificate requests for all installed or selected CA certificates. Disabling this may reduce the size of the IKE_AUTH message if the server does not support fragmentation. But it only works if the server doesn\'t require certificate requests to send back the server certificate
+ @string/profile_use_ocsp_label
+ @string/profile_use_ocsp_hint
+ @string/profile_use_crl_label
+ @string/profile_use_crl_hint
+ @string/profile_strict_revocation_label
+ @string/profile_strict_revocation_hint
+
+
+ Local
+ Specifies information about the client
+ Identity/username for EAP authentication (Optional)
+ If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it. If it is set, the user is not able to change it. In both cases the user may optionally enter the password
+ @string/profile_local_id_label
+ @string/profile_local_id_hint_user
+ @string/profile_user_certificate_label
+ Base64-encoded PKCS#12-container with the client certificate and private key and optional certificate chain (the latter might cause warnings on older Android releases, see Android VPN client configuration for details). Not necessary for username/password-based EAP authentication or if the user already has the certificate/key installed as it may be selected while importing the profile
+ User certificate password (Optional)
+ Password required to extract the private key of the PKCS#12-container for installation
+ @string/profile_rsa_pss_label
+ @string/profile_rsa_pss_hint
+
+
+ @string/profile_split_tunneling_label
+ @string/profile_split_tunneling_intro
+ @string/profile_included_subnets_label
+ @string/profile_included_subnets_hint
+ @string/profile_excluded_subnets_label
+ @string/profile_excluded_subnets_hint
+ @string/profile_split_tunnelingv4_title
+ Specifies whether to block IPv4 traffic that\'s not destined for the VPN. Forces all IPv4 traffic via VPN (traffic that does not match the negotiated traffic selector is then just dropped). Thus this is basically equivalent to including 0.0.0.0/0 in subnets
+ @string/profile_split_tunnelingv6_title
+ Specifies whether to block IPv6 traffic that\'s not destined for the VPN. Forces all IPv6 traffic via VPN (traffic that does not match the negotiated traffic selector is then just dropped). Thus this is basically equivalent to including ::/0 in subnets
+
+
diff --git a/src/frontends/android/app/src/main/res/values-zh-rCN/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/strings_managed_configuration.xml
new file mode 100644
index 000000000..6a58f62ad
--- /dev/null
+++ b/src/frontends/android/app/src/main/res/values-zh-rCN/strings_managed_configuration.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+ Allow profile creation
+ Specifies whether users are allowed to add their own profiles
+ Allow profile import
+ Specifies whether users are allowed to import their own profiles
+ Show existing profiles
+ Specifies whether users can continue to see and use their previously created profiles
+ Allow certificate import
+ Specifies whether users are allowed to import certificates
+ Allow modifying settings
+ Specifies whether users are allowed change global app settings
+ @string/pref_default_vpn_profile
+ Unique identifier of the VPN profile to use by default, use the value \"mru\" for the most recently used profile
+ @string/pref_power_whitelist_title
+ @string/pref_power_whitelist_summary
+ VPN profiles
+ Collection of managed VPN profiles
+ VPN profile
+ A managed VPN profile
+
+
+ Unique identifier
+ Unique identifier of the VPN profile. Version 4 UUIDs (random-generated) are recommended
+ @string/profile_name_label
+ @string/profile_name_hint
+ @string/profile_vpn_type_label
+ The type of client authentication used by the VPN profile
+ Apps allowed to use the VPN (Optional)
+ Space-separated list of package names; all other apps will not see/use the VPN
+ Apps excluded from using the VPN (Optional)
+ Space-separated list of package names of apps excluded from using the VPN; only used of allow list is empty
+ @string/profile_proposals_ike_label
+ @string/profile_proposals_ike_hint
+ @string/profile_proposals_esp_label
+ @string/profile_proposals_esp_hint
+ @string/profile_mtu_label
+ @string/profile_mtu_hint
+ @string/profile_nat_keepalive_label
+ @string/profile_nat_keepalive_hint
+ @string/profile_dns_servers_label
+ @string/profile_dns_servers_hint
+ @string/profile_ipv6_transport_label
+ @string/profile_ipv6_transport_hint
+
+
+ Remote
+ Specifies information about the server
+ @string/profile_gateway_label
+ @string/profile_gateway_hint
+ @string/profile_port_label
+ @string/profile_port_hint
+ @string/profile_remote_id_label
+ @string/profile_remote_id_hint
+ CA or server certificate (Optional)
+ Base64-encoded CA or server certificate. Is imported into the app, not the system keystore. If not set, automatic CA certificate selection is enabled
+ Send certificate requests
+ Specifies whether to send certificate requests for all installed or selected CA certificates. Disabling this may reduce the size of the IKE_AUTH message if the server does not support fragmentation. But it only works if the server doesn\'t require certificate requests to send back the server certificate
+ @string/profile_use_ocsp_label
+ @string/profile_use_ocsp_hint
+ @string/profile_use_crl_label
+ @string/profile_use_crl_hint
+ @string/profile_strict_revocation_label
+ @string/profile_strict_revocation_hint
+
+
+ Local
+ Specifies information about the client
+ Identity/username for EAP authentication (Optional)
+ If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it. If it is set, the user is not able to change it. In both cases the user may optionally enter the password
+ @string/profile_local_id_label
+ @string/profile_local_id_hint_user
+ @string/profile_user_certificate_label
+ Base64-encoded PKCS#12-container with the client certificate and private key and optional certificate chain (the latter might cause warnings on older Android releases, see Android VPN client configuration for details). Not necessary for username/password-based EAP authentication or if the user already has the certificate/key installed as it may be selected while importing the profile
+ User certificate password (Optional)
+ Password required to extract the private key of the PKCS#12-container for installation
+ @string/profile_rsa_pss_label
+ @string/profile_rsa_pss_hint
+
+
+ @string/profile_split_tunneling_label
+ @string/profile_split_tunneling_intro
+ @string/profile_included_subnets_label
+ @string/profile_included_subnets_hint
+ @string/profile_excluded_subnets_label
+ @string/profile_excluded_subnets_hint
+ @string/profile_split_tunnelingv4_title
+ Specifies whether to block IPv4 traffic that\'s not destined for the VPN. Forces all IPv4 traffic via VPN (traffic that does not match the negotiated traffic selector is then just dropped). Thus this is basically equivalent to including 0.0.0.0/0 in subnets
+ @string/profile_split_tunnelingv6_title
+ Specifies whether to block IPv6 traffic that\'s not destined for the VPN. Forces all IPv6 traffic via VPN (traffic that does not match the negotiated traffic selector is then just dropped). Thus this is basically equivalent to including ::/0 in subnets
+
+
diff --git a/src/frontends/android/app/src/main/res/values-zh-rTW/strings_managed_configuration.xml b/src/frontends/android/app/src/main/res/values-zh-rTW/strings_managed_configuration.xml
new file mode 100644
index 000000000..6a58f62ad
--- /dev/null
+++ b/src/frontends/android/app/src/main/res/values-zh-rTW/strings_managed_configuration.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+ Allow profile creation
+ Specifies whether users are allowed to add their own profiles
+ Allow profile import
+ Specifies whether users are allowed to import their own profiles
+ Show existing profiles
+ Specifies whether users can continue to see and use their previously created profiles
+ Allow certificate import
+ Specifies whether users are allowed to import certificates
+ Allow modifying settings
+ Specifies whether users are allowed change global app settings
+ @string/pref_default_vpn_profile
+ Unique identifier of the VPN profile to use by default, use the value \"mru\" for the most recently used profile
+ @string/pref_power_whitelist_title
+ @string/pref_power_whitelist_summary
+ VPN profiles
+ Collection of managed VPN profiles
+ VPN profile
+ A managed VPN profile
+
+
+ Unique identifier
+ Unique identifier of the VPN profile. Version 4 UUIDs (random-generated) are recommended
+ @string/profile_name_label
+ @string/profile_name_hint
+ @string/profile_vpn_type_label
+ The type of client authentication used by the VPN profile
+ Apps allowed to use the VPN (Optional)
+ Space-separated list of package names; all other apps will not see/use the VPN
+ Apps excluded from using the VPN (Optional)
+ Space-separated list of package names of apps excluded from using the VPN; only used of allow list is empty
+ @string/profile_proposals_ike_label
+ @string/profile_proposals_ike_hint
+ @string/profile_proposals_esp_label
+ @string/profile_proposals_esp_hint
+ @string/profile_mtu_label
+ @string/profile_mtu_hint
+ @string/profile_nat_keepalive_label
+ @string/profile_nat_keepalive_hint
+ @string/profile_dns_servers_label
+ @string/profile_dns_servers_hint
+ @string/profile_ipv6_transport_label
+ @string/profile_ipv6_transport_hint
+
+
+ Remote
+ Specifies information about the server
+ @string/profile_gateway_label
+ @string/profile_gateway_hint
+ @string/profile_port_label
+ @string/profile_port_hint
+ @string/profile_remote_id_label
+ @string/profile_remote_id_hint
+ CA or server certificate (Optional)
+ Base64-encoded CA or server certificate. Is imported into the app, not the system keystore. If not set, automatic CA certificate selection is enabled
+ Send certificate requests
+ Specifies whether to send certificate requests for all installed or selected CA certificates. Disabling this may reduce the size of the IKE_AUTH message if the server does not support fragmentation. But it only works if the server doesn\'t require certificate requests to send back the server certificate
+ @string/profile_use_ocsp_label
+ @string/profile_use_ocsp_hint
+ @string/profile_use_crl_label
+ @string/profile_use_crl_hint
+ @string/profile_strict_revocation_label
+ @string/profile_strict_revocation_hint
+
+
+ Local
+ Specifies information about the client
+ Identity/username for EAP authentication (Optional)
+ If this is required (for username/password-based EAP authentication) but not configured here, the user is prompted for it. If it is set, the user is not able to change it. In both cases the user may optionally enter the password
+ @string/profile_local_id_label
+ @string/profile_local_id_hint_user
+ @string/profile_user_certificate_label
+ Base64-encoded PKCS#12-container with the client certificate and private key and optional certificate chain (the latter might cause warnings on older Android releases, see Android VPN client configuration for details). Not necessary for username/password-based EAP authentication or if the user already has the certificate/key installed as it may be selected while importing the profile
+ User certificate password (Optional)
+ Password required to extract the private key of the PKCS#12-container for installation
+ @string/profile_rsa_pss_label
+ @string/profile_rsa_pss_hint
+
+
+ @string/profile_split_tunneling_label
+ @string/profile_split_tunneling_intro
+ @string/profile_included_subnets_label
+ @string/profile_included_subnets_hint
+ @string/profile_excluded_subnets_label
+ @string/profile_excluded_subnets_hint
+ @string/profile_split_tunnelingv4_title
+ Specifies whether to block IPv4 traffic that\'s not destined for the VPN. Forces all IPv4 traffic via VPN (traffic that does not match the negotiated traffic selector is then just dropped). Thus this is basically equivalent to including 0.0.0.0/0 in subnets
+ @string/profile_split_tunnelingv6_title
+ Specifies whether to block IPv6 traffic that\'s not destined for the VPN. Forces all IPv6 traffic via VPN (traffic that does not match the negotiated traffic selector is then just dropped). Thus this is basically equivalent to including ::/0 in subnets
+
+
From 7db629e4bc8623d3a5d0b6393fe6c2838ab184c3 Mon Sep 17 00:00:00 2001
From: Tobias Brunner
Date: Wed, 21 Feb 2024 08:29:54 +0100
Subject: [PATCH 45/45] android: New release after adding support for managed
configurations
---
src/frontends/android/app/build.gradle | 5 +++--
.../app/src/main/play/listings/de-DE/full-description.txt | 1 +
.../app/src/main/play/listings/en-US/full-description.txt | 1 +
.../app/src/main/play/release-notes/de-DE/default.txt | 6 ++----
.../app/src/main/play/release-notes/en-US/default.txt | 6 ++----
5 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle
index 43135e444..44b9695a3 100644
--- a/src/frontends/android/app/build.gradle
+++ b/src/frontends/android/app/build.gradle
@@ -8,8 +8,9 @@ android {
compileSdk 34
minSdkVersion 21
targetSdkVersion 33
- versionCode 80
- versionName "2.4.2"
+
+ versionCode 81
+ versionName "2.5.0"
externalNativeBuild {
ndkBuild {
diff --git a/src/frontends/android/app/src/main/play/listings/de-DE/full-description.txt b/src/frontends/android/app/src/main/play/listings/de-DE/full-description.txt
index e4c741d85..3922313c3 100644
--- a/src/frontends/android/app/src/main/play/listings/de-DE/full-description.txt
+++ b/src/frontends/android/app/src/main/play/listings/de-DE/full-description.txt
@@ -16,6 +16,7 @@ Dies ist die offizielle Android-Portierung der populären strongSwan VPN-Lösung
Die IPsec-Implementierung unterstützt derzeit die AES-CBC, AES-GCM, ChaCha20/Poly1305 und SHA1/SHA2-Algorithmen
Passwörter werden zurzeit als Klartext in der Datenbank gespeichert (nur wenn diese mit einem Profil gespeichert werden)
VPN Profile können von Dateien importiert werden
+
Unterstützt verwaltete Konfigurationen via Enterprise Mobility Management (EMM)
Details und ein Changelog sind in unserer Dokumentation zu finden: https://docs.strongswan.org/docs/5.9/os/androidVpnClient.html
diff --git a/src/frontends/android/app/src/main/play/listings/en-US/full-description.txt b/src/frontends/android/app/src/main/play/listings/en-US/full-description.txt
index 1966148e6..57c2fcfa6 100644
--- a/src/frontends/android/app/src/main/play/listings/en-US/full-description.txt
+++ b/src/frontends/android/app/src/main/play/listings/en-US/full-description.txt
@@ -16,6 +16,7 @@ Official Android port of the popular strongSwan VPN solution.
The IPsec implementation currently supports the AES-CBC, AES-GCM, ChaCha20/Poly1305 and SHA1/SHA2 algorithms
Passwords are currently stored as cleartext in the database (only if stored with a profile)
VPN profiles may be imported from files
+
Supports managed configurations via enterprise mobility management (EMM)
Details and a changelog can be found in our documentation: https://docs.strongswan.org/docs/5.9/os/androidVpnClient.html
diff --git a/src/frontends/android/app/src/main/play/release-notes/de-DE/default.txt b/src/frontends/android/app/src/main/play/release-notes/de-DE/default.txt
index 55437a9cf..0aa929312 100644
--- a/src/frontends/android/app/src/main/play/release-notes/de-DE/default.txt
+++ b/src/frontends/android/app/src/main/play/release-notes/de-DE/default.txt
@@ -1,5 +1,3 @@
-# 2.4.2 #
+# 2.5.0 #
-- Ziel-SDK auf Android 13 erhöht und frage um Erlaubnis, um Status-Mitteilung anzuzeigen
-- Hardwarebeschleunigung in OpenSSL aktiviert
-- Verwendet einen stabileren Ansatz, um die Quell-IP zu ermitteln
+- Unterstützung für verwaltete Konfigurationen via Enterprise Mobility Management (EMM)
diff --git a/src/frontends/android/app/src/main/play/release-notes/en-US/default.txt b/src/frontends/android/app/src/main/play/release-notes/en-US/default.txt
index e57010a8e..c0fe425c7 100644
--- a/src/frontends/android/app/src/main/play/release-notes/en-US/default.txt
+++ b/src/frontends/android/app/src/main/play/release-notes/en-US/default.txt
@@ -1,5 +1,3 @@
-# 2.4.2 #
+# 2.5.0 #
-- Increased target SDK to Android 13 and ask for permission to show status notification
-- Enable hardware acceleration in OpenSSL
-- Use a more stable approach to determine source IP
+- Support for managed configurations via enterprise mobility management (EMM)