android: Allow configuration of a user certificate

This commit is contained in:
Tobias Brunner
2012-08-31 18:24:43 +02:00
parent 3f9e90f618
commit f46da851ab
8 changed files with 198 additions and 9 deletions
@@ -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"
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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();
}
}
}