aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/frontends/android/AndroidManifest.xml18
-rw-r--r--src/frontends/android/jni/libandroidbridge/backend/android_private_key.c123
-rw-r--r--src/frontends/android/jni/libandroidbridge/charonservice.c4
-rw-r--r--src/frontends/android/project.properties2
-rw-r--r--src/frontends/android/res/layout/remediation_instruction_item.xml4
-rw-r--r--src/frontends/android/res/layout/two_line_button.xml4
-rw-r--r--src/frontends/android/res/menu/certificates.xml28
-rw-r--r--src/frontends/android/res/menu/main.xml6
-rw-r--r--src/frontends/android/res/values-de/strings.xml8
-rw-r--r--src/frontends/android/res/values-pl/strings.xml8
-rw-r--r--src/frontends/android/res/values-ru/strings.xml8
-rw-r--r--src/frontends/android/res/values-ua/strings.xml8
-rw-r--r--src/frontends/android/res/values/strings.xml8
-rw-r--r--src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java27
-rw-r--r--src/frontends/android/src/org/strongswan/android/logic/StrongSwanApplication.java48
-rw-r--r--src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java128
-rw-r--r--src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreProvider.java29
-rw-r--r--src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreSpi.java139
-rw-r--r--src/frontends/android/src/org/strongswan/android/security/LocalCertificateStore.java230
-rw-r--r--src/frontends/android/src/org/strongswan/android/security/TrustedCertificateEntry.java (renamed from src/frontends/android/src/org/strongswan/android/data/TrustedCertificateEntry.java)2
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java80
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/MainActivity.java17
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateImportActivity.java223
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java38
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java147
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java50
-rw-r--r--src/frontends/android/src/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java2
-rw-r--r--src/frontends/android/src/org/strongswan/android/utils/Utils.java40
28 files changed, 1243 insertions, 186 deletions
diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml
index e3e7ec631..1a5af0d15 100644
--- a/src/frontends/android/AndroidManifest.xml
+++ b/src/frontends/android/AndroidManifest.xml
@@ -20,12 +20,13 @@
android:versionCode="20"
android:versionName="1.3.4" >
- <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="18" />
+ <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
+ android:name=".logic.StrongSwanApplication"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/ApplicationTheme"
@@ -63,7 +64,20 @@
android:label="@string/strongswan_shortcut" >
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
- <action android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".ui.TrustedCertificateImportActivity"
+ android:label="@string/import_certificate"
+ android:theme="@android:style/Theme.Holo.Dialog.NoActionBar" >
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="application/x-x509-ca-cert" />
+ <data android:mimeType="application/x-x509-server-cert" />
+ <data android:mimeType="application/x-pem-file" />
+ <data android:mimeType="application/pkix-cert" />
</intent-filter>
</activity>
diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_private_key.c b/src/frontends/android/jni/libandroidbridge/backend/android_private_key.c
index 1aeabac2f..1985f0e98 100644
--- a/src/frontends/android/jni/libandroidbridge/backend/android_private_key.c
+++ b/src/frontends/android/jni/libandroidbridge/backend/android_private_key.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 Tobias Brunner
+ * Copyright (C) 2012-2014 Tobias Brunner
* Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@@ -17,6 +17,7 @@
#include "../android_jni.h"
#include <utils/debug.h>
+#include <asn1/asn1.h>
typedef struct private_private_key_t private_private_key_t;
@@ -57,35 +58,62 @@ METHOD(private_key_t, sign, bool,
{
JNIEnv *env;
jmethodID method_id;
- const char *method;
+ const char *method = NULL;
jstring jmethod;
jobject jsignature;
jbyteArray jdata, jsigarray;
- switch (scheme)
+ switch (this->pubkey->get_type(this->pubkey))
{
- case SIGN_RSA_EMSA_PKCS1_MD5:
- method = "MD5withRSA";
+ case KEY_RSA:
+ switch (scheme)
+ {
+ case SIGN_RSA_EMSA_PKCS1_MD5:
+ method = "MD5withRSA";
+ break;
+ case SIGN_RSA_EMSA_PKCS1_SHA1:
+ method = "SHA1withRSA";
+ break;
+ case SIGN_RSA_EMSA_PKCS1_SHA224:
+ method = "SHA224withRSA";
+ break;
+ case SIGN_RSA_EMSA_PKCS1_SHA256:
+ method = "SHA256withRSA";
+ break;
+ case SIGN_RSA_EMSA_PKCS1_SHA384:
+ method = "SHA384withRSA";
+ break;
+ case SIGN_RSA_EMSA_PKCS1_SHA512:
+ method = "SHA512withRSA";
+ break;
+ default:
+ break;
+ }
break;
- case SIGN_RSA_EMSA_PKCS1_SHA1:
- method = "SHA1withRSA";
- break;
- case SIGN_RSA_EMSA_PKCS1_SHA224:
- method = "SHA224withRSA";
- break;
- case SIGN_RSA_EMSA_PKCS1_SHA256:
- method = "SHA256withRSA";
- break;
- case SIGN_RSA_EMSA_PKCS1_SHA384:
- method = "SHA384withRSA";
- break;
- case SIGN_RSA_EMSA_PKCS1_SHA512:
- method = "SHA512withRSA";
+ case KEY_ECDSA:
+ switch (scheme)
+ {
+ case SIGN_ECDSA_256:
+ method = "SHA256withECDSA";
+ break;
+ case SIGN_ECDSA_384:
+ method = "SHA384withECDSA";
+ break;
+ case SIGN_ECDSA_521:
+ method = "SHA512withECDSA";
+ break;
+ default:
+ break;
+ }
break;
default:
- DBG1(DBG_LIB, "signature scheme %N not supported via JNI",
- signature_scheme_names, scheme);
- return FALSE;
+ break;
+ }
+ if (!method)
+ {
+ DBG1(DBG_LIB, "signature scheme %N not supported via JNI",
+ signature_scheme_names, scheme);
+ return FALSE;
}
androidjni_attach_thread(&env);
@@ -142,7 +170,54 @@ METHOD(private_key_t, sign, bool,
{
goto failed;
}
- *signature = chunk_from_byte_array(env, jsigarray);
+ if (this->pubkey->get_type(this->pubkey) == KEY_ECDSA)
+ {
+ chunk_t encoded, parse, r, s;
+ size_t len = 0;
+
+ switch (scheme)
+ {
+ case SIGN_ECDSA_256:
+ len = 32;
+ break;
+ case SIGN_ECDSA_384:
+ len = 48;
+ break;
+ case SIGN_ECDSA_521:
+ len = 66;
+ break;
+ default:
+ break;
+ }
+
+ /* we get an ASN.1 encoded sequence of integers r and s */
+ parse = encoded = chunk_from_byte_array(env, jsigarray);
+ if (asn1_unwrap(&parse, &parse) != ASN1_SEQUENCE ||
+ asn1_unwrap(&parse, &r) != ASN1_INTEGER ||
+ asn1_unwrap(&parse, &s) != ASN1_INTEGER)
+ {
+ chunk_free(&encoded);
+ goto failed;
+ }
+ r = chunk_skip_zero(r);
+ s = chunk_skip_zero(s);
+ if (r.len > len || s.len > len)
+ {
+ chunk_free(&encoded);
+ goto failed;
+ }
+
+ /* concatenate r and s (forced to the defined length) */
+ *signature = chunk_alloc(2*len);
+ memset(signature->ptr, 0, signature->len);
+ memcpy(signature->ptr + (len - r.len), r.ptr, r.len);
+ memcpy(signature->ptr + len + (len - s.len), s.ptr, s.len);
+ chunk_free(&encoded);
+ }
+ else
+ {
+ *signature = chunk_from_byte_array(env, jsigarray);
+ }
androidjni_detach_thread();
return TRUE;
@@ -157,7 +232,7 @@ failed:
METHOD(private_key_t, get_type, key_type_t,
private_private_key_t *this)
{
- return KEY_RSA;
+ return this->pubkey->get_type(this->pubkey);
}
METHOD(private_key_t, decrypt, bool,
diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c
index 707bb3df0..32bf28f09 100644
--- a/src/frontends/android/jni/libandroidbridge/charonservice.c
+++ b/src/frontends/android/jni/libandroidbridge/charonservice.c
@@ -299,12 +299,12 @@ METHOD(charonservice_t, get_trusted_certificates, linked_list_t*,
method_id = (*env)->GetMethodID(env,
android_charonvpnservice_class,
- "getTrustedCertificates", "(Ljava/lang/String;)[[B");
+ "getTrustedCertificates", "()[[B");
if (!method_id)
{
goto failed;
}
- jcerts = (*env)->CallObjectMethod(env, this->vpn_service, method_id, NULL);
+ jcerts = (*env)->CallObjectMethod(env, this->vpn_service, method_id);
if (!jcerts || androidjni_exception_occurred(env))
{
goto failed;
diff --git a/src/frontends/android/project.properties b/src/frontends/android/project.properties
index 730e911f2..a5578ba09 100644
--- a/src/frontends/android/project.properties
+++ b/src/frontends/android/project.properties
@@ -8,4 +8,4 @@
# project structure.
# Project target.
-target=android-14
+target=android-19
diff --git a/src/frontends/android/res/layout/remediation_instruction_item.xml b/src/frontends/android/res/layout/remediation_instruction_item.xml
index 30dfb2219..c25e6c123 100644
--- a/src/frontends/android/res/layout/remediation_instruction_item.xml
+++ b/src/frontends/android/res/layout/remediation_instruction_item.xml
@@ -13,7 +13,7 @@
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
-->
-<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dip"
@@ -44,4 +44,4 @@
android:ellipsize="end"
android:textIsSelectable="false" />
-</TwoLineListItem>
+</RelativeLayout>
diff --git a/src/frontends/android/res/layout/two_line_button.xml b/src/frontends/android/res/layout/two_line_button.xml
index c8c25811b..89d095295 100644
--- a/src/frontends/android/res/layout/two_line_button.xml
+++ b/src/frontends/android/res/layout/two_line_button.xml
@@ -13,7 +13,7 @@
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
-->
-<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
@@ -36,4 +36,4 @@
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary" />
-</TwoLineListItem>
+</RelativeLayout>
diff --git a/src/frontends/android/res/menu/certificates.xml b/src/frontends/android/res/menu/certificates.xml
new file mode 100644
index 000000000..6066cab60
--- /dev/null
+++ b/src/frontends/android/res/menu/certificates.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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 <http://www.fsf.org/copyleft/gpl.txt>.
+
+ 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item
+ android:id="@+id/menu_import_certificate"
+ android:title="@string/import_certificate"
+ android:showAsAction="withText" />
+
+ <item
+ android:id="@+id/menu_reload_certs"
+ android:title="@string/reload_trusted_certs"
+ android:showAsAction="withText" />
+
+</menu>
diff --git a/src/frontends/android/res/menu/main.xml b/src/frontends/android/res/menu/main.xml
index 4063110da..3dde5227e 100644
--- a/src/frontends/android/res/menu/main.xml
+++ b/src/frontends/android/res/menu/main.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2012 Tobias Brunner
+ Copyright (C) 2012-2014 Tobias Brunner
Hochschule fuer Technik Rapperswil
This program is free software; you can redistribute it and/or modify it
@@ -16,8 +16,8 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
- android:id="@+id/menu_reload_certs"
- android:title="@string/reload_trusted_certs"
+ android:id="@+id/menu_manage_certs"
+ android:title="@string/trusted_certs_title"
android:showAsAction="withText" />
<item
diff --git a/src/frontends/android/res/values-de/strings.xml b/src/frontends/android/res/values-de/strings.xml
index db7698135..491fe8a8a 100644
--- a/src/frontends/android/res/values-de/strings.xml
+++ b/src/frontends/android/res/values-de/strings.xml
@@ -20,7 +20,6 @@
<!-- Application -->
<string name="app_name">strongSwan VPN Client</string>
<string name="main_activity_name">strongSwan</string>
- <string name="reload_trusted_certs">CA-Zertifikate neu laden</string>
<string name="show_log">Log anzeigen</string>
<string name="search">Suchen</string>
<string name="vpn_not_supported_title">VPN nicht unterstützt</string>
@@ -76,8 +75,15 @@
<!-- Trusted certificate selection -->
<string name="trusted_certs_title">CA-Zertifikate</string>
<string name="no_certificates">Keine Zertifikate</string>
+ <string name="reload_trusted_certs">CA-Zertifikate neu laden</string>
<string name="system_tab">System</string>
<string name="user_tab">Benutzer</string>
+ <string name="local_tab">Importiert</string>
+ <string name="delete_certificate_question">Zertifikat löschen?</string>
+ <string name="delete_certificate">Das Zertifikat wird permanent entfernt!</string>
+ <string name="import_certificate">Zertifikat importieren</string>
+ <string name="cert_imported_successfully">Zertifikat erfolgreich importiert</string>
+ <string name="cert_import_failed">Zertifikat-Import fehlgeschlagen</string>
<!-- VPN state fragment -->
<string name="state_label">Status:</string>
diff --git a/src/frontends/android/res/values-pl/strings.xml b/src/frontends/android/res/values-pl/strings.xml
index 7aa9c51a7..d0cfa48f1 100644
--- a/src/frontends/android/res/values-pl/strings.xml
+++ b/src/frontends/android/res/values-pl/strings.xml
@@ -20,7 +20,6 @@
<!-- Application -->
<string name="app_name">strongSwan klient VPN</string>
<string name="main_activity_name">strongSwan</string>
- <string name="reload_trusted_certs">Przeładuj certyfikaty CA</string>
<string name="show_log">Pokaż log</string>
<string name="search">Szukaj</string>
<string name="vpn_not_supported_title">Nie obsługiwany VPN</string>
@@ -76,8 +75,15 @@
<!-- Trusted certificate selection -->
<string name="trusted_certs_title">Certyfikaty CA</string>
<string name="no_certificates">Brak certyfikatów</string>
+ <string name="reload_trusted_certs">Przeładuj certyfikaty CA</string>
<string name="system_tab">System</string>
<string name="user_tab">Użytkownik</string>
+ <string name="local_tab">Imported</string>
+ <string name="delete_certificate_question">Delete certificate?</string>
+ <string name="delete_certificate">The certificate will be permanently removed!</string>
+ <string name="import_certificate">Import certificate</string>
+ <string name="cert_imported_successfully">Certificate successfully imported</string>
+ <string name="cert_import_failed">Failed to import certificate</string>
<!-- VPN state fragment -->
<string name="state_label">Status:</string>
diff --git a/src/frontends/android/res/values-ru/strings.xml b/src/frontends/android/res/values-ru/strings.xml
index 3838485af..eb69183db 100644
--- a/src/frontends/android/res/values-ru/strings.xml
+++ b/src/frontends/android/res/values-ru/strings.xml
@@ -17,7 +17,6 @@
<!-- Application -->
<string name="app_name">Клиент strongSwan VPN</string>
<string name="main_activity_name">strongSwan</string>
- <string name="reload_trusted_certs">Обновить сертификат CA</string>
<string name="show_log">Журнал</string>
<string name="search">Поиск</string>
<string name="vpn_not_supported_title">VPN не поддерживается</string>
@@ -73,8 +72,15 @@
<!-- Trusted certificate selection -->
<string name="trusted_certs_title">Сертификаты CA</string>
<string name="no_certificates">Нет доступных сертификатов</string>
+ <string name="reload_trusted_certs">Обновить сертификат CA</string>
<string name="system_tab">Система</string>
<string name="user_tab">Пользователь</string>
+ <string name="local_tab">Imported</string>
+ <string name="delete_certificate_question">Delete certificate?</string>
+ <string name="delete_certificate">The certificate will be permanently removed!</string>
+ <string name="import_certificate">Import certificate</string>
+ <string name="cert_imported_successfully">Certificate successfully imported</string>
+ <string name="cert_import_failed">Failed to import certificate</string>
<!-- VPN state fragment -->
<string name="state_label">Статус:</string>
diff --git a/src/frontends/android/res/values-ua/strings.xml b/src/frontends/android/res/values-ua/strings.xml
index df016ff8f..e23b9b9b2 100644
--- a/src/frontends/android/res/values-ua/strings.xml
+++ b/src/frontends/android/res/values-ua/strings.xml
@@ -18,7 +18,6 @@
<!-- Application -->
<string name="app_name">strongSwan VPN клієнт</string>
<string name="main_activity_name">strongSwan</string>
- <string name="reload_trusted_certs">Перезавантажити CA сертифікати</string>
<string name="show_log">Перегляд журналу</string>
<string name="search">Пошук</string>
<string name="vpn_not_supported_title">VPN не підтримуеться</string>
@@ -74,8 +73,15 @@
<!-- Trusted certificate selection -->
<string name="trusted_certs_title">Сертифікати CA</string>
<string name="no_certificates">Немає сертифікатів</string>
+ <string name="reload_trusted_certs">Перезавантажити CA сертифікати</string>
<string name="system_tab">Система</string>
<string name="user_tab">Користувач</string>
+ <string name="local_tab">Imported</string>
+ <string name="delete_certificate_question">Delete certificate?</string>
+ <string name="delete_certificate">The certificate will be permanently removed!</string>
+ <string name="import_certificate">Import certificate</string>
+ <string name="cert_imported_successfully">Certificate successfully imported</string>
+ <string name="cert_import_failed">Failed to import certificate</string>
<!-- VPN state fragment -->
<string name="state_label">Статус:</string>
diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml
index 180948969..933a80aff 100644
--- a/src/frontends/android/res/values/strings.xml
+++ b/src/frontends/android/res/values/strings.xml
@@ -20,7 +20,6 @@
<!-- Application -->
<string name="app_name">strongSwan VPN Client</string>
<string name="main_activity_name">strongSwan</string>
- <string name="reload_trusted_certs">Reload CA certificates</string>
<string name="show_log">View log</string>
<string name="search">Search</string>
<string name="vpn_not_supported_title">VPN not supported</string>
@@ -76,8 +75,15 @@
<!-- Trusted certificate selection -->
<string name="trusted_certs_title">CA certificates</string>
<string name="no_certificates">No certificates</string>
+ <string name="reload_trusted_certs">Reload CA certificates</string>
<string name="system_tab">System</string>
<string name="user_tab">User</string>
+ <string name="local_tab">Imported</string>
+ <string name="delete_certificate_question">Delete certificate?</string>
+ <string name="delete_certificate">The certificate will be permanently removed!</string>
+ <string name="import_certificate">Import certificate</string>
+ <string name="cert_imported_successfully">Certificate successfully imported</string>
+ <string name="cert_import_failed">Failed to import certificate</string>
<!-- VPN state fragment -->
<string name="state_label">Status:</string>
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 e45a7d9bd..31172ab44 100644
--- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java
+++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java
@@ -419,49 +419,30 @@ public class CharonVpnService extends VpnService implements Runnable
* Function called via JNI to generate a list of DER encoded CA certificates
* as byte array.
*
- * @param hash optional alias (only hash part), if given matching certificates are returned
* @return a list of DER encoded CA certificates
*/
- private byte[][] getTrustedCertificates(String hash)
+ private byte[][] getTrustedCertificates()
{
ArrayList<byte[]> certs = new ArrayList<byte[]>();
TrustedCertificateManager certman = TrustedCertificateManager.getInstance();
try
{
- if (hash != null)
+ String alias = this.mCurrentCertificateAlias;
+ if (alias != null)
{
- String alias = "user:" + hash + ".0";
X509Certificate cert = certman.getCACertificateFromAlias(alias);
if (cert == null)
{
- alias = "system:" + hash + ".0";
- cert = certman.getCACertificateFromAlias(alias);
- }
- if (cert == null)
- {
return null;
}
certs.add(cert.getEncoded());
}
else
{
- String alias = this.mCurrentCertificateAlias;
- if (alias != null)
+ for (X509Certificate cert : certman.getAllCACertificates().values())
{
- X509Certificate cert = certman.getCACertificateFromAlias(alias);
- if (cert == null)
- {
- return null;
- }
certs.add(cert.getEncoded());
}
- else
- {
- for (X509Certificate cert : certman.getAllCACertificates().values())
- {
- certs.add(cert.getEncoded());
- }
- }
}
}
catch (CertificateEncodingException e)
diff --git a/src/frontends/android/src/org/strongswan/android/logic/StrongSwanApplication.java b/src/frontends/android/src/org/strongswan/android/logic/StrongSwanApplication.java
new file mode 100644
index 000000000..d642b67b3
--- /dev/null
+++ b/src/frontends/android/src/org/strongswan/android/logic/StrongSwanApplication.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2014 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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.logic;
+
+import java.security.Security;
+
+import org.strongswan.android.security.LocalCertificateKeyStoreProvider;
+
+import android.app.Application;
+import android.content.Context;
+
+public class StrongSwanApplication extends Application
+{
+ private static Context mContext;
+
+ static {
+ Security.addProvider(new LocalCertificateKeyStoreProvider());
+ }
+
+ @Override
+ public void onCreate()
+ {
+ super.onCreate();
+ StrongSwanApplication.mContext = getApplicationContext();
+ }
+
+ /**
+ * Returns the current application context
+ * @return context
+ */
+ public static Context getContext()
+ {
+ return StrongSwanApplication.mContext;
+ }
+}
diff --git a/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java b/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java
index 95fdecf14..82a7cbe4e 100644
--- a/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java
+++ b/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 Tobias Brunner
+ * Copyright (C) 2012-2014 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* Hochschule fuer Technik Rapperswil
@@ -21,6 +21,7 @@ import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -32,13 +33,49 @@ public class TrustedCertificateManager
private static final String TAG = TrustedCertificateManager.class.getSimpleName();
private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
private Hashtable<String, X509Certificate> mCACerts = new Hashtable<String, X509Certificate>();
+ private volatile boolean mReload;
private boolean mLoaded;
+ private final ArrayList<KeyStore> mKeyStores = new ArrayList<KeyStore>();
+
+ public enum TrustedCertificateSource
+ {
+ SYSTEM("system:"),
+ USER("user:"),
+ LOCAL("local:");
+
+ private final String mPrefix;
+
+ private TrustedCertificateSource(String prefix)
+ {
+ mPrefix = prefix;
+ }
+
+ private String getPrefix()
+ {
+ return mPrefix;
+ }
+ }
/**
* Private constructor to prevent instantiation from other classes.
*/
private TrustedCertificateManager()
{
+ for (String name : new String[] { "LocalCertificateStore", "AndroidCAStore" })
+ {
+ KeyStore store;
+ try
+ {
+ store = KeyStore.getInstance(name);
+ store.load(null,null);
+ mKeyStores.add(store);
+ }
+ catch (Exception e)
+ {
+ Log.e(TAG, "Unable to load KeyStore: " + name);
+ e.printStackTrace();
+ }
+ }
}
/**
@@ -58,16 +95,14 @@ public class TrustedCertificateManager
}
/**
- * Forces a load/reload of the cached CA certificates.
- * As this takes a while it should be called asynchronously.
+ * Invalidates the current load state so that the next call to load()
+ * will force a reload of the cached CA certificates.
* @return reference to itself
*/
- public TrustedCertificateManager reload()
+ public TrustedCertificateManager reset()
{
- Log.d(TAG, "Force reload of cached CA certificates");
- this.mLock.writeLock().lock();
- loadCertificates();
- this.mLock.writeLock().unlock();
+ Log.d(TAG, "Force reload of cached CA certificates on next load");
+ this.mReload = true;
return this;
}
@@ -81,8 +116,9 @@ public class TrustedCertificateManager
{
Log.d(TAG, "Ensure cached CA certificates are loaded");
this.mLock.writeLock().lock();
- if (!this.mLoaded)
+ if (!this.mLoaded || this.mReload)
{
+ this.mReload = false;
loadCertificates();
}
this.mLock.writeLock().unlock();
@@ -96,29 +132,23 @@ public class TrustedCertificateManager
private void loadCertificates()
{
Log.d(TAG, "Load cached CA certificates");
- try
- {
- KeyStore store = KeyStore.getInstance("AndroidCAStore");
- store.load(null, null);
- this.mCACerts = fetchCertificates(store);
- this.mLoaded = true;
- Log.d(TAG, "Cached CA certificates loaded");
- }
- catch (Exception ex)
+ Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>();
+ for (KeyStore store : this.mKeyStores)
{
- ex.printStackTrace();
- this.mCACerts = new Hashtable<String, X509Certificate>();
+ fetchCertificates(certs, store);
}
+ this.mCACerts = certs;
+ this.mLoaded = true;
+ Log.d(TAG, "Cached CA certificates loaded");
}
/**
* Load all X.509 certificates from the given KeyStore.
+ * @param certs Hashtable to store certificates in
* @param store KeyStore to load certificates from
- * @return Hashtable mapping aliases to certificates
*/
- private Hashtable<String, X509Certificate> fetchCertificates(KeyStore store)
+ private void fetchCertificates(Hashtable<String, X509Certificate> certs, KeyStore store)
{
- Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>();
try
{
Enumeration<String> aliases = store.aliases();
@@ -137,7 +167,6 @@ public class TrustedCertificateManager
{
ex.printStackTrace();
}
- return certs;
}
/**
@@ -157,27 +186,28 @@ public class TrustedCertificateManager
else
{ /* if we cannot get the lock load it directly from the KeyStore,
* should be fast for a single certificate */
- try
+ for (KeyStore store : this.mKeyStores)
{
- KeyStore store = KeyStore.getInstance("AndroidCAStore");
- store.load(null, null);
- Certificate cert = store.getCertificate(alias);
- if (cert != null && cert instanceof X509Certificate)
+ try
{
- certificate = (X509Certificate)cert;
+ Certificate cert = store.getCertificate(alias);
+ if (cert != null && cert instanceof X509Certificate)
+ {
+ certificate = (X509Certificate)cert;
+ break;
+ }
+ }
+ catch (KeyStoreException e)
+ {
+ e.printStackTrace();
}
}
- catch (Exception e)
- {
- e.printStackTrace();
- }
-
}
return certificate;
}
/**
- * Get all CA certificates (from the system and user keystore).
+ * Get all CA certificates (from all keystores).
* @return Hashtable mapping aliases to certificates
*/
@SuppressWarnings("unchecked")
@@ -191,35 +221,17 @@ public class TrustedCertificateManager
}
/**
- * Get only the system-wide CA certificates.
- * @return Hashtable mapping aliases to certificates
- */
- public Hashtable<String, X509Certificate> getSystemCACertificates()
- {
- Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>();
- this.mLock.readLock().lock();
- for (String alias : this.mCACerts.keySet())
- {
- if (alias.startsWith("system:"))
- {
- certs.put(alias, this.mCACerts.get(alias));
- }
- }
- this.mLock.readLock().unlock();
- return certs;
- }
-
- /**
- * Get only the CA certificates installed by the user.
+ * Get all certificates from the given source.
+ * @param source type to filter certificates
* @return Hashtable mapping aliases to certificates
*/
- public Hashtable<String, X509Certificate> getUserCACertificates()
+ public Hashtable<String, X509Certificate> getCACertificates(TrustedCertificateSource source)
{
Hashtable<String, X509Certificate> certs = new Hashtable<String, X509Certificate>();
this.mLock.readLock().lock();
for (String alias : this.mCACerts.keySet())
{
- if (alias.startsWith("user:"))
+ if (alias.startsWith(source.getPrefix()))
{
certs.put(alias, this.mCACerts.get(alias));
}
diff --git a/src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreProvider.java b/src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreProvider.java
new file mode 100644
index 000000000..c49b1044f
--- /dev/null
+++ b/src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreProvider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.security;
+
+import java.security.Provider;
+
+public class LocalCertificateKeyStoreProvider extends Provider
+{
+ private static final long serialVersionUID = 3515038332469843219L;
+
+ public LocalCertificateKeyStoreProvider()
+ {
+ super("LocalCertificateKeyStoreProvider", 1.0, "KeyStore provider for local certificates");
+ put("KeyStore.LocalCertificateStore", LocalCertificateKeyStoreSpi.class.getName());
+ }
+}
diff --git a/src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreSpi.java b/src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreSpi.java
new file mode 100644
index 000000000..64a48a9bb
--- /dev/null
+++ b/src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreSpi.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2014 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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.security;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.KeyStoreException;
+import java.security.KeyStoreSpi;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+
+public class LocalCertificateKeyStoreSpi extends KeyStoreSpi
+{
+ private final LocalCertificateStore mStore = new LocalCertificateStore();
+
+ @Override
+ public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException
+ {
+ return null;
+ }
+
+ @Override
+ public Certificate[] engineGetCertificateChain(String alias)
+ {
+ return null;
+ }
+
+ @Override
+ public Certificate engineGetCertificate(String alias)
+ {
+ return mStore.getCertificate(alias);
+ }
+
+ @Override
+ public Date engineGetCreationDate(String alias)
+ {
+ return mStore.getCreationDate(alias);
+ }
+
+ @Override
+ public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) throws KeyStoreException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException
+ {
+ /* we ignore the given alias as the store calculates it on its own,
+ * duplicates are replaced */
+ if (!mStore.addCertificate(cert))
+ {
+ throw new KeyStoreException();
+ }
+ }
+
+ @Override
+ public void engineDeleteEntry(String alias) throws KeyStoreException
+ {
+ mStore.deleteCertificate(alias);
+ }
+
+ @Override
+ public Enumeration<String> engineAliases()
+ {
+ return Collections.enumeration(mStore.aliases());
+ }
+
+ @Override
+ public boolean engineContainsAlias(String alias)
+ {
+ return mStore.containsAlias(alias);
+ }
+
+ @Override
+ public int engineSize()
+ {
+ return mStore.aliases().size();
+ }
+
+ @Override
+ public boolean engineIsKeyEntry(String alias)
+ {
+ return false;
+ }
+
+ @Override
+ public boolean engineIsCertificateEntry(String alias)
+ {
+ return engineContainsAlias(alias);
+ }
+
+ @Override
+ public String engineGetCertificateAlias(Certificate cert)
+ {
+ return mStore.getCertificateAlias(cert);
+ }
+
+ @Override
+ public void engineStore(OutputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void engineLoad(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException
+ {
+ if (stream != null)
+ {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/src/frontends/android/src/org/strongswan/android/security/LocalCertificateStore.java b/src/frontends/android/src/org/strongswan/android/security/LocalCertificateStore.java
new file mode 100644
index 000000000..cec5c603d
--- /dev/null
+++ b/src/frontends/android/src/org/strongswan/android/security/LocalCertificateStore.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2014 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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.security;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.regex.Pattern;
+
+import org.strongswan.android.logic.StrongSwanApplication;
+import org.strongswan.android.utils.Utils;
+
+import android.content.Context;
+
+public class LocalCertificateStore
+{
+ private static final String FILE_PREFIX = "certificate-";
+ private static final String ALIAS_PREFIX = "local:";
+ private static final Pattern ALIAS_PATTERN = Pattern.compile("^" + ALIAS_PREFIX + "[0-9a-f]{40}$");
+
+ /**
+ * Add the given certificate to the store
+ * @param cert the certificate to add
+ * @return true if successful
+ */
+ public boolean addCertificate(Certificate cert)
+ {
+ if (!(cert instanceof X509Certificate))
+ { /* only accept X.509 certificates */
+ return false;
+ }
+ String keyid = getKeyId(cert);
+ if (keyid == null)
+ {
+ return false;
+ }
+ FileOutputStream out;
+ try
+ {
+ /* we replace any existing file with the same alias */
+ out = StrongSwanApplication.getContext().openFileOutput(FILE_PREFIX + keyid, Context.MODE_PRIVATE);
+ try
+ {
+ out.write(cert.getEncoded());
+ return true;
+ }
+ catch (CertificateEncodingException e)
+ {
+ e.printStackTrace();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ finally
+ {
+ try
+ {
+ out.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+ catch (FileNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ /**
+ * Delete the certificate with the given alias
+ * @param alias a certificate's alias
+ */
+ public void deleteCertificate(String alias)
+ {
+ if (ALIAS_PATTERN.matcher(alias).matches())
+ {
+ alias = alias.substring(ALIAS_PREFIX.length());
+ StrongSwanApplication.getContext().deleteFile(FILE_PREFIX + alias);
+ }
+ }
+
+ /**
+ * Retrieve the certificate with the given alias
+ * @param alias a certificate's alias
+ * @return certificate object or null
+ */
+ public X509Certificate getCertificate(String alias)
+ {
+ if (!ALIAS_PATTERN.matcher(alias).matches())
+ {
+ return null;
+ }
+ alias = alias.substring(ALIAS_PREFIX.length());
+ try
+ {
+ FileInputStream in = StrongSwanApplication.getContext().openFileInput(FILE_PREFIX + alias);
+ try
+ {
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ X509Certificate certificate = (X509Certificate)factory.generateCertificate(in);
+ return certificate;
+ }
+ catch (CertificateException e)
+ {
+ e.printStackTrace();
+ }
+ finally
+ {
+ try
+ {
+ in.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+ catch (FileNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the creation date of the certificate with the given alias
+ * @param alias certificate alias
+ * @return creation date or null if not found
+ */
+ public Date getCreationDate(String alias)
+ {
+ if (!ALIAS_PATTERN.matcher(alias).matches())
+ {
+ return null;
+ }
+ alias = alias.substring(ALIAS_PREFIX.length());
+ File file = StrongSwanApplication.getContext().getFileStreamPath(FILE_PREFIX + alias);
+ return file.exists() ? new Date(file.lastModified()) : null;
+ }
+
+ /**
+ * Returns a list of all known certificate aliases
+ * @return list of aliases
+ */
+ public ArrayList<String> aliases()
+ {
+ ArrayList<String> list = new ArrayList<String>();
+ for (String file : StrongSwanApplication.getContext().fileList())
+ {
+ if (file.startsWith(FILE_PREFIX))
+ {
+ list.add(ALIAS_PREFIX + file.substring(FILE_PREFIX.length()));
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Check if the store contains a certificate with the given alias
+ * @param alias certificate alias
+ * @return true if the store contains the certificate
+ */
+ public boolean containsAlias(String alias)
+ {
+ return getCreationDate(alias) != null;
+ }
+
+ /**
+ * Returns a certificate alias based on a SHA-1 hash of the public key.
+ *
+ * @param cert certificate to get an alias for
+ * @return hex encoded alias, or null if failed
+ */
+ public String getCertificateAlias(Certificate cert)
+ {
+ String keyid = getKeyId(cert);
+ return keyid != null ? ALIAS_PREFIX + keyid : null;
+ }
+
+ /**
+ * Calculates the SHA-1 hash of the public key of the given certificate.
+ * @param cert certificate to get the key ID from
+ * @return hex encoded SHA-1 hash of the public key or null if failed
+ */
+ private String getKeyId(Certificate cert)
+ {
+ MessageDigest md;
+ try
+ {
+ md = java.security.MessageDigest.getInstance("SHA1");
+ byte[] hash = md.digest(cert.getPublicKey().getEncoded());
+ return Utils.bytesToHex(hash);
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
diff --git a/src/frontends/android/src/org/strongswan/android/data/TrustedCertificateEntry.java b/src/frontends/android/src/org/strongswan/android/security/TrustedCertificateEntry.java
index de7ea32b4..143741faf 100644
--- a/src/frontends/android/src/org/strongswan/android/data/TrustedCertificateEntry.java
+++ b/src/frontends/android/src/org/strongswan/android/security/TrustedCertificateEntry.java
@@ -13,7 +13,7 @@
* for more details.
*/
-package org.strongswan.android.data;
+package org.strongswan.android.security;
import java.security.cert.X509Certificate;
diff --git a/src/frontends/android/src/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java b/src/frontends/android/src/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java
new file mode 100644
index 000000000..c381900c6
--- /dev/null
+++ b/src/frontends/android/src/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.ui;
+
+import org.strongswan.android.R;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+/**
+ * Class that displays a confirmation dialog to delete a selected local
+ * certificate.
+ */
+public class CertificateDeleteConfirmationDialog extends DialogFragment
+{
+ public static final String ALIAS = "alias";
+ OnCertificateDeleteListener mListener;
+
+ /**
+ * Interface that can be implemented by parent activities to get the
+ * alias of the certificate to delete, if the user confirms the deletion.
+ */
+ public interface OnCertificateDeleteListener
+ {
+ public void onDelete(String alias);
+ }
+
+ @Override
+ public void onAttach(Activity activity)
+ {
+ super.onAttach(activity);
+ if (activity instanceof OnCertificateDeleteListener)
+ {
+ mListener = (OnCertificateDeleteListener)activity;
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState)
+ {
+ return new AlertDialog.Builder(getActivity())
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setTitle(R.string.delete_certificate_question)
+ .setMessage(R.string.delete_certificate)
+ .setPositiveButton(R.string.delete_profile, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton)
+ {
+ if (mListener != null)
+ {
+ mListener.onDelete(getArguments().getString(ALIAS));
+ }
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
+ {
+ dismiss();
+ }
+ }).create();
+ }
+}
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 3cf395054..a2ad80e82 100644
--- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java
+++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java
@@ -101,7 +101,7 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
bar.setDisplayShowTitleEnabled(false);
/* load CA certificates in a background task */
- new CertificateLoadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, false);
+ new LoadCertificatesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
@@ -140,8 +140,9 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
{
switch (item.getItemId())
{
- case R.id.menu_reload_certs:
- new CertificateLoadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, true);
+ case R.id.menu_manage_certs:
+ Intent certIntent = new Intent(this, TrustedCertificatesActivity.class);
+ startActivity(certIntent);
return true;
case R.id.menu_show_log:
Intent logIntent = new Intent(this, LogActivity.class);
@@ -280,9 +281,9 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
}
/**
- * Class that loads or reloads the cached CA certificates.
+ * Class that loads the cached CA certificates.
*/
- private class CertificateLoadTask extends AsyncTask<Boolean, Void, TrustedCertificateManager>
+ private class LoadCertificatesTask extends AsyncTask<Void, Void, TrustedCertificateManager>
{
@Override
protected void onPreExecute()
@@ -290,12 +291,8 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen
setProgressBarIndeterminateVisibility(true);
}
@Override
- protected TrustedCertificateManager doInBackground(Boolean... params)
+ protected TrustedCertificateManager doInBackground(Void... params)
{
- if (params.length > 0 && params[0])
- { /* force a reload of the certificates */
- return TrustedCertificateManager.getInstance().reload();
- }
return TrustedCertificateManager.getInstance().load();
}
@Override
diff --git a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateImportActivity.java b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateImportActivity.java
new file mode 100644
index 000000000..61bd2c9a2
--- /dev/null
+++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateImportActivity.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2014 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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.ui;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import org.strongswan.android.R;
+import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.logic.TrustedCertificateManager;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentTransaction;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.widget.Toast;
+
+public class TrustedCertificateImportActivity extends Activity
+{
+ private static final int OPEN_DOCUMENT = 0;
+ private static final String DIALOG_TAG = "Dialog";
+
+ /* same as those listed in the manifest */
+ private static final String[] ACCEPTED_MIME_TYPES = {
+ "application/x-x509-ca-cert",
+ "application/x-x509-server-cert",
+ "application/x-pem-file",
+ "application/pkix-cert"
+ };
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState != null)
+ { /* do nothing when we are restoring */
+ return;
+ }
+
+ Intent intent = getIntent();
+ String action = intent.getAction();
+ if (Intent.ACTION_VIEW.equals(action))
+ {
+ importCertificate(intent.getData());
+ }
+ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
+ {
+ Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ openIntent.setType("*/*");
+ openIntent.putExtra(Intent.EXTRA_MIME_TYPES, ACCEPTED_MIME_TYPES);
+ startActivityForResult(openIntent, OPEN_DOCUMENT);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data)
+ {
+ switch (requestCode)
+ {
+ case OPEN_DOCUMENT:
+ if (resultCode == Activity.RESULT_OK && data != null)
+ {
+ importCertificate(data.getData());
+ return;
+ }
+ finish();
+ return;
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ /**
+ * Import the file pointed to by the given URI as a certificate.
+ * @param uri
+ */
+ private void importCertificate(Uri uri)
+ {
+ X509Certificate certificate = parseCertificate(uri);
+ if (certificate == null)
+ {
+ Toast.makeText(this, R.string.cert_import_failed, Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+ /* Ask the user whether to import the certificate. This is particularly
+ * necessary because the import activity can be triggered by any app on
+ * the system. Also, if our app is the only one that is registered to
+ * open certificate files by MIME type the user would have no idea really
+ * where the file was imported just by reading the Toast we display. */
+ ConfirmImportDialog dialog = new ConfirmImportDialog();
+ Bundle args = new Bundle();
+ args.putSerializable(VpnProfileDataSource.KEY_CERTIFICATE, certificate);
+ dialog.setArguments(args);
+ FragmentTransaction ft = getFragmentManager().beginTransaction();
+ ft.add(dialog, DIALOG_TAG);
+ ft.commit();
+ }
+
+ /**
+ * Load the file from the given URI and try to parse it as X.509 certificate.
+ * @param uri
+ * @return certificate or null
+ */
+ private X509Certificate parseCertificate(Uri uri)
+ {
+ X509Certificate certificate = null;
+ try
+ {
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ InputStream in = getContentResolver().openInputStream(uri);
+ certificate = (X509Certificate)factory.generateCertificate(in);
+ /* we don't check whether it's actually a CA certificate or not */
+ }
+ catch (CertificateException e)
+ {
+ e.printStackTrace();
+ }
+ catch (FileNotFoundException e)
+ {
+ e.printStackTrace();
+ }
+ return certificate;
+ }
+
+
+ /**
+ * Try to store the given certificate in the KeyStore.
+ * @param certificate
+ * @return whether it was successfully stored
+ */
+ private boolean storeCertificate(X509Certificate certificate)
+ {
+ try
+ {
+ KeyStore store = KeyStore.getInstance("LocalCertificateStore");
+ store.load(null, null);
+ store.setCertificateEntry(null, certificate);
+ TrustedCertificateManager.getInstance().reset();
+ return true;
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * Class that displays a confirmation dialog when a certificate should get
+ * imported. If the user confirms the import we try to store it.
+ */
+ public static class ConfirmImportDialog extends DialogFragment
+ {
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState)
+ {
+ final X509Certificate certificate;
+
+ certificate = (X509Certificate)getArguments().getSerializable(VpnProfileDataSource.KEY_CERTIFICATE);
+
+ return new AlertDialog.Builder(getActivity())
+ .setIcon(R.drawable.ic_launcher)
+ .setTitle(R.string.import_certificate)
+ .setMessage(certificate.getSubjectDN().toString())
+ .setPositiveButton(R.string.import_certificate, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton)
+ {
+ TrustedCertificateImportActivity activity = (TrustedCertificateImportActivity)getActivity();
+ if (activity.storeCertificate(certificate))
+ {
+ Toast.makeText(getActivity(), R.string.cert_imported_successfully, Toast.LENGTH_LONG).show();
+ getActivity().setResult(Activity.RESULT_OK);
+ }
+ else
+ {
+ Toast.makeText(getActivity(), R.string.cert_import_failed, Toast.LENGTH_LONG).show();
+ }
+ getActivity().finish();
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which)
+ {
+ getActivity().finish();
+ }
+ }).create();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog)
+ {
+ getActivity().finish();
+ }
+ }
+}
diff --git a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java
index 4e8e0ddeb..8bd39c435 100644
--- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java
+++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 Tobias Brunner
+ * Copyright (C) 2012-2014 Tobias Brunner
* Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@@ -23,8 +23,9 @@ import java.util.List;
import java.util.Map.Entry;
import org.strongswan.android.R;
-import org.strongswan.android.data.TrustedCertificateEntry;
import org.strongswan.android.logic.TrustedCertificateManager;
+import org.strongswan.android.logic.TrustedCertificateManager.TrustedCertificateSource;
+import org.strongswan.android.security.TrustedCertificateEntry;
import org.strongswan.android.ui.adapter.TrustedCertificateAdapter;
import android.app.Activity;
@@ -45,9 +46,10 @@ import android.widget.SearchView.OnQueryTextListener;
public class TrustedCertificateListFragment extends ListFragment implements LoaderCallbacks<List<TrustedCertificateEntry>>, OnQueryTextListener
{
+ public static final String EXTRA_CERTIFICATE_SOURCE = "certificate_source";
private OnTrustedCertificateSelectedListener mListener;
private TrustedCertificateAdapter mAdapter;
- private boolean mUser;
+ private TrustedCertificateSource mSource = TrustedCertificateSource.SYSTEM;
/**
* The activity containing this fragment should implement this interface
@@ -69,8 +71,11 @@ public class TrustedCertificateListFragment extends ListFragment implements Load
setListShown(false);
- /* non empty arguments mean we list user certificate */
- mUser = getArguments() != null;
+ Bundle arguments = getArguments();
+ if (arguments != null)
+ {
+ mSource = (TrustedCertificateSource)arguments.getSerializable(EXTRA_CERTIFICATE_SOURCE);
+ }
getLoaderManager().initLoader(0, null, this);
}
@@ -118,10 +123,22 @@ public class TrustedCertificateListFragment extends ListFragment implements Load
return true;
}
+ /**
+ * Reset the loader of this list fragment
+ */
+ public void reset()
+ {
+ if (isResumed())
+ {
+ setListShown(false);
+ }
+ getLoaderManager().restartLoader(0, null, this);
+ }
+
@Override
public Loader<List<TrustedCertificateEntry>> onCreateLoader(int id, Bundle args)
{ /* we don't need the id as we have only one loader */
- return new CertificateListLoader(getActivity(), mUser);
+ return new CertificateListLoader(getActivity(), mSource);
}
@Override
@@ -157,22 +174,21 @@ public class TrustedCertificateListFragment extends ListFragment implements Load
public static class CertificateListLoader extends AsyncTaskLoader<List<TrustedCertificateEntry>>
{
private List<TrustedCertificateEntry> mData;
- private final boolean mUser;
+ private final TrustedCertificateSource mSource;
- public CertificateListLoader(Context context, boolean user)
+ public CertificateListLoader(Context context, TrustedCertificateSource source)
{
super(context);
- mUser = user;
+ mSource = source;
}
@Override
public List<TrustedCertificateEntry> loadInBackground()
{
TrustedCertificateManager certman = TrustedCertificateManager.getInstance().load();
- Hashtable<String,X509Certificate> certificates;
+ Hashtable<String,X509Certificate> certificates = certman.getCACertificates(mSource);
List<TrustedCertificateEntry> selected;
- certificates = mUser ? certman.getUserCACertificates() : certman.getSystemCACertificates();
selected = new ArrayList<TrustedCertificateEntry>();
for (Entry<String, X509Certificate> entry : certificates.entrySet())
{
diff --git a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java
index 967d25a02..663950c16 100644
--- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java
+++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 Tobias Brunner
+ * Copyright (C) 2012-2014 Tobias Brunner
* Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@@ -15,9 +15,14 @@
package org.strongswan.android.ui;
+import java.security.KeyStore;
+
import org.strongswan.android.R;
-import org.strongswan.android.data.TrustedCertificateEntry;
import org.strongswan.android.data.VpnProfileDataSource;
+import org.strongswan.android.logic.TrustedCertificateManager;
+import org.strongswan.android.logic.TrustedCertificateManager.TrustedCertificateSource;
+import org.strongswan.android.security.TrustedCertificateEntry;
+import org.strongswan.android.ui.CertificateDeleteConfirmationDialog.OnCertificateDeleteListener;
import android.app.ActionBar;
import android.app.ActionBar.Tab;
@@ -25,11 +30,18 @@ import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Intent;
+import android.os.Build;
import android.os.Bundle;
+import android.view.Menu;
import android.view.MenuItem;
-public class TrustedCertificatesActivity extends Activity implements TrustedCertificateListFragment.OnTrustedCertificateSelectedListener
+public class TrustedCertificatesActivity extends Activity implements TrustedCertificateListFragment.OnTrustedCertificateSelectedListener, OnCertificateDeleteListener
{
+ public static final String SELECT_CERTIFICATE = "org.strongswan.android.action.SELECT_CERTIFICATE";
+ private static final String DIALOG_TAG = "Dialog";
+ private static final int IMPORT_CERTIFICATE = 0;
+ private boolean mSelect;
+
@Override
public void onCreate(Bundle savedInstanceState)
{
@@ -40,19 +52,31 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+ TrustedCertificatesTabListener listener;
+ listener = new TrustedCertificatesTabListener(this, "system", TrustedCertificateSource.SYSTEM);
actionBar.addTab(actionBar
.newTab()
.setText(R.string.system_tab)
- .setTabListener(new TrustedCertificatesTabListener(this, "system", false)));
+ .setTag(listener)
+ .setTabListener(listener));
+ listener = new TrustedCertificatesTabListener(this, "user", TrustedCertificateSource.USER);
actionBar.addTab(actionBar
.newTab()
.setText(R.string.user_tab)
- .setTabListener(new TrustedCertificatesTabListener(this, "user", true)));
+ .setTag(listener)
+ .setTabListener(listener));
+ listener = new TrustedCertificatesTabListener(this, "local", TrustedCertificateSource.LOCAL);
+ actionBar.addTab(actionBar
+ .newTab()
+ .setText(R.string.local_tab)
+ .setTag(listener)
+ .setTabListener(listener));
if (savedInstanceState != null)
{
actionBar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
}
+ mSelect = SELECT_CERTIFICATE.equals(getIntent().getAction());
}
@Override
@@ -63,6 +87,23 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert
}
@Override
+ public boolean onCreateOptionsMenu(Menu menu)
+ {
+ getMenuInflater().inflate(R.menu.certificates, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu)
+ {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
+ {
+ menu.removeItem(R.id.menu_import_certificate);
+ }
+ return true;
+ }
+
+ @Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
@@ -70,30 +111,95 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert
case android.R.id.home:
finish();
return true;
+ case R.id.menu_reload_certs:
+ reloadCertificates();
+ return true;
+ case R.id.menu_import_certificate:
+ Intent intent = new Intent(this, TrustedCertificateImportActivity.class);
+ startActivityForResult(intent, IMPORT_CERTIFICATE);
+ return true;
}
return super.onOptionsItemSelected(item);
}
@Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data)
+ {
+ switch (requestCode)
+ {
+ case IMPORT_CERTIFICATE:
+ if (resultCode == Activity.RESULT_OK)
+ {
+ reloadCertificates();
+ }
+ return;
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
public void onTrustedCertificateSelected(TrustedCertificateEntry selected)
{
- /* the user selected a certificate, return to calling activity */
- Intent intent = new Intent();
- intent.putExtra(VpnProfileDataSource.KEY_CERTIFICATE, selected.getAlias());
- setResult(Activity.RESULT_OK, intent);
- finish();
+ if (mSelect)
+ {
+ /* the user selected a certificate, return to calling activity */
+ Intent intent = new Intent();
+ intent.putExtra(VpnProfileDataSource.KEY_CERTIFICATE, selected.getAlias());
+ setResult(Activity.RESULT_OK, intent);
+ finish();
+ }
+ else
+ {
+ TrustedCertificatesTabListener listener;
+ listener = (TrustedCertificatesTabListener)getActionBar().getSelectedTab().getTag();
+ if (listener.mTag == "local")
+ {
+ Bundle args = new Bundle();
+ args.putString(CertificateDeleteConfirmationDialog.ALIAS, selected.getAlias());
+ CertificateDeleteConfirmationDialog dialog = new CertificateDeleteConfirmationDialog();
+ dialog.setArguments(args);
+ dialog.show(this.getFragmentManager(), DIALOG_TAG);
+ }
+ }
+ }
+
+ @Override
+ public void onDelete(String alias)
+ {
+ try
+ {
+ KeyStore store = KeyStore.getInstance("LocalCertificateStore");
+ store.load(null, null);
+ store.deleteEntry(alias);
+ reloadCertificates();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ private void reloadCertificates()
+ {
+ TrustedCertificateManager.getInstance().reset();
+ for (int i = 0; i < getActionBar().getTabCount(); i++)
+ {
+ Tab tab = getActionBar().getTabAt(i);
+ TrustedCertificatesTabListener listener = (TrustedCertificatesTabListener)tab.getTag();
+ listener.reset();
+ }
}
public static class TrustedCertificatesTabListener implements ActionBar.TabListener
{
private final String mTag;
- private final boolean mUser;
+ private final TrustedCertificateSource mSource;
private Fragment mFragment;
- public TrustedCertificatesTabListener(Activity activity, String tag, boolean user)
+ public TrustedCertificatesTabListener(Activity activity, String tag, TrustedCertificateSource source)
{
mTag = tag;
- mUser = user;
+ mSource = source;
/* check to see if we already have a fragment for this tab, probably
* from a previously saved state. if so, deactivate it, because the
* initial state is that no tab is shown */
@@ -112,10 +218,9 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert
if (mFragment == null)
{
mFragment = new TrustedCertificateListFragment();
- if (mUser)
- { /* use non empty arguments to indicate this */
- mFragment.setArguments(new Bundle());
- }
+ Bundle args = new Bundle();
+ args.putSerializable(TrustedCertificateListFragment.EXTRA_CERTIFICATE_SOURCE, mSource);
+ mFragment.setArguments(args);
ft.add(android.R.id.content, mFragment, mTag);
}
else
@@ -138,5 +243,13 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert
{
/* nothing to be done */
}
+
+ public void reset()
+ {
+ if (mFragment != null)
+ {
+ ((TrustedCertificateListFragment)mFragment).reset();
+ }
+ }
}
}
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 baad9611d..39d37005d 100644
--- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java
+++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 Tobias Brunner
+ * Copyright (C) 2012-2014 Tobias Brunner
* Copyright (C) 2012 Giuliano Grassi
* Copyright (C) 2012 Ralf Sager
* Hochschule fuer Technik Rapperswil
@@ -20,11 +20,11 @@ package org.strongswan.android.ui;
import java.security.cert.X509Certificate;
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 org.strongswan.android.security.TrustedCertificateEntry;
import android.app.Activity;
import android.app.AlertDialog;
@@ -52,8 +52,9 @@ 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.TwoLineListItem;
+import android.widget.TextView;
public class VpnProfileDetailActivity extends Activity
{
@@ -73,10 +74,10 @@ public class VpnProfileDetailActivity extends Activity
private EditText mUsername;
private EditText mPassword;
private ViewGroup mUserCertificate;
- private TwoLineListItem mSelectUserCert;
+ private RelativeLayout mSelectUserCert;
private CheckBox mCheckAuto;
- private TwoLineListItem mSelectCert;
- private TwoLineListItem mTncNotice;
+ private RelativeLayout mSelectCert;
+ private RelativeLayout mTncNotice;
@Override
public void onCreate(Bundle savedInstanceState)
@@ -94,17 +95,17 @@ public class VpnProfileDetailActivity extends Activity
mName = (EditText)findViewById(R.id.name);
mGateway = (EditText)findViewById(R.id.gateway);
mSelectVpnType = (Spinner)findViewById(R.id.vpn_type);
- mTncNotice = (TwoLineListItem)findViewById(R.id.tnc_notice);
+ mTncNotice = (RelativeLayout)findViewById(R.id.tnc_notice);
mUsernamePassword = (ViewGroup)findViewById(R.id.username_password_group);
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);
+ mSelectUserCert = (RelativeLayout)findViewById(R.id.select_user_certificate);
mCheckAuto = (CheckBox)findViewById(R.id.ca_auto);
- mSelectCert = (TwoLineListItem)findViewById(R.id.select_certificate);
+ mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate);
mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
@@ -122,8 +123,8 @@ public class VpnProfileDetailActivity extends Activity
}
});
- mTncNotice.getText1().setText(R.string.tnc_notice_title);
- mTncNotice.getText2().setText(R.string.tnc_notice_subtitle);
+ ((TextView)mTncNotice.findViewById(android.R.id.text1)).setText(R.string.tnc_notice_title);
+ ((TextView)mTncNotice.findViewById(android.R.id.text2)).setText(R.string.tnc_notice_subtitle);
mTncNotice.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v)
@@ -147,6 +148,7 @@ public class VpnProfileDetailActivity extends Activity
public void onClick(View v)
{
Intent intent = new Intent(VpnProfileDetailActivity.this, TrustedCertificatesActivity.class);
+ intent.setAction(TrustedCertificatesActivity.SELECT_CERTIFICATE);
startActivityForResult(intent, SELECT_TRUSTED_CERTIFICATE);
}
});
@@ -246,19 +248,19 @@ public class VpnProfileDetailActivity extends Activity
{
if (mUserCertLoading != null)
{
- mSelectUserCert.getText1().setText(mUserCertLoading);
- mSelectUserCert.getText2().setText(R.string.loading);
+ ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(mUserCertLoading);
+ ((TextView)mSelectUserCert.findViewById(android.R.id.text2)).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());
+ ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError(null);
+ ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(mUserCertEntry.getAlias());
+ ((TextView)mSelectUserCert.findViewById(android.R.id.text2)).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);
+ ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(R.string.profile_user_select_certificate_label);
+ ((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(R.string.profile_user_select_certificate);
}
}
}
@@ -295,13 +297,13 @@ public class VpnProfileDetailActivity extends Activity
if (mCertEntry != null)
{
- mSelectCert.getText1().setText(mCertEntry.getSubjectPrimary());
- mSelectCert.getText2().setText(mCertEntry.getSubjectSecondary());
+ ((TextView)mSelectCert.findViewById(android.R.id.text1)).setText(mCertEntry.getSubjectPrimary());
+ ((TextView)mSelectCert.findViewById(android.R.id.text2)).setText(mCertEntry.getSubjectSecondary());
}
else
{
- mSelectCert.getText1().setText(R.string.profile_ca_select_certificate_label);
- mSelectCert.getText2().setText(R.string.profile_ca_select_certificate);
+ ((TextView)mSelectCert.findViewById(android.R.id.text1)).setText(R.string.profile_ca_select_certificate_label);
+ ((TextView)mSelectCert.findViewById(android.R.id.text2)).setText(R.string.profile_ca_select_certificate);
}
}
else
@@ -357,7 +359,7 @@ public class VpnProfileDetailActivity extends Activity
}
if (mVpnType.getRequiresCertificate() && mUserCertEntry == null)
{ /* let's show an error icon */
- mSelectUserCert.getText1().setError("");
+ ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError("");
valid = false;
}
if (!mCheckAuto.isChecked() && mCertEntry == null)
@@ -545,7 +547,7 @@ public class VpnProfileDetailActivity extends Activity
}
else
{ /* previously selected certificate is not here anymore */
- mSelectUserCert.getText1().setError("");
+ ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError("");
mUserCertEntry = null;
}
mUserCertLoading = null;
diff --git a/src/frontends/android/src/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java b/src/frontends/android/src/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java
index a97360d58..3795bb199 100644
--- a/src/frontends/android/src/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java
+++ b/src/frontends/android/src/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java
@@ -18,7 +18,7 @@ package org.strongswan.android.ui.adapter;
import java.util.List;
import org.strongswan.android.R;
-import org.strongswan.android.data.TrustedCertificateEntry;
+import org.strongswan.android.security.TrustedCertificateEntry;
import android.content.Context;
import android.view.LayoutInflater;
diff --git a/src/frontends/android/src/org/strongswan/android/utils/Utils.java b/src/frontends/android/src/org/strongswan/android/utils/Utils.java
new file mode 100644
index 000000000..b5c447f31
--- /dev/null
+++ b/src/frontends/android/src/org/strongswan/android/utils/Utils.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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.utils;
+
+
+public class Utils
+{
+ static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
+
+ /**
+ * Converts the given byte array to a hexadecimal string encoding.
+ *
+ * @param bytes byte array to convert
+ * @return hex string
+ */
+ public static String bytesToHex(byte[] bytes)
+ {
+ char[] hex = new char[bytes.length * 2];
+ for (int i = 0; i < bytes.length; i++)
+ {
+ int value = bytes[i];
+ hex[i*2] = HEXDIGITS[(value & 0xf0) >> 4];
+ hex[i*2+1] = HEXDIGITS[ value & 0x0f];
+ }
+ return new String(hex);
+ }
+}