Compare commits

...

22 Commits

Author SHA1 Message Date
Tobias Brunner e6a904de69 testing: Fix build of tkm and tkm-rpc 1 year ago
Tobias Brunner ce607d774b github: Use latest version of SonarScanner 1 year ago
Tobias Brunner 1c4e134f26 kernel-netlink: Initialize ifreq structs when detecting offload capability 2 years ago
Noel Kuntze 5d6e69a6e4 gitignore: Add nbproject/ 2 years ago
Tobias Brunner 4ae9b482f2 Ignore android-* tags when using `git describe` 2 years ago
Tobias Brunner 1aa68a78f8 Merge branch 'android-updates' 2 years ago
Tobias Brunner 358d3d0ba1 android: New release after adding button to install PKCS#12 and SDK update 2 years ago
Tobias Brunner a14337bcde android: Request QUERY_ALL_PACKAGES permission 2 years ago
Tobias Brunner 3eadd0fc3a android: Add Triple-T metadata for F-Droid 2 years ago
Tobias Brunner 2f1bf11dca android: Add a button to install user certificates 2 years ago
Tobias Brunner b3cdbe6693 android: Replace deprecated Switch with SwitchCompat 2 years ago
Tobias Brunner 26354d0aba android: Replace deprecated AsyncTask instances 2 years ago
Tobias Brunner a8cc146240 android: Migrate from deprecated ViewPager to ViewPager2 for CA cert lists 2 years ago
Tobias Brunner c976165533 android: Replace deprecated FileObserver() constructor with newer SDKs 2 years ago
Tobias Brunner dc351a30e1 android: Replace usage of deprecated Handler() constructor 2 years ago
Tobias Brunner 93c494e295 android: Replace deprecated onActivityCreated() with onViewCreated() 2 years ago
Tobias Brunner 7cd50aeb64 android: Replace deprecated startActivityForResult/onActivityResult usage 2 years ago
Tobias Brunner a885e38265 android: Set compile-/targetSdkVersion to 30 2 years ago
Tobias Brunner 6f3725ea8b android: Replace jcenter with mavenCentral repository 2 years ago
Tobias Brunner 5831009941 android: Update dependencies 2 years ago
Tobias Brunner 44e16a63ef android: Update Gradle plugin 2 years ago
Tobias Brunner 2f9114bce1 Use wolfSSL 4.8.0 for tests 2 years ago
  1. 2
      .github/workflows/sonarcloud.yml
  2. 1
      .gitignore
  3. 2
      scripts/git-version
  4. 4
      scripts/test.sh
  5. 22
      src/frontends/android/app/build.gradle
  6. 3
      src/frontends/android/app/src/main/AndroidManifest.xml
  7. 2
      src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
  8. 27
      src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java
  9. 6
      src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java
  10. 31
      src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java
  11. 20
      src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java
  12. 6
      src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java
  13. 6
      src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java
  14. 5
      src/frontends/android/app/src/main/java/org/strongswan/android/ui/SelectedApplicationsListFragment.java
  15. 37
      src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java
  16. 6
      src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java
  17. 82
      src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java
  18. 95
      src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileControlActivity.java
  19. 181
      src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java
  20. 56
      src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileImportActivity.java
  21. 8
      src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java
  22. 1
      src/frontends/android/app/src/main/play/default-language.txt
  23. 41
      src/frontends/android/app/src/main/play/listings/de-DE/full-description.txt
  24. 1
      src/frontends/android/app/src/main/play/listings/de-DE/short-description.txt
  25. 41
      src/frontends/android/app/src/main/play/listings/en-US/full-description.txt
  26. BIN
      src/frontends/android/app/src/main/play/listings/en-US/graphics/feature-graphic/feature.png
  27. BIN
      src/frontends/android/app/src/main/play/listings/en-US/graphics/icon/icon.png
  28. BIN
      src/frontends/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png
  29. BIN
      src/frontends/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png
  30. BIN
      src/frontends/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png
  31. 1
      src/frontends/android/app/src/main/play/listings/en-US/short-description.txt
  32. 1
      src/frontends/android/app/src/main/play/listings/en-US/title.txt
  33. 11
      src/frontends/android/app/src/main/play/release-notes/de-DE/default.txt
  34. 11
      src/frontends/android/app/src/main/play/release-notes/en-US/default.txt
  35. 20
      src/frontends/android/app/src/main/res/layout/profile_detail_view.xml
  36. 2
      src/frontends/android/app/src/main/res/layout/trusted_certificates_activity.xml
  37. 1
      src/frontends/android/app/src/main/res/values-de/strings.xml
  38. 1
      src/frontends/android/app/src/main/res/values-pl/strings.xml
  39. 1
      src/frontends/android/app/src/main/res/values-ru/strings.xml
  40. 1
      src/frontends/android/app/src/main/res/values-ua/strings.xml
  41. 1
      src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml
  42. 1
      src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml
  43. 1
      src/frontends/android/app/src/main/res/values/strings.xml
  44. 6
      src/frontends/android/build.gradle
  45. 2
      src/frontends/android/gradle/wrapper/gradle-wrapper.properties
  46. 4
      src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c
  47. 2
      testing/scripts/build-strongswan
  48. 2
      testing/scripts/recipes/006_tkm-rpc.mk
  49. 2
      testing/scripts/recipes/010_tkm.mk
  50. 2
      testing/scripts/recipes/012_wolfssl.mk

