From b9fd95f4767552305d39b5f4b595a3afdb3115db Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 11:03:25 +0200 Subject: [PATCH 01/25] android: Use correct tag to define category for CREATE_SHORTCUT intent-filter --- src/frontends/android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index e3e7ec631..1775e3f78 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -63,7 +63,7 @@ android:label="@string/strongswan_shortcut" > - + From 9d994ba5eaa0f92ecaa6ff95d0b133307312fc8f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 11:22:19 +0200 Subject: [PATCH 02/25] android: Remove unused hash argument from getTrustedCertificates() --- .../jni/libandroidbridge/charonservice.c | 4 +-- .../android/logic/CharonVpnService.java | 27 +++---------------- 2 files changed, 6 insertions(+), 25 deletions(-) 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/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,25 +419,19 @@ 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 certs = new ArrayList(); 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; } @@ -445,23 +439,10 @@ public class CharonVpnService extends VpnService implements Runnable } 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) From 140ce41a392353174657d1bdaada744fe99c30f5 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 11:08:35 +0200 Subject: [PATCH 03/25] android: Add utility method to convert a byte array to a hex string --- .../org/strongswan/android/utils/Utils.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/frontends/android/src/org/strongswan/android/utils/Utils.java 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 . + * + * 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); + } +} From 7229bdd5c7f2b05914ddd9f181eec8134f87a0a3 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 12:34:46 +0200 Subject: [PATCH 04/25] android: Target latest SDK version --- src/frontends/android/AndroidManifest.xml | 2 +- src/frontends/android/project.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 1775e3f78..721d0c0fe 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -20,7 +20,7 @@ android:versionCode="20" android:versionName="1.3.4" > - + 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 From 6684195505c8d034cffab60e621921cc77f4bdde Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 12:35:54 +0200 Subject: [PATCH 05/25] android: Subclass Application to provide static access to the application context --- src/frontends/android/AndroidManifest.xml | 1 + .../android/logic/StrongSwanApplication.java | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/frontends/android/src/org/strongswan/android/logic/StrongSwanApplication.java diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 721d0c0fe..e2d25e4ac 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -26,6 +26,7 @@ . + * + * 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 android.app.Application; +import android.content.Context; + +public class StrongSwanApplication extends Application +{ + private static Context mContext; + + @Override + public void onCreate() + { + super.onCreate(); + StrongSwanApplication.mContext = getApplicationContext(); + } + + /** + * Returns the current application context + * @return context + */ + public static Context getContext() + { + return StrongSwanApplication.mContext; + } +} From 463a6cd005fc369bf165b57f46a735d00cf68dcb Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 12:40:53 +0200 Subject: [PATCH 06/25] android: Move TrustedCertificateEntry to a new package --- .../android/{data => security}/TrustedCertificateEntry.java | 2 +- .../strongswan/android/ui/TrustedCertificateListFragment.java | 2 +- .../org/strongswan/android/ui/TrustedCertificatesActivity.java | 2 +- .../src/org/strongswan/android/ui/VpnProfileDetailActivity.java | 2 +- .../android/ui/adapter/TrustedCertificateAdapter.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename src/frontends/android/src/org/strongswan/android/{data => security}/TrustedCertificateEntry.java (98%) diff --git a/src/frontends/android/src/org/strongswan/android/data/TrustedCertificateEntry.java b/src/frontends/android/src/org/strongswan/android/security/TrustedCertificateEntry.java similarity index 98% rename from src/frontends/android/src/org/strongswan/android/data/TrustedCertificateEntry.java rename to 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/TrustedCertificateListFragment.java b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java index 4e8e0ddeb..2f07b96c2 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java @@ -23,8 +23,8 @@ 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.security.TrustedCertificateEntry; import org.strongswan.android.ui.adapter.TrustedCertificateAdapter; import android.app.Activity; 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..2874207e8 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java @@ -16,8 +16,8 @@ package org.strongswan.android.ui; import org.strongswan.android.R; -import org.strongswan.android.data.TrustedCertificateEntry; import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.security.TrustedCertificateEntry; import android.app.ActionBar; import android.app.ActionBar.Tab; 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..6cc6b0c02 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -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; 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; From 275888d255e2d0620758c9159be54ee5128f62e2 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 13:28:16 +0200 Subject: [PATCH 07/25] android: Add local certificate store The class manages certificates stored in files within the app's private data directory. --- .../security/LocalCertificateStore.java | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 src/frontends/android/src/org/strongswan/android/security/LocalCertificateStore.java 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 . + * + * 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 aliases() + { + ArrayList list = new ArrayList(); + 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; + } +} From 544267889e57fa2cd9404ff6c3e2b5fa85d0d702 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 13:30:35 +0200 Subject: [PATCH 08/25] android: Add KeyStoreSpi implementation that uses LocalCertificateStore --- .../security/LocalCertificateKeyStoreSpi.java | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreSpi.java 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 . + * + * 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 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(); + } + } +} From 5eb429704699d1d17107817528acdd9ba5a7645c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 13:45:02 +0200 Subject: [PATCH 09/25] android: Add Provider for the local certificate store --- .../LocalCertificateKeyStoreProvider.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreProvider.java 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 . + * + * 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()); + } +} From 8d3a058abc31584d6edd186e96659eb780c417b5 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 13:45:31 +0200 Subject: [PATCH 10/25] android: Register local certificate store provider when the app is initialized --- .../strongswan/android/logic/StrongSwanApplication.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/frontends/android/src/org/strongswan/android/logic/StrongSwanApplication.java b/src/frontends/android/src/org/strongswan/android/logic/StrongSwanApplication.java index 0d146b7a4..d642b67b3 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/StrongSwanApplication.java +++ b/src/frontends/android/src/org/strongswan/android/logic/StrongSwanApplication.java @@ -15,6 +15,10 @@ package org.strongswan.android.logic; +import java.security.Security; + +import org.strongswan.android.security.LocalCertificateKeyStoreProvider; + import android.app.Application; import android.content.Context; @@ -22,6 +26,10 @@ public class StrongSwanApplication extends Application { private static Context mContext; + static { + Security.addProvider(new LocalCertificateKeyStoreProvider()); + } + @Override public void onCreate() { From 8cdce00eb1160b9f1ce5d6df4e1423ba11bf1d26 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 15:13:50 +0200 Subject: [PATCH 11/25] android: Cache certificates from multiple KeyStores Including the new local one. --- .../logic/TrustedCertificateManager.java | 100 +++++++++++------- 1 file changed, 60 insertions(+), 40 deletions(-) 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..a5cea4499 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; @@ -33,12 +34,28 @@ public class TrustedCertificateManager private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock(); private Hashtable mCACerts = new Hashtable(); private boolean mLoaded; + private final ArrayList mKeyStores = new ArrayList(); /** * 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(); + } + } } /** @@ -96,29 +113,23 @@ public class TrustedCertificateManager private void loadCertificates() { Log.d(TAG, "Load cached CA certificates"); - try + Hashtable certs = new Hashtable(); + for (KeyStore store : this.mKeyStores) { - 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) - { - ex.printStackTrace(); - this.mCACerts = new Hashtable(); + 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 fetchCertificates(KeyStore store) + private void fetchCertificates(Hashtable certs, KeyStore store) { - Hashtable certs = new Hashtable(); try { Enumeration aliases = store.aliases(); @@ -137,7 +148,6 @@ public class TrustedCertificateManager { ex.printStackTrace(); } - return certs; } /** @@ -157,27 +167,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") @@ -196,17 +207,7 @@ public class TrustedCertificateManager */ public Hashtable getSystemCACertificates() { - Hashtable certs = new Hashtable(); - 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; + return getCertificates("system:"); } /** @@ -214,12 +215,31 @@ public class TrustedCertificateManager * @return Hashtable mapping aliases to certificates */ public Hashtable getUserCACertificates() + { + return getCertificates("user:"); + } + + /** + * Get only the local CA certificates installed by the user. + * @return Hashtable mapping aliases to certificates + */ + public Hashtable getLocalCACertificates() + { + return getCertificates("local:"); + } + + /** + * Get all certificates whose aliases start with the given prefix. + * @param prefix prefix to filter certificates + * @return Hashtable mapping aliases to certificates + */ + private Hashtable getCertificates(String prefix) { Hashtable certs = new Hashtable(); this.mLock.readLock().lock(); for (String alias : this.mCACerts.keySet()) { - if (alias.startsWith("user:")) + if (alias.startsWith(prefix)) { certs.put(alias, this.mCACerts.get(alias)); } From 3b2b536b707520f4084cd6ab8ccab41d515aced2 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 11 Jun 2014 14:48:08 +0200 Subject: [PATCH 12/25] android: Change how CA certificates from different sources are accessed --- .../logic/TrustedCertificateManager.java | 54 ++++++++----------- .../ui/TrustedCertificateListFragment.java | 3 +- 2 files changed, 25 insertions(+), 32 deletions(-) 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 a5cea4499..6e0c9f7a7 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java +++ b/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java @@ -36,6 +36,25 @@ public class TrustedCertificateManager private boolean mLoaded; private final ArrayList mKeyStores = new ArrayList(); + 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. */ @@ -202,44 +221,17 @@ public class TrustedCertificateManager } /** - * Get only the system-wide CA certificates. + * Get all certificates from the given source. + * @param source type to filter certificates * @return Hashtable mapping aliases to certificates */ - public Hashtable getSystemCACertificates() - { - return getCertificates("system:"); - } - - /** - * Get only the CA certificates installed by the user. - * @return Hashtable mapping aliases to certificates - */ - public Hashtable getUserCACertificates() - { - return getCertificates("user:"); - } - - /** - * Get only the local CA certificates installed by the user. - * @return Hashtable mapping aliases to certificates - */ - public Hashtable getLocalCACertificates() - { - return getCertificates("local:"); - } - - /** - * Get all certificates whose aliases start with the given prefix. - * @param prefix prefix to filter certificates - * @return Hashtable mapping aliases to certificates - */ - private Hashtable getCertificates(String prefix) + public Hashtable getCACertificates(TrustedCertificateSource source) { Hashtable certs = new Hashtable(); this.mLock.readLock().lock(); for (String alias : this.mCACerts.keySet()) { - if (alias.startsWith(prefix)) + if (alias.startsWith(source.getPrefix())) { certs.put(alias, this.mCACerts.get(alias)); } 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 2f07b96c2..918393fe6 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java @@ -24,6 +24,7 @@ import java.util.Map.Entry; import org.strongswan.android.R; 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; @@ -172,7 +173,7 @@ public class TrustedCertificateListFragment extends ListFragment implements Load Hashtable certificates; List selected; - certificates = mUser ? certman.getUserCACertificates() : certman.getSystemCACertificates(); + certificates = mUser ? certman.getCACertificates(TrustedCertificateSource.USER) : certman.getCACertificates(TrustedCertificateSource.SYSTEM); selected = new ArrayList(); for (Entry entry : certificates.entrySet()) { From f21a69dbece0bb1dd7ed720ddcb14fa8e1553fcf Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 16:15:25 +0200 Subject: [PATCH 13/25] android: Allow selection of local certificates --- .../android/res/values-de/strings.xml | 1 + .../android/res/values-pl/strings.xml | 1 + .../android/res/values-ru/strings.xml | 1 + .../android/res/values-ua/strings.xml | 1 + src/frontends/android/res/values/strings.xml | 1 + .../ui/TrustedCertificateListFragment.java | 23 +++++++++++-------- .../ui/TrustedCertificatesActivity.java | 22 ++++++++++-------- 7 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/frontends/android/res/values-de/strings.xml b/src/frontends/android/res/values-de/strings.xml index db7698135..1f283bbf5 100644 --- a/src/frontends/android/res/values-de/strings.xml +++ b/src/frontends/android/res/values-de/strings.xml @@ -78,6 +78,7 @@ Keine Zertifikate System Benutzer + Importiert Status: diff --git a/src/frontends/android/res/values-pl/strings.xml b/src/frontends/android/res/values-pl/strings.xml index 7aa9c51a7..8b13d4abe 100644 --- a/src/frontends/android/res/values-pl/strings.xml +++ b/src/frontends/android/res/values-pl/strings.xml @@ -78,6 +78,7 @@ Brak certyfikatów System Użytkownik + Imported Status: diff --git a/src/frontends/android/res/values-ru/strings.xml b/src/frontends/android/res/values-ru/strings.xml index 3838485af..4a20a9b86 100644 --- a/src/frontends/android/res/values-ru/strings.xml +++ b/src/frontends/android/res/values-ru/strings.xml @@ -75,6 +75,7 @@ Нет доступных сертификатов Система Пользователь + Imported Статус: diff --git a/src/frontends/android/res/values-ua/strings.xml b/src/frontends/android/res/values-ua/strings.xml index df016ff8f..18759f3ea 100644 --- a/src/frontends/android/res/values-ua/strings.xml +++ b/src/frontends/android/res/values-ua/strings.xml @@ -76,6 +76,7 @@ Немає сертифікатів Система Користувач + Imported Статус: diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 180948969..e5f47fc6c 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -78,6 +78,7 @@ No certificates System User + Imported Status: 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 918393fe6..ba4445fcf 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 @@ -46,9 +46,10 @@ import android.widget.SearchView.OnQueryTextListener; public class TrustedCertificateListFragment extends ListFragment implements LoaderCallbacks>, 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 @@ -70,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); } @@ -122,7 +126,7 @@ public class TrustedCertificateListFragment extends ListFragment implements Load @Override public Loader> 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 @@ -158,22 +162,21 @@ public class TrustedCertificateListFragment extends ListFragment implements Load public static class CertificateListLoader extends AsyncTaskLoader> { private List 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 loadInBackground() { TrustedCertificateManager certman = TrustedCertificateManager.getInstance().load(); - Hashtable certificates; + Hashtable certificates = certman.getCACertificates(mSource); List selected; - certificates = mUser ? certman.getCACertificates(TrustedCertificateSource.USER) : certman.getCACertificates(TrustedCertificateSource.SYSTEM); selected = new ArrayList(); for (Entry 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 2874207e8..3ee378196 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java @@ -17,6 +17,7 @@ package org.strongswan.android.ui; import org.strongswan.android.R; import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.logic.TrustedCertificateManager.TrustedCertificateSource; import org.strongswan.android.security.TrustedCertificateEntry; import android.app.ActionBar; @@ -43,11 +44,15 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert actionBar.addTab(actionBar .newTab() .setText(R.string.system_tab) - .setTabListener(new TrustedCertificatesTabListener(this, "system", false))); + .setTabListener(new TrustedCertificatesTabListener(this, "system", TrustedCertificateSource.SYSTEM))); actionBar.addTab(actionBar .newTab() .setText(R.string.user_tab) - .setTabListener(new TrustedCertificatesTabListener(this, "user", true))); + .setTabListener(new TrustedCertificatesTabListener(this, "user", TrustedCertificateSource.USER))); + actionBar.addTab(actionBar + .newTab() + .setText(R.string.local_tab) + .setTabListener(new TrustedCertificatesTabListener(this, "local", TrustedCertificateSource.LOCAL))); if (savedInstanceState != null) { @@ -87,13 +92,13 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert 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 +117,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 From 9c841b1f34eb589876ff9db0cbc5787895f56aef Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 17:34:49 +0200 Subject: [PATCH 14/25] android: Set action when using TrustedCertificatesActivity to select a certificate --- .../org/strongswan/android/ui/TrustedCertificatesActivity.java | 2 ++ .../src/org/strongswan/android/ui/VpnProfileDetailActivity.java | 1 + 2 files changed, 3 insertions(+) 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 3ee378196..eab290f83 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java @@ -31,6 +31,8 @@ import android.view.MenuItem; public class TrustedCertificatesActivity extends Activity implements TrustedCertificateListFragment.OnTrustedCertificateSelectedListener { + public static final String SELECT_CERTIFICATE = "org.strongswan.android.action.SELECT_CERTIFICATE"; + @Override public void onCreate(Bundle savedInstanceState) { 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 6cc6b0c02..74158cd81 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -147,6 +147,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); } }); From 1353f08fbc7729a2dfc0be9cf0b8de67598941a8 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 17:40:24 +0200 Subject: [PATCH 15/25] android: Only close TrustedCertificatesActivity on click when selecting a certificate --- .../android/ui/TrustedCertificatesActivity.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) 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 eab290f83..34f3d062a 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 @@ -32,6 +32,7 @@ import android.view.MenuItem; public class TrustedCertificatesActivity extends Activity implements TrustedCertificateListFragment.OnTrustedCertificateSelectedListener { public static final String SELECT_CERTIFICATE = "org.strongswan.android.action.SELECT_CERTIFICATE"; + private boolean mSelect; @Override public void onCreate(Bundle savedInstanceState) @@ -60,6 +61,7 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert { actionBar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); } + mSelect = SELECT_CERTIFICATE.equals(getIntent().getAction()); } @Override @@ -84,11 +86,14 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert @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(); + } } public static class TrustedCertificatesTabListener implements ActionBar.TabListener From 2312985b2a454c2ff14767c9ed1060c7712aade6 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 17:46:15 +0200 Subject: [PATCH 16/25] android: Replace option to reload CA certificates with CA certificate view The reload option will be added there. --- src/frontends/android/res/menu/main.xml | 6 +++--- .../org/strongswan/android/ui/MainActivity.java | 17 +++++++---------- 2 files changed, 10 insertions(+), 13 deletions(-) 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 @@ + + + + + diff --git a/src/frontends/android/res/values-de/strings.xml b/src/frontends/android/res/values-de/strings.xml index 1f283bbf5..269ac67ab 100644 --- a/src/frontends/android/res/values-de/strings.xml +++ b/src/frontends/android/res/values-de/strings.xml @@ -20,7 +20,6 @@ strongSwan VPN Client strongSwan - CA-Zertifikate neu laden Log anzeigen Suchen VPN nicht unterstützt @@ -76,6 +75,7 @@ CA-Zertifikate Keine Zertifikate + CA-Zertifikate neu laden System Benutzer Importiert diff --git a/src/frontends/android/res/values-pl/strings.xml b/src/frontends/android/res/values-pl/strings.xml index 8b13d4abe..1e05a66a5 100644 --- a/src/frontends/android/res/values-pl/strings.xml +++ b/src/frontends/android/res/values-pl/strings.xml @@ -20,7 +20,6 @@ strongSwan klient VPN strongSwan - Przeładuj certyfikaty CA Pokaż log Szukaj Nie obsługiwany VPN @@ -76,6 +75,7 @@ Certyfikaty CA Brak certyfikatów + Przeładuj certyfikaty CA System Użytkownik Imported diff --git a/src/frontends/android/res/values-ru/strings.xml b/src/frontends/android/res/values-ru/strings.xml index 4a20a9b86..d90c28f9c 100644 --- a/src/frontends/android/res/values-ru/strings.xml +++ b/src/frontends/android/res/values-ru/strings.xml @@ -17,7 +17,6 @@ Клиент strongSwan VPN strongSwan - Обновить сертификат CA Журнал Поиск VPN не поддерживается @@ -73,6 +72,7 @@ Сертификаты CA Нет доступных сертификатов + Обновить сертификат CA Система Пользователь Imported diff --git a/src/frontends/android/res/values-ua/strings.xml b/src/frontends/android/res/values-ua/strings.xml index 18759f3ea..fe1e619ba 100644 --- a/src/frontends/android/res/values-ua/strings.xml +++ b/src/frontends/android/res/values-ua/strings.xml @@ -18,7 +18,6 @@ strongSwan VPN клієнт strongSwan - Перезавантажити CA сертифікати Перегляд журналу Пошук VPN не підтримуеться @@ -74,6 +73,7 @@ Сертифікати CA Немає сертифікатів + Перезавантажити CA сертифікати Система Користувач Imported diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index e5f47fc6c..7a8008888 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -20,7 +20,6 @@ strongSwan VPN Client strongSwan - Reload CA certificates View log Search VPN not supported @@ -76,6 +75,7 @@ CA certificates No certificates + Reload CA certificates System User Imported 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 34f3d062a..03cf84375 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java @@ -17,6 +17,7 @@ package org.strongswan.android.ui; import org.strongswan.android.R; 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; @@ -26,8 +27,11 @@ import android.app.Activity; import android.app.Fragment; import android.app.FragmentTransaction; import android.content.Intent; +import android.os.AsyncTask; import android.os.Bundle; +import android.view.Menu; import android.view.MenuItem; +import android.view.Window; public class TrustedCertificatesActivity extends Activity implements TrustedCertificateListFragment.OnTrustedCertificateSelectedListener { @@ -38,6 +42,7 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.trusted_certificates_activity); ActionBar actionBar = getActionBar(); @@ -71,6 +76,13 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert outState.putInt("tab", getActionBar().getSelectedNavigationIndex()); } + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + getMenuInflater().inflate(R.menu.certificates, menu); + return true; + } + @Override public boolean onOptionsItemSelected(MenuItem item) { @@ -79,6 +91,9 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert case android.R.id.home: finish(); return true; + case R.id.menu_reload_certs: + new ReloadCertificatesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + return true; } return super.onOptionsItemSelected(item); } @@ -150,4 +165,26 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert /* nothing to be done */ } } + + /** + * Class that reloads the cached CA certificates. + */ + private class ReloadCertificatesTask extends AsyncTask + { + @Override + protected void onPreExecute() + { + setProgressBarIndeterminateVisibility(true); + } + @Override + protected TrustedCertificateManager doInBackground(Void... params) + { + return TrustedCertificateManager.getInstance().reload(); + } + @Override + protected void onPostExecute(TrustedCertificateManager result) + { + setProgressBarIndeterminateVisibility(false); + } + } } From 918200378dfb8cfcc923e39a4e2d5f2ce2399e66 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 18:21:11 +0200 Subject: [PATCH 18/25] android: Change how CA certificate reloads are initiated --- .../android/logic/TrustedCertificateManager.java | 16 ++++++++-------- .../android/ui/TrustedCertificatesActivity.java | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) 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 6e0c9f7a7..82a7cbe4e 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java +++ b/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java @@ -33,6 +33,7 @@ public class TrustedCertificateManager private static final String TAG = TrustedCertificateManager.class.getSimpleName(); private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock(); private Hashtable mCACerts = new Hashtable(); + private volatile boolean mReload; private boolean mLoaded; private final ArrayList mKeyStores = new ArrayList(); @@ -94,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; } @@ -117,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(); 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 03cf84375..fbdd9af96 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java @@ -179,7 +179,7 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert @Override protected TrustedCertificateManager doInBackground(Void... params) { - return TrustedCertificateManager.getInstance().reload(); + return TrustedCertificateManager.getInstance().reset().load(); } @Override protected void onPostExecute(TrustedCertificateManager result) From eb01649079e7834624cc628f3a023acd57ade5b5 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 18:44:08 +0200 Subject: [PATCH 19/25] android: Reload CA certificates without AsyncTask We already use loaders in the GUI that can handle this asynchronously. --- .../ui/TrustedCertificateListFragment.java | 12 +++++ .../ui/TrustedCertificatesActivity.java | 53 ++++++++++--------- 2 files changed, 39 insertions(+), 26 deletions(-) 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 ba4445fcf..8bd39c435 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java @@ -123,6 +123,18 @@ 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> onCreateLoader(int id, Bundle args) { /* we don't need the id as we have only one loader */ 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 fbdd9af96..c175fb9bf 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java @@ -27,11 +27,9 @@ import android.app.Activity; import android.app.Fragment; import android.app.FragmentTransaction; import android.content.Intent; -import android.os.AsyncTask; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; -import android.view.Window; public class TrustedCertificatesActivity extends Activity implements TrustedCertificateListFragment.OnTrustedCertificateSelectedListener { @@ -42,25 +40,31 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.trusted_certificates_activity); ActionBar actionBar = getActionBar(); 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", TrustedCertificateSource.SYSTEM))); + .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", TrustedCertificateSource.USER))); + .setTag(listener) + .setTabListener(listener)); + listener = new TrustedCertificatesTabListener(this, "local", TrustedCertificateSource.LOCAL); actionBar.addTab(actionBar .newTab() .setText(R.string.local_tab) - .setTabListener(new TrustedCertificatesTabListener(this, "local", TrustedCertificateSource.LOCAL))); + .setTag(listener) + .setTabListener(listener)); if (savedInstanceState != null) { @@ -92,7 +96,7 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert finish(); return true; case R.id.menu_reload_certs: - new ReloadCertificatesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + reloadCertificates(); return true; } return super.onOptionsItemSelected(item); @@ -111,6 +115,17 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert } } + 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; @@ -164,27 +179,13 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert { /* nothing to be done */ } - } - /** - * Class that reloads the cached CA certificates. - */ - private class ReloadCertificatesTask extends AsyncTask - { - @Override - protected void onPreExecute() + public void reset() { - setProgressBarIndeterminateVisibility(true); - } - @Override - protected TrustedCertificateManager doInBackground(Void... params) - { - return TrustedCertificateManager.getInstance().reset().load(); - } - @Override - protected void onPostExecute(TrustedCertificateManager result) - { - setProgressBarIndeterminateVisibility(false); + if (mFragment != null) + { + ((TrustedCertificateListFragment)mFragment).reset(); + } } } } From ac200bcda58756a3553df87caaee5b4701441fe3 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 19:52:40 +0200 Subject: [PATCH 20/25] android: Imported certificates may be clicked to delete them --- .../android/res/values-de/strings.xml | 2 + .../android/res/values-pl/strings.xml | 2 + .../android/res/values-ru/strings.xml | 2 + .../android/res/values-ua/strings.xml | 2 + src/frontends/android/res/values/strings.xml | 2 + .../CertificateDeleteConfirmationDialog.java | 80 +++++++++++++++++++ .../ui/TrustedCertificatesActivity.java | 35 +++++++- 7 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/frontends/android/src/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java diff --git a/src/frontends/android/res/values-de/strings.xml b/src/frontends/android/res/values-de/strings.xml index 269ac67ab..c9b6f9d14 100644 --- a/src/frontends/android/res/values-de/strings.xml +++ b/src/frontends/android/res/values-de/strings.xml @@ -79,6 +79,8 @@ System Benutzer Importiert + Zertifikat löschen? + Das Zertifikat wird permanent entfernt! Status: diff --git a/src/frontends/android/res/values-pl/strings.xml b/src/frontends/android/res/values-pl/strings.xml index 1e05a66a5..5bde18c60 100644 --- a/src/frontends/android/res/values-pl/strings.xml +++ b/src/frontends/android/res/values-pl/strings.xml @@ -79,6 +79,8 @@ System Użytkownik Imported + Delete certificate? + The certificate will be permanently removed! Status: diff --git a/src/frontends/android/res/values-ru/strings.xml b/src/frontends/android/res/values-ru/strings.xml index d90c28f9c..f61b251fe 100644 --- a/src/frontends/android/res/values-ru/strings.xml +++ b/src/frontends/android/res/values-ru/strings.xml @@ -76,6 +76,8 @@ Система Пользователь Imported + Delete certificate? + The certificate will be permanently removed! Статус: diff --git a/src/frontends/android/res/values-ua/strings.xml b/src/frontends/android/res/values-ua/strings.xml index fe1e619ba..bff97ecb1 100644 --- a/src/frontends/android/res/values-ua/strings.xml +++ b/src/frontends/android/res/values-ua/strings.xml @@ -77,6 +77,8 @@ Система Користувач Imported + Delete certificate? + The certificate will be permanently removed! Статус: diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 7a8008888..f03d3e428 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -79,6 +79,8 @@ System User Imported + Delete certificate? + The certificate will be permanently removed! Status: 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 . + * + * 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/TrustedCertificatesActivity.java b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java index c175fb9bf..1211ef545 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java @@ -15,11 +15,14 @@ package org.strongswan.android.ui; +import java.security.KeyStore; + import org.strongswan.android.R; 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; @@ -31,9 +34,10 @@ 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 boolean mSelect; @Override @@ -113,6 +117,35 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert 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() From 94cc8f6a720ed034f6c6561a03c20257df3b2260 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 30 May 2014 20:16:57 +0200 Subject: [PATCH 21/25] android: Add activity to import certificate files Such files can e.g. be opened from the Download view, if they are associated with one of the supported mime-types. --- src/frontends/android/AndroidManifest.xml | 12 ++++ .../android/res/values-de/strings.xml | 3 + .../android/res/values-pl/strings.xml | 3 + .../android/res/values-ru/strings.xml | 3 + .../android/res/values-ua/strings.xml | 3 + src/frontends/android/res/values/strings.xml | 3 + .../ui/TrustedCertificateImportActivity.java | 62 +++++++++++++++++++ 7 files changed, 89 insertions(+) create mode 100644 src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateImportActivity.java diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index e2d25e4ac..887063faa 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -67,6 +67,18 @@ + + + + + + + + + + Importiert Zertifikat löschen? Das Zertifikat wird permanent entfernt! + Zertifikat importieren + Zertifikat erfolgreich importiert + Zertifikat-Import fehlgeschlagen Status: diff --git a/src/frontends/android/res/values-pl/strings.xml b/src/frontends/android/res/values-pl/strings.xml index 5bde18c60..d0cfa48f1 100644 --- a/src/frontends/android/res/values-pl/strings.xml +++ b/src/frontends/android/res/values-pl/strings.xml @@ -81,6 +81,9 @@ Imported Delete certificate? The certificate will be permanently removed! + Import certificate + Certificate successfully imported + Failed to import certificate Status: diff --git a/src/frontends/android/res/values-ru/strings.xml b/src/frontends/android/res/values-ru/strings.xml index f61b251fe..eb69183db 100644 --- a/src/frontends/android/res/values-ru/strings.xml +++ b/src/frontends/android/res/values-ru/strings.xml @@ -78,6 +78,9 @@ Imported Delete certificate? The certificate will be permanently removed! + Import certificate + Certificate successfully imported + Failed to import certificate Статус: diff --git a/src/frontends/android/res/values-ua/strings.xml b/src/frontends/android/res/values-ua/strings.xml index bff97ecb1..e23b9b9b2 100644 --- a/src/frontends/android/res/values-ua/strings.xml +++ b/src/frontends/android/res/values-ua/strings.xml @@ -79,6 +79,9 @@ Imported Delete certificate? The certificate will be permanently removed! + Import certificate + Certificate successfully imported + Failed to import certificate Статус: diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index f03d3e428..933a80aff 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -81,6 +81,9 @@ Imported Delete certificate? The certificate will be permanently removed! + Import certificate + Certificate successfully imported + Failed to import certificate Status: 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..663c414e2 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateImportActivity.java @@ -0,0 +1,62 @@ +/* + * 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 . + * + * 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.InputStream; +import java.security.KeyStore; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import org.strongswan.android.R; +import org.strongswan.android.logic.TrustedCertificateManager; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.widget.Toast; + +public class TrustedCertificateImportActivity extends Activity +{ + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + String action = intent.getAction(); + if (Intent.ACTION_VIEW.equals(action)) + { + try + { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + InputStream in = getContentResolver().openInputStream(intent.getData()); + X509Certificate certificate = (X509Certificate)factory.generateCertificate(in); + /* we don't check whether it's actually a CA certificate or not */ + KeyStore store = KeyStore.getInstance("LocalCertificateStore"); + store.load(null, null); + store.setCertificateEntry(null, certificate); + TrustedCertificateManager.getInstance().reset(); + Toast.makeText(this, R.string.cert_imported_successfully, Toast.LENGTH_LONG).show(); + } + catch (Exception e) + { + Toast.makeText(this, R.string.cert_import_failed, Toast.LENGTH_LONG).show(); + e.printStackTrace(); + } + } + finish(); + } +} From 1ed922c9189bc6487105f5ece750d3c1fc8bc866 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 31 May 2014 16:49:01 +0200 Subject: [PATCH 22/25] android: Use Storage Access Framework to import certificates Thanks to the SAF, introduced with Android 4.4, browsing and opening files on the system is very easy to implement. On older systems the menu option is removed. --- .../android/res/menu/certificates.xml | 5 ++ .../ui/TrustedCertificateImportActivity.java | 87 +++++++++++++++---- .../ui/TrustedCertificatesActivity.java | 31 +++++++ 3 files changed, 106 insertions(+), 17 deletions(-) diff --git a/src/frontends/android/res/menu/certificates.xml b/src/frontends/android/res/menu/certificates.xml index c735e0c70..6066cab60 100644 --- a/src/frontends/android/res/menu/certificates.xml +++ b/src/frontends/android/res/menu/certificates.xml @@ -15,6 +15,11 @@ --> + + = 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); + return; } finish(); } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + switch (requestCode) + { + case OPEN_DOCUMENT: + if (resultCode == Activity.RESULT_OK && data != null) + { + if (importCertificate(data.getData())) + { + setResult(Activity.RESULT_OK); + } + } + finish(); + return; + } + super.onActivityResult(requestCode, resultCode, data); + } + + /** + * Try to import the file pointed to by the given URI as a certificate. + * @param uri + * @return whether the import was successful + */ + private boolean importCertificate(Uri uri) + { + try + { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + InputStream in = getContentResolver().openInputStream(uri); + X509Certificate certificate = (X509Certificate)factory.generateCertificate(in); + /* we don't check whether it's actually a CA certificate or not */ + KeyStore store = KeyStore.getInstance("LocalCertificateStore"); + store.load(null, null); + store.setCertificateEntry(null, certificate); + TrustedCertificateManager.getInstance().reset(); + Toast.makeText(this, R.string.cert_imported_successfully, Toast.LENGTH_LONG).show(); + return true; + } + catch (Exception e) + { + Toast.makeText(this, R.string.cert_import_failed, Toast.LENGTH_LONG).show(); + e.printStackTrace(); + } + return false; + } } 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 1211ef545..663950c16 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java @@ -30,6 +30,7 @@ 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; @@ -38,6 +39,7 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert { 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 @@ -91,6 +93,16 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert 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) { @@ -102,10 +114,29 @@ public class TrustedCertificatesActivity extends Activity implements TrustedCert 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) { From 3dc92ff9cfaac6293e7643d24945f612103f7694 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 5 Jun 2014 19:06:34 +0200 Subject: [PATCH 23/25] android: Show a confirmation dialog before importing certificates Since the import activity can be triggered by any other app on the system we shouldn't just import every certificate we get. Also, in some situations (e.g. if no passphrase has been set yet for the system-wide certificate store) we are the only application that can open certificate files. So if a user clicked on a certificate file she would just get a confirmation Toast about a successful import, with no indication whatsoever where the certificate was actually imported. The new dialog shows the app icon to indicate that strongSwan is involved. --- src/frontends/android/AndroidManifest.xml | 3 +- .../ui/TrustedCertificateImportActivity.java | 134 ++++++++++++++++-- 2 files changed, 123 insertions(+), 14 deletions(-) diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 887063faa..1a5af0d15 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -69,7 +69,8 @@ + android:label="@string/import_certificate" + android:theme="@android:style/Theme.Holo.Dialog.NoActionBar" > diff --git a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateImportActivity.java b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateImportActivity.java index f8a9438b6..61bd2c9a2 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateImportActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateImportActivity.java @@ -15,16 +15,24 @@ 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; @@ -34,6 +42,7 @@ 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 = { @@ -49,6 +58,11 @@ public class TrustedCertificateImportActivity extends Activity { 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)) @@ -61,9 +75,7 @@ public class TrustedCertificateImportActivity extends Activity openIntent.setType("*/*"); openIntent.putExtra(Intent.EXTRA_MIME_TYPES, ACCEPTED_MIME_TYPES); startActivityForResult(openIntent, OPEN_DOCUMENT); - return; } - finish(); } @Override @@ -74,10 +86,8 @@ public class TrustedCertificateImportActivity extends Activity case OPEN_DOCUMENT: if (resultCode == Activity.RESULT_OK && data != null) { - if (importCertificate(data.getData())) - { - setResult(Activity.RESULT_OK); - } + importCertificate(data.getData()); + return; } finish(); return; @@ -86,30 +96,128 @@ public class TrustedCertificateImportActivity extends Activity } /** - * Try to import the file pointed to by the given URI as a certificate. + * Import the file pointed to by the given URI as a certificate. * @param uri - * @return whether the import was successful */ - private boolean importCertificate(Uri 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); - X509Certificate certificate = (X509Certificate)factory.generateCertificate(in); + 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(); - Toast.makeText(this, R.string.cert_imported_successfully, Toast.LENGTH_LONG).show(); return true; } catch (Exception e) { - Toast.makeText(this, R.string.cert_import_failed, Toast.LENGTH_LONG).show(); 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(); } - return false; } } From 7073bfe4e9a618eea5b27a18e22362b501bc4b30 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 8 Jul 2014 13:56:54 +0200 Subject: [PATCH 24/25] android: Add support for ECDSA private keys With 4.4.4 these work fine now. --- .../backend/android_private_key.c | 123 ++++++++++++++---- 1 file changed, 99 insertions(+), 24 deletions(-) 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 +#include 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, From d4bf6bfb159a7231dd056a0028ce04aef0db4c4c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 14 Jul 2014 14:24:31 +0200 Subject: [PATCH 25/25] android: Do not use deprecated TwoLineListItem --- .../layout/remediation_instruction_item.xml | 4 +- .../android/res/layout/two_line_button.xml | 4 +- .../android/ui/VpnProfileDetailActivity.java | 47 ++++++++++--------- 3 files changed, 28 insertions(+), 27 deletions(-) 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. --> - - + 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. --> - - + 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 74158cd81..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 @@ -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) @@ -247,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); } } } @@ -296,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 @@ -358,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) @@ -546,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;