android: Allow configuration of a user certificate
authorTobias Brunner <tobias@strongswan.org>
Tue, 28 Aug 2012 12:09:18 +0000 (14:09 +0200)
committerTobias Brunner <tobias@strongswan.org>
Fri, 31 Aug 2012 16:24:43 +0000 (18:24 +0200)
src/frontends/android/res/layout/profile_detail_view.xml
src/frontends/android/res/values-de/arrays.xml
src/frontends/android/res/values-de/strings.xml
src/frontends/android/res/values-pl/arrays.xml
src/frontends/android/res/values-pl/strings.xml
src/frontends/android/res/values/arrays.xml
src/frontends/android/res/values/strings.xml
src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java

index 2a52ae0..39c9434 100644 (file)
@@ -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
 
         </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"
index 8661a37..efa4bcb 100644 (file)
@@ -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
index 9e415ab..a04da72 100644 (file)
@@ -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&#8230;</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>
index ed14934..3e1af5f 100644 (file)
@@ -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
index 4a474e8..54f4259 100644 (file)
@@ -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&#8230;</string>
 
     <!-- Log view -->
     <string name="log_title">Log</string>
     <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>
index 62164f7..21576f2 100644 (file)
@@ -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
index 29cd643..3e4b746 100644 (file)
@@ -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&#8230;</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>
index 6cdf97b..91e521c 100644 (file)
@@ -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();
+               }
+       }
 }