diff options
author | Tobias Brunner <tobias@strongswan.org> | 2012-08-28 14:09:18 +0200 |
---|---|---|
committer | Tobias Brunner <tobias@strongswan.org> | 2012-08-31 18:24:43 +0200 |
commit | f46da851ab51a1b6ef6b9817f49a866fb25e1c09 (patch) | |
tree | 492ead8a34dd8958d3d84d246f54b7971ec70fbf /src | |
parent | 3f9e90f618b1abb5f25c62d44b2a059cf1b6b78c (diff) | |
download | strongswan-f46da851ab51a1b6ef6b9817f49a866fb25e1c09.tar.bz2 strongswan-f46da851ab51a1b6ef6b9817f49a866fb25e1c09.tar.xz |
android: Allow configuration of a user certificate
Diffstat (limited to 'src')
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" /> <LinearLayout @@ -100,6 +100,24 @@ </LinearLayout> + <LinearLayout + android:id="@+id/user_certificate_group" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="@string/profile_user_certificate_label" /> + + <include + android:id="@+id/select_user_certificate" + layout="@layout/certificate_selector" /> + + </LinearLayout> + <TextView android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/src/frontends/android/res/values-de/arrays.xml b/src/frontends/android/res/values-de/arrays.xml index 8661a37be..efa4bcb03 100644 --- a/src/frontends/android/res/values-de/arrays.xml +++ b/src/frontends/android/res/values-de/arrays.xml @@ -17,5 +17,6 @@ <!-- the order here must match the enum entries in VpnType.java --> <string-array name="vpn_types"> <item>IKEv2 EAP (Benutzername/Passwort)</item> + <item>IKEv2 Zertifikat</item> </string-array> </resources>
\ 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 @@ <string name="search">Suchen</string> <string name="vpn_not_supported_title">VPN nicht unterstützt</string> <string name="vpn_not_supported">Ihr Gerät unterstützt keine VPN Anwendungen.\nBitte kontaktieren Sie den Hersteller.</string> + <string name="loading">Laden…</string> <!-- Log view --> <string name="log_title">Log</string> @@ -53,6 +54,9 @@ <string name="profile_username_label">Benutzername:</string> <string name="profile_password_label">Passwort:</string> <string name="profile_password_hint">(anfordern wenn benötigt)</string> + <string name="profile_user_certificate_label">Benutzer-Zertifikat:</string> + <string name="profile_user_select_certificate_label">Benutzer-Zertifikat auswählen</string> + <string name="profile_user_select_certificate">Wählen Sie ein bestimmtes Benutzer-Zertifikat</string> <string name="profile_ca_label">CA-Zertifikat:</string> <string name="profile_ca_auto_label">Automatisch wählen</string> <string name="profile_ca_select_certificate_label">CA-Zertifikat auswählen</string> 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 @@ <!-- the order here must match the enum entries in VpnType.java --> <string-array name="vpn_types"> <item>IKEv2 EAP (użytkownik/hasło)</item> + <item>IKEv2 certyfikat</item> </string-array> </resources>
\ 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 @@ <string name="search">Szukaj</string> <string name="vpn_not_supported_title">Nie obsługiwany VPN</string> <string name="vpn_not_supported">Urządzenie nie obsługuje aplikacji VPN.\nProszę skontaktować się z producentem.</string> + <string name="loading">Wczytywanie…</string> <!-- Log view --> <string name="log_title">Log</string> @@ -54,7 +55,10 @@ <string name="profile_vpn_type_label">Typ:</string> <string name="profile_username_label">Użytkownik:</string> <string name="profile_password_label">Hasło:</string> - <string name="profile_password_hint">(w razie potrzebz zapromptuj)</string> + <string name="profile_password_hint">(w razie potrzeby zapromptuj)</string> + <string name="profile_user_certificate_label">Certyfikat użytkownika:</string> + <string name="profile_user_select_certificate_label">Wybierz certyfikat użytkownika</string> + <string name="profile_user_select_certificate">>Wybierz określony certyfikat użytkownika</string> <string name="profile_ca_label">Certyfikat CA:</string> <string name="profile_ca_auto_label">Wybierz automatycznie</string> <string name="profile_ca_select_certificate_label">Wybierz certyfikat CA</string> 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 @@ <!-- the order here must match the enum entries in VpnType.java --> <string-array name="vpn_types"> <item>IKEv2 EAP (Username/Password)</item> + <item>IKEv2 Certificate</item> </string-array> </resources>
\ 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 @@ <string name="search">Search</string> <string name="vpn_not_supported_title">VPN not supported</string> <string name="vpn_not_supported">Your device does not support VPN applications.\nPlease contact the manufacturer.</string> + <string name="loading">Loading…</string> <!-- Log view --> <string name="log_title">Log</string> @@ -53,6 +54,9 @@ <string name="profile_username_label">Username:</string> <string name="profile_password_label">Password:</string> <string name="profile_password_hint">(prompt when needed)</string> + <string name="profile_user_certificate_label">User certificate:</string> + <string name="profile_user_select_certificate_label">Select user certificate</string> + <string name="profile_user_select_certificate">Select a specific user certificate</string> <string name="profile_ca_label">CA certificate:</string> <string name="profile_ca_auto_label">Select automatically</string> <string name="profile_ca_select_certificate_label">Select CA certificate</string> 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<Void, Void, X509Certificate> + { + 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(); + } + } } |