From e59b78254a7b24be826b75bad0250ac4241bfaad Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 6 Jul 2017 15:16:24 +0200 Subject: [PATCH 01/11] android: Catch NullPointerException when calling VpnService.prepare() According to the Play Console this occurs occasionally. --- .../main/java/org/strongswan/android/ui/MainActivity.java | 6 ++++++ 1 file changed, 6 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 efdeddd48..a7006cd49 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 @@ -218,6 +218,12 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_during_lockdown); return; } + catch (NullPointerException ex) + { + /* not sure when this happens exactly, but apparently it does */ + VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported); + return; + } /* store profile info until the user grants us permission */ mProfileInfo = profileInfo; if (intent != null) From 1a9261a923e72f4f223b7e00c980df19c5a1f417 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 6 Jul 2017 15:40:42 +0200 Subject: [PATCH 02/11] android: Catch NullPointerException when parsing invalid certificates --- .../security/TrustedCertificateEntry.java | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java b/src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java index 5e9873d1b..4a1dc8e5f 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java @@ -43,29 +43,38 @@ public class TrustedCertificateEntry implements Comparable Date: Mon, 10 Jul 2017 12:17:45 +0200 Subject: [PATCH 03/11] android: Catch OutOfMemoryError when importing profiles Not sure if this is actually caused because e.g. the file is too large or due to some encoding issue. --- .../android/ui/VpnProfileImportActivity.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 5cfcf4895..cee95c36f 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 @@ -65,6 +65,7 @@ import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.lang.OutOfMemoryError; import java.net.URL; import java.net.UnknownHostException; import java.security.KeyStore; @@ -715,7 +716,14 @@ public class VpnProfileImportActivity extends AppCompatActivity } if (in != null) { - result.Profile = streamToString(in); + try + { + result.Profile = streamToString(in); + } + catch (OutOfMemoryError e) + { /* just use a generic exception */ + result.ThrownException = new RuntimeException(); + } } return result; } From 0b4f7d646b5694fa65afa1da2eae1e819fb02d1f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 11 Jul 2017 09:44:35 +0200 Subject: [PATCH 04/11] android: Only apply app filter on Android 5 and newer --- .../java/org/strongswan/android/logic/CharonVpnService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 d8e07a873..f9fef4ffc 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 @@ -974,7 +974,8 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe builder.addRoute("::", 0); } /* apply selected applications */ - if (mSelectedApps.size() > 0) + if (mSelectedApps.size() > 0 && + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { switch (mAppHandling) { From 98ab757284779616423789a682eb2a00056fbf50 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 24 Aug 2017 15:18:32 +0200 Subject: [PATCH 05/11] android: Hide app selection in profile editor on Android < 5 --- .../strongswan/android/ui/VpnProfileDetailActivity.java | 8 ++++++++ .../app/src/main/res/layout/profile_detail_view.xml | 1 + 2 files changed, 9 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 8bf5fd2b2..8792fd7e4 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,6 +22,7 @@ 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.security.KeyChain; import android.security.KeyChainAliasCallback; @@ -187,6 +188,13 @@ public class VpnProfileDetailActivity extends AppCompatActivity mName.setAdapter(completeAdapter); mRemoteId.setAdapter(completeAdapter); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) + { + 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) {} 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 199a7dfee..f765dbcf1 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 @@ -341,6 +341,7 @@ android:text="@string/profile_split_tunnelingv6_title" /> Date: Mon, 21 Aug 2017 15:26:06 +0200 Subject: [PATCH 06/11] android: Pass absolute path to the app's data directory via JNI --- .../org/strongswan/android/logic/CharonVpnService.java | 7 +++++-- .../app/src/main/jni/libandroidbridge/charonservice.c | 10 ++++++---- 2 files changed, 11 insertions(+), 6 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 f9fef4ffc..ba3d434ce 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 @@ -74,6 +74,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe public static final int VPN_STATE_NOTIFICATION_ID = 1; private String mLogFile; + private String mAppDir; private VpnProfileDataSource mDataSource; private Thread mConnectionHandler; private VpnProfile mCurrentProfile; @@ -152,6 +153,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe public void onCreate() { mLogFile = getFilesDir().getAbsolutePath() + File.separator + LOG_FILE; + mAppDir = getFilesDir().getAbsolutePath(); mDataSource = new VpnProfileDataSource(this); mDataSource.open(); @@ -244,7 +246,7 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe addNotification(); BuilderAdapter builder = new BuilderAdapter(mCurrentProfile); - if (initializeCharon(builder, mLogFile, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD))) + if (initializeCharon(builder, mLogFile, mAppDir, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD))) { Log.i(TAG, "charon started"); SettingsWriter writer = new SettingsWriter(); @@ -645,10 +647,11 @@ public class CharonVpnService extends VpnService implements Runnable, VpnStateSe * * @param builder BuilderAdapter for this connection * @param logfile absolute path to the logfile + * @param appdir absolute path to the data directory of the app * @param byod enable BYOD features * @return TRUE if initialization was successful */ - public native boolean initializeCharon(BuilderAdapter builder, String logfile, boolean byod); + public native boolean initializeCharon(BuilderAdapter builder, String logfile, String appdir, boolean byod); /** * Deinitialize charon, provided by libandroidbridge.so diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c index f74936edb..5eb1bd9c0 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c @@ -515,7 +515,7 @@ static void set_options(char *logfile) * Initialize the charonservice object */ static void charonservice_init(JNIEnv *env, jobject service, jobject builder, - jboolean byod) + char *appdir, jboolean byod) { private_charonservice_t *this; static plugin_feature_t features[] = { @@ -602,11 +602,11 @@ static void segv_handler(int signal) * Initialize charon and the libraries via JNI */ JNI_METHOD(CharonVpnService, initializeCharon, jboolean, - jobject builder, jstring jlogfile, jboolean byod) + jobject builder, jstring jlogfile, jstring jappdir, jboolean byod) { struct sigaction action; struct utsname utsname; - char *logfile, *plugins; + char *logfile, *appdir, *plugins; /* logging for library during initialization, as we have no bus yet */ dbg = dbg_android; @@ -640,7 +640,9 @@ JNI_METHOD(CharonVpnService, initializeCharon, jboolean, charon->load_loggers(charon); - charonservice_init(env, this, builder, byod); + appdir = androidjni_convert_jstring(env, jappdir); + charonservice_init(env, this, builder, appdir, byod); + free(appdir); if (uname(&utsname) != 0) { From 0bebbae9e3834442ce5b5fbc30f9cafb9784ae9e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 21 Aug 2017 15:53:20 +0200 Subject: [PATCH 07/11] android: Cache CRLs in app directory Fixes #2405. --- .../libandroidbridge/backend/android_creds.c | 131 +++++++++++++++++- .../libandroidbridge/backend/android_creds.h | 8 +- .../main/jni/libandroidbridge/charonservice.c | 3 +- 3 files changed, 132 insertions(+), 10 deletions(-) diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.c index ddc032638..0c460a9cf 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.c @@ -1,6 +1,6 @@ /* - * Copyright (C) 2012 Tobias Brunner - * Hochschule fuer Technik Rapperswil + * Copyright (C) 2012-2017 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil * * 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 @@ -13,6 +13,11 @@ * for more details. */ +#include +#include +#include +#include + #include "android_creds.h" #include "../charonservice.h" @@ -21,6 +26,8 @@ #include #include +#define CRL_PREFIX "crl-" + typedef struct private_android_creds_t private_android_creds_t; /** @@ -47,6 +54,11 @@ struct private_android_creds_t { * TRUE if certificates have been loaded via JNI */ bool loaded; + + /** + * Directory for CRLs + */ + char *crldir; }; /** @@ -86,15 +98,79 @@ static void load_trusted_certificates(private_android_creds_t *this) } } +/** + * Load a CRL from a file + */ +static void load_crl(private_android_creds_t *this, char *file) +{ + certificate_t *cert; + time_t now, notAfter; + + cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL, + BUILD_FROM_FILE, file, BUILD_END); + if (cert) + { + now = time(NULL); + if (cert->get_validity(cert, &now, NULL, ¬After)) + { + DBG1(DBG_CFG, "loaded crl issued by '%Y'", cert->get_issuer(cert)); + this->creds->add_crl(this->creds, (crl_t*)cert); + } + else + { + DBG1(DBG_CFG, "deleted crl issued by '%Y', expired (%V ago)", + cert->get_issuer(cert), &now, ¬After); + unlink(file); + } + } + else + { + DBG1(DBG_CFG, "loading crl failed"); + unlink(file); + } +} + +/** + * Load cached CRLs + */ +static void load_crls(private_android_creds_t *this) +{ + enumerator_t *enumerator; + struct stat st; + char *rel, *abs; + + enumerator = enumerator_create_directory(this->crldir); + if (enumerator) + { + while (enumerator->enumerate(enumerator, &rel, &abs, &st)) + { + if (S_ISREG(st.st_mode) && strpfx(rel, CRL_PREFIX)) + { + load_crl(this, abs); + } + } + enumerator->destroy(enumerator); + } + else + { + DBG1(DBG_CFG, " reading directory '%s' failed", this->crldir); + } +} + METHOD(credential_set_t, create_cert_enumerator, enumerator_t*, private_android_creds_t *this, certificate_type_t cert, key_type_t key, identification_t *id, bool trusted) { enumerator_t *enumerator; - if (cert != CERT_ANY && cert != CERT_X509) + switch (cert) { - return NULL; + case CERT_ANY: + case CERT_X509: + case CERT_X509_CRL: + break; + default: + return NULL; } this->lock->read_lock(this->lock); if (!this->loaded) @@ -104,6 +180,7 @@ METHOD(credential_set_t, create_cert_enumerator, enumerator_t*, /* check again after acquiring the write lock */ if (!this->loaded) { + load_crls(this); load_trusted_certificates(this); this->loaded = TRUE; } @@ -116,6 +193,46 @@ METHOD(credential_set_t, create_cert_enumerator, enumerator_t*, this->lock); } +METHOD(credential_set_t, cache_cert, void, + private_android_creds_t *this, certificate_t *cert) +{ + if (this->crldir && cert->get_type(cert) == CERT_X509_CRL) + { + /* CRLs get written to ///crl-[-delta] */ + crl_t *crl = (crl_t*)cert; + + cert->get_ref(cert); + if (this->creds->add_crl(this->creds, crl)) + { + char buf[BUF_LEN]; + chunk_t chunk, hex; + bool is_delta_crl; + + is_delta_crl = crl->is_delta_crl(crl, NULL); + chunk = crl->get_authKeyIdentifier(crl); + hex = chunk_to_hex(chunk, NULL, FALSE); + snprintf(buf, sizeof(buf), "%s/%s%s%s", this->crldir, CRL_PREFIX, + hex.ptr, is_delta_crl ? "-delta" : ""); + free(hex.ptr); + + if (cert->get_encoding(cert, CERT_ASN1_DER, &chunk)) + { + if (chunk_write(chunk, buf, 022, TRUE)) + { + DBG1(DBG_CFG, " written crl to file (%d bytes)", + chunk.len); + } + else + { + DBG1(DBG_CFG, " writing crl to file failed: %s", + strerror(errno)); + } + free(chunk.ptr); + } + } + } +} + METHOD(android_creds_t, add_username_password, void, private_android_creds_t *this, char *username, char *password) { @@ -224,13 +341,14 @@ METHOD(android_creds_t, destroy, void, clear(this); this->creds->destroy(this->creds); this->lock->destroy(this->lock); + free(this->crldir); free(this); } /** * Described in header. */ -android_creds_t *android_creds_create() +android_creds_t *android_creds_create(char *crldir) { private_android_creds_t *this; @@ -241,7 +359,7 @@ android_creds_t *android_creds_create() .create_shared_enumerator = _create_shared_enumerator, .create_private_enumerator = _create_private_enumerator, .create_cdp_enumerator = (void*)return_null, - .cache_cert = (void*)nop, + .cache_cert = _cache_cert, }, .add_username_password = _add_username_password, .load_user_certificate = _load_user_certificate, @@ -250,6 +368,7 @@ android_creds_t *android_creds_create() }, .creds = mem_cred_create(), .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + .crldir = strdupnull(crldir), ); return &this->public; diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.h b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.h index 918708f14..869e0afea 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.h +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 2012 Tobias Brunner - * Hochschule fuer Technik Rapperswil + * Copyright (C) 2012-2017 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil * * 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 @@ -67,8 +67,10 @@ struct android_creds_t { /** * Create an android_creds instance. + * + * @param crldir directory for cached CRLs */ -android_creds_t *android_creds_create(); +android_creds_t *android_creds_create(char *crldir); #endif /** ANDROID_CREDS_H_ @}*/ diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c index 5eb1bd9c0..62427338a 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c @@ -526,6 +526,7 @@ static void charonservice_init(JNIEnv *env, jobject service, jobject builder, PLUGIN_CALLBACK(charonservice_register, NULL), PLUGIN_PROVIDE(CUSTOM, "android-backend"), PLUGIN_DEPENDS(CUSTOM, "libcharon"), + PLUGIN_DEPENDS(CERT_DECODE, CERT_X509_CRL), PLUGIN_REGISTER(FETCHER, android_fetcher_create), PLUGIN_PROVIDE(FETCHER, "http://"), PLUGIN_PROVIDE(FETCHER, "https://"), @@ -544,7 +545,7 @@ static void charonservice_init(JNIEnv *env, jobject service, jobject builder, .get_network_manager = _get_network_manager, }, .attr = android_attr_create(), - .creds = android_creds_create(), + .creds = android_creds_create(appdir), .builder = vpnservice_builder_create(builder), .network_manager = network_manager_create(service), .sockets = linked_list_create(), From 6e39240a3e72b22093165f530134a8b1cbdccd71 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 1 Sep 2017 19:26:52 +0200 Subject: [PATCH 08/11] android: Add option to clear cached CRLs --- .../strongswan/android/ui/MainActivity.java | 84 +++++++++++++++++++ .../android/app/src/main/res/menu/main.xml | 7 +- .../app/src/main/res/values-de/strings.xml | 8 ++ .../app/src/main/res/values-pl/strings.xml | 8 ++ .../app/src/main/res/values-ru/strings.xml | 8 ++ .../app/src/main/res/values-ua/strings.xml | 8 ++ .../src/main/res/values-zh-rCN/strings.xml | 8 ++ .../src/main/res/values-zh-rTW/strings.xml | 8 ++ .../app/src/main/res/values/strings.xml | 8 ++ 9 files changed, 146 insertions(+), 1 deletion(-) 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 a7006cd49..f2a9eaaf8 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 @@ -36,6 +36,7 @@ import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatDialogFragment; +import android.text.format.Formatter; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -54,12 +55,17 @@ import org.strongswan.android.logic.VpnStateService; import org.strongswan.android.logic.VpnStateService.State; import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + public class MainActivity extends AppCompatActivity implements OnVpnProfileSelectedListener { public static final String CONTACT_EMAIL = "android@strongswan.org"; 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_CRL_LIST = "org.strongswan.android.CRL_LIST"; /** * Use "bring your own device" (BYOD) features */ @@ -190,6 +196,9 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec Intent certIntent = new Intent(this, TrustedCertificatesActivity.class); startActivity(certIntent); return true; + case R.id.menu_crl_cache: + clearCRLs(); + return true; case R.id.menu_show_log: Intent logIntent = new Intent(this, LogActivity.class); startActivity(logIntent); @@ -367,6 +376,36 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec } } + /** + * Ask the user whether to clear the CRL cache. + */ + private void clearCRLs() + { + final String FILE_PREFIX = "crl-"; + ArrayList list = new ArrayList<>(); + + for (String file : fileList()) + { + if (file.startsWith(FILE_PREFIX)) + { + list.add(file); + } + } + if (list.size() == 0) + { + Toast.makeText(this, R.string.clear_crl_cache_msg_none, Toast.LENGTH_SHORT).show(); + return; + } + removeFragmentByTag(DIALOG_TAG); + + Bundle args = new Bundle(); + args.putStringArrayList(EXTRA_CRL_LIST, list); + + CRLCacheDialog dialog = new CRLCacheDialog(); + dialog.setArguments(args); + dialog.show(this.getSupportFragmentManager(), DIALOG_TAG); + } + /** * Class that loads the cached CA certificates. */ @@ -551,4 +590,49 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec }).create(); } } + + /** + * Confirmation dialog to clear CRL cache + */ + public static class CRLCacheDialog extends AppCompatDialogFragment + { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + final List list = getArguments().getStringArrayList(EXTRA_CRL_LIST); + String size; + long s = 0; + + for (String file : list) + { + File crl = getActivity().getFileStreamPath(file); + s += crl.length(); + } + 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() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + dismiss(); + } + }) + .setPositiveButton(R.string.clear, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int whichButton) + { + for (String file : list) + { + 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/res/menu/main.xml b/src/frontends/android/app/src/main/res/menu/main.xml index 3c8da084b..48a541b3d 100644 --- a/src/frontends/android/app/src/main/res/menu/main.xml +++ b/src/frontends/android/app/src/main/res/menu/main.xml @@ -1,7 +1,7 @@ Status: 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 424837adf..c6a8b53f3 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 @@ -129,6 +129,14 @@ Import certificate Certificate successfully imported Failed to import certificate + CRL cache + Clear CRL cache? + The CRL cache is empty + + The CRL cache contains %1$d file (%2$s). + The CRL cache contains %1$d files (%2$s). + + Clear Status: 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 a53772014..b47c4993a 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 @@ -126,6 +126,14 @@ Import certificate Certificate successfully imported Failed to import certificate + CRL cache + Clear CRL cache? + The CRL cache is empty + + The CRL cache contains %1$d file (%2$s). + The CRL cache contains %1$d files (%2$s). + + Clear Статус: diff --git a/src/frontends/android/app/src/main/res/values-ua/strings.xml b/src/frontends/android/app/src/main/res/values-ua/strings.xml index 4d3600952..69258b048 100644 --- a/src/frontends/android/app/src/main/res/values-ua/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ua/strings.xml @@ -127,6 +127,14 @@ Import certificate Certificate successfully imported Failed to import certificate + CRL cache + Clear CRL cache? + The CRL cache is empty + + The CRL cache contains %1$d file (%2$s). + The CRL cache contains %1$d files (%2$s). + + Clear Статус: 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 606bd2783..a88e7aa9e 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 @@ -126,6 +126,14 @@ 导入证书 证书已成功被导入 证书导入失败 + CRL cache + Clear CRL cache? + The CRL cache is empty + + The CRL cache contains %1$d file (%2$s). + The CRL cache contains %1$d files (%2$s). + + Clear 状态: 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 52f21f8da..8ef0feb2e 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 @@ -126,6 +126,14 @@ 導入憑證 憑證已經成功匯入 憑證匯入失敗 + CRL cache + Clear CRL cache? + The CRL cache is empty + + The CRL cache contains %1$d file (%2$s). + The CRL cache contains %1$d files (%2$s). + + Clear 狀態: 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 4ee6be390..a02de6b1c 100644 --- a/src/frontends/android/app/src/main/res/values/strings.xml +++ b/src/frontends/android/app/src/main/res/values/strings.xml @@ -129,6 +129,14 @@ Import certificate Certificate successfully imported Failed to import certificate + CRL cache + Clear CRL cache? + The CRL cache is empty + + The CRL cache contains %1$d file (%2$s). + The CRL cache contains %1$d files (%2$s). + + Clear Status: From 829cc56a534a67e904f0e1615258fc9c66e51cff Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 23 Aug 2017 16:14:36 +0200 Subject: [PATCH 09/11] android: Add support to POST data via SimpleFetcher That's required for OCSP verification. --- .../android/logic/SimpleFetcher.java | 22 ++++++++- .../backend/android_fetcher.c | 46 +++++++++++++++++-- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/SimpleFetcher.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/SimpleFetcher.java index e7f029ba2..b7334cee1 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/SimpleFetcher.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/SimpleFetcher.java @@ -17,16 +17,18 @@ package org.strongswan.android.logic; import android.support.annotation.Keep; +import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; @Keep public class SimpleFetcher { - public static byte[] fetch(String uri) throws IOException + public static byte[] fetch(String uri, byte[] data, String contentType) throws IOException { URL url = new URL(uri); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); @@ -34,6 +36,18 @@ public class SimpleFetcher conn.setReadTimeout(10000); try { + if (contentType != null) + { + conn.setRequestProperty("Content-Type", contentType); + } + if (data != null) + { + conn.setDoOutput(true); + conn.setFixedLengthStreamingMode(data.length); + OutputStream out = new BufferedOutputStream(conn.getOutputStream()); + out.write(data); + out.close(); + } return streamToArray(conn.getInputStream()); } finally @@ -42,7 +56,7 @@ public class SimpleFetcher } } - private static byte[] streamToArray(InputStream in) + private static byte[] streamToArray(InputStream in) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; @@ -60,6 +74,10 @@ public class SimpleFetcher { e.printStackTrace(); } + finally + { + in.close(); + } return null; } } diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_fetcher.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_fetcher.c index ce714106b..2e23f8b10 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_fetcher.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_fetcher.c @@ -31,6 +31,16 @@ struct android_fetcher_t { * Callback function */ fetcher_callback_t cb; + + /** + * Data to POST + */ + chunk_t data; + + /** + * Type of data to POST + */ + char *request_type; }; METHOD(fetcher_t, fetch, status_t, @@ -38,8 +48,8 @@ METHOD(fetcher_t, fetch, status_t, { JNIEnv *env; jmethodID method_id; - jobjectArray jdata; - jstring juri; + jobjectArray jdata = NULL; + jstring juri, jct = NULL; chunk_t data; status_t status = FAILED; @@ -51,7 +61,7 @@ METHOD(fetcher_t, fetch, status_t, androidjni_attach_thread(&env); /* can't use FindClass here as this is not called by the main thread */ method_id = (*env)->GetStaticMethodID(env, android_simple_fetcher_class, - "fetch", "(Ljava/lang/String;)[B"); + "fetch", "(Ljava/lang/String;[BLjava/lang/String;)[B"); if (!method_id) { goto failed; @@ -61,8 +71,24 @@ METHOD(fetcher_t, fetch, status_t, { goto failed; } + if (this->request_type) + { + jct = (*env)->NewStringUTF(env, this->request_type); + if (!jct) + { + goto failed; + } + } + if (this->data.ptr) + { + jdata = byte_array_from_chunk(env, this->data); + if (!jdata) + { + goto failed; + } + } jdata = (*env)->CallStaticObjectMethod(env, android_simple_fetcher_class, - method_id, juri); + method_id, juri, jdata, jct); if (!jdata || androidjni_exception_occurred(env)) { goto failed; @@ -97,6 +123,16 @@ METHOD(fetcher_t, set_option, bool, this->cb = va_arg(args, fetcher_callback_t); break; } + case FETCH_REQUEST_DATA: + { + this->data = chunk_clone(va_arg(args, chunk_t)); + break; + } + case FETCH_REQUEST_TYPE: + { + this->request_type = strdup(va_arg(args, char*)); + break; + } default: supported = FALSE; break; @@ -108,6 +144,8 @@ METHOD(fetcher_t, set_option, bool, METHOD(fetcher_t, destroy, void, android_fetcher_t *this) { + chunk_clear(&this->data); + free(this->request_type); free(this); } From 037b353d2cad6067b14a3e83ce873075b6ad1e8e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 23 Aug 2017 16:15:48 +0200 Subject: [PATCH 10/11] android: Load x509 plugin to generate OCSP requests and parse responses BoringSSL does not support OpenSSL's OCSP API. --- src/frontends/android/app/src/main/jni/Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontends/android/app/src/main/jni/Android.mk b/src/frontends/android/app/src/main/jni/Android.mk index 1cffa5938..a46139421 100644 --- a/src/frontends/android/app/src/main/jni/Android.mk +++ b/src/frontends/android/app/src/main/jni/Android.mk @@ -7,7 +7,7 @@ strongswan_USE_BYOD := true strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \ chapoly curve25519 pkcs1 pkcs8 pem xcbc hmac socket-default revocation \ - eap-identity eap-mschapv2 eap-md5 eap-gtc eap-tls + eap-identity eap-mschapv2 eap-md5 eap-gtc eap-tls x509 ifneq ($(strongswan_USE_BYOD),) strongswan_BYOD_PLUGINS := eap-ttls eap-tnc tnc-imc tnc-tnccs tnccs-20 From 1926efaddea782cd252f032f5b74caa5f9158a46 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 22 Aug 2017 18:28:39 +0200 Subject: [PATCH 11/11] android: Add disconnect button to dialog if already connected to profile --- .../strongswan/android/ui/MainActivity.java | 91 ++++++++++++------- 1 file changed, 57 insertions(+), 34 deletions(-) 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 f2a9eaaf8..c0e375da4 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 @@ -474,42 +474,65 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec button = R.string.disconnect; } - return new AlertDialog.Builder(getActivity()) + DialogInterface.OnClickListener connectListener = new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + MainActivity activity = (MainActivity)getActivity(); + activity.startVpnProfile(profileInfo); + } + }; + DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + MainActivity activity = (MainActivity)getActivity(); + if (activity.mService != null) + { + activity.mService.disconnect(); + } + } + }; + DialogInterface.OnClickListener cancelListener = new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + dismiss(); + if (!profileInfo.getBoolean(PROFILE_FOREGROUND)) + { /* if the app was not in the foreground before this action was triggered + * externally, we just close the activity if canceled */ + getActivity().finish(); + } + } + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) .setIcon(icon) .setTitle(String.format(getString(title), profileInfo.getString(PROFILE_NAME))) - .setMessage(message) - .setPositiveButton(button, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int whichButton) - { - MainActivity activity = (MainActivity)getActivity(); - if (profileInfo.getBoolean(PROFILE_DISCONNECT)) - { - if (activity.mService != null) - { - activity.mService.disconnect(); - } - } - else - { - activity.startVpnProfile(profileInfo); - } - } - }) - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - dismiss(); - if (!profileInfo.getBoolean(PROFILE_FOREGROUND)) - { /* if the app was not in the foreground before this action was triggered - * externally, we just close the activity if canceled */ - getActivity().finish(); - } - } - }).create(); + .setMessage(message); + + if (profileInfo.getBoolean(PROFILE_DISCONNECT)) + { + builder.setPositiveButton(button, disconnectListener); + } + else + { + builder.setPositiveButton(button, connectListener); + } + + if (profileInfo.getBoolean(PROFILE_RECONNECT)) + { + builder.setNegativeButton(R.string.disconnect, disconnectListener); + builder.setNeutralButton(android.R.string.cancel, cancelListener); + } + else + { + builder.setNegativeButton(android.R.string.cancel, cancelListener); + } + return builder.create(); } }