From e09f4120d4cb5377ace641cb59bfa7e9d0551f9a Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 20 Aug 2012 18:07:06 +0200 Subject: [PATCH 01/18] android: Added a field to store selected user certificate --- .../strongswan/android/data/VpnProfile.java | 16 ++++++++++++--- .../android/data/VpnProfileDataSource.java | 20 +++++++++++++------ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java index 053f91555..fea9e5c9f 100644 --- a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java +++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java @@ -19,7 +19,7 @@ package org.strongswan.android.data; public class VpnProfile implements Cloneable { - private String mName, mGateway, mUsername, mPassword, mCertificate; + private String mName, mGateway, mUsername, mPassword, mCertificate, mUserCertificate; private long mId = -1; public long getId() @@ -77,9 +77,19 @@ public class VpnProfile implements Cloneable return mCertificate; } - public void setCertificateAlias(String certificate) + public void setCertificateAlias(String alias) { - this.mCertificate = certificate; + this.mCertificate = alias; + } + + public String getUserCertificateAlias() + { + return mUserCertificate; + } + + public void setUserCertificateAlias(String alias) + { + this.mUserCertificate = alias; } @Override diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java index 18632ad6f..a6c6f5e3f 100644 --- a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java +++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java @@ -37,6 +37,7 @@ public class VpnProfileDataSource 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"; private DatabaseHelper mDbHelper; private SQLiteDatabase mDatabase; @@ -45,7 +46,7 @@ public class VpnProfileDataSource private static final String DATABASE_NAME = "strongswan.db"; private static final String TABLE_VPNPROFILE = "vpnprofile"; - private static final int DATABASE_VERSION = 1; + private static final int DATABASE_VERSION = 2; public static final String DATABASE_CREATE = "CREATE TABLE " + TABLE_VPNPROFILE + " (" + @@ -54,7 +55,8 @@ public class VpnProfileDataSource KEY_GATEWAY + " TEXT NOT NULL," + KEY_USERNAME + " TEXT NOT NULL," + KEY_PASSWORD + " TEXT," + - KEY_CERTIFICATE + " TEXT" + + KEY_CERTIFICATE + " TEXT," + + KEY_USER_CERTIFICATE + " TEXT" + ");"; private final String[] ALL_COLUMNS = new String[] { KEY_ID, @@ -62,7 +64,8 @@ public class VpnProfileDataSource KEY_GATEWAY, KEY_USERNAME, KEY_PASSWORD, - KEY_CERTIFICATE + KEY_CERTIFICATE, + KEY_USER_CERTIFICATE, }; private static class DatabaseHelper extends SQLiteOpenHelper @@ -82,9 +85,12 @@ public class VpnProfileDataSource public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(TAG, "Upgrading database from version " + oldVersion + - " to " + newVersion + ", which will destroy all old data"); - db.execSQL("DROP TABLE IF EXISTS " + TABLE_VPNPROFILE); - onCreate(db); + " to " + newVersion); + if (oldVersion < 2) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_USER_CERTIFICATE + + " TEXT;"); + } } } @@ -215,6 +221,7 @@ public class VpnProfileDataSource profile.setUsername(cursor.getString(cursor.getColumnIndex(KEY_USERNAME))); profile.setPassword(cursor.getString(cursor.getColumnIndex(KEY_PASSWORD))); profile.setCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_CERTIFICATE))); + profile.setUserCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_USER_CERTIFICATE))); return profile; } @@ -226,6 +233,7 @@ public class VpnProfileDataSource 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()); return values; } } From aa550401921997438cee1f02eb48b8e6f7d3226d Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 24 Aug 2012 14:55:31 +0200 Subject: [PATCH 02/18] android: LoginDialog refactored so it also works when the device is rotated --- .../strongswan/android/ui/MainActivity.java | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java index bc5030ea5..b02c60902 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java @@ -31,7 +31,6 @@ import android.app.AlertDialog.Builder; import android.app.Dialog; import android.app.DialogFragment; import android.content.ActivityNotFoundException; -import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.VpnService; @@ -50,8 +49,8 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen private static final String SHOW_ERROR_DIALOG = "errordialog"; private static final int PREPARE_VPN_SERVICE = 0; - private VpnProfile activeProfile; private AlertDialog mErrorDialog; + private Bundle mProfileInfo; @Override public void onCreate(Bundle savedInstanceState) @@ -116,10 +115,13 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen /** * Prepare the VpnService. If this succeeds the current VPN profile is * started. + * @param profileInfo a bundle containing the information about the profile to be started */ - protected void prepareVpnService() + protected void prepareVpnService(Bundle profileInfo) { Intent intent = VpnService.prepare(this); + /* store profile info until the user grants us permission */ + mProfileInfo = profileInfo; if (intent != null) { try @@ -136,7 +138,7 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen } } else - { + { /* user already granted permission to use VpnService */ onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null); } } @@ -147,12 +149,10 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen switch (requestCode) { case PREPARE_VPN_SERVICE: - if (resultCode == RESULT_OK && activeProfile != null) + if (resultCode == RESULT_OK && mProfileInfo != null) { Intent intent = new Intent(this, CharonVpnService.class); - intent.putExtra(VpnProfileDataSource.KEY_ID, activeProfile.getId()); - /* submit the password as the profile might not store one */ - intent.putExtra(VpnProfileDataSource.KEY_PASSWORD, activeProfile.getPassword()); + intent.putExtras(mProfileInfo); this.startService(intent); } break; @@ -164,14 +164,19 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen @Override public void onVpnProfileSelected(VpnProfile profile) { - activeProfile = profile; - if (activeProfile.getPassword() == null) + Bundle profileInfo = new Bundle(); + profileInfo.putLong(VpnProfileDataSource.KEY_ID, profile.getId()); + profileInfo.putString(VpnProfileDataSource.KEY_USERNAME, profile.getUsername()); + if (profile.getPassword() == null) { - new LoginDialog().show(getFragmentManager(), "LoginDialog"); + LoginDialog login = new LoginDialog(); + login.setArguments(profileInfo); + login.show(getFragmentManager(), "LoginDialog"); } else { - prepareVpnService(); + profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, profile.getPassword()); + prepareVpnService(profileInfo); } } @@ -220,28 +225,32 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen } } - private class LoginDialog extends DialogFragment + /** + * Class that displays a login dialog and initiates the selected VPN + * profile if the user confirms the dialog. + */ + public static class LoginDialog extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); + 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); - username.setText(activeProfile.getUsername()); + username.setText(profileInfo.getString(VpnProfileDataSource.KEY_USERNAME)); final EditText password = (EditText)view.findViewById(R.id.password); - Builder adb = new AlertDialog.Builder(MainActivity.this); + Builder adb = new AlertDialog.Builder(getActivity()); adb.setView(view); adb.setTitle(getString(R.string.login_title)); adb.setPositiveButton(R.string.login_confirm, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int whichButton) { - /* let's work on a clone of the profile when updating the password */ - activeProfile = activeProfile.clone(); - activeProfile.setPassword(password.getText().toString().trim()); - prepareVpnService(); + MainActivity activity = (MainActivity)getActivity(); + profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, password.getText().toString().trim()); + activity.prepareVpnService(profileInfo); } }); adb.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { From 8a9956762c6364641e81bb69e3420a3f1b30020d Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 24 Aug 2012 15:06:39 +0200 Subject: [PATCH 03/18] android: Simplified handling of error dialog that is displayed if VpnService API is not supported --- .../strongswan/android/ui/MainActivity.java | 68 +++++++------------ 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java index b02c60902..4a1bb6dc9 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java @@ -46,10 +46,8 @@ import android.widget.EditText; public class MainActivity extends Activity implements OnVpnProfileSelectedListener { public static final String CONTACT_EMAIL = "android@strongswan.org"; - private static final String SHOW_ERROR_DIALOG = "errordialog"; private static final int PREPARE_VPN_SERVICE = 0; - private AlertDialog mErrorDialog; private Bundle mProfileInfo; @Override @@ -62,32 +60,10 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen ActionBar bar = getActionBar(); bar.setDisplayShowTitleEnabled(false); - if (savedInstanceState != null && savedInstanceState.getBoolean(SHOW_ERROR_DIALOG)) - { - showVpnNotSupportedError(); - } - /* load CA certificates in a background task */ new CertificateLoadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, false); } - @Override - protected void onSaveInstanceState(Bundle outState) - { - super.onSaveInstanceState(outState); - outState.putBoolean(SHOW_ERROR_DIALOG, mErrorDialog != null); - } - - @Override - protected void onDestroy() - { - super.onDestroy(); - if (mErrorDialog != null) - { /* avoid any errors about leaked windows */ - mErrorDialog.dismiss(); - } - } - @Override public boolean onCreateOptionsMenu(Menu menu) { @@ -134,7 +110,7 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen * don't have the VPN components built into the system image. * com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog * will not be found then */ - showVpnNotSupportedError(); + new VpnNotSupportedError().show(getFragmentManager(), "ErrorDialog"); } } else @@ -180,25 +156,6 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen } } - /** - * Show an error dialog if case the device lacks VPN support. - */ - private void showVpnNotSupportedError() - { - mErrorDialog = new AlertDialog.Builder(this) - .setTitle(R.string.vpn_not_supported_title) - .setMessage(getString(R.string.vpn_not_supported)) - .setCancelable(false) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) - { - mErrorDialog = null; - dialog.dismiss(); - } - }).show(); - } - /** * Class that loads or reloads the cached CA certificates. */ @@ -263,4 +220,27 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen return adb.create(); } } + + /** + * Class representing an error message which is displayed if VpnService is + * not supported on the current device. + */ + public static class VpnNotSupportedError extends DialogFragment + { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.vpn_not_supported_title) + .setMessage(getString(R.string.vpn_not_supported)) + .setCancelable(false) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) + { + dialog.dismiss(); + } + }).create(); + } + } } From 1f6f501978a089c2e3ea3e679b56c86beb472316 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 27 Aug 2012 15:33:58 +0200 Subject: [PATCH 04/18] android: Enum added for VPN types --- .../org/strongswan/android/data/VpnType.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/frontends/android/src/org/strongswan/android/data/VpnType.java diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnType.java b/src/frontends/android/src/org/strongswan/android/data/VpnType.java new file mode 100644 index 000000000..35b32da53 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/data/VpnType.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * 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 + * 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; + +public enum VpnType +{ + IKEV2_EAP("ikev2-eap", true, false), + IKEV2_CERT("ikev2-cert", false, true); + + private String mIdentifier; + private boolean mCertificate; + private boolean mUsernamePassword; + + /** + * Enum which provides additional information about the supported VPN types. + * + * @param id identifier used to store and transmit this specific type + * @param userpass true if username and password are required + * @param certificate true if a client certificate is required + */ + VpnType(String id, boolean userpass, boolean certificate) + { + mIdentifier = id; + mUsernamePassword = userpass; + mCertificate = certificate; + } + + /** + * The identifier used to store this value in the database + * @return identifier + */ + public String getIdentifier() + { + return mIdentifier; + } + + /** + * Whether username and password are required for this type of VPN. + * + * @return true if username and password are required + */ + public boolean getRequiresUsernamePassword() + { + return mUsernamePassword; + } + + /** + * Whether a certificate is required for this type of VPN. + * + * @return true if a certificate is required + */ + public boolean getRequiresCertificate() + { + return mCertificate; + } + + /** + * Get the enum entry with the given identifier. + * + * @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) + { + for (VpnType type : VpnType.values()) + { + if (identifier.equals(type.mIdentifier)) + { + return type; + } + } + return VpnType.IKEV2_EAP; + } +} From 48f51d9454a976d82714f1d8b961f789b021f50f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 27 Aug 2012 15:34:53 +0200 Subject: [PATCH 05/18] android: Field added to store the type of a VPN profile --- .../src/org/strongswan/android/data/VpnProfile.java | 11 +++++++++++ .../android/data/VpnProfileDataSource.java | 12 +++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java index fea9e5c9f..8323826d2 100644 --- a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java +++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java @@ -20,6 +20,7 @@ package org.strongswan.android.data; public class VpnProfile implements Cloneable { private String mName, mGateway, mUsername, mPassword, mCertificate, mUserCertificate; + private VpnType mVpnType; private long mId = -1; public long getId() @@ -52,6 +53,16 @@ public class VpnProfile implements Cloneable this.mGateway = gateway; } + public VpnType getVpnType() + { + return mVpnType; + } + + public void setVpnType(VpnType type) + { + this.mVpnType = type; + } + public String getUsername() { return mUsername; diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java index a6c6f5e3f..ccc88c1df 100644 --- a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java +++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java @@ -34,6 +34,7 @@ public class VpnProfileDataSource public static final String KEY_ID = "_id"; 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"; @@ -46,13 +47,14 @@ public class VpnProfileDataSource private static final String DATABASE_NAME = "strongswan.db"; private static final String TABLE_VPNPROFILE = "vpnprofile"; - private static final int DATABASE_VERSION = 2; + private static final int DATABASE_VERSION = 3; public static final String DATABASE_CREATE = "CREATE TABLE " + TABLE_VPNPROFILE + " (" + KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + KEY_NAME + " TEXT NOT NULL," + KEY_GATEWAY + " TEXT NOT NULL," + + KEY_VPN_TYPE + " TEXT NOT NULL," + KEY_USERNAME + " TEXT NOT NULL," + KEY_PASSWORD + " TEXT," + KEY_CERTIFICATE + " TEXT," + @@ -62,6 +64,7 @@ public class VpnProfileDataSource KEY_ID, KEY_NAME, KEY_GATEWAY, + KEY_VPN_TYPE, KEY_USERNAME, KEY_PASSWORD, KEY_CERTIFICATE, @@ -91,6 +94,11 @@ public class VpnProfileDataSource 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 '';"); + } } } @@ -218,6 +226,7 @@ public class VpnProfileDataSource profile.setId(cursor.getLong(cursor.getColumnIndex(KEY_ID))); profile.setName(cursor.getString(cursor.getColumnIndex(KEY_NAME))); profile.setGateway(cursor.getString(cursor.getColumnIndex(KEY_GATEWAY))); + profile.setVpnType(VpnType.fromIdentifier(cursor.getString(cursor.getColumnIndex(KEY_VPN_TYPE)))); profile.setUsername(cursor.getString(cursor.getColumnIndex(KEY_USERNAME))); profile.setPassword(cursor.getString(cursor.getColumnIndex(KEY_PASSWORD))); profile.setCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_CERTIFICATE))); @@ -230,6 +239,7 @@ public class VpnProfileDataSource ContentValues values = new ContentValues(); 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()); From 825c192d4f07251a60fc0bdca7e5b1ea27878050 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 27 Aug 2012 16:51:41 +0200 Subject: [PATCH 06/18] android: Spinner added to select the VPN type --- .../res/layout/profile_detail_view.xml | 51 ++++++++++----- .../android/res/values-de/arrays.xml | 21 ++++++ .../android/res/values-de/strings.xml | 1 + .../android/res/values-pl/arrays.xml | 21 ++++++ .../android/res/values-pl/strings.xml | 1 + src/frontends/android/res/values/arrays.xml | 21 ++++++ src/frontends/android/res/values/strings.xml | 1 + .../org/strongswan/android/data/VpnType.java | 1 + .../android/ui/VpnProfileDetailActivity.java | 64 ++++++++++++++++--- 9 files changed, 158 insertions(+), 24 deletions(-) create mode 100644 src/frontends/android/res/values-de/arrays.xml create mode 100644 src/frontends/android/res/values-pl/arrays.xml create mode 100644 src/frontends/android/res/values/arrays.xml diff --git a/src/frontends/android/res/layout/profile_detail_view.xml b/src/frontends/android/res/layout/profile_detail_view.xml index d9ccca546..1ca562a18 100644 --- a/src/frontends/android/res/layout/profile_detail_view.xml +++ b/src/frontends/android/res/layout/profile_detail_view.xml @@ -56,28 +56,49 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" - android:text="@string/profile_username_label" /> + android:text="@string/profile_vpn_type_label" /> - + android:spinnerMode="dialog" + android:entries="@array/vpn_types" /> - + android:orientation="vertical" > - + + + + + + + + + + + + + + IKEv2 EAP (Benutzername/Passwort) + + \ No newline at end of file diff --git a/src/frontends/android/res/values-de/strings.xml b/src/frontends/android/res/values-de/strings.xml index 9f3f637a8..9e415ab8b 100644 --- a/src/frontends/android/res/values-de/strings.xml +++ b/src/frontends/android/res/values-de/strings.xml @@ -49,6 +49,7 @@ Profilname: (Gateway-Adresse verwenden) Gateway: + Typ: Benutzername: Passwort: (anfordern wenn benötigt) diff --git a/src/frontends/android/res/values-pl/arrays.xml b/src/frontends/android/res/values-pl/arrays.xml new file mode 100644 index 000000000..ed14934e0 --- /dev/null +++ b/src/frontends/android/res/values-pl/arrays.xml @@ -0,0 +1,21 @@ + + + + + + IKEv2 EAP (użytkownik/hasło) + + \ No newline at end of file diff --git a/src/frontends/android/res/values-pl/strings.xml b/src/frontends/android/res/values-pl/strings.xml index e7d4670d9..4a474e88b 100644 --- a/src/frontends/android/res/values-pl/strings.xml +++ b/src/frontends/android/res/values-pl/strings.xml @@ -51,6 +51,7 @@ Nazwa profilu: (użyj adresu bramki) Bramka: + Typ: Użytkownik: Hasło: (w razie potrzebz zapromptuj) diff --git a/src/frontends/android/res/values/arrays.xml b/src/frontends/android/res/values/arrays.xml new file mode 100644 index 000000000..62164f7c2 --- /dev/null +++ b/src/frontends/android/res/values/arrays.xml @@ -0,0 +1,21 @@ + + + + + + IKEv2 EAP (Username/Password) + + \ No newline at end of file diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index bc7fa4a1d..29cd6430b 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -49,6 +49,7 @@ Profile Name: (use gateway address) Gateway: + Type: Username: Password: (prompt when needed) diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnType.java b/src/frontends/android/src/org/strongswan/android/data/VpnType.java index 35b32da53..44a4fa6b4 100644 --- a/src/frontends/android/src/org/strongswan/android/data/VpnType.java +++ b/src/frontends/android/src/org/strongswan/android/data/VpnType.java @@ -17,6 +17,7 @@ package org.strongswan.android.data; public enum VpnType { + /* the order here must match the items in R.array.vpn_types */ IKEV2_EAP("ikev2-eap", true, false), IKEV2_CERT("ikev2-cert", false, true); diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java index 73365b40c..73fa3017b 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -23,6 +23,7 @@ import org.strongswan.android.R; import org.strongswan.android.data.TrustedCertificateEntry; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.data.VpnType; import org.strongswan.android.logic.TrustedCertificateManager; import android.app.Activity; @@ -36,11 +37,15 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.RelativeLayout; +import android.widget.Spinner; import android.widget.TextView; public class VpnProfileDetailActivity extends Activity @@ -50,9 +55,12 @@ public class VpnProfileDetailActivity extends Activity private VpnProfileDataSource mDataSource; private Long mId; private TrustedCertificateEntry mCertEntry; + private VpnType mVpnType = VpnType.IKEV2_EAP; private VpnProfile mProfile; private EditText mName; private EditText mGateway; + private Spinner mSelectVpnType; + private ViewGroup mUsernamePassword; private EditText mUsername; private EditText mPassword; private CheckBox mCheckAuto; @@ -60,7 +68,6 @@ public class VpnProfileDetailActivity extends Activity private TextView mCertTitle; private TextView mCertSubtitle; - @Override public void onCreate(Bundle savedInstanceState) { @@ -75,15 +82,35 @@ public class VpnProfileDetailActivity extends Activity setContentView(R.layout.profile_detail_view); mName = (EditText)findViewById(R.id.name); - mPassword = (EditText)findViewById(R.id.password); mGateway = (EditText)findViewById(R.id.gateway); + mSelectVpnType = (Spinner)findViewById(R.id.vpn_type); + + mUsernamePassword = (ViewGroup)findViewById(R.id.username_password_group); mUsername = (EditText)findViewById(R.id.username); + mPassword = (EditText)findViewById(R.id.password); mCheckAuto = (CheckBox)findViewById(R.id.ca_auto); mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate); mCertTitle = (TextView)findViewById(R.id.select_certificate_title); mCertSubtitle = (TextView)findViewById(R.id.select_certificate_subtitle); + + mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) + { + mVpnType = VpnType.values()[position]; + updateClientCredentialView(); + } + + @Override + public void onNothingSelected(AdapterView parent) + { /* should not happen */ + mVpnType = VpnType.IKEV2_EAP; + updateClientCredentialView(); + } + }); + mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) @@ -110,6 +137,7 @@ public class VpnProfileDetailActivity extends Activity loadProfileData(savedInstanceState); + updateClientCredentialView(); updateCertificateSelector(); } @@ -178,6 +206,14 @@ public class VpnProfileDetailActivity extends Activity } } + /** + * Update the UI to enter client credentials depending on the type of VPN currently selected + */ + private void updateClientCredentialView() + { + mUsernamePassword.setVisibility(mVpnType.getRequiresUsernamePassword() ? View.VISIBLE : View.GONE); + } + /** * Show an alert in case the previously selected certificate is not found anymore * or the user did not select a certificate in the spinner. @@ -262,10 +298,13 @@ public class VpnProfileDetailActivity extends Activity mGateway.setError(getString(R.string.alert_text_no_input_gateway)); valid = false; } - if (mUsername.getText().toString().trim().isEmpty()) + if (mVpnType.getRequiresUsernamePassword()) { - mUsername.setError(getString(R.string.alert_text_no_input_username)); - valid = false; + if (mUsername.getText().toString().trim().isEmpty()) + { + mUsername.setError(getString(R.string.alert_text_no_input_username)); + valid = false; + } } if (!mCheckAuto.isChecked() && mCertEntry == null) { @@ -285,10 +324,14 @@ public class VpnProfileDetailActivity extends Activity String gateway = mGateway.getText().toString().trim(); mProfile.setName(name.isEmpty() ? gateway : name); mProfile.setGateway(gateway); - mProfile.setUsername(mUsername.getText().toString().trim()); - String password = mPassword.getText().toString().trim(); - password = password.isEmpty() ? null : password; - mProfile.setPassword(password); + mProfile.setVpnType(mVpnType); + if (mVpnType.getRequiresUsernamePassword()) + { + mProfile.setUsername(mUsername.getText().toString().trim()); + String password = mPassword.getText().toString().trim(); + password = password.isEmpty() ? null : password; + mProfile.setPassword(password); + } String certAlias = mCheckAuto.isChecked() ? null : mCertEntry.getAlias(); mProfile.setCertificateAlias(certAlias); } @@ -310,6 +353,7 @@ public class VpnProfileDetailActivity extends Activity { mName.setText(mProfile.getName()); mGateway.setText(mProfile.getGateway()); + mVpnType = mProfile.getVpnType(); mUsername.setText(mProfile.getUsername()); mPassword.setText(mProfile.getPassword()); alias = mProfile.getCertificateAlias(); @@ -323,6 +367,8 @@ public class VpnProfileDetailActivity extends Activity } } + mSelectVpnType.setSelection(mVpnType.ordinal()); + /* check if the user selected a certificate previously */ alias = savedInstanceState == null ? alias : savedInstanceState.getString(VpnProfileDataSource.KEY_CERTIFICATE); mCheckAuto.setChecked(alias == null); From 7fedacb2e710d41abb4482cb6f0b5f23aacc7c35 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 27 Aug 2012 16:53:07 +0200 Subject: [PATCH 07/18] android: Don't try to load the profile with ID 0 --- .../src/org/strongswan/android/ui/VpnProfileDetailActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java index 73fa3017b..2134cb394 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -346,7 +346,7 @@ public class VpnProfileDetailActivity extends Activity String alias = null; getActionBar().setTitle(R.string.add_profile); - if (mId != null) + if (mId != null && mId != 0) { mProfile = mDataSource.getVpnProfile(mId); if (mProfile != null) From d0f6481eb0807c3664d137a9c30e84fe35a44e87 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 27 Aug 2012 17:58:09 +0200 Subject: [PATCH 08/18] android: Separate view added to select certificates --- .../res/layout/certificate_selector.xml | 39 +++++++++++++++++++ .../res/layout/profile_detail_view.xml | 27 +------------ .../android/ui/VpnProfileDetailActivity.java | 20 ++++------ 3 files changed, 48 insertions(+), 38 deletions(-) create mode 100644 src/frontends/android/res/layout/certificate_selector.xml diff --git a/src/frontends/android/res/layout/certificate_selector.xml b/src/frontends/android/res/layout/certificate_selector.xml new file mode 100644 index 000000000..c8c25811b --- /dev/null +++ b/src/frontends/android/res/layout/certificate_selector.xml @@ -0,0 +1,39 @@ + + + + + + + + + diff --git a/src/frontends/android/res/layout/profile_detail_view.xml b/src/frontends/android/res/layout/profile_detail_view.xml index 1ca562a18..2a52ae032 100644 --- a/src/frontends/android/res/layout/profile_detail_view.xml +++ b/src/frontends/android/res/layout/profile_detail_view.xml @@ -112,32 +112,9 @@ android:layout_height="wrap_content" android:text="@string/profile_ca_auto_label" /> - - - - - - - + layout="@layout/certificate_selector" /> diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java index 2134cb394..6cdf97b4b 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -44,9 +44,8 @@ import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; -import android.widget.RelativeLayout; import android.widget.Spinner; -import android.widget.TextView; +import android.widget.TwoLineListItem; public class VpnProfileDetailActivity extends Activity { @@ -64,9 +63,7 @@ public class VpnProfileDetailActivity extends Activity private EditText mUsername; private EditText mPassword; private CheckBox mCheckAuto; - private RelativeLayout mSelectCert; - private TextView mCertTitle; - private TextView mCertSubtitle; + private TwoLineListItem mSelectCert; @Override public void onCreate(Bundle savedInstanceState) @@ -90,10 +87,7 @@ public class VpnProfileDetailActivity extends Activity mPassword = (EditText)findViewById(R.id.password); mCheckAuto = (CheckBox)findViewById(R.id.ca_auto); - mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate); - mCertTitle = (TextView)findViewById(R.id.select_certificate_title); - mCertSubtitle = (TextView)findViewById(R.id.select_certificate_subtitle); - + mSelectCert = (TwoLineListItem)findViewById(R.id.select_certificate); mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() { @Override @@ -246,13 +240,13 @@ public class VpnProfileDetailActivity extends Activity if (mCertEntry != null) { - mCertTitle.setText(mCertEntry.getSubjectPrimary()); - mCertSubtitle.setText(mCertEntry.getSubjectSecondary()); + mSelectCert.getText1().setText(mCertEntry.getSubjectPrimary()); + mSelectCert.getText2().setText(mCertEntry.getSubjectSecondary()); } else { - mCertTitle.setText(R.string.profile_ca_select_certificate_label); - mCertSubtitle.setText(R.string.profile_ca_select_certificate); + mSelectCert.getText1().setText(R.string.profile_ca_select_certificate_label); + mSelectCert.getText2().setText(R.string.profile_ca_select_certificate); } } else From 3f9e90f618b1abb5f25c62d44b2a059cf1b6b78c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 28 Aug 2012 12:41:56 +0200 Subject: [PATCH 09/18] android: Remove NOT NULL constraint from username column --- .../android/data/VpnProfileDataSource.java | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java index ccc88c1df..6fd68d0c8 100644 --- a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java +++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java @@ -26,6 +26,7 @@ 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; public class VpnProfileDataSource @@ -47,7 +48,7 @@ public class VpnProfileDataSource private static final String DATABASE_NAME = "strongswan.db"; private static final String TABLE_VPNPROFILE = "vpnprofile"; - private static final int DATABASE_VERSION = 3; + private static final int DATABASE_VERSION = 4; public static final String DATABASE_CREATE = "CREATE TABLE " + TABLE_VPNPROFILE + " (" + @@ -55,12 +56,12 @@ public class VpnProfileDataSource KEY_NAME + " TEXT NOT NULL," + KEY_GATEWAY + " TEXT NOT NULL," + KEY_VPN_TYPE + " TEXT NOT NULL," + - KEY_USERNAME + " TEXT NOT NULL," + + KEY_USERNAME + " TEXT," + KEY_PASSWORD + " TEXT," + KEY_CERTIFICATE + " TEXT," + KEY_USER_CERTIFICATE + " TEXT" + ");"; - private final String[] ALL_COLUMNS = new String[] { + private static final String[] ALL_COLUMNS = new String[] { KEY_ID, KEY_NAME, KEY_GATEWAY, @@ -99,6 +100,29 @@ public class VpnProfileDataSource db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_VPN_TYPE + " TEXT DEFAULT '';"); } + if (oldVersion < 4) + { /* remove NOT NULL constraint from username column */ + updateColumns(db); + } + } + + private void updateColumns(SQLiteDatabase db) + { + db.beginTransaction(); + try + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " RENAME TO tmp_" + TABLE_VPNPROFILE + ";"); + db.execSQL(DATABASE_CREATE); + StringBuilder insert = new StringBuilder("INSERT INTO " + TABLE_VPNPROFILE + " SELECT "); + SQLiteQueryBuilder.appendColumns(insert, ALL_COLUMNS); + db.execSQL(insert.append(" FROM tmp_" + TABLE_VPNPROFILE + ";").toString()); + db.execSQL("DROP TABLE tmp_" + TABLE_VPNPROFILE + ";"); + db.setTransactionSuccessful(); + } + finally + { + db.endTransaction(); + } } } From f46da851ab51a1b6ef6b9817f49a866fb25e1c09 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 28 Aug 2012 14:09:18 +0200 Subject: [PATCH 10/18] android: Allow configuration of a user certificate --- .../res/layout/profile_detail_view.xml | 20 ++- .../android/res/values-de/arrays.xml | 1 + .../android/res/values-de/strings.xml | 4 + .../android/res/values-pl/arrays.xml | 1 + .../android/res/values-pl/strings.xml | 6 +- src/frontends/android/res/values/arrays.xml | 1 + src/frontends/android/res/values/strings.xml | 4 + .../android/ui/VpnProfileDetailActivity.java | 170 +++++++++++++++++- 8 files changed, 198 insertions(+), 9 deletions(-) diff --git a/src/frontends/android/res/layout/profile_detail_view.xml b/src/frontends/android/res/layout/profile_detail_view.xml index 2a52ae032..39c94348b 100644 --- a/src/frontends/android/res/layout/profile_detail_view.xml +++ b/src/frontends/android/res/layout/profile_detail_view.xml @@ -62,7 +62,7 @@ android:id="@+id/vpn_type" android:layout_width="match_parent" android:layout_height="wrap_content" - android:spinnerMode="dialog" + android:spinnerMode="dropdown" android:entries="@array/vpn_types" /> + + + + + + + + IKEv2 EAP (Benutzername/Passwort) + IKEv2 Zertifikat \ No newline at end of file diff --git a/src/frontends/android/res/values-de/strings.xml b/src/frontends/android/res/values-de/strings.xml index 9e415ab8b..a04da7208 100644 --- a/src/frontends/android/res/values-de/strings.xml +++ b/src/frontends/android/res/values-de/strings.xml @@ -25,6 +25,7 @@ Suchen VPN nicht unterstützt Ihr Gerät unterstützt keine VPN Anwendungen.\nBitte kontaktieren Sie den Hersteller. + Laden… Log @@ -53,6 +54,9 @@ Benutzername: Passwort: (anfordern wenn benötigt) + Benutzer-Zertifikat: + Benutzer-Zertifikat auswählen + Wählen Sie ein bestimmtes Benutzer-Zertifikat CA-Zertifikat: Automatisch wählen CA-Zertifikat auswählen diff --git a/src/frontends/android/res/values-pl/arrays.xml b/src/frontends/android/res/values-pl/arrays.xml index ed14934e0..3e1af5f82 100644 --- a/src/frontends/android/res/values-pl/arrays.xml +++ b/src/frontends/android/res/values-pl/arrays.xml @@ -17,5 +17,6 @@ IKEv2 EAP (użytkownik/hasło) + IKEv2 certyfikat \ No newline at end of file diff --git a/src/frontends/android/res/values-pl/strings.xml b/src/frontends/android/res/values-pl/strings.xml index 4a474e88b..54f4259ae 100644 --- a/src/frontends/android/res/values-pl/strings.xml +++ b/src/frontends/android/res/values-pl/strings.xml @@ -27,6 +27,7 @@ Szukaj Nie obsługiwany VPN Urządzenie nie obsługuje aplikacji VPN.\nProszę skontaktować się z producentem. + Wczytywanie… Log @@ -54,7 +55,10 @@ Typ: Użytkownik: Hasło: - (w razie potrzebz zapromptuj) + (w razie potrzeby zapromptuj) + Certyfikat użytkownika: + Wybierz certyfikat użytkownika + >Wybierz określony certyfikat użytkownika Certyfikat CA: Wybierz automatycznie Wybierz certyfikat CA diff --git a/src/frontends/android/res/values/arrays.xml b/src/frontends/android/res/values/arrays.xml index 62164f7c2..21576f22c 100644 --- a/src/frontends/android/res/values/arrays.xml +++ b/src/frontends/android/res/values/arrays.xml @@ -17,5 +17,6 @@ IKEv2 EAP (Username/Password) + IKEv2 Certificate \ No newline at end of file diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 29cd6430b..3e4b746fd 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -25,6 +25,7 @@ Search VPN not supported Your device does not support VPN applications.\nPlease contact the manufacturer. + Loading… Log @@ -53,6 +54,9 @@ Username: Password: (prompt when needed) + User certificate: + Select user certificate + Select a specific user certificate CA certificate: Select automatically Select CA certificate diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java index 6cdf97b4b..91e521cf4 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -28,9 +28,14 @@ import org.strongswan.android.logic.TrustedCertificateManager; import android.app.Activity; import android.app.AlertDialog; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.os.AsyncTask; import android.os.Bundle; +import android.security.KeyChain; +import android.security.KeyChainAliasCallback; +import android.security.KeyChainException; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -54,6 +59,8 @@ public class VpnProfileDetailActivity extends Activity private VpnProfileDataSource mDataSource; private Long mId; private TrustedCertificateEntry mCertEntry; + private String mUserCertLoading; + private TrustedCertificateEntry mUserCertEntry; private VpnType mVpnType = VpnType.IKEV2_EAP; private VpnProfile mProfile; private EditText mName; @@ -62,6 +69,8 @@ public class VpnProfileDetailActivity extends Activity private ViewGroup mUsernamePassword; private EditText mUsername; private EditText mPassword; + private ViewGroup mUserCertificate; + private TwoLineListItem mSelectUserCert; private CheckBox mCheckAuto; private TwoLineListItem mSelectCert; @@ -86,6 +95,9 @@ public class VpnProfileDetailActivity extends Activity mUsername = (EditText)findViewById(R.id.username); mPassword = (EditText)findViewById(R.id.password); + mUserCertificate = (ViewGroup)findViewById(R.id.user_certificate_group); + mSelectUserCert = (TwoLineListItem)findViewById(R.id.select_user_certificate); + mCheckAuto = (CheckBox)findViewById(R.id.ca_auto); mSelectCert = (TwoLineListItem)findViewById(R.id.select_certificate); @@ -94,17 +106,19 @@ public class VpnProfileDetailActivity extends Activity public void onItemSelected(AdapterView parent, View view, int position, long id) { mVpnType = VpnType.values()[position]; - updateClientCredentialView(); + updateCredentialView(); } @Override public void onNothingSelected(AdapterView parent) { /* should not happen */ mVpnType = VpnType.IKEV2_EAP; - updateClientCredentialView(); + updateCredentialView(); } }); + mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener()); + mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) @@ -131,7 +145,7 @@ public class VpnProfileDetailActivity extends Activity loadProfileData(savedInstanceState); - updateClientCredentialView(); + updateCredentialView(); updateCertificateSelector(); } @@ -150,6 +164,10 @@ public class VpnProfileDetailActivity extends Activity { outState.putLong(VpnProfileDataSource.KEY_ID, mId); } + if (mUserCertEntry != null) + { + outState.putString(VpnProfileDataSource.KEY_USER_CERTIFICATE, mUserCertEntry.getAlias()); + } if (mCertEntry != null) { outState.putString(VpnProfileDataSource.KEY_CERTIFICATE, mCertEntry.getAlias()); @@ -201,11 +219,32 @@ public class VpnProfileDetailActivity extends Activity } /** - * Update the UI to enter client credentials depending on the type of VPN currently selected + * Update the UI to enter credentials depending on the type of VPN currently selected */ - private void updateClientCredentialView() + private void updateCredentialView() { mUsernamePassword.setVisibility(mVpnType.getRequiresUsernamePassword() ? View.VISIBLE : View.GONE); + mUserCertificate.setVisibility(mVpnType.getRequiresCertificate() ? View.VISIBLE : View.GONE); + + if (mVpnType.getRequiresCertificate()) + { + if (mUserCertLoading != null) + { + mSelectUserCert.getText1().setText(mUserCertLoading); + mSelectUserCert.getText2().setText(R.string.loading); + } + else if (mUserCertEntry != null) + { /* clear any errors and set the new data */ + mSelectUserCert.getText1().setError(null); + mSelectUserCert.getText1().setText(mUserCertEntry.getAlias()); + mSelectUserCert.getText2().setText(mUserCertEntry.getCertificate().getSubjectDN().toString()); + } + else + { + mSelectUserCert.getText1().setText(R.string.profile_user_select_certificate_label); + mSelectUserCert.getText2().setText(R.string.profile_user_select_certificate); + } + } } /** @@ -300,6 +339,11 @@ public class VpnProfileDetailActivity extends Activity valid = false; } } + if (mVpnType.getRequiresCertificate() && mUserCertEntry == null) + { /* let's show an error icon */ + mSelectUserCert.getText1().setError(""); + valid = false; + } if (!mCheckAuto.isChecked() && mCertEntry == null) { showCertificateAlert(); @@ -326,6 +370,10 @@ public class VpnProfileDetailActivity extends Activity password = password.isEmpty() ? null : password; mProfile.setPassword(password); } + if (mVpnType.getRequiresCertificate()) + { + mProfile.setUserCertificateAlias(mUserCertEntry.getAlias()); + } String certAlias = mCheckAuto.isChecked() ? null : mCertEntry.getAlias(); mProfile.setCertificateAlias(certAlias); } @@ -337,7 +385,7 @@ public class VpnProfileDetailActivity extends Activity */ private void loadProfileData(Bundle savedInstanceState) { - String alias = null; + String useralias = null, alias = null; getActionBar().setTitle(R.string.add_profile); if (mId != null && mId != 0) @@ -350,6 +398,7 @@ public class VpnProfileDetailActivity extends Activity mVpnType = mProfile.getVpnType(); mUsername.setText(mProfile.getUsername()); mPassword.setText(mProfile.getPassword()); + useralias = mProfile.getUserCertificateAlias(); alias = mProfile.getCertificateAlias(); getActionBar().setTitle(mProfile.getName()); } @@ -363,7 +412,16 @@ public class VpnProfileDetailActivity extends Activity mSelectVpnType.setSelection(mVpnType.ordinal()); - /* check if the user selected a certificate previously */ + /* check if the user selected a user certificate previously */ + useralias = savedInstanceState == null ? useralias: savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE); + if (useralias != null) + { + UserCertificateLoader loader = new UserCertificateLoader(this, useralias); + mUserCertLoading = useralias; + loader.execute(); + } + + /* check if the user selected a CA certificate previously */ alias = savedInstanceState == null ? alias : savedInstanceState.getString(VpnProfileDataSource.KEY_CERTIFICATE); mCheckAuto.setChecked(alias == null); if (alias != null) @@ -380,4 +438,102 @@ public class VpnProfileDetailActivity extends Activity } } } + + private class SelectUserCertOnClickListener implements OnClickListener, KeyChainAliasCallback + { + @Override + public void onClick(View v) + { + String useralias = mUserCertEntry != null ? mUserCertEntry.getAlias() : null; + KeyChain.choosePrivateKeyAlias(VpnProfileDetailActivity.this, this, new String[] { "RSA" }, null, null, -1, useralias); + } + + @Override + public void alias(final String alias) + { + if (alias != null) + { /* otherwise the dialog was canceled, the request denied */ + try + { + final X509Certificate[] chain = KeyChain.getCertificateChain(VpnProfileDetailActivity.this, alias); + /* alias() is not called from our main thread */ + runOnUiThread(new Runnable() { + @Override + public void run() + { + if (chain != null && chain.length > 0) + { + mUserCertEntry = new TrustedCertificateEntry(alias, chain[0]); + } + updateCredentialView(); + } + }); + } + catch (KeyChainException e) + { + e.printStackTrace(); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + } + } + + /** + * 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 + { + private final Context mContext; + private final String mAlias; + + public UserCertificateLoader(Context context, String alias) + { + mContext = context; + mAlias = alias; + } + + @Override + protected X509Certificate doInBackground(Void... params) + { + X509Certificate[] chain = null; + try + { + chain = KeyChain.getCertificateChain(mContext, mAlias); + } + catch (KeyChainException e) + { + e.printStackTrace(); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + if (chain != null && chain.length > 0) + { + return chain[0]; + } + return null; + } + + @Override + protected void onPostExecute(X509Certificate result) + { + if (result != null) + { + mUserCertEntry = new TrustedCertificateEntry(mAlias, result); + } + else + { /* previously selected certificate is not here anymore */ + mSelectUserCert.getText1().setError(""); + mUserCertEntry = null; + } + mUserCertLoading = null; + updateCredentialView(); + } + } } From 6de38fe88aac4706dd9f2b7377695e4ae8c5d6bd Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 28 Aug 2012 14:47:00 +0200 Subject: [PATCH 11/18] android: Display the selected certificate alias in the profile list --- .../android/res/layout/profile_list_item.xml | 10 ++++++++++ .../android/ui/adapter/VpnProfileAdapter.java | 20 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/frontends/android/res/layout/profile_list_item.xml b/src/frontends/android/res/layout/profile_list_item.xml index f55c8357a..93df7b649 100644 --- a/src/frontends/android/res/layout/profile_list_item.xml +++ b/src/frontends/android/res/layout/profile_list_item.xml @@ -46,4 +46,14 @@ android:textAppearance="?android:attr/textAppearanceSmall" android:layout_marginLeft="15dp" /> + + diff --git a/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java b/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java index 39e3e586a..85dc8370a 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java +++ b/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java @@ -64,7 +64,25 @@ public class VpnProfileAdapter extends ArrayAdapter tv = (TextView)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.setText(getContext().getString(R.string.profile_username_label) + " " + profile.getUsername()); + if (profile.getVpnType().getRequiresUsernamePassword()) + { /* if the view is reused we make sure it is visible */ + tv.setVisibility(View.VISIBLE); + tv.setText(getContext().getString(R.string.profile_username_label) + " " + profile.getUsername()); + } + else + { + tv.setVisibility(View.GONE); + } + tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_certificate); + if (profile.getVpnType().getRequiresCertificate()) + { + tv.setText(getContext().getString(R.string.profile_user_certificate_label) + " " + profile.getUserCertificateAlias()); + tv.setVisibility(View.VISIBLE); + } + else + { + tv.setVisibility(View.GONE); + } return vpnProfileView; } From 655362464e4fd0da6adca2abe044b59f15c592a6 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 28 Aug 2012 15:29:35 +0200 Subject: [PATCH 12/18] android: Make sure NULL jstrings are converted properly --- .../android/jni/libandroidbridge/android_jni.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.h b/src/frontends/android/jni/libandroidbridge/android_jni.h index 774d37d7e..bafd6b72e 100644 --- a/src/frontends/android/jni/libandroidbridge/android_jni.h +++ b/src/frontends/android/jni/libandroidbridge/android_jni.h @@ -90,13 +90,16 @@ static inline bool androidjni_exception_occurred(JNIEnv *env) */ static inline char *androidjni_convert_jstring(JNIEnv *env, jstring jstr) { - char *str; + char *str = NULL; jsize len; - len = (*env)->GetStringUTFLength(env, jstr); - str = malloc(len + 1); - (*env)->GetStringUTFRegion(env, jstr, 0, len, str); - str[len] = '\0'; + if (jstr) + { + len = (*env)->GetStringUTFLength(env, jstr); + str = malloc(len + 1); + (*env)->GetStringUTFRegion(env, jstr, 0, len, str); + str[len] = '\0'; + } return str; } From 5eb7ad3a38a59e22a2c5f344992eb1ade524083b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 28 Aug 2012 15:32:14 +0200 Subject: [PATCH 13/18] android: Pass the type of VPN to the native parts --- .../android/jni/libandroidbridge/charonservice.c | 11 +++++++---- .../strongswan/android/logic/CharonVpnService.java | 5 +++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index fab99ac10..57d118faf 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -260,7 +260,8 @@ METHOD(charonservice_t, get_vpnservice_builder, vpnservice_builder_t*, * @param username username (gets owned) * @param password password (gets owned) */ -static void initiate(char *local, char *gateway, char *username, char *password) +static void initiate(char *type, char *local, char *gateway, + char *username, char *password) { private_charonservice_t *this = (private_charonservice_t*)charonservice; @@ -271,6 +272,7 @@ static void initiate(char *local, char *gateway, char *username, char *password) DESTROY_IF(this->service); this->service = android_service_create(local, gateway, username); + free(type); } /** @@ -477,15 +479,16 @@ JNI_METHOD(CharonVpnService, deinitializeCharon, void) * Initiate SA */ JNI_METHOD(CharonVpnService, initiate, void, - jstring jlocal_address, jstring jgateway, jstring jusername, + jstring jtype, jstring jlocal_address, jstring jgateway, jstring jusername, jstring jpassword) { - char *local_address, *gateway, *username, *password; + char *type, *local_address, *gateway, *username, *password; + type = androidjni_convert_jstring(env, jtype); local_address = androidjni_convert_jstring(env, jlocal_address); gateway = androidjni_convert_jstring(env, jgateway); username = androidjni_convert_jstring(env, jusername); password = androidjni_convert_jstring(env, jpassword); - initiate(local_address, gateway, username, password); + initiate(type, local_address, gateway, username, password); } diff --git a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java index c9c1ad02a..71fc04611 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -214,7 +214,8 @@ public class CharonVpnService extends VpnService implements Runnable Log.i(TAG, "charon started"); String local_address = getLocalIPv4Address(); - initiate(local_address != null ? local_address : "0.0.0.0", + initiate(mCurrentProfile.getVpnType().getIdentifier(), + local_address != null ? local_address : "0.0.0.0", mCurrentProfile.getGateway(), mCurrentProfile.getUsername(), mCurrentProfile.getPassword()); } @@ -436,7 +437,7 @@ public class CharonVpnService extends VpnService implements Runnable /** * Initiate VPN, provided by libandroidbridge.so */ - public native void initiate(String local_address, String gateway, + public native void initiate(String type, String local_address, String gateway, String username, String password); /** From 69f731a9d8d94d755851ed624cadd45e822aae70 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 28 Aug 2012 16:45:46 +0200 Subject: [PATCH 14/18] android: Enable pkcs8 plugin --- Android.mk | 2 +- src/frontends/android/jni/Android.mk | 2 +- src/libstrongswan/Android.mk | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Android.mk b/Android.mk index ad7093d94..663b363cf 100644 --- a/Android.mk +++ b/Android.mk @@ -17,7 +17,7 @@ include $(CLEAR_VARS) # this is the list of plugins that are built into libstrongswan and charon # also these plugins are loaded by default (if not changed in strongswan.conf) strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \ - pkcs1 pem xcbc hmac kernel-netlink socket-default android \ + pkcs1 pkcs8 pem xcbc hmac kernel-netlink socket-default android \ stroke eap-identity eap-mschapv2 eap-md5 eap-gtc ifneq ($(strongswan_BUILD_SCEPCLIENT),) diff --git a/src/frontends/android/jni/Android.mk b/src/frontends/android/jni/Android.mk index a603a733e..326565f2b 100644 --- a/src/frontends/android/jni/Android.mk +++ b/src/frontends/android/jni/Android.mk @@ -2,7 +2,7 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \ - pkcs1 pem xcbc hmac socket-default \ + pkcs1 pkcs8 pem xcbc hmac socket-default \ eap-identity eap-mschapv2 eap-md5 eap-gtc strongswan_PLUGINS := $(strongswan_CHARON_PLUGINS) diff --git a/src/libstrongswan/Android.mk b/src/libstrongswan/Android.mk index 3b2d7eaaa..9125079a4 100644 --- a/src/libstrongswan/Android.mk +++ b/src/libstrongswan/Android.mk @@ -67,6 +67,8 @@ LOCAL_SRC_FILES += $(call add_plugin, pem) LOCAL_SRC_FILES += $(call add_plugin, pkcs1) +LOCAL_SRC_FILES += $(call add_plugin, pkcs8) + LOCAL_SRC_FILES += $(call add_plugin, pkcs11) LOCAL_SRC_FILES += $(call add_plugin, pubkey) From 38e866c3ddd7851260419d0e6ba59b316675570e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 28 Aug 2012 17:01:37 +0200 Subject: [PATCH 15/18] android: Don't show the password dialog if not required --- .../android/src/org/strongswan/android/ui/MainActivity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java index 4a1bb6dc9..6ebfdcafc 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java @@ -143,7 +143,8 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen Bundle profileInfo = new Bundle(); profileInfo.putLong(VpnProfileDataSource.KEY_ID, profile.getId()); profileInfo.putString(VpnProfileDataSource.KEY_USERNAME, profile.getUsername()); - if (profile.getPassword() == null) + if (profile.getVpnType().getRequiresUsernamePassword() && + profile.getPassword() == null) { LoginDialog login = new LoginDialog(); login.setArguments(profileInfo); From 3aba33868b1f5574d4230aca15f2281149d6c326 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 28 Aug 2012 17:02:53 +0200 Subject: [PATCH 16/18] android: Added JNI method to retrieve user certificate and private key To simplify things the private key, the user certificate and the CA certificates are all put into the same list. --- .../jni/libandroidbridge/charonservice.c | 73 +++++++++++++++---- .../jni/libandroidbridge/charonservice.h | 11 +++ .../android/logic/CharonVpnService.java | 40 ++++++++++ 3 files changed, 110 insertions(+), 14 deletions(-) diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 57d118faf..8d07dd5b6 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -199,6 +199,33 @@ failed: return FALSE; } +/** + * Converts the given Java array of byte arrays (byte[][]) to a linked list + * of chunk_t objects. + */ +static linked_list_t *convert_array_of_byte_arrays(JNIEnv *env, + jobjectArray jarray) +{ + linked_list_t *list; + jsize i; + + list = linked_list_create(); + for (i = 0; i < (*env)->GetArrayLength(env, jarray); ++i) + { + chunk_t *chunk; + jbyteArray jbytearray; + + chunk = malloc_thing(chunk_t); + list->insert_last(list, chunk); + + jbytearray = (*env)->GetObjectArrayElement(env, jarray, i); + *chunk = chunk_alloc((*env)->GetArrayLength(env, jbytearray)); + (*env)->GetByteArrayRegion(env, jbytearray, 0, chunk->len, chunk->ptr); + (*env)->DeleteLocalRef(env, jbytearray); + } + return list; +} + METHOD(charonservice_t, get_trusted_certificates, linked_list_t*, private_charonservice_t *this) { @@ -206,7 +233,6 @@ METHOD(charonservice_t, get_trusted_certificates, linked_list_t*, jmethodID method_id; jobjectArray jcerts; linked_list_t *list; - jsize i; androidjni_attach_thread(&env); @@ -222,21 +248,39 @@ METHOD(charonservice_t, get_trusted_certificates, linked_list_t*, { goto failed; } - list = linked_list_create(); - for (i = 0; i < (*env)->GetArrayLength(env, jcerts); ++i) + list = convert_array_of_byte_arrays(env, jcerts); + androidjni_detach_thread(); + return list; + +failed: + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return NULL; +} + +METHOD(charonservice_t, get_user_certificate, linked_list_t*, + private_charonservice_t *this) +{ + JNIEnv *env; + jmethodID method_id; + jobjectArray jencodings; + linked_list_t *list; + + androidjni_attach_thread(&env); + + method_id = (*env)->GetMethodID(env, + android_charonvpnservice_class, + "getUserCertificate", "()[[B"); + if (!method_id) { - chunk_t *ca_cert; - jbyteArray jcert; - - ca_cert = malloc_thing(chunk_t); - list->insert_last(list, ca_cert); - - jcert = (*env)->GetObjectArrayElement(env, jcerts, i); - *ca_cert = chunk_alloc((*env)->GetArrayLength(env, jcert)); - (*env)->GetByteArrayRegion(env, jcert, 0, ca_cert->len, ca_cert->ptr); - (*env)->DeleteLocalRef(env, jcert); + goto failed; } - (*env)->DeleteLocalRef(env, jcerts); + jencodings = (*env)->CallObjectMethod(env, this->vpn_service, method_id, NULL); + if (!jencodings) + { + goto failed; + } + list = convert_array_of_byte_arrays(env, jencodings); androidjni_detach_thread(); return list; @@ -323,6 +367,7 @@ static void charonservice_init(JNIEnv *env, jobject service, jobject builder) .update_status = _update_status, .bypass_socket = _bypass_socket, .get_trusted_certificates = _get_trusted_certificates, + .get_user_certificate = _get_user_certificate, .get_vpnservice_builder = _get_vpnservice_builder, }, .attr = android_attr_create(), diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.h b/src/frontends/android/jni/libandroidbridge/charonservice.h index 706eaa220..507010bad 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.h +++ b/src/frontends/android/jni/libandroidbridge/charonservice.h @@ -85,6 +85,17 @@ struct charonservice_t { */ linked_list_t *(*get_trusted_certificates)(charonservice_t *this); + /** + * Get the configured user certificate chain and private key via JNI + * + * The first item in the returned list is the private key, followed by the + * user certificate and any remaining elements of the certificate chain. + * + * @return list of DER encoded objects (as chunk_t*), + * NULL on failure + */ + linked_list_t *(*get_user_certificate)(charonservice_t *this); + /** * Get the current vpnservice_builder_t object * diff --git a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java index 71fc04611..9b502e89a 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -21,6 +21,7 @@ import java.io.File; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; +import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -42,6 +43,8 @@ import android.net.VpnService; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; +import android.security.KeyChain; +import android.security.KeyChainException; import android.util.Log; public class CharonVpnService extends VpnService implements Runnable @@ -54,6 +57,7 @@ public class CharonVpnService extends VpnService implements Runnable private Thread mConnectionHandler; private VpnProfile mCurrentProfile; private volatile String mCurrentCertificateAlias; + private volatile String mCurrentUserCertificateAlias; private VpnProfile mNextProfile; private volatile boolean mProfileUpdated; private volatile boolean mTerminate; @@ -203,6 +207,7 @@ public class CharonVpnService extends VpnService implements Runnable /* store this in a separate (volatile) variable to avoid * a possible deadlock during deinitialization */ mCurrentCertificateAlias = mCurrentProfile.getCertificateAlias(); + mCurrentUserCertificateAlias = mCurrentProfile.getUserCertificateAlias(); setProfile(mCurrentProfile); setError(ErrorState.NO_ERROR); @@ -421,6 +426,41 @@ public class CharonVpnService extends VpnService implements Runnable return certs.toArray(new byte[certs.size()][]); } + /** + * Function called via JNI to get a list containing the DER encoded private key + * and DER encoded certificates of the user selected certificate chain (beginning + * with the user certificate). + * + * Since this method is called from a thread of charon's thread pool we are safe + * to call methods on KeyChain directly. + * + * @return list containing the private key and certificates (first element is the key) + * @throws InterruptedException + * @throws KeyChainException + * @throws CertificateEncodingException + */ + private byte[][] getUserCertificate() throws KeyChainException, InterruptedException, CertificateEncodingException + { + ArrayList encodings = new ArrayList(); + String alias = mCurrentUserCertificateAlias; + PrivateKey key = KeyChain.getPrivateKey(getApplicationContext(), alias); + if (key == null) + { + return null; + } + encodings.add(key.getEncoded()); + X509Certificate[] chain = KeyChain.getCertificateChain(getApplicationContext(), alias); + if (chain == null || chain.length == 0) + { + return null; + } + for (X509Certificate cert : chain) + { + encodings.add(cert.getEncoded()); + } + return encodings.toArray(new byte[encodings.size()][]); + } + /** * Initialization of charon, provided by libandroidbridge.so * From 094a059bcfdb0589f864b373fb694e87a86a8b19 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 28 Aug 2012 17:05:14 +0200 Subject: [PATCH 17/18] android: android_creds_t can provide a user's private key and certificate --- .../libandroidbridge/backend/android_creds.c | 85 ++++++++++++++++++- .../libandroidbridge/backend/android_creds.h | 7 ++ 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_creds.c b/src/frontends/android/jni/libandroidbridge/backend/android_creds.c index 27023d721..931f22316 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_creds.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_creds.c @@ -49,6 +49,15 @@ struct private_android_creds_t { bool loaded; }; +/** + * Free allocated DER encoding + */ +static void free_encoding(chunk_t *chunk) +{ + chunk_free(chunk); + free(chunk); +} + /** * Load trusted certificates via charonservice (JNI). */ @@ -71,8 +80,7 @@ static void load_trusted_certificates(private_android_creds_t *this) cert->get_subject(cert)); this->creds->add_cert(this->creds, TRUE, cert); } - chunk_free(current); - free(current); + free_encoding(current); } certs->destroy(certs); } @@ -130,6 +138,76 @@ METHOD(credential_set_t, create_shared_enumerator, enumerator_t*, type, me, other); } +METHOD(android_creds_t, load_user_certificate, certificate_t*, + private_android_creds_t *this) +{ + linked_list_t *encodings; + certificate_t *cert = NULL, *ca_cert; + private_key_t *key = NULL; + chunk_t *current; + + encodings = charonservice->get_user_certificate(charonservice); + if (!encodings) + { + return NULL; + } + + while (encodings->remove_first(encodings, (void**)¤t) == SUCCESS) + { + if (!key) + { /* the first element is the private key, we assume RSA */ + key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, + BUILD_BLOB_ASN1_DER, *current, BUILD_END); + if (key) + { + this->creds->add_key(this->creds, key); + free_encoding(current); + continue; + } + goto failed; + } + if (!cert) + { /* the next element is the user certificate */ + cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_BLOB_ASN1_DER, *current, BUILD_END); + if (cert) + { + DBG1(DBG_CFG, "loaded user certificate '%Y' and private key", + cert->get_subject(cert)); + cert = this->creds->add_cert_ref(this->creds, TRUE, cert); + free_encoding(current); + continue; + } + goto failed; + } + /* the rest are CA certificates, we ignore failures */ + ca_cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_BLOB_ASN1_DER, *current, BUILD_END); + if (ca_cert) + { + DBG1(DBG_CFG, "loaded CA certificate '%Y'", + ca_cert->get_subject(ca_cert)); + this->creds->add_cert(this->creds, TRUE, ca_cert); + } + free_encoding(current); + } + encodings->destroy(encodings); + return cert; + +failed: + DBG1(DBG_CFG, "failed to load user certificate and private key"); + free_encoding(current); + encodings->destroy_function(encodings, (void*)free_encoding); + return NULL; +} + +METHOD(credential_set_t, create_private_enumerator, enumerator_t*, + private_android_creds_t *this, key_type_t type, identification_t *id) +{ + return this->creds->set.create_private_enumerator(&this->creds->set, + type, id); +} + METHOD(android_creds_t, clear, void, private_android_creds_t *this) { @@ -160,11 +238,12 @@ android_creds_t *android_creds_create() .set = { .create_cert_enumerator = _create_cert_enumerator, .create_shared_enumerator = _create_shared_enumerator, - .create_private_enumerator = (void*)return_null, + .create_private_enumerator = _create_private_enumerator, .create_cdp_enumerator = (void*)return_null, .cache_cert = (void*)nop, }, .add_username_password = _add_username_password, + .load_user_certificate = _load_user_certificate, .clear = _clear, .destroy = _destroy, }, diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_creds.h b/src/frontends/android/jni/libandroidbridge/backend/android_creds.h index 33de838c1..a3ecddde4 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_creds.h +++ b/src/frontends/android/jni/libandroidbridge/backend/android_creds.h @@ -46,6 +46,13 @@ struct android_creds_t { void (*add_username_password)(android_creds_t *this, char *username, char *password); + /** + * Load the user certificate and private key + * + * @preturn loaded client certificate, NULL on failure + */ + certificate_t *(*load_user_certificate)(android_creds_t *this); + /** * Clear the cached certificates and stored credentials. */ From c89cc2269259fcc2ea140e199cef9eff230e4e80 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 28 Aug 2012 17:11:55 +0200 Subject: [PATCH 18/18] android: Native parts handle ikev2-cert VPN type --- .../backend/android_service.c | 71 ++++++++++++++++--- .../backend/android_service.h | 8 ++- .../jni/libandroidbridge/charonservice.c | 8 +-- 3 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c index dfc0d2342..29b6d45af 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -43,11 +43,21 @@ struct private_android_service_t { */ android_service_t public; + /** + * credential set + */ + android_creds_t *creds; + /** * current IKE_SA */ ike_sa_t *ike_sa; + /** + * the type of VPN + */ + char *type; + /** * local ipv4 address */ @@ -63,6 +73,11 @@ struct private_android_service_t { */ char *username; + /** + * password + */ + char *password; + /** * lock to safely access the TUN device fd */ @@ -430,12 +445,42 @@ static job_requeue_t initiate(private_android_service_t *this) host_create_from_string("0.0.0.0", 0) /* virt */, NULL, FALSE, NULL, NULL); /* pool, mediation */ + /* local auth config */ + if (streq("ikev2-eap", this->type)) + { + auth = auth_cfg_create(); + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); + user = identification_create_from_string(this->username); + auth->add(auth, AUTH_RULE_IDENTITY, user); - auth = auth_cfg_create(); - auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); - user = identification_create_from_string(this->username); - auth->add(auth, AUTH_RULE_IDENTITY, user); - peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE); + this->creds->add_username_password(this->creds, this->username, + this->password); + memwipe(this->password, strlen(this->password)); + peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE); + } + else if (streq("ikev2-cert", this->type)) + { + certificate_t *cert; + identification_t *id; + + cert = this->creds->load_user_certificate(this->creds); + if (!cert) + { + peer_cfg->destroy(peer_cfg); + charonservice->update_status(charonservice, + CHARONSERVICE_GENERIC_ERROR); + return JOB_REQUEUE_NONE; + + } + auth = auth_cfg_create(); + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); + auth->add(auth, AUTH_RULE_SUBJECT_CERT, cert); + id = cert->get_subject(cert); + auth->add(auth, AUTH_RULE_IDENTITY, id->clone(id)); + peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE); + } + + /* remote auth config */ auth = auth_cfg_create(); auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); gateway = identification_create_from_string(this->gateway); @@ -492,17 +537,24 @@ METHOD(android_service_t, destroy, void, /* make sure the tun device is actually closed */ close_tun_device(this); this->lock->destroy(this->lock); + free(this->type); free(this->local_address); - free(this->username); free(this->gateway); + free(this->username); + if (this->password) + { + memwipe(this->password, strlen(this->password)); + free(this->password); + } free(this); } /** * See header */ -android_service_t *android_service_create(char *local_address, char *gateway, - char *username) +android_service_t *android_service_create(android_creds_t *creds, char *type, + char *local_address, char *gateway, + char *username, char *password) { private_android_service_t *this; @@ -520,7 +572,10 @@ android_service_t *android_service_create(char *local_address, char *gateway, .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), .local_address = local_address, .username = username, + .password = password, .gateway = gateway, + .creds = creds, + .type = type, .tunfd = -1, ); diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.h b/src/frontends/android/jni/libandroidbridge/backend/android_service.h index a7bd8b059..52c3dc5c8 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.h +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.h @@ -51,11 +51,15 @@ struct android_service_t { * Create an Android service instance. Queues a job that starts initiation of a * new IKE SA. * + * @param creds Android specific credential set + * @param type VPN type (see VpnType.java) * @param local_address local ip address * @param gateway gateway address * @param username user name (local identity) + * @param password password (if any) */ -android_service_t *android_service_create(char *local_address, char *gateway, - char *username); +android_service_t *android_service_create(android_creds_t *creds, char *type, + char *local_address, char *gateway, + char *username, char *password); #endif /** ANDROID_SERVICE_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 8d07dd5b6..59ec62fc7 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -310,13 +310,9 @@ static void initiate(char *type, char *local, char *gateway, private_charonservice_t *this = (private_charonservice_t*)charonservice; this->creds->clear(this->creds); - this->creds->add_username_password(this->creds, username, password); - memwipe(password, strlen(password)); - free(password); - DESTROY_IF(this->service); - this->service = android_service_create(local, gateway, username); - free(type); + this->service = android_service_create(this->creds, type, local, gateway, + username, password); } /**