@ -46,7 +46,7 @@ jobs:
# for C builds, so we follow the "any CI" instructions
- name: Install sonar-scanner
env:
SONAR_SCANNER_VERSION: 4.4.0.2170
SONAR_SCANNER_VERSION: 4.6.2.2472
run: |
export SONAR_SCANNER_HOME=$HOME/.sonar/sonar-scanner-$SONAR_SCANNER_VERSION-linux
curl --create-dirs -sSLo $HOME/.sonar/sonar-scanner.zip https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip

1
.gitignore vendored

@ -52,3 +52,4 @@ coverage/
/*.files
/*.includes
test-driver
nbproject/

@ -6,7 +6,7 @@ TARBALL=$SRCDIR/.tarball-git-version
if test -f $TARBALL; then
V=$(cat $TARBALL)
elif test -d $SRCDIR/.git; then
V=$(git -C $SRCDIR describe --tags HEAD 2>/dev/null)
V=$(git -C $SRCDIR describe --exclude 'android-*' --tags HEAD 2>/dev/null)
fi
if test -z "$V"; then

@ -37,7 +37,7 @@ build_botan()
build_wolfssl()
{
WOLFSSL_REV=0caf3ba456f1 # v4.7.1r + SHA-3 fix
WOLFSSL_REV=v4.8.0-stable
WOLFSSL_DIR=$DEPS_BUILD_DIR/wolfssl
if test -d "$WOLFSSL_DIR"; then
@ -468,7 +468,7 @@ sonarcloud)
-Dsonar.projectKey=${SONAR_PROJECT} \
-Dsonar.organization=${SONAR_ORGANIZATION} \
-Dsonar.login=${SONAR_TOKEN} \
-Dsonar.projectVersion=$(git describe)+${BUILD_NUMBER} \
-Dsonar.projectVersion=$(git describe --exclude 'android-*')+${BUILD_NUMBER} \
-Dsonar.sources=. \
-Dsonar.cfamily.threads=2 \
-Dsonar.cfamily.cache.enabled=true \

@ -1,14 +1,14 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
compileSdkVersion 30
defaultConfig {
applicationId "org.strongswan.android"
minSdkVersion 15
targetSdkVersion 29
versionCode 74
versionName "2.3.2"
targetSdkVersion 30
versionCode 75
versionName "2.3.3"
}
sourceSets.main {
@ -46,13 +46,13 @@ android {
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.2.1'
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.28.2'
testImplementation 'org.powermock:powermock-core:2.0.2'
testImplementation 'org.powermock:powermock-module-junit4:2.0.2'
testImplementation 'org.powermock:powermock-api-mockito2:2.0.2'
implementation 'com.google.android.material:material:1.4.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:3.11.2'
testImplementation 'org.powermock:powermock-core:2.0.9'
testImplementation 'org.powermock:powermock-module-junit4:2.0.9'
testImplementation 'org.powermock:powermock-api-mockito2:2.0.9'
}

@ -24,6 +24,9 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<!-- necessary to allow users to select ex-/included apps and EAP-TNC -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<application
android:name=".logic.StrongSwanApplication"

@ -192,7 +192,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe
mAppDir = getFilesDir().getAbsolutePath();
/* handler used to do changes in the main UI thread */
mHandler = new Handler();
mHandler = new Handler(getMainLooper());
mDataSource = new VpnProfileDataSource(this);
mDataSource.open();

@ -16,6 +16,9 @@
package org.strongswan.android.logic;
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;
@ -23,10 +26,16 @@ 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
{
private static Context mContext;
private final ExecutorService mExecutorService = Executors.newFixedThreadPool(4);
private final Handler mMainHandler = HandlerCompat.createAsync(Looper.getMainLooper());
static {
Security.addProvider(new LocalCertificateKeyStoreProvider());
@ -48,6 +57,24 @@ public class StrongSwanApplication extends Application
return StrongSwanApplication.mContext;
}
/**
* Returns a thread pool to run tasks in separate threads
* @return thread pool
*/
public Executor getExecutor()
{
return mExecutorService;
}
/**
* Returns a handler to execute stuff by the main thread.
* @return handler
*/
public Handler getHandler()
{
return mMainHandler;
}
/*
* The libraries are extracted to /data/data/org.strongswan.android/...
* during installation. On newer releases most are loaded in JNI_OnLoad.

@ -22,6 +22,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
@ -107,7 +108,7 @@ public class VpnStateService extends Service
{
/* this handler allows us to notify listeners from the UI thread and
* not from the threads that actually report any state changes */
mHandler = new RetryHandler(this);
mHandler = new RetryHandler(getMainLooper(), this);
}
@Override
@ -536,8 +537,9 @@ public class VpnStateService extends Service
private static class RetryHandler extends Handler {
WeakReference<VpnStateService> mService;
public RetryHandler(VpnStateService service)
public RetryHandler(Looper looper, VpnStateService service)
{
super(looper);
mService = new WeakReference<>(service);
}

@ -16,9 +16,11 @@
package org.strongswan.android.ui;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.FileObserver;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -36,6 +38,7 @@ import java.io.StringReader;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;
public class LogFragment extends Fragment
@ -55,9 +58,17 @@ public class LogFragment extends Fragment
mLogFilePath = getActivity().getFilesDir() + File.separator + CharonVpnService.LOG_FILE;
mLogHandler = new Handler();
mLogHandler = new Handler(Looper.getMainLooper());
mDirectoryObserver = new LogDirectoryObserver(getActivity().getFilesDir().getAbsolutePath());
File logdir = getActivity().getFilesDir();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
{
mDirectoryObserver = new LogDirectoryObserver(logdir);
}
else
{
mDirectoryObserver = new LogDirectoryObserver(logdir.getAbsolutePath());
}
}
@Override
@ -223,14 +234,20 @@ public class LogFragment extends Fragment
*/
private class LogDirectoryObserver extends FileObserver
{
private final File mFile;
private long mSize;
private static final int mMask = FileObserver.CREATE | FileObserver.MODIFY | FileObserver.DELETE;
private final File mFile = new File(mLogFilePath);
private long mSize = mFile.length();
@SuppressWarnings("deprecation")
public LogDirectoryObserver(String path)
{
super(path, FileObserver.CREATE | FileObserver.MODIFY | FileObserver.DELETE);
mFile = new File(mLogFilePath);
mSize = mFile.length();
super(path, mMask);
}
@RequiresApi(api = Build.VERSION_CODES.Q)
public LogDirectoryObserver(File path)
{
super(path, mMask);
}
@Override

@ -20,7 +20,6 @@ package org.strongswan.android.ui;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.text.format.Formatter;
@ -30,6 +29,7 @@ import android.widget.Toast;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfile;
import org.strongswan.android.logic.StrongSwanApplication;
import org.strongswan.android.logic.TrustedCertificateManager;
import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener;
@ -68,8 +68,10 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
bar.setDisplayShowTitleEnabled(false);
bar.setIcon(R.mipmap.ic_app);
/* load CA certificates in a background task */
new LoadCertificatesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
/* load CA certificates in a background thread */
((StrongSwanApplication)getApplication()).getExecutor().execute(() -> {
TrustedCertificateManager.getInstance().load();
});
}
@Override
@ -157,18 +159,6 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec
dialog.show(this.getSupportFragmentManager(), DIALOG_TAG);
}
/**
* Class that loads the cached CA certificates.
*/
private class LoadCertificatesTask extends AsyncTask<Void, Void, TrustedCertificateManager>
{
@Override
protected TrustedCertificateManager doInBackground(Void... params)
{
return TrustedCertificateManager.getInstance().load();
}
}
/**
* Dismiss dialog if shown
*/

@ -26,6 +26,8 @@ import android.widget.TextView;
import org.strongswan.android.R;
import org.strongswan.android.logic.imc.RemediationInstruction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.ListFragment;
public class RemediationInstructionFragment extends ListFragment
@ -49,9 +51,9 @@ public class RemediationInstructionFragment extends ListFragment
}
@Override
public void onActivityCreated(Bundle savedInstanceState)
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState != null)
{

@ -26,6 +26,8 @@ import org.strongswan.android.ui.adapter.RemediationInstructionAdapter;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.ListFragment;
public class RemediationInstructionsFragment extends ListFragment
@ -46,9 +48,9 @@ public class RemediationInstructionsFragment extends ListFragment
}
@Override
public void onActivityCreated(Bundle savedInstanceState)
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState != null)
{

@ -40,6 +40,7 @@ import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.ListFragment;
@ -53,9 +54,9 @@ public class SelectedApplicationsListFragment extends ListFragment implements Lo
private SortedSet<String> mSelection;
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState)
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
super.onViewCreated(view, savedInstanceState);
setHasOptionsMenu(true);
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

@ -16,7 +16,6 @@
package org.strongswan.android.ui;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
@ -37,6 +36,8 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDialogFragment;
@ -44,10 +45,21 @@ import androidx.fragment.app.FragmentTransaction;
public class TrustedCertificateImportActivity extends AppCompatActivity
{
private static final int OPEN_DOCUMENT = 0;
private static final String DIALOG_TAG = "Dialog";
private Uri mCertificateUri;
private final ActivityResultLauncher<Intent> mOpenDocument = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK && result.getData() != null)
{
mCertificateUri = result.getData().getData();
return;
}
finish();
}
);
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public void onCreate(Bundle savedInstanceState)
@ -71,7 +83,7 @@ public class TrustedCertificateImportActivity extends AppCompatActivity
openIntent.setType("*/*");
try
{
startActivityForResult(openIntent, OPEN_DOCUMENT);
mOpenDocument.launch(openIntent);
}
catch (ActivityNotFoundException e)
{ /* some devices are unable to browse for files */
@ -81,23 +93,6 @@ public class TrustedCertificateImportActivity extends AppCompatActivity
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode)
{
case OPEN_DOCUMENT:
if (resultCode == Activity.RESULT_OK && data != null)
{
mCertificateUri = data.getData();
return;
}
finish();
return;
}
}
@Override
protected void onPostResume()
{
@ -214,7 +209,7 @@ public class TrustedCertificateImportActivity extends AppCompatActivity
if (activity.storeCertificate(certificate))
{
Toast.makeText(getActivity(), R.string.cert_imported_successfully, Toast.LENGTH_LONG).show();
getActivity().setResult(Activity.RESULT_OK);
getActivity().setResult(RESULT_OK);
}
else
{

@ -41,6 +41,8 @@ import java.util.Map.Entry;
import java.util.Observable;
import java.util.Observer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.ListFragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.app.LoaderManager.LoaderCallbacks;
@ -63,9 +65,9 @@ public class TrustedCertificateListFragment extends ListFragment implements Load
}
@Override
public void onActivityCreated(Bundle savedInstanceState)
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
super.onViewCreated(view, savedInstanceState);
setHasOptionsMenu(true);
setEmptyText(getString(R.string.no_certificates));

@ -15,8 +15,6 @@
package org.strongswan.android.ui;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
@ -24,6 +22,7 @@ import android.view.Menu;
import android.view.MenuItem;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import org.strongswan.android.R;
import org.strongswan.android.data.VpnProfileDataSource;
@ -34,22 +33,34 @@ import org.strongswan.android.ui.CertificateDeleteConfirmationDialog.OnCertifica
import java.security.KeyStore;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
public class TrustedCertificatesActivity extends AppCompatActivity implements TrustedCertificateListFragment.OnTrustedCertificateSelectedListener, OnCertificateDeleteListener
{
public static final String SELECT_CERTIFICATE = "org.strongswan.android.action.SELECT_CERTIFICATE";
private static final String DIALOG_TAG = "Dialog";
private static final int IMPORT_CERTIFICATE = 0;
private TrustedCertificatesPagerAdapter mAdapter;
private ViewPager mPager;
private ViewPager2 mPager;
private boolean mSelect;
private final ActivityResultLauncher<Intent> mImportCertificate = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK)
{
reloadCertificates();
}
}
);
@Override
public void onCreate(Bundle savedInstanceState)
{
@ -59,13 +70,15 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
mAdapter = new TrustedCertificatesPagerAdapter(getSupportFragmentManager(), this);
mAdapter = new TrustedCertificatesPagerAdapter(this);
mPager = (ViewPager)findViewById(R.id.viewpager);
mPager = (ViewPager2)findViewById(R.id.viewpager);
mPager.setAdapter(mAdapter);
TabLayout tabs = (TabLayout)findViewById(R.id.tabs);
tabs.setupWithViewPager(mPager);
new TabLayoutMediator(tabs, mPager, (tab, position) -> {
tab.setText(mAdapter.getTitle(position));
}).attach();
mSelect = SELECT_CERTIFICATE.equals(getIntent().getAction());
}
@ -100,27 +113,12 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr
return true;
case R.id.menu_import_certificate:
Intent intent = new Intent(this, TrustedCertificateImportActivity.class);
startActivityForResult(intent, IMPORT_CERTIFICATE);
mImportCertificate.launch(intent);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
switch (requestCode)
{
case IMPORT_CERTIFICATE:
if (resultCode == Activity.RESULT_OK)
{
reloadCertificates();
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onTrustedCertificateSelected(TrustedCertificateEntry selected)
{
@ -129,7 +127,7 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr
/* the user selected a certificate, return to calling activity */
Intent intent = new Intent();
intent.putExtra(VpnProfileDataSource.KEY_CERTIFICATE, selected.getAlias());
setResult(Activity.RESULT_OK, intent);
setResult(RESULT_OK, intent);
finish();
}
else if (mAdapter.getSource(mPager.getCurrentItem()) == TrustedCertificateSource.LOCAL)
@ -163,28 +161,21 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr
TrustedCertificateManager.getInstance().reset();
}
public static class TrustedCertificatesPagerAdapter extends FragmentPagerAdapter
public static class TrustedCertificatesPagerAdapter extends FragmentStateAdapter
{
private TrustedCertificatesTab mTabs[];
public TrustedCertificatesPagerAdapter(FragmentManager fm, Context context)
public TrustedCertificatesPagerAdapter(@NonNull FragmentActivity fragmentActivity)
{
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
super(fragmentActivity);
mTabs = new TrustedCertificatesTab[]{
new TrustedCertificatesTab(context.getString(R.string.system_tab), TrustedCertificateSource.SYSTEM),
new TrustedCertificatesTab(context.getString(R.string.user_tab), TrustedCertificateSource.USER),
new TrustedCertificatesTab(context.getString(R.string.local_tab), TrustedCertificateSource.LOCAL),
new TrustedCertificatesTab(fragmentActivity.getString(R.string.system_tab), TrustedCertificateSource.SYSTEM),
new TrustedCertificatesTab(fragmentActivity.getString(R.string.user_tab), TrustedCertificateSource.USER),
new TrustedCertificatesTab(fragmentActivity.getString(R.string.local_tab), TrustedCertificateSource.LOCAL),
};
}
@Override
public int getCount()
{
return mTabs.length;
}
@Override
public CharSequence getPageTitle(int position)
public CharSequence getTitle(int position)
{
return mTabs[position].getTitle();
}
@ -195,7 +186,14 @@ public class TrustedCertificatesActivity extends AppCompatActivity implements Tr
}
@Override
public Fragment getItem(int position)
public int getItemCount()
{
return mTabs.length;
}
@NonNull
@Override
public Fragment createFragment(int position)
{
TrustedCertificateListFragment fragment = new TrustedCertificateListFragment();
Bundle args = new Bundle();

@ -44,6 +44,8 @@ import org.strongswan.android.logic.VpnStateService;
import org.strongswan.android.logic.VpnStateService.State;
import org.strongswan.android.utils.Constants;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
@ -59,8 +61,6 @@ public class VpnProfileControlActivity extends AppCompatActivity
public static final String DISCONNECT = "org.strongswan.android.action.DISCONNECT";
public static final String EXTRA_VPN_PROFILE_ID = "org.strongswan.android.VPN_PROFILE_ID";
private static final int PREPARE_VPN_SERVICE = 0;
private static final int ADD_TO_POWER_WHITELIST = 1;
private static final String WAITING_FOR_RESULT = "WAITING_FOR_RESULT";
private static final String PROFILE_NAME = "PROFILE_NAME";
private static final String PROFILE_REQUIRES_PASSWORD = "REQUIRES_PASSWORD";
@ -87,6 +87,33 @@ public class VpnProfileControlActivity extends AppCompatActivity
}
};
private final ActivityResultLauncher<Intent> mPrepareVpnService = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
mWaitingForResult = false;
if (result.getResultCode() == RESULT_OK && mProfileInfo != null)
{
onVpnServicePrepared();
}
else
{ /* this happens if the always-on VPN feature is activated by a different app or the user declined */
VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_no_permission);
}
}
);
private final ActivityResultLauncher<Intent> mAddToPowerWhitelist = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
mWaitingForResult = false;
if (mProfileInfo != null && mService != null)
{
mService.connect(mProfileInfo, true);
}
finish();
}
);
@Override
public void onCreate(Bundle savedInstanceState)
{
@ -173,7 +200,7 @@ public class VpnProfileControlActivity extends AppCompatActivity
try
{
mWaitingForResult = true;
startActivityForResult(intent, PREPARE_VPN_SERVICE);
mPrepareVpnService.launch(intent);
}
catch (ActivityNotFoundException ex)
{
@ -187,7 +214,23 @@ public class VpnProfileControlActivity extends AppCompatActivity
}
else
{ /* user already granted permission to use VpnService */
onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null);
onVpnServicePrepared();
}
}
/**
* Called once the VpnService has been prepared and permission has been granted
* by the user.
*/
protected void onVpnServicePrepared()
{
if (checkPowerWhitelist())
{
if (mService != null)
{
mService.connect(mProfileInfo, true);
}
finish();
}
}
@ -219,48 +262,6 @@ public class VpnProfileControlActivity extends AppCompatActivity
return true;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
switch (requestCode)
{
case PREPARE_VPN_SERVICE:
mWaitingForResult = false;
if (resultCode == RESULT_OK && mProfileInfo != null)
{
if (checkPowerWhitelist())
{
if (mService != null)
{
mService.connect(mProfileInfo, true);
}
finish();
}
}
else
{ /* this happens if the always-on VPN feature is activated by a different app or the user declined */
if (getSupportFragmentManager().isStateSaved())
{ /* onActivityResult() might be called when we aren't active anymore e.g. if the
* user pressed the home button, if the activity is started again we land here
* before onNewIntent() is called */
return;
}
VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_no_permission);
}
break;
case ADD_TO_POWER_WHITELIST:
mWaitingForResult = false;
if (mProfileInfo != null && mService != null)
{
mService.connect(mProfileInfo, true);
}
finish();
break;
default:
super.onActivityResult(requestCode, resultCode, data);
}
}
/**
* Check if we are currently connected to a VPN connection
*
@ -597,7 +598,7 @@ public class VpnProfileControlActivity extends AppCompatActivity
activity.mWaitingForResult = true;
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
Uri.parse("package:" + activity.getPackageName()));
activity.startActivityForResult(intent, ADD_TO_POWER_WHITELIST);
activity.mAddToPowerWhitelist.launch(intent);
}).create();
}

@ -21,9 +21,9 @@ import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.security.KeyChainException;
@ -43,6 +43,7 @@ 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;
@ -50,7 +51,6 @@ import android.widget.EditText;
import android.widget.MultiAutoCompleteTextView;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.TextView;
import org.strongswan.android.R;
@ -59,6 +59,7 @@ import org.strongswan.android.data.VpnProfile.SelectedAppsHandling;
import org.strongswan.android.data.VpnProfileDataSource;
import org.strongswan.android.data.VpnType;
import org.strongswan.android.data.VpnType.VpnTypeFeature;
import org.strongswan.android.logic.StrongSwanApplication;
import org.strongswan.android.logic.TrustedCertificateManager;
import org.strongswan.android.security.TrustedCertificateEntry;
import org.strongswan.android.ui.adapter.CertificateIdentitiesAdapter;
@ -73,18 +74,19 @@ import java.util.ArrayList;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.Executor;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDialogFragment;
import androidx.appcompat.widget.SwitchCompat;
import androidx.core.text.HtmlCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
public class VpnProfileDetailActivity extends AppCompatActivity
{
private static final int SELECT_TRUSTED_CERTIFICATE = 0;
private static final int SELECT_APPLICATIONS = 1;
private VpnProfileDataSource mDataSource;
private Long mId;
private TrustedCertificateEntry mCertEntry;
@ -119,12 +121,12 @@ public class VpnProfileDetailActivity extends AppCompatActivity
private TextInputLayoutHelper mMTUWrap;
private EditText mPort;
private TextInputLayoutHelper mPortWrap;
private Switch mCertReq;
private Switch mUseCrl;
private Switch mUseOcsp;
private Switch mStrictRevocation;
private Switch mRsaPss;
private Switch mIPv6Transport;
private SwitchCompat mCertReq;
private SwitchCompat mUseCrl;
private SwitchCompat mUseOcsp;
private SwitchCompat mStrictRevocation;
private SwitchCompat mRsaPss;
private SwitchCompat mIPv6Transport;
private EditText mNATKeepalive;
private TextInputLayoutHelper mNATKeepaliveWrap;
private EditText mIncludedSubnets;
@ -144,6 +146,41 @@ public class VpnProfileDetailActivity extends AppCompatActivity
private EditText mDnsServers;
private TextInputLayoutHelper mDnsServersWrap;
private final ActivityResultLauncher<Intent> mInstallPKCS12 = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK)
{
mSelectUserCert.performClick();
}
}
);
private final ActivityResultLauncher<Intent> mSelectTrustedCertificate = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK)
{
String alias = result.getData().getStringExtra(VpnProfileDataSource.KEY_CERTIFICATE);
X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias);
mCertEntry = certificate == null ? null : new TrustedCertificateEntry(alias, certificate);
updateCertificateSelector();
}
}
);
private final ActivityResultLauncher<Intent> mSelectApplications = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK)
{
ArrayList<String> selection = result.getData().getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
mSelectedApps = new TreeSet<>(selection);
updateAppsSelector();
}
}
);
@Override
public void onCreate(Bundle savedInstanceState)
{
@ -190,7 +227,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
mPortWrap = (TextInputLayoutHelper) findViewById(R.id.port_wrap);
mNATKeepalive = (EditText)findViewById(R.id.nat_keepalive);
mNATKeepaliveWrap = (TextInputLayoutHelper) findViewById(R.id.nat_keepalive_wrap);
mCertReq = (Switch)findViewById(R.id.cert_req);
mCertReq = findViewById(R.id.cert_req);
mUseCrl = findViewById(R.id.use_crl);
mUseOcsp = findViewById(R.id.use_ocsp);
mStrictRevocation= findViewById(R.id.strict_revocation);
@ -283,6 +320,10 @@ public class VpnProfileDetailActivity extends AppCompatActivity
});
mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener());
((Button)findViewById(R.id.install_user_certificate)).setOnClickListener(v -> {
Intent intent = KeyChain.createInstallIntent();
mInstallPKCS12.launch(intent);
});
mSelectUserIdAdapter = new CertificateIdentitiesAdapter(this);
mLocalId.setAdapter(mSelectUserIdAdapter);
@ -300,7 +341,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
{
Intent intent = new Intent(VpnProfileDetailActivity.this, TrustedCertificatesActivity.class);
intent.setAction(TrustedCertificatesActivity.SELECT_CERTIFICATE);
startActivityForResult(intent, SELECT_TRUSTED_CERTIFICATE);
mSelectTrustedCertificate.launch(intent);
}
});
@ -334,7 +375,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity
{
Intent intent = new Intent(VpnProfileDetailActivity.this, SelectedApplicationsActivity.class);
intent.putExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST, new ArrayList<>(mSelectedApps));
startActivityForResult(intent, SELECT_APPLICATIONS);
mSelectApplications.launch(intent);
}
});
@ -404,33 +445,6 @@ public class VpnProfileDetailActivity extends AppCompatActivity
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
switch (requestCode)
{
case SELECT_TRUSTED_CERTIFICATE:
if (resultCode == RESULT_OK)
{
String alias = data.getStringExtra(VpnProfileDataSource.KEY_CERTIFICATE);
X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias);
mCertEntry = certificate == null ? null : new TrustedCertificateEntry(alias, certificate);
updateCertificateSelector();
}
break;
case SELECT_APPLICATIONS:
if (resultCode == RESULT_OK)
{
ArrayList<String> selection = data.getStringArrayListExtra(VpnProfileDataSource.KEY_SELECTED_APPS_LIST);
mSelectedApps = new TreeSet<>(selection);
updateAppsSelector();
}
break;
default:
super.onActivityResult(requestCode, resultCode, data);
}
}
/**
* Update the UI to enter credentials depending on the type of VPN currently selected
*/
@ -791,9 +805,22 @@ public class VpnProfileDetailActivity extends AppCompatActivity
useralias = savedInstanceState == null ? useralias : savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE);
if (useralias != null)
{
UserCertificateLoader loader = new UserCertificateLoader(this, useralias);
mUserCertLoading = useralias;
loader.execute();
UserCertificateLoader loader = new UserCertificateLoader(((StrongSwanApplication)getApplication()).getExecutor(),
((StrongSwanApplication)getApplication()).getHandler());
loader.loadCertifiate(this, useralias, result -> {
if (result != null)
{
mUserCertEntry = new TrustedCertificateEntry(mUserCertLoading, result);
}
else
{ /* previously selected certificate is not here anymore */
((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError("");
mUserCertEntry = null;
}
mUserCertLoading = null;
updateCredentialView();
});
}
/* check if the user selected a CA certificate previously */
@ -961,55 +988,53 @@ public class VpnProfileDetailActivity extends AppCompatActivity
}
}
/**
* Callback interface for the user certificate loader.
*/
private interface UserCertificateLoaderCallback {
void onComplete(X509Certificate result);
}
/**
* Load the selected user certificate asynchronously. This cannot be done
* from the main thread as getCertificateChain() calls back to our main
* thread to bind to the KeyChain service resulting in a deadlock.
*/
private class UserCertificateLoader extends AsyncTask<Void, Void, X509Certificate>
private class UserCertificateLoader
{
private final Context mContext;
private final String mAlias;
private final Executor mExecutor;
private final Handler mHandler;
public UserCertificateLoader(Context context, String alias)
public UserCertificateLoader(Executor executor, Handler handler)
{
mContext = context;
mAlias = alias;
mExecutor = executor;
mHandler = handler;
}
@Override
protected X509Certificate doInBackground(Void... params)
public void loadCertifiate(Context context, String alias, UserCertificateLoaderCallback callback)
{
X509Certificate[] chain = null;
try
{
chain = KeyChain.getCertificateChain(mContext, mAlias);
}
catch (KeyChainException | InterruptedException e)
{
e.printStackTrace();
}
if (chain != null && chain.length > 0)
{
return chain[0];