From a50f3037ad30fa6020a4ec56e6afe3da7ca1731d Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 22 Oct 2015 17:18:14 +0200 Subject: android: Migrate to the Gradle build system This uses a manual way to trigger the NDK build (the default with on-the-fly Android.mk files does not work for us). --- src/frontends/android/.classpath | 9 - src/frontends/android/.gitignore | 11 +- src/frontends/android/AndroidManifest.xml | 107 --- src/frontends/android/README.ndk | 4 +- src/frontends/android/app/build.gradle | 42 + .../android/app/src/main/AndroidManifest.xml | 103 +++ .../android/data/LogContentProvider.java | 149 ++++ .../org/strongswan/android/data/VpnProfile.java | 170 ++++ .../android/data/VpnProfileDataSource.java | 308 ++++++++ .../java/org/strongswan/android/data/VpnType.java | 94 +++ .../strongswan/android/logic/CharonVpnService.java | 857 +++++++++++++++++++++ .../strongswan/android/logic/NetworkManager.java | 68 ++ .../android/logic/StrongSwanApplication.java | 48 ++ .../android/logic/TrustedCertificateManager.java | 242 ++++++ .../strongswan/android/logic/VpnStateService.java | 360 +++++++++ .../strongswan/android/logic/imc/AndroidImc.java | 99 +++ .../org/strongswan/android/logic/imc/ImcState.java | 58 ++ .../android/logic/imc/RemediationInstruction.java | 273 +++++++ .../android/logic/imc/attributes/Attribute.java | 28 + .../logic/imc/attributes/AttributeType.java | 100 +++ .../logic/imc/attributes/DeviceIdAttribute.java | 45 ++ .../imc/attributes/InstalledPackagesAttribute.java | 67 ++ .../logic/imc/attributes/PortFilterAttribute.java | 65 ++ .../imc/attributes/PrivateEnterpriseNumber.java | 65 ++ .../attributes/ProductInformationAttribute.java | 47 ++ .../logic/imc/attributes/SettingsAttribute.java | 78 ++ .../imc/attributes/StringVersionAttribute.java | 68 ++ .../android/logic/imc/collectors/Collector.java | 30 + .../logic/imc/collectors/DeviceIdCollector.java | 45 ++ .../imc/collectors/InstalledPackagesCollector.java | 55 ++ .../logic/imc/collectors/PortFilterCollector.java | 79 ++ .../collectors/ProductInformationCollector.java | 30 + .../android/logic/imc/collectors/Protocol.java | 60 ++ .../logic/imc/collectors/SettingsCollector.java | 61 ++ .../imc/collectors/StringVersionCollector.java | 33 + .../security/LocalCertificateKeyStoreProvider.java | 29 + .../security/LocalCertificateKeyStoreSpi.java | 139 ++++ .../android/security/LocalCertificateStore.java | 230 ++++++ .../android/security/TrustedCertificateEntry.java | 133 ++++ .../ui/CertificateDeleteConfirmationDialog.java | 80 ++ .../strongswan/android/ui/ImcStateFragment.java | 207 +++++ .../org/strongswan/android/ui/LogActivity.java | 86 +++ .../org/strongswan/android/ui/LogFragment.java | 227 ++++++ .../org/strongswan/android/ui/LogScrollView.java | 78 ++ .../org/strongswan/android/ui/MainActivity.java | 440 +++++++++++ .../android/ui/RemediationInstructionFragment.java | 109 +++ .../ui/RemediationInstructionsActivity.java | 94 +++ .../ui/RemediationInstructionsFragment.java | 122 +++ .../ui/TrustedCertificateImportActivity.java | 223 ++++++ .../android/ui/TrustedCertificateListFragment.java | 233 ++++++ .../android/ui/TrustedCertificatesActivity.java | 255 ++++++ .../android/ui/VpnProfileDetailActivity.java | 653 ++++++++++++++++ .../android/ui/VpnProfileListFragment.java | 289 +++++++ .../android/ui/VpnProfileSelectActivity.java | 52 ++ .../strongswan/android/ui/VpnStateFragment.java | 414 ++++++++++ .../ui/adapter/RemediationInstructionAdapter.java | 71 ++ .../ui/adapter/TrustedCertificateAdapter.java | 71 ++ .../android/ui/adapter/VpnProfileAdapter.java | 107 +++ .../android/utils/BufferedByteWriter.java | 185 +++++ .../strongswan/android/utils/SettingsWriter.java | 160 ++++ .../java/org/strongswan/android/utils/Utils.java | 40 + src/frontends/android/app/src/main/jni/.gitignore | 2 + src/frontends/android/app/src/main/jni/Android.mk | 84 ++ .../android/app/src/main/jni/Application.mk | 3 + .../app/src/main/jni/libandroidbridge/Android.mk | 63 ++ .../src/main/jni/libandroidbridge/android_jni.c | 151 ++++ .../src/main/jni/libandroidbridge/android_jni.h | 159 ++++ .../jni/libandroidbridge/backend/android_attr.c | 131 ++++ .../jni/libandroidbridge/backend/android_attr.h | 52 ++ .../jni/libandroidbridge/backend/android_creds.c | 256 ++++++ .../jni/libandroidbridge/backend/android_creds.h | 74 ++ .../libandroidbridge/backend/android_dns_proxy.c | 419 ++++++++++ .../libandroidbridge/backend/android_dns_proxy.h | 86 +++ .../libandroidbridge/backend/android_private_key.c | 360 +++++++++ .../libandroidbridge/backend/android_private_key.h | 38 + .../jni/libandroidbridge/backend/android_service.c | 848 ++++++++++++++++++++ .../jni/libandroidbridge/backend/android_service.h | 60 ++ .../main/jni/libandroidbridge/byod/imc_android.c | 722 +++++++++++++++++ .../main/jni/libandroidbridge/byod/imc_android.h | 30 + .../jni/libandroidbridge/byod/imc_android_state.c | 187 +++++ .../jni/libandroidbridge/byod/imc_android_state.h | 52 ++ .../src/main/jni/libandroidbridge/charonservice.c | 706 +++++++++++++++++ .../src/main/jni/libandroidbridge/charonservice.h | 167 ++++ .../jni/libandroidbridge/kernel/android_ipsec.c | 198 +++++ .../jni/libandroidbridge/kernel/android_ipsec.h | 48 ++ .../main/jni/libandroidbridge/kernel/android_net.c | 314 ++++++++ .../main/jni/libandroidbridge/kernel/android_net.h | 35 + .../jni/libandroidbridge/kernel/network_manager.c | 233 ++++++ .../jni/libandroidbridge/kernel/network_manager.h | 89 +++ .../main/jni/libandroidbridge/vpnservice_builder.c | 279 +++++++ .../main/jni/libandroidbridge/vpnservice_builder.h | 102 +++ .../app/src/main/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 4953 bytes .../app/src/main/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 3149 bytes .../src/main/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 6786 bytes .../remediation_instruction_background_large.xml | 32 + .../app/src/main/res/drawable/state_background.xml | 32 + .../res/layout-large/remediation_instructions.xml | 44 ++ .../app/src/main/res/layout/imc_state_fragment.xml | 69 ++ .../app/src/main/res/layout/log_activity.xml | 26 + .../app/src/main/res/layout/log_fragment.xml | 41 + .../app/src/main/res/layout/login_dialog.xml | 51 ++ .../android/app/src/main/res/layout/main.xml | 40 + .../src/main/res/layout/profile_detail_view.xml | 203 +++++ .../src/main/res/layout/profile_list_fragment.xml | 38 + .../app/src/main/res/layout/profile_list_item.xml | 59 ++ .../main/res/layout/remediation_instruction.xml | 55 ++ .../res/layout/remediation_instruction_item.xml | 47 ++ .../main/res/layout/remediation_instructions.xml | 21 + .../res/layout/trusted_certificates_activity.xml | 20 + .../main/res/layout/trusted_certificates_item.xml | 36 + .../app/src/main/res/layout/two_line_button.xml | 39 + .../app/src/main/res/layout/vpn_profile_select.xml | 30 + .../app/src/main/res/layout/vpn_state_fragment.xml | 86 +++ .../android/app/src/main/res/menu/certificates.xml | 28 + .../android/app/src/main/res/menu/log.xml | 23 + .../android/app/src/main/res/menu/main.xml | 28 + .../android/app/src/main/res/menu/profile_edit.xml | 28 + .../android/app/src/main/res/menu/profile_list.xml | 22 + .../app/src/main/res/menu/profile_list_context.xml | 24 + .../android/app/src/main/res/values-de/arrays.xml | 25 + .../android/app/src/main/res/values-de/strings.xml | 134 ++++ .../android/app/src/main/res/values-pl/arrays.xml | 25 + .../android/app/src/main/res/values-pl/strings.xml | 134 ++++ .../android/app/src/main/res/values-ru/arrays.xml | 24 + .../android/app/src/main/res/values-ru/strings.xml | 132 ++++ .../android/app/src/main/res/values-ua/arrays.xml | 24 + .../android/app/src/main/res/values-ua/strings.xml | 133 ++++ .../android/app/src/main/res/values/arrays.xml | 25 + .../android/app/src/main/res/values/attrs.xml | 20 + .../android/app/src/main/res/values/colors.xml | 33 + .../android/app/src/main/res/values/strings.xml | 134 ++++ .../android/app/src/main/res/values/styles.xml | 21 + src/frontends/android/build.gradle | 14 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + src/frontends/android/gradlew | 164 ++++ src/frontends/android/gradlew.bat | 90 +++ src/frontends/android/jni/.gitignore | 2 - src/frontends/android/jni/Android.mk | 84 -- src/frontends/android/jni/Application.mk | 3 - .../android/jni/libandroidbridge/Android.mk | 63 -- .../android/jni/libandroidbridge/android_jni.c | 151 ---- .../android/jni/libandroidbridge/android_jni.h | 159 ---- .../jni/libandroidbridge/backend/android_attr.c | 131 ---- .../jni/libandroidbridge/backend/android_attr.h | 52 -- .../jni/libandroidbridge/backend/android_creds.c | 256 ------ .../jni/libandroidbridge/backend/android_creds.h | 74 -- .../libandroidbridge/backend/android_dns_proxy.c | 419 ---------- .../libandroidbridge/backend/android_dns_proxy.h | 86 --- .../libandroidbridge/backend/android_private_key.c | 360 --------- .../libandroidbridge/backend/android_private_key.h | 38 - .../jni/libandroidbridge/backend/android_service.c | 848 -------------------- .../jni/libandroidbridge/backend/android_service.h | 60 -- .../jni/libandroidbridge/byod/imc_android.c | 722 ----------------- .../jni/libandroidbridge/byod/imc_android.h | 30 - .../jni/libandroidbridge/byod/imc_android_state.c | 187 ----- .../jni/libandroidbridge/byod/imc_android_state.h | 52 -- .../android/jni/libandroidbridge/charonservice.c | 706 ----------------- .../android/jni/libandroidbridge/charonservice.h | 167 ---- .../jni/libandroidbridge/kernel/android_ipsec.c | 198 ----- .../jni/libandroidbridge/kernel/android_ipsec.h | 48 -- .../jni/libandroidbridge/kernel/android_net.c | 314 -------- .../jni/libandroidbridge/kernel/android_net.h | 35 - .../jni/libandroidbridge/kernel/network_manager.c | 233 ------ .../jni/libandroidbridge/kernel/network_manager.h | 89 --- .../jni/libandroidbridge/vpnservice_builder.c | 279 ------- .../jni/libandroidbridge/vpnservice_builder.h | 102 --- src/frontends/android/proguard.cfg | 40 - src/frontends/android/project.properties | 11 - .../android/res/drawable-hdpi/ic_launcher.png | Bin 4953 -> 0 bytes .../android/res/drawable-mdpi/ic_launcher.png | Bin 3149 -> 0 bytes .../android/res/drawable-xhdpi/ic_launcher.png | Bin 6786 -> 0 bytes .../remediation_instruction_background_large.xml | 32 - .../android/res/drawable/state_background.xml | 32 - .../res/layout-large/remediation_instructions.xml | 44 -- .../android/res/layout/imc_state_fragment.xml | 69 -- src/frontends/android/res/layout/log_activity.xml | 26 - src/frontends/android/res/layout/log_fragment.xml | 41 - src/frontends/android/res/layout/login_dialog.xml | 51 -- src/frontends/android/res/layout/main.xml | 40 - .../android/res/layout/profile_detail_view.xml | 203 ----- .../android/res/layout/profile_list_fragment.xml | 38 - .../android/res/layout/profile_list_item.xml | 59 -- .../android/res/layout/remediation_instruction.xml | 55 -- .../res/layout/remediation_instruction_item.xml | 47 -- .../res/layout/remediation_instructions.xml | 21 - .../res/layout/trusted_certificates_activity.xml | 20 - .../res/layout/trusted_certificates_item.xml | 36 - .../android/res/layout/two_line_button.xml | 39 - .../android/res/layout/vpn_profile_select.xml | 30 - .../android/res/layout/vpn_state_fragment.xml | 86 --- src/frontends/android/res/menu/certificates.xml | 28 - src/frontends/android/res/menu/log.xml | 23 - src/frontends/android/res/menu/main.xml | 28 - src/frontends/android/res/menu/profile_edit.xml | 28 - src/frontends/android/res/menu/profile_list.xml | 22 - .../android/res/menu/profile_list_context.xml | 24 - src/frontends/android/res/values-de/arrays.xml | 25 - src/frontends/android/res/values-de/strings.xml | 134 ---- src/frontends/android/res/values-pl/arrays.xml | 25 - src/frontends/android/res/values-pl/strings.xml | 134 ---- src/frontends/android/res/values-ru/arrays.xml | 24 - src/frontends/android/res/values-ru/strings.xml | 132 ---- src/frontends/android/res/values-ua/arrays.xml | 24 - src/frontends/android/res/values-ua/strings.xml | 133 ---- src/frontends/android/res/values/arrays.xml | 25 - src/frontends/android/res/values/attrs.xml | 20 - src/frontends/android/res/values/colors.xml | 33 - src/frontends/android/res/values/strings.xml | 134 ---- src/frontends/android/res/values/styles.xml | 21 - src/frontends/android/settings.gradle | 1 + .../android/data/LogContentProvider.java | 149 ---- .../org/strongswan/android/data/VpnProfile.java | 170 ---- .../android/data/VpnProfileDataSource.java | 308 -------- .../src/org/strongswan/android/data/VpnType.java | 94 --- .../strongswan/android/logic/CharonVpnService.java | 857 --------------------- .../strongswan/android/logic/NetworkManager.java | 68 -- .../android/logic/StrongSwanApplication.java | 48 -- .../android/logic/TrustedCertificateManager.java | 242 ------ .../strongswan/android/logic/VpnStateService.java | 360 --------- .../strongswan/android/logic/imc/AndroidImc.java | 99 --- .../org/strongswan/android/logic/imc/ImcState.java | 58 -- .../android/logic/imc/RemediationInstruction.java | 273 ------- .../android/logic/imc/attributes/Attribute.java | 28 - .../logic/imc/attributes/AttributeType.java | 100 --- .../logic/imc/attributes/DeviceIdAttribute.java | 45 -- .../imc/attributes/InstalledPackagesAttribute.java | 67 -- .../logic/imc/attributes/PortFilterAttribute.java | 65 -- .../imc/attributes/PrivateEnterpriseNumber.java | 65 -- .../attributes/ProductInformationAttribute.java | 47 -- .../logic/imc/attributes/SettingsAttribute.java | 78 -- .../imc/attributes/StringVersionAttribute.java | 68 -- .../android/logic/imc/collectors/Collector.java | 30 - .../logic/imc/collectors/DeviceIdCollector.java | 45 -- .../imc/collectors/InstalledPackagesCollector.java | 55 -- .../logic/imc/collectors/PortFilterCollector.java | 79 -- .../collectors/ProductInformationCollector.java | 30 - .../android/logic/imc/collectors/Protocol.java | 60 -- .../logic/imc/collectors/SettingsCollector.java | 61 -- .../imc/collectors/StringVersionCollector.java | 33 - .../security/LocalCertificateKeyStoreProvider.java | 29 - .../security/LocalCertificateKeyStoreSpi.java | 139 ---- .../android/security/LocalCertificateStore.java | 230 ------ .../android/security/TrustedCertificateEntry.java | 133 ---- .../ui/CertificateDeleteConfirmationDialog.java | 80 -- .../strongswan/android/ui/ImcStateFragment.java | 207 ----- .../src/org/strongswan/android/ui/LogActivity.java | 86 --- .../src/org/strongswan/android/ui/LogFragment.java | 227 ------ .../org/strongswan/android/ui/LogScrollView.java | 78 -- .../org/strongswan/android/ui/MainActivity.java | 440 ----------- .../android/ui/RemediationInstructionFragment.java | 109 --- .../ui/RemediationInstructionsActivity.java | 94 --- .../ui/RemediationInstructionsFragment.java | 122 --- .../ui/TrustedCertificateImportActivity.java | 223 ------ .../android/ui/TrustedCertificateListFragment.java | 233 ------ .../android/ui/TrustedCertificatesActivity.java | 255 ------ .../android/ui/VpnProfileDetailActivity.java | 653 ---------------- .../android/ui/VpnProfileListFragment.java | 289 ------- .../android/ui/VpnProfileSelectActivity.java | 52 -- .../strongswan/android/ui/VpnStateFragment.java | 414 ---------- .../ui/adapter/RemediationInstructionAdapter.java | 71 -- .../ui/adapter/TrustedCertificateAdapter.java | 71 -- .../android/ui/adapter/VpnProfileAdapter.java | 107 --- .../android/utils/BufferedByteWriter.java | 185 ----- .../strongswan/android/utils/SettingsWriter.java | 160 ---- .../src/org/strongswan/android/utils/Utils.java | 40 - 266 files changed, 16772 insertions(+), 16516 deletions(-) delete mode 100644 src/frontends/android/.classpath delete mode 100644 src/frontends/android/AndroidManifest.xml create mode 100644 src/frontends/android/app/build.gradle create mode 100644 src/frontends/android/app/src/main/AndroidManifest.xml create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/LogContentProvider.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnType.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/NetworkManager.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/TrustedCertificateManager.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/AndroidImc.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/ImcState.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/RemediationInstruction.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/Attribute.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/AttributeType.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/DeviceIdAttribute.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/InstalledPackagesAttribute.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/PortFilterAttribute.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/PrivateEnterpriseNumber.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/ProductInformationAttribute.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/SettingsAttribute.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/StringVersionAttribute.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/Collector.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/DeviceIdCollector.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/InstalledPackagesCollector.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/PortFilterCollector.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/ProductInformationCollector.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/Protocol.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/SettingsCollector.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/StringVersionCollector.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/security/LocalCertificateKeyStoreProvider.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/security/LocalCertificateKeyStoreSpi.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/security/LocalCertificateStore.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogActivity.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogScrollView.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/RemediationInstructionAdapter.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/utils/BufferedByteWriter.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/utils/SettingsWriter.java create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/utils/Utils.java create mode 100644 src/frontends/android/app/src/main/jni/.gitignore create mode 100644 src/frontends/android/app/src/main/jni/Android.mk create mode 100644 src/frontends/android/app/src/main/jni/Application.mk create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/Android.mk create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.c create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.h create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_attr.c create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_attr.h create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.c create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.h create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_dns_proxy.c create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_dns_proxy.h create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_private_key.c create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_private_key.h create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.h create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android.c create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android.h create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android_state.c create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android_state.h create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.h create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_ipsec.c create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_ipsec.h create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_net.c create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_net.h create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/kernel/network_manager.c create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/kernel/network_manager.h create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/vpnservice_builder.c create mode 100644 src/frontends/android/app/src/main/jni/libandroidbridge/vpnservice_builder.h create mode 100644 src/frontends/android/app/src/main/res/drawable-hdpi/ic_launcher.png create mode 100644 src/frontends/android/app/src/main/res/drawable-mdpi/ic_launcher.png create mode 100644 src/frontends/android/app/src/main/res/drawable-xhdpi/ic_launcher.png create mode 100644 src/frontends/android/app/src/main/res/drawable/remediation_instruction_background_large.xml create mode 100644 src/frontends/android/app/src/main/res/drawable/state_background.xml create mode 100644 src/frontends/android/app/src/main/res/layout-large/remediation_instructions.xml create mode 100644 src/frontends/android/app/src/main/res/layout/imc_state_fragment.xml create mode 100644 src/frontends/android/app/src/main/res/layout/log_activity.xml create mode 100644 src/frontends/android/app/src/main/res/layout/log_fragment.xml create mode 100644 src/frontends/android/app/src/main/res/layout/login_dialog.xml create mode 100644 src/frontends/android/app/src/main/res/layout/main.xml create mode 100644 src/frontends/android/app/src/main/res/layout/profile_detail_view.xml create mode 100644 src/frontends/android/app/src/main/res/layout/profile_list_fragment.xml create mode 100644 src/frontends/android/app/src/main/res/layout/profile_list_item.xml create mode 100644 src/frontends/android/app/src/main/res/layout/remediation_instruction.xml create mode 100644 src/frontends/android/app/src/main/res/layout/remediation_instruction_item.xml create mode 100644 src/frontends/android/app/src/main/res/layout/remediation_instructions.xml create mode 100644 src/frontends/android/app/src/main/res/layout/trusted_certificates_activity.xml create mode 100644 src/frontends/android/app/src/main/res/layout/trusted_certificates_item.xml create mode 100644 src/frontends/android/app/src/main/res/layout/two_line_button.xml create mode 100644 src/frontends/android/app/src/main/res/layout/vpn_profile_select.xml create mode 100644 src/frontends/android/app/src/main/res/layout/vpn_state_fragment.xml create mode 100644 src/frontends/android/app/src/main/res/menu/certificates.xml create mode 100644 src/frontends/android/app/src/main/res/menu/log.xml create mode 100644 src/frontends/android/app/src/main/res/menu/main.xml create mode 100644 src/frontends/android/app/src/main/res/menu/profile_edit.xml create mode 100644 src/frontends/android/app/src/main/res/menu/profile_list.xml create mode 100644 src/frontends/android/app/src/main/res/menu/profile_list_context.xml create mode 100644 src/frontends/android/app/src/main/res/values-de/arrays.xml create mode 100644 src/frontends/android/app/src/main/res/values-de/strings.xml create mode 100644 src/frontends/android/app/src/main/res/values-pl/arrays.xml create mode 100644 src/frontends/android/app/src/main/res/values-pl/strings.xml create mode 100644 src/frontends/android/app/src/main/res/values-ru/arrays.xml create mode 100644 src/frontends/android/app/src/main/res/values-ru/strings.xml create mode 100644 src/frontends/android/app/src/main/res/values-ua/arrays.xml create mode 100644 src/frontends/android/app/src/main/res/values-ua/strings.xml create mode 100644 src/frontends/android/app/src/main/res/values/arrays.xml create mode 100644 src/frontends/android/app/src/main/res/values/attrs.xml create mode 100644 src/frontends/android/app/src/main/res/values/colors.xml create mode 100644 src/frontends/android/app/src/main/res/values/strings.xml create mode 100644 src/frontends/android/app/src/main/res/values/styles.xml create mode 100644 src/frontends/android/build.gradle create mode 100644 src/frontends/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 src/frontends/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 src/frontends/android/gradlew create mode 100644 src/frontends/android/gradlew.bat delete mode 100644 src/frontends/android/jni/.gitignore delete mode 100644 src/frontends/android/jni/Android.mk delete mode 100644 src/frontends/android/jni/Application.mk delete mode 100644 src/frontends/android/jni/libandroidbridge/Android.mk delete mode 100644 src/frontends/android/jni/libandroidbridge/android_jni.c delete mode 100644 src/frontends/android/jni/libandroidbridge/android_jni.h delete mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_attr.c delete mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_attr.h delete mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_creds.c delete mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_creds.h delete mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c delete mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h delete mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_private_key.c delete mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_private_key.h delete mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_service.c delete mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_service.h delete mode 100644 src/frontends/android/jni/libandroidbridge/byod/imc_android.c delete mode 100644 src/frontends/android/jni/libandroidbridge/byod/imc_android.h delete mode 100644 src/frontends/android/jni/libandroidbridge/byod/imc_android_state.c delete mode 100644 src/frontends/android/jni/libandroidbridge/byod/imc_android_state.h delete mode 100644 src/frontends/android/jni/libandroidbridge/charonservice.c delete mode 100644 src/frontends/android/jni/libandroidbridge/charonservice.h delete mode 100644 src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c delete mode 100644 src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.h delete mode 100644 src/frontends/android/jni/libandroidbridge/kernel/android_net.c delete mode 100644 src/frontends/android/jni/libandroidbridge/kernel/android_net.h delete mode 100644 src/frontends/android/jni/libandroidbridge/kernel/network_manager.c delete mode 100644 src/frontends/android/jni/libandroidbridge/kernel/network_manager.h delete mode 100644 src/frontends/android/jni/libandroidbridge/vpnservice_builder.c delete mode 100644 src/frontends/android/jni/libandroidbridge/vpnservice_builder.h delete mode 100644 src/frontends/android/proguard.cfg delete mode 100644 src/frontends/android/project.properties delete mode 100644 src/frontends/android/res/drawable-hdpi/ic_launcher.png delete mode 100644 src/frontends/android/res/drawable-mdpi/ic_launcher.png delete mode 100644 src/frontends/android/res/drawable-xhdpi/ic_launcher.png delete mode 100644 src/frontends/android/res/drawable/remediation_instruction_background_large.xml delete mode 100644 src/frontends/android/res/drawable/state_background.xml delete mode 100644 src/frontends/android/res/layout-large/remediation_instructions.xml delete mode 100644 src/frontends/android/res/layout/imc_state_fragment.xml delete mode 100644 src/frontends/android/res/layout/log_activity.xml delete mode 100644 src/frontends/android/res/layout/log_fragment.xml delete mode 100644 src/frontends/android/res/layout/login_dialog.xml delete mode 100644 src/frontends/android/res/layout/main.xml delete mode 100644 src/frontends/android/res/layout/profile_detail_view.xml delete mode 100644 src/frontends/android/res/layout/profile_list_fragment.xml delete mode 100644 src/frontends/android/res/layout/profile_list_item.xml delete mode 100644 src/frontends/android/res/layout/remediation_instruction.xml delete mode 100644 src/frontends/android/res/layout/remediation_instruction_item.xml delete mode 100644 src/frontends/android/res/layout/remediation_instructions.xml delete mode 100644 src/frontends/android/res/layout/trusted_certificates_activity.xml delete mode 100644 src/frontends/android/res/layout/trusted_certificates_item.xml delete mode 100644 src/frontends/android/res/layout/two_line_button.xml delete mode 100644 src/frontends/android/res/layout/vpn_profile_select.xml delete mode 100644 src/frontends/android/res/layout/vpn_state_fragment.xml delete mode 100644 src/frontends/android/res/menu/certificates.xml delete mode 100644 src/frontends/android/res/menu/log.xml delete mode 100644 src/frontends/android/res/menu/main.xml delete mode 100644 src/frontends/android/res/menu/profile_edit.xml delete mode 100644 src/frontends/android/res/menu/profile_list.xml delete mode 100644 src/frontends/android/res/menu/profile_list_context.xml delete mode 100644 src/frontends/android/res/values-de/arrays.xml delete mode 100644 src/frontends/android/res/values-de/strings.xml delete mode 100644 src/frontends/android/res/values-pl/arrays.xml delete mode 100644 src/frontends/android/res/values-pl/strings.xml delete mode 100644 src/frontends/android/res/values-ru/arrays.xml delete mode 100644 src/frontends/android/res/values-ru/strings.xml delete mode 100644 src/frontends/android/res/values-ua/arrays.xml delete mode 100644 src/frontends/android/res/values-ua/strings.xml delete mode 100644 src/frontends/android/res/values/arrays.xml delete mode 100644 src/frontends/android/res/values/attrs.xml delete mode 100644 src/frontends/android/res/values/colors.xml delete mode 100644 src/frontends/android/res/values/strings.xml delete mode 100644 src/frontends/android/res/values/styles.xml create mode 100644 src/frontends/android/settings.gradle delete mode 100644 src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java delete mode 100644 src/frontends/android/src/org/strongswan/android/data/VpnProfile.java delete mode 100644 src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java delete mode 100644 src/frontends/android/src/org/strongswan/android/data/VpnType.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/NetworkManager.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/StrongSwanApplication.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/VpnStateService.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/AndroidImc.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/ImcState.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/RemediationInstruction.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/attributes/Attribute.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/attributes/AttributeType.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/attributes/DeviceIdAttribute.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/attributes/InstalledPackagesAttribute.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/attributes/PortFilterAttribute.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/attributes/PrivateEnterpriseNumber.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/attributes/ProductInformationAttribute.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/attributes/SettingsAttribute.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/attributes/StringVersionAttribute.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/collectors/Collector.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/collectors/DeviceIdCollector.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/collectors/InstalledPackagesCollector.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/collectors/PortFilterCollector.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/collectors/ProductInformationCollector.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/collectors/Protocol.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/collectors/SettingsCollector.java delete mode 100644 src/frontends/android/src/org/strongswan/android/logic/imc/collectors/StringVersionCollector.java delete mode 100644 src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreProvider.java delete mode 100644 src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreSpi.java delete mode 100644 src/frontends/android/src/org/strongswan/android/security/LocalCertificateStore.java delete mode 100644 src/frontends/android/src/org/strongswan/android/security/TrustedCertificateEntry.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/ImcStateFragment.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/LogActivity.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/LogFragment.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/LogScrollView.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/MainActivity.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/RemediationInstructionFragment.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/RemediationInstructionsActivity.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/RemediationInstructionsFragment.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateImportActivity.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/VpnProfileSelectActivity.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/adapter/RemediationInstructionAdapter.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java delete mode 100644 src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java delete mode 100644 src/frontends/android/src/org/strongswan/android/utils/BufferedByteWriter.java delete mode 100644 src/frontends/android/src/org/strongswan/android/utils/SettingsWriter.java delete mode 100644 src/frontends/android/src/org/strongswan/android/utils/Utils.java (limited to 'src') diff --git a/src/frontends/android/.classpath b/src/frontends/android/.classpath deleted file mode 100644 index 51769745b..000000000 --- a/src/frontends/android/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/frontends/android/.gitignore b/src/frontends/android/.gitignore index a031dba58..7e4bcfd4e 100644 --- a/src/frontends/android/.gitignore +++ b/src/frontends/android/.gitignore @@ -1,4 +1,7 @@ -bin/ -gen/ -libs/ -obj/ +.gradle/ +.idea/ +app/build/ +app/src/main/libs +app/src/main/obj +*.iml +local.properties diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml deleted file mode 100644 index 65a8275de..000000000 --- a/src/frontends/android/AndroidManifest.xml +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/frontends/android/README.ndk b/src/frontends/android/README.ndk index 699fa3f11..7c8cd309e 100644 --- a/src/frontends/android/README.ndk +++ b/src/frontends/android/README.ndk @@ -1,5 +1,5 @@ -To build this within the NDK several things have to be added in the jni -folder: +To build this within the NDK several things have to be added in the +app/src/main/jni/ folder: - strongswan: The strongSwan sources. This can either be an extracted tarball, or a symlink to the Git repository. To build from the repository the sources diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle new file mode 100644 index 000000000..8280f501f --- /dev/null +++ b/src/frontends/android/app/build.gradle @@ -0,0 +1,42 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 21 + buildToolsVersion "22.0.1" + + defaultConfig { + applicationId "org.strongswan.android" + minSdkVersion 15 + targetSdkVersion 22 + versionCode 28 + versionName "1.5.0" + } + + sourceSets.main { + jni.srcDirs = [] // disables the default ndk-build call (with on-the-fly Android.mk files) + jniLibs.srcDir 'src/main/libs' + } + + task buildNative(type: Exec) { + workingDir 'src/main/jni' + commandLine "${android.ndkDirectory}/ndk-build", '-j', Runtime.runtime.availableProcessors() + } + + task cleanNative(type: Exec) { + workingDir 'src/main/jni' + commandLine "${android.ndkDirectory}/ndk-build", 'clean' + } + + tasks.withType(JavaCompile) { + compileTask -> compileTask.dependsOn buildNative + } + + clean.dependsOn 'cleanNative' + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} diff --git a/src/frontends/android/app/src/main/AndroidManifest.xml b/src/frontends/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..2ab833cdb --- /dev/null +++ b/src/frontends/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/LogContentProvider.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/LogContentProvider.java new file mode 100644 index 000000000..370a8d5e4 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/LogContentProvider.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2012 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.data; + +import java.io.File; +import java.io.FileNotFoundException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.concurrent.ConcurrentHashMap; + +import org.strongswan.android.logic.CharonVpnService; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.os.SystemClock; +import android.provider.OpenableColumns; + +public class LogContentProvider extends ContentProvider +{ + private static final String AUTHORITY = "org.strongswan.android.content.log"; + /* an Uri is valid for 30 minutes */ + private static final long URI_VALIDITY = 30 * 60 * 1000; + private static ConcurrentHashMap mUris = new ConcurrentHashMap(); + private File mLogFile; + + public LogContentProvider() + { + } + + @Override + public boolean onCreate() + { + mLogFile = new File(getContext().getFilesDir(), CharonVpnService.LOG_FILE); + return true; + } + + /** + * The log file can only be accessed by Uris created with this method + * @return null if failed to create the Uri + */ + public static Uri createContentUri() + { + SecureRandom random; + try + { + random = SecureRandom.getInstance("SHA1PRNG"); + } + catch (NoSuchAlgorithmException e) + { + return null; + } + Uri uri = Uri.parse("content://" + AUTHORITY + "/" + random.nextLong()); + mUris.put(uri, SystemClock.uptimeMillis()); + return uri; + } + + @Override + public String getType(Uri uri) + { + /* MIME type for our log file */ + return "text/plain"; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) + { + /* this is called by apps to find out the name and size of the file. + * since we only provide a single file this is simple to implement */ + if (projection == null || projection.length < 1) + { + return null; + } + Long timestamp = mUris.get(uri); + if (timestamp == null) + { /* don't check the validity as this information is not really private */ + return null; + } + MatrixCursor cursor = new MatrixCursor(projection, 1); + if (OpenableColumns.DISPLAY_NAME.equals(cursor.getColumnName(0))) + { + cursor.newRow().add(CharonVpnService.LOG_FILE); + } + else if (OpenableColumns.SIZE.equals(cursor.getColumnName(0))) + { + cursor.newRow().add(mLogFile.length()); + } + else + { + return null; + } + return cursor; + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException + { + Long timestamp = mUris.get(uri); + if (timestamp != null) + { + long elapsed = SystemClock.uptimeMillis() - timestamp; + if (elapsed > 0 && elapsed < URI_VALIDITY) + { /* we fail if clock wrapped, should happen rarely though */ + return ParcelFileDescriptor.open(mLogFile, ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_READ_ONLY); + } + mUris.remove(uri); + } + return super.openFile(uri, mode); + } + + @Override + public Uri insert(Uri uri, ContentValues values) + { + /* not supported */ + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) + { + /* not supported */ + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, + String[] selectionArgs) + { + /* not supported */ + return 0; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java new file mode 100644 index 000000000..5c64ad0e5 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfile.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2012-2015 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.data; + + +public class VpnProfile implements Cloneable +{ + /* While storing this as EnumSet would be nicer this simplifies storing it in a database */ + public static final int SPLIT_TUNNELING_BLOCK_IPV4 = 1; + public static final int SPLIT_TUNNELING_BLOCK_IPV6 = 2; + + private String mName, mGateway, mUsername, mPassword, mCertificate, mUserCertificate; + private Integer mMTU, mPort, mSplitTunneling; + private VpnType mVpnType; + private long mId = -1; + + public long getId() + { + return mId; + } + + public void setId(long id) + { + this.mId = id; + } + + public String getName() + { + return mName; + } + + public void setName(String name) + { + this.mName = name; + } + + public String getGateway() + { + return mGateway; + } + + public void setGateway(String gateway) + { + this.mGateway = gateway; + } + + public VpnType getVpnType() + { + return mVpnType; + } + + public void setVpnType(VpnType type) + { + this.mVpnType = type; + } + + public String getUsername() + { + return mUsername; + } + + public void setUsername(String username) + { + this.mUsername = username; + } + + public String getPassword() + { + return mPassword; + } + + public void setPassword(String password) + { + this.mPassword = password; + } + + public String getCertificateAlias() + { + return mCertificate; + } + + public void setCertificateAlias(String alias) + { + this.mCertificate = alias; + } + + public String getUserCertificateAlias() + { + return mUserCertificate; + } + + public void setUserCertificateAlias(String alias) + { + this.mUserCertificate = alias; + } + + public Integer getMTU() + { + return mMTU; + } + + public void setMTU(Integer mtu) + { + this.mMTU = mtu; + } + + public Integer getPort() + { + return mPort; + } + + public void setPort(Integer port) + { + this.mPort = port; + } + + public Integer getSplitTunneling() + { + return mSplitTunneling; + } + + public void setSplitTunneling(Integer splitTunneling) + { + this.mSplitTunneling = splitTunneling; + } + + @Override + public String toString() + { + return mName; + } + + @Override + public boolean equals(Object o) + { + if (o != null && o instanceof VpnProfile) + { + return this.mId == ((VpnProfile)o).getId(); + } + return false; + } + + @Override + public VpnProfile clone() + { + try + { + return (VpnProfile)super.clone(); + } + catch (CloneNotSupportedException e) + { + throw new AssertionError(); + } + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java new file mode 100644 index 000000000..45e9b8650 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnProfileDataSource.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2012-2015 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.data; + +import java.util.ArrayList; +import java.util.List; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.util.Log; + +public class VpnProfileDataSource +{ + private static final String TAG = VpnProfileDataSource.class.getSimpleName(); + public static final String KEY_ID = "_id"; + public static final String KEY_NAME = "name"; + public static final String KEY_GATEWAY = "gateway"; + public static final String KEY_VPN_TYPE = "vpn_type"; + public static final String KEY_USERNAME = "username"; + public static final String KEY_PASSWORD = "password"; + public static final String KEY_CERTIFICATE = "certificate"; + public static final String KEY_USER_CERTIFICATE = "user_certificate"; + public static final String KEY_MTU = "mtu"; + public static final String KEY_PORT = "port"; + public static final String KEY_SPLIT_TUNNELING = "split_tunneling"; + + private DatabaseHelper mDbHelper; + private SQLiteDatabase mDatabase; + private final Context mContext; + + private static final String DATABASE_NAME = "strongswan.db"; + private static final String TABLE_VPNPROFILE = "vpnprofile"; + + private static final int DATABASE_VERSION = 7; + + public static final String DATABASE_CREATE = + "CREATE TABLE " + TABLE_VPNPROFILE + " (" + + KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + KEY_NAME + " TEXT NOT NULL," + + KEY_GATEWAY + " TEXT NOT NULL," + + KEY_VPN_TYPE + " TEXT NOT NULL," + + KEY_USERNAME + " TEXT," + + KEY_PASSWORD + " TEXT," + + KEY_CERTIFICATE + " TEXT," + + KEY_USER_CERTIFICATE + " TEXT," + + KEY_MTU + " INTEGER," + + KEY_PORT + " INTEGER," + + KEY_SPLIT_TUNNELING + " INTEGER" + + ");"; + private static final String[] ALL_COLUMNS = new String[] { + KEY_ID, + KEY_NAME, + KEY_GATEWAY, + KEY_VPN_TYPE, + KEY_USERNAME, + KEY_PASSWORD, + KEY_CERTIFICATE, + KEY_USER_CERTIFICATE, + KEY_MTU, + KEY_PORT, + KEY_SPLIT_TUNNELING, + }; + + private static class DatabaseHelper extends SQLiteOpenHelper + { + public DatabaseHelper(Context context) + { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase database) + { + database.execSQL(DATABASE_CREATE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) + { + Log.w(TAG, "Upgrading database from version " + oldVersion + + " to " + newVersion); + if (oldVersion < 2) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_USER_CERTIFICATE + + " TEXT;"); + } + if (oldVersion < 3) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_VPN_TYPE + + " TEXT DEFAULT '';"); + } + if (oldVersion < 4) + { /* remove NOT NULL constraint from username column */ + updateColumns(db); + } + if (oldVersion < 5) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_MTU + + " INTEGER;"); + } + if (oldVersion < 6) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_PORT + + " INTEGER;"); + } + if (oldVersion < 7) + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SPLIT_TUNNELING + + " INTEGER;"); + } + } + + private void updateColumns(SQLiteDatabase db) + { + db.beginTransaction(); + try + { + db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " RENAME TO tmp_" + TABLE_VPNPROFILE + ";"); + db.execSQL(DATABASE_CREATE); + StringBuilder insert = new StringBuilder("INSERT INTO " + TABLE_VPNPROFILE + " SELECT "); + SQLiteQueryBuilder.appendColumns(insert, ALL_COLUMNS); + db.execSQL(insert.append(" FROM tmp_" + TABLE_VPNPROFILE + ";").toString()); + db.execSQL("DROP TABLE tmp_" + TABLE_VPNPROFILE + ";"); + db.setTransactionSuccessful(); + } + finally + { + db.endTransaction(); + } + } + } + + /** + * Construct a new VPN profile data source. The context is used to + * open/create the database. + * @param context context used to access the database + */ + public VpnProfileDataSource(Context context) + { + this.mContext = context; + } + + /** + * Open the VPN profile data source. The database is automatically created + * if it does not yet exist. If that fails an exception is thrown. + * @return itself (allows to chain initialization calls) + * @throws SQLException if the database could not be opened or created + */ + public VpnProfileDataSource open() throws SQLException + { + if (mDbHelper == null) + { + mDbHelper = new DatabaseHelper(mContext); + mDatabase = mDbHelper.getWritableDatabase(); + } + return this; + } + + /** + * Close the data source. + */ + public void close() + { + if (mDbHelper != null) + { + mDbHelper.close(); + mDbHelper = null; + } + } + + /** + * Insert the given VPN profile into the database. On success the Id of + * the object is updated and the object returned. + * + * @param profile the profile to add + * @return the added VPN profile or null, if failed + */ + public VpnProfile insertProfile(VpnProfile profile) + { + ContentValues values = ContentValuesFromVpnProfile(profile); + long insertId = mDatabase.insert(TABLE_VPNPROFILE, null, values); + if (insertId == -1) + { + return null; + } + profile.setId(insertId); + return profile; + } + + /** + * Updates the given VPN profile in the database. + * @param profile the profile to update + * @return true if update succeeded, false otherwise + */ + public boolean updateVpnProfile(VpnProfile profile) + { + long id = profile.getId(); + ContentValues values = ContentValuesFromVpnProfile(profile); + return mDatabase.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + id, null) > 0; + } + + /** + * Delete the given VPN profile from the database. + * @param profile the profile to delete + * @return true if deleted, false otherwise + */ + public boolean deleteVpnProfile(VpnProfile profile) + { + long id = profile.getId(); + return mDatabase.delete(TABLE_VPNPROFILE, KEY_ID + " = " + id, null) > 0; + } + + /** + * Get a single VPN profile from the database. + * @param id the ID of the VPN profile + * @return the profile or null, if not found + */ + public VpnProfile getVpnProfile(long id) + { + VpnProfile profile = null; + Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, + KEY_ID + "=" + id, null, null, null, null); + if (cursor.moveToFirst()) + { + profile = VpnProfileFromCursor(cursor); + } + cursor.close(); + return profile; + } + + /** + * Get a list of all VPN profiles stored in the database. + * @return list of VPN profiles + */ + public List getAllVpnProfiles() + { + List vpnProfiles = new ArrayList(); + + Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, null, null, null, null, null); + cursor.moveToFirst(); + while (!cursor.isAfterLast()) + { + VpnProfile vpnProfile = VpnProfileFromCursor(cursor); + vpnProfiles.add(vpnProfile); + cursor.moveToNext(); + } + cursor.close(); + return vpnProfiles; + } + + private VpnProfile VpnProfileFromCursor(Cursor cursor) + { + VpnProfile profile = new VpnProfile(); + profile.setId(cursor.getLong(cursor.getColumnIndex(KEY_ID))); + profile.setName(cursor.getString(cursor.getColumnIndex(KEY_NAME))); + profile.setGateway(cursor.getString(cursor.getColumnIndex(KEY_GATEWAY))); + profile.setVpnType(VpnType.fromIdentifier(cursor.getString(cursor.getColumnIndex(KEY_VPN_TYPE)))); + profile.setUsername(cursor.getString(cursor.getColumnIndex(KEY_USERNAME))); + profile.setPassword(cursor.getString(cursor.getColumnIndex(KEY_PASSWORD))); + profile.setCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_CERTIFICATE))); + profile.setUserCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_USER_CERTIFICATE))); + profile.setMTU(getInt(cursor, cursor.getColumnIndex(KEY_MTU))); + profile.setPort(getInt(cursor, cursor.getColumnIndex(KEY_PORT))); + profile.setSplitTunneling(getInt(cursor, cursor.getColumnIndex(KEY_SPLIT_TUNNELING))); + return profile; + } + + private ContentValues ContentValuesFromVpnProfile(VpnProfile profile) + { + ContentValues values = new ContentValues(); + values.put(KEY_NAME, profile.getName()); + values.put(KEY_GATEWAY, profile.getGateway()); + values.put(KEY_VPN_TYPE, profile.getVpnType().getIdentifier()); + values.put(KEY_USERNAME, profile.getUsername()); + values.put(KEY_PASSWORD, profile.getPassword()); + values.put(KEY_CERTIFICATE, profile.getCertificateAlias()); + values.put(KEY_USER_CERTIFICATE, profile.getUserCertificateAlias()); + values.put(KEY_MTU, profile.getMTU()); + values.put(KEY_PORT, profile.getPort()); + values.put(KEY_SPLIT_TUNNELING, profile.getSplitTunneling()); + return values; + } + + private Integer getInt(Cursor cursor, int columnIndex) + { + return cursor.isNull(columnIndex) ? null : cursor.getInt(columnIndex); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnType.java b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnType.java new file mode 100644 index 000000000..bb7fd09f3 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/data/VpnType.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2012-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.data; + +import java.util.EnumSet; + +public enum VpnType +{ + /* the order here must match the items in R.array.vpn_types */ + IKEV2_EAP("ikev2-eap", EnumSet.of(VpnTypeFeature.USER_PASS)), + IKEV2_CERT("ikev2-cert", EnumSet.of(VpnTypeFeature.CERTIFICATE)), + IKEV2_CERT_EAP("ikev2-cert-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.CERTIFICATE)), + IKEV2_EAP_TLS("ikev2-eap-tls", EnumSet.of(VpnTypeFeature.CERTIFICATE)), + IKEV2_BYOD_EAP("ikev2-byod-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.BYOD)); + + /** + * Features of a VPN type. + */ + public enum VpnTypeFeature + { + /** client certificate is required */ + CERTIFICATE, + /** username and password are required */ + USER_PASS, + /** enable BYOD features */ + BYOD; + } + + private String mIdentifier; + private EnumSet mFeatures; + + /** + * Enum which provides additional information about the supported VPN types. + * + * @param id identifier used to store and transmit this specific type + * @param features of the given VPN type + * @param certificate true if a client certificate is required + */ + VpnType(String id, EnumSet features) + { + mIdentifier = id; + mFeatures = features; + } + + /** + * The identifier used to store this value in the database + * @return identifier + */ + public String getIdentifier() + { + return mIdentifier; + } + + /** + * Checks whether a feature is supported/required by this type of VPN. + * + * @return true if the feature is supported/required + */ + public boolean has(VpnTypeFeature feature) + { + return mFeatures.contains(feature); + } + + /** + * Get the enum entry with the given identifier. + * + * @param identifier get the enum entry with this identifier + * @return the enum entry, or the default if not found + */ + public static VpnType fromIdentifier(String identifier) + { + for (VpnType type : VpnType.values()) + { + if (identifier.equals(type.mIdentifier)) + { + return type; + } + } + return VpnType.IKEV2_EAP; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java new file mode 100644 index 000000000..e5241d5a7 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java @@ -0,0 +1,857 @@ +/* + * Copyright (C) 2012-2015 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.logic; + +import java.io.File; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.PrivateKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.data.VpnType.VpnTypeFeature; +import org.strongswan.android.logic.VpnStateService.ErrorState; +import org.strongswan.android.logic.VpnStateService.State; +import org.strongswan.android.logic.imc.ImcState; +import org.strongswan.android.logic.imc.RemediationInstruction; +import org.strongswan.android.ui.MainActivity; +import org.strongswan.android.utils.SettingsWriter; + +import android.annotation.TargetApi; +import android.app.PendingIntent; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.net.VpnService; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.security.KeyChain; +import android.security.KeyChainException; +import android.system.OsConstants; +import android.util.Log; + +public class CharonVpnService extends VpnService implements Runnable +{ + private static final String TAG = CharonVpnService.class.getSimpleName(); + public static final String LOG_FILE = "charon.log"; + + private String mLogFile; + private VpnProfileDataSource mDataSource; + private Thread mConnectionHandler; + private VpnProfile mCurrentProfile; + private volatile String mCurrentCertificateAlias; + private volatile String mCurrentUserCertificateAlias; + private VpnProfile mNextProfile; + private volatile boolean mProfileUpdated; + private volatile boolean mTerminate; + private volatile boolean mIsDisconnecting; + private VpnStateService mService; + private final Object mServiceLock = new Object(); + private final ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceDisconnected(ComponentName name) + { /* since the service is local this is theoretically only called when the process is terminated */ + synchronized (mServiceLock) + { + mService = null; + } + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) + { + synchronized (mServiceLock) + { + mService = ((VpnStateService.LocalBinder)service).getService(); + } + /* we are now ready to start the handler thread */ + mConnectionHandler.start(); + } + }; + + /** + * as defined in charonservice.h + */ + static final int STATE_CHILD_SA_UP = 1; + static final int STATE_CHILD_SA_DOWN = 2; + static final int STATE_AUTH_ERROR = 3; + static final int STATE_PEER_AUTH_ERROR = 4; + static final int STATE_LOOKUP_ERROR = 5; + static final int STATE_UNREACHABLE_ERROR = 6; + static final int STATE_GENERIC_ERROR = 7; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) + { + if (intent != null) + { + Bundle bundle = intent.getExtras(); + VpnProfile profile = null; + if (bundle != null) + { + profile = mDataSource.getVpnProfile(bundle.getLong(VpnProfileDataSource.KEY_ID)); + if (profile != null) + { + String password = bundle.getString(VpnProfileDataSource.KEY_PASSWORD); + profile.setPassword(password); + } + } + setNextProfile(profile); + } + return START_NOT_STICKY; + } + + @Override + public void onCreate() + { + mLogFile = getFilesDir().getAbsolutePath() + File.separator + LOG_FILE; + + mDataSource = new VpnProfileDataSource(this); + mDataSource.open(); + /* use a separate thread as main thread for charon */ + mConnectionHandler = new Thread(this); + /* the thread is started when the service is bound */ + bindService(new Intent(this, VpnStateService.class), + mServiceConnection, Service.BIND_AUTO_CREATE); + } + + @Override + public void onRevoke() + { /* the system revoked the rights grated with the initial prepare() call. + * called when the user clicks disconnect in the system's VPN dialog */ + setNextProfile(null); + } + + @Override + public void onDestroy() + { + mTerminate = true; + setNextProfile(null); + try + { + mConnectionHandler.join(); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + if (mService != null) + { + unbindService(mServiceConnection); + } + mDataSource.close(); + } + + /** + * Set the profile that is to be initiated next. Notify the handler thread. + * + * @param profile the profile to initiate + */ + private void setNextProfile(VpnProfile profile) + { + synchronized (this) + { + this.mNextProfile = profile; + mProfileUpdated = true; + notifyAll(); + } + } + + @Override + public void run() + { + while (true) + { + synchronized (this) + { + try + { + while (!mProfileUpdated) + { + wait(); + } + + mProfileUpdated = false; + stopCurrentConnection(); + if (mNextProfile == null) + { + setState(State.DISABLED); + if (mTerminate) + { + break; + } + } + else + { + mCurrentProfile = mNextProfile; + mNextProfile = null; + + /* store this in a separate (volatile) variable to avoid + * a possible deadlock during deinitialization */ + mCurrentCertificateAlias = mCurrentProfile.getCertificateAlias(); + mCurrentUserCertificateAlias = mCurrentProfile.getUserCertificateAlias(); + + startConnection(mCurrentProfile); + mIsDisconnecting = false; + + BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName(), mCurrentProfile.getSplitTunneling()); + if (initializeCharon(builder, mLogFile, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD))) + { + Log.i(TAG, "charon started"); + SettingsWriter writer = new SettingsWriter(); + writer.setValue("global.language", Locale.getDefault().getLanguage()); + writer.setValue("global.mtu", mCurrentProfile.getMTU()); + writer.setValue("connection.type", mCurrentProfile.getVpnType().getIdentifier()); + writer.setValue("connection.server", mCurrentProfile.getGateway()); + writer.setValue("connection.port", mCurrentProfile.getPort()); + writer.setValue("connection.username", mCurrentProfile.getUsername()); + writer.setValue("connection.password", mCurrentProfile.getPassword()); + initiate(writer.serialize()); + } + else + { + Log.e(TAG, "failed to start charon"); + setError(ErrorState.GENERIC_ERROR); + setState(State.DISABLED); + mCurrentProfile = null; + } + } + } + catch (InterruptedException ex) + { + stopCurrentConnection(); + setState(State.DISABLED); + } + } + } + } + + /** + * Stop any existing connection by deinitializing charon. + */ + private void stopCurrentConnection() + { + synchronized (this) + { + if (mCurrentProfile != null) + { + setState(State.DISCONNECTING); + mIsDisconnecting = true; + deinitializeCharon(); + Log.i(TAG, "charon stopped"); + mCurrentProfile = null; + } + } + } + + /** + * Notify the state service about a new connection attempt. + * Called by the handler thread. + * + * @param profile currently active VPN profile + */ + private void startConnection(VpnProfile profile) + { + synchronized (mServiceLock) + { + if (mService != null) + { + mService.startConnection(profile); + } + } + } + + /** + * Update the current VPN state on the state service. Called by the handler + * thread and any of charon's threads. + * + * @param state current state + */ + private void setState(State state) + { + synchronized (mServiceLock) + { + if (mService != null) + { + mService.setState(state); + } + } + } + + /** + * Set an error on the state service. Called by the handler thread and any + * of charon's threads. + * + * @param error error state + */ + private void setError(ErrorState error) + { + synchronized (mServiceLock) + { + if (mService != null) + { + mService.setError(error); + } + } + } + + /** + * Set the IMC state on the state service. Called by the handler thread and + * any of charon's threads. + * + * @param state IMC state + */ + private void setImcState(ImcState state) + { + synchronized (mServiceLock) + { + if (mService != null) + { + mService.setImcState(state); + } + } + } + + /** + * Set an error on the state service. Called by the handler thread and any + * of charon's threads. + * + * @param error error state + */ + private void setErrorDisconnect(ErrorState error) + { + synchronized (mServiceLock) + { + if (mService != null) + { + if (!mIsDisconnecting) + { + mService.setError(error); + } + } + } + } + + /** + * Updates the state of the current connection. + * Called via JNI by different threads (but not concurrently). + * + * @param status new state + */ + public void updateStatus(int status) + { + switch (status) + { + case STATE_CHILD_SA_DOWN: + if (!mIsDisconnecting) + { + setState(State.CONNECTING); + } + break; + case STATE_CHILD_SA_UP: + setState(State.CONNECTED); + break; + case STATE_AUTH_ERROR: + setErrorDisconnect(ErrorState.AUTH_FAILED); + break; + case STATE_PEER_AUTH_ERROR: + setErrorDisconnect(ErrorState.PEER_AUTH_FAILED); + break; + case STATE_LOOKUP_ERROR: + setErrorDisconnect(ErrorState.LOOKUP_FAILED); + break; + case STATE_UNREACHABLE_ERROR: + setErrorDisconnect(ErrorState.UNREACHABLE); + break; + case STATE_GENERIC_ERROR: + setErrorDisconnect(ErrorState.GENERIC_ERROR); + break; + default: + Log.e(TAG, "Unknown status code received"); + break; + } + } + + /** + * Updates the IMC state of the current connection. + * Called via JNI by different threads (but not concurrently). + * + * @param value new state + */ + public void updateImcState(int value) + { + ImcState state = ImcState.fromValue(value); + if (state != null) + { + setImcState(state); + } + } + + /** + * Add a remediation instruction to the VPN state service. + * Called via JNI by different threads (but not concurrently). + * + * @param xml XML text + */ + public void addRemediationInstruction(String xml) + { + for (RemediationInstruction instruction : RemediationInstruction.fromXml(xml)) + { + synchronized (mServiceLock) + { + if (mService != null) + { + mService.addRemediationInstruction(instruction); + } + } + } + } + + /** + * Function called via JNI to generate a list of DER encoded CA certificates + * as byte array. + * + * @return a list of DER encoded CA certificates + */ + private byte[][] getTrustedCertificates() + { + ArrayList certs = new ArrayList(); + TrustedCertificateManager certman = TrustedCertificateManager.getInstance(); + try + { + String alias = this.mCurrentCertificateAlias; + if (alias != null) + { + 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) + { + e.printStackTrace(); + return null; + } + return certs.toArray(new byte[certs.size()][]); + } + + /** + * Function called via JNI to get a list containing the DER encoded certificates + * of the user selected certificate chain (beginning with the user certificate). + * + * Since this method is called from a thread of charon's thread pool we are safe + * to call methods on KeyChain directly. + * + * @return list containing the certificates (first element is the user certificate) + * @throws InterruptedException + * @throws KeyChainException + * @throws CertificateEncodingException + */ + private byte[][] getUserCertificate() throws KeyChainException, InterruptedException, CertificateEncodingException + { + ArrayList encodings = new ArrayList(); + X509Certificate[] chain = KeyChain.getCertificateChain(getApplicationContext(), mCurrentUserCertificateAlias); + if (chain == null || chain.length == 0) + { + return null; + } + for (X509Certificate cert : chain) + { + encodings.add(cert.getEncoded()); + } + return encodings.toArray(new byte[encodings.size()][]); + } + + /** + * Function called via JNI to get the private key the user selected. + * + * Since this method is called from a thread of charon's thread pool we are safe + * to call methods on KeyChain directly. + * + * @return the private key + * @throws InterruptedException + * @throws KeyChainException + * @throws CertificateEncodingException + */ + private PrivateKey getUserKey() throws KeyChainException, InterruptedException + { + return KeyChain.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias); + } + + /** + * Initialization of charon, provided by libandroidbridge.so + * + * @param builder BuilderAdapter for this connection + * @param logfile absolute path to the logfile + * @param boyd enable BYOD features + * @return TRUE if initialization was successful + */ + public native boolean initializeCharon(BuilderAdapter builder, String logfile, boolean byod); + + /** + * Deinitialize charon, provided by libandroidbridge.so + */ + public native void deinitializeCharon(); + + /** + * Initiate VPN, provided by libandroidbridge.so + */ + public native void initiate(String config); + + /** + * Adapter for VpnService.Builder which is used to access it safely via JNI. + * There is a corresponding C object to access it from native code. + */ + public class BuilderAdapter + { + private final String mName; + private final Integer mSplitTunneling; + private VpnService.Builder mBuilder; + private BuilderCache mCache; + private BuilderCache mEstablishedCache; + + public BuilderAdapter(String name, Integer splitTunneling) + { + mName = name; + mSplitTunneling = splitTunneling; + mBuilder = createBuilder(name); + mCache = new BuilderCache(mSplitTunneling); + } + + private VpnService.Builder createBuilder(String name) + { + VpnService.Builder builder = new CharonVpnService.Builder(); + builder.setSession(mName); + + /* even though the option displayed in the system dialog says "Configure" + * we just use our main Activity */ + Context context = getApplicationContext(); + Intent intent = new Intent(context, MainActivity.class); + PendingIntent pending = PendingIntent.getActivity(context, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + builder.setConfigureIntent(pending); + return builder; + } + + public synchronized boolean addAddress(String address, int prefixLength) + { + try + { + mCache.addAddress(address, prefixLength); + } + catch (IllegalArgumentException ex) + { + return false; + } + return true; + } + + public synchronized boolean addDnsServer(String address) + { + try + { + mBuilder.addDnsServer(address); + mCache.recordAddressFamily(address); + } + catch (IllegalArgumentException ex) + { + return false; + } + return true; + } + + public synchronized boolean addRoute(String address, int prefixLength) + { + try + { + mCache.addRoute(address, prefixLength); + } + catch (IllegalArgumentException ex) + { + return false; + } + return true; + } + + public synchronized boolean addSearchDomain(String domain) + { + try + { + mBuilder.addSearchDomain(domain); + } + catch (IllegalArgumentException ex) + { + return false; + } + return true; + } + + public synchronized boolean setMtu(int mtu) + { + try + { + mCache.setMtu(mtu); + } + catch (IllegalArgumentException ex) + { + return false; + } + return true; + } + + public synchronized int establish() + { + ParcelFileDescriptor fd; + try + { + mCache.applyData(mBuilder); + fd = mBuilder.establish(); + } + catch (Exception ex) + { + ex.printStackTrace(); + return -1; + } + if (fd == null) + { + return -1; + } + /* now that the TUN device is created we don't need the current + * builder anymore, but we might need another when reestablishing */ + mBuilder = createBuilder(mName); + mEstablishedCache = mCache; + mCache = new BuilderCache(mSplitTunneling); + return fd.detachFd(); + } + + public synchronized int establishNoDns() + { + ParcelFileDescriptor fd; + + if (mEstablishedCache == null) + { + return -1; + } + try + { + Builder builder = createBuilder(mName); + mEstablishedCache.applyData(builder); + fd = builder.establish(); + } + catch (Exception ex) + { + ex.printStackTrace(); + return -1; + } + if (fd == null) + { + return -1; + } + return fd.detachFd(); + } + } + + /** + * Cache non DNS related information so we can recreate the builder without + * that information when reestablishing IKE_SAs + */ + public class BuilderCache + { + private final List mAddresses = new ArrayList(); + private final List mRoutesIPv4 = new ArrayList(); + private final List mRoutesIPv6 = new ArrayList(); + private final int mSplitTunneling; + private int mMtu; + private boolean mIPv4Seen, mIPv6Seen; + + public BuilderCache(Integer splitTunneling) + { + mSplitTunneling = splitTunneling != null ? splitTunneling : 0; + } + + public void addAddress(String address, int prefixLength) + { + mAddresses.add(new PrefixedAddress(address, prefixLength)); + recordAddressFamily(address); + } + + public void addRoute(String address, int prefixLength) + { + try + { + if (isIPv6(address)) + { + mRoutesIPv6.add(new PrefixedAddress(address, prefixLength)); + } + else + { + mRoutesIPv4.add(new PrefixedAddress(address, prefixLength)); + } + } + catch (UnknownHostException ex) + { + ex.printStackTrace(); + } + } + + public void setMtu(int mtu) + { + mMtu = mtu; + } + + public void recordAddressFamily(String address) + { + try + { + if (isIPv6(address)) + { + mIPv6Seen = true; + } + else + { + mIPv4Seen = true; + } + } + catch (UnknownHostException ex) + { + ex.printStackTrace(); + } + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public void applyData(VpnService.Builder builder) + { + for (PrefixedAddress address : mAddresses) + { + builder.addAddress(address.mAddress, address.mPrefix); + } + /* add routes depending on whether split tunneling is allowed or not, + * that is, whether we have to handle and block non-VPN traffic */ + if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) == 0) + { + if (mIPv4Seen) + { /* split tunneling is used depending on the routes */ + for (PrefixedAddress route : mRoutesIPv4) + { + builder.addRoute(route.mAddress, route.mPrefix); + } + } + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + { /* allow traffic that would otherwise be blocked to bypass the VPN */ + builder.allowFamily(OsConstants.AF_INET); + } + } + else if (mIPv4Seen) + { /* only needed if we've seen any addresses. otherwise, traffic + * is blocked by default (we also install no routes in that case) */ + builder.addRoute("0.0.0.0", 0); + } + /* same thing for IPv6 */ + if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) == 0) + { + if (mIPv6Seen) + { + for (PrefixedAddress route : mRoutesIPv6) + { + builder.addRoute(route.mAddress, route.mPrefix); + } + } + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + { + builder.allowFamily(OsConstants.AF_INET6); + } + } + else if (mIPv6Seen) + { + builder.addRoute("::", 0); + } + builder.setMtu(mMtu); + } + + private boolean isIPv6(String address) throws UnknownHostException + { + InetAddress addr = InetAddress.getByName(address); + if (addr instanceof Inet4Address) + { + return false; + } + else if (addr instanceof Inet6Address) + { + return true; + } + return false; + } + + private class PrefixedAddress + { + public String mAddress; + public int mPrefix; + + public PrefixedAddress(String address, int prefix) + { + this.mAddress = address; + this.mPrefix = prefix; + } + } + } + + /* + * The libraries are extracted to /data/data/org.strongswan.android/... + * during installation. On newer releases most are loaded in JNI_OnLoad. + */ + static + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) + { + System.loadLibrary("strongswan"); + + if (MainActivity.USE_BYOD) + { + System.loadLibrary("tncif"); + System.loadLibrary("tnccs"); + System.loadLibrary("imcv"); + } + + System.loadLibrary("hydra"); + System.loadLibrary("charon"); + System.loadLibrary("ipsec"); + } + System.loadLibrary("androidbridge"); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/NetworkManager.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/NetworkManager.java new file mode 100644 index 000000000..ebe1d0080 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/NetworkManager.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012-2015 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.logic; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +public class NetworkManager extends BroadcastReceiver +{ + private final Context mContext; + private boolean mRegistered; + + public NetworkManager(Context context) + { + mContext = context; + } + + public void Register() + { + mContext.registerReceiver(this, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + } + + public void Unregister() + { + mContext.unregisterReceiver(this); + } + + public boolean isConnected() + { + ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo info = null; + if (cm != null) + { + info = cm.getActiveNetworkInfo(); + } + return info != null && info.isConnected(); + } + + @Override + public void onReceive(Context context, Intent intent) + { + networkChanged(!isConnected()); + } + + /** + * Notify the native parts about a network change + * + * @param disconnected true if no connection is available at the moment + */ + public native void networkChanged(boolean disconnected); +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java new file mode 100644 index 000000000..d642b67b3 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/StrongSwanApplication.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.logic; + +import java.security.Security; + +import org.strongswan.android.security.LocalCertificateKeyStoreProvider; + +import android.app.Application; +import android.content.Context; + +public class StrongSwanApplication extends Application +{ + private static Context mContext; + + static { + Security.addProvider(new LocalCertificateKeyStoreProvider()); + } + + @Override + public void onCreate() + { + super.onCreate(); + StrongSwanApplication.mContext = getApplicationContext(); + } + + /** + * Returns the current application context + * @return context + */ + public static Context getContext() + { + return StrongSwanApplication.mContext; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/TrustedCertificateManager.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/TrustedCertificateManager.java new file mode 100644 index 000000000..82a7cbe4e --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/TrustedCertificateManager.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2012-2014 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.logic; + +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; + +import android.util.Log; + +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(); + + public enum TrustedCertificateSource + { + SYSTEM("system:"), + USER("user:"), + LOCAL("local:"); + + private final String mPrefix; + + private TrustedCertificateSource(String prefix) + { + mPrefix = prefix; + } + + private String getPrefix() + { + return mPrefix; + } + } + + /** + * Private constructor to prevent instantiation from other classes. + */ + private TrustedCertificateManager() + { + for (String name : new String[] { "LocalCertificateStore", "AndroidCAStore" }) + { + KeyStore store; + try + { + store = KeyStore.getInstance(name); + store.load(null,null); + mKeyStores.add(store); + } + catch (Exception e) + { + Log.e(TAG, "Unable to load KeyStore: " + name); + e.printStackTrace(); + } + } + } + + /** + * This is not instantiated until the first call to getInstance() + */ + private static class Singleton { + public static final TrustedCertificateManager mInstance = new TrustedCertificateManager(); + } + + /** + * Get the single instance of the CA certificate manager. + * @return CA certificate manager + */ + public static TrustedCertificateManager getInstance() + { + return Singleton.mInstance; + } + + /** + * 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 reset() + { + Log.d(TAG, "Force reload of cached CA certificates on next load"); + this.mReload = true; + return this; + } + + /** + * Ensures that the certificates are loaded but does not force a reload. + * As this takes a while if the certificates are not loaded yet it should + * be called asynchronously. + * @return reference to itself + */ + public TrustedCertificateManager load() + { + Log.d(TAG, "Ensure cached CA certificates are loaded"); + this.mLock.writeLock().lock(); + if (!this.mLoaded || this.mReload) + { + this.mReload = false; + loadCertificates(); + } + this.mLock.writeLock().unlock(); + return this; + } + + /** + * Opens the CA certificate KeyStore and loads the cached certificates. + * The lock must be locked when calling this method. + */ + private void loadCertificates() + { + Log.d(TAG, "Load cached CA certificates"); + Hashtable certs = new Hashtable(); + for (KeyStore store : this.mKeyStores) + { + 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 + */ + private void fetchCertificates(Hashtable certs, KeyStore store) + { + try + { + Enumeration aliases = store.aliases(); + while (aliases.hasMoreElements()) + { + String alias = aliases.nextElement(); + Certificate cert; + cert = store.getCertificate(alias); + if (cert != null && cert instanceof X509Certificate) + { + certs.put(alias, (X509Certificate)cert); + } + } + } + catch (KeyStoreException ex) + { + ex.printStackTrace(); + } + } + + /** + * Retrieve the CA certificate with the given alias. + * @param alias alias of the certificate to get + * @return the certificate, null if not found + */ + public X509Certificate getCACertificateFromAlias(String alias) + { + X509Certificate certificate = null; + + if (this.mLock.readLock().tryLock()) + { + certificate = this.mCACerts.get(alias); + this.mLock.readLock().unlock(); + } + else + { /* if we cannot get the lock load it directly from the KeyStore, + * should be fast for a single certificate */ + for (KeyStore store : this.mKeyStores) + { + try + { + Certificate cert = store.getCertificate(alias); + if (cert != null && cert instanceof X509Certificate) + { + certificate = (X509Certificate)cert; + break; + } + } + catch (KeyStoreException e) + { + e.printStackTrace(); + } + } + } + return certificate; + } + + /** + * Get all CA certificates (from all keystores). + * @return Hashtable mapping aliases to certificates + */ + @SuppressWarnings("unchecked") + public Hashtable getAllCACertificates() + { + Hashtable certs; + this.mLock.readLock().lock(); + certs = (Hashtable)this.mCACerts.clone(); + this.mLock.readLock().unlock(); + return certs; + } + + /** + * Get all certificates from the given source. + * @param source type to filter certificates + * @return Hashtable mapping aliases to certificates + */ + public Hashtable getCACertificates(TrustedCertificateSource source) + { + Hashtable certs = new Hashtable(); + this.mLock.readLock().lock(); + for (String alias : this.mCACerts.keySet()) + { + if (alias.startsWith(source.getPrefix())) + { + certs.put(alias, this.mCACerts.get(alias)); + } + } + this.mLock.readLock().unlock(); + return certs; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java new file mode 100644 index 000000000..7b40e942f --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2012-2013 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.logic; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Callable; + +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.logic.imc.ImcState; +import org.strongswan.android.logic.imc.RemediationInstruction; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; + +public class VpnStateService extends Service +{ + private final List mListeners = new ArrayList(); + private final IBinder mBinder = new LocalBinder(); + private long mConnectionID = 0; + private Handler mHandler; + private VpnProfile mProfile; + private State mState = State.DISABLED; + private ErrorState mError = ErrorState.NO_ERROR; + private ImcState mImcState = ImcState.UNKNOWN; + private final LinkedList mRemediationInstructions = new LinkedList(); + + public enum State + { + DISABLED, + CONNECTING, + CONNECTED, + DISCONNECTING, + } + + public enum ErrorState + { + NO_ERROR, + AUTH_FAILED, + PEER_AUTH_FAILED, + LOOKUP_FAILED, + UNREACHABLE, + GENERIC_ERROR, + } + + /** + * Listener interface for bound clients that are interested in changes to + * this Service. + */ + public interface VpnStateListener + { + public void stateChanged(); + } + + /** + * Simple Binder that allows to directly access this Service class itself + * after binding to it. + */ + public class LocalBinder extends Binder + { + public VpnStateService getService() + { + return VpnStateService.this; + } + } + + @Override + public void onCreate() + { + /* this handler allows us to notify listeners from the UI thread and + * not from the threads that actually report any state changes */ + mHandler = new Handler(); + } + + @Override + public IBinder onBind(Intent intent) + { + return mBinder; + } + + @Override + public void onDestroy() + { + } + + /** + * Register a listener with this Service. We assume this is called from + * the main thread so no synchronization is happening. + * + * @param listener listener to register + */ + public void registerListener(VpnStateListener listener) + { + mListeners.add(listener); + } + + /** + * Unregister a listener from this Service. + * + * @param listener listener to unregister + */ + public void unregisterListener(VpnStateListener listener) + { + mListeners.remove(listener); + } + + /** + * Get the current VPN profile. + * + * @return profile + */ + public VpnProfile getProfile() + { /* only updated from the main thread so no synchronization needed */ + return mProfile; + } + + /** + * Get the current connection ID. May be used to track which state + * changes have already been handled. + * + * Is increased when startConnection() is called. + * + * @return connection ID + */ + public long getConnectionID() + { /* only updated from the main thread so no synchronization needed */ + return mConnectionID; + } + + /** + * Get the current state. + * + * @return state + */ + public State getState() + { /* only updated from the main thread so no synchronization needed */ + return mState; + } + + /** + * Get the current error, if any. + * + * @return error + */ + public ErrorState getErrorState() + { /* only updated from the main thread so no synchronization needed */ + return mError; + } + + /** + * Get the current IMC state, if any. + * + * @return imc state + */ + public ImcState getImcState() + { /* only updated from the main thread so no synchronization needed */ + return mImcState; + } + + /** + * Get the remediation instructions, if any. + * + * @return read-only list of instructions + */ + public List getRemediationInstructions() + { /* only updated from the main thread so no synchronization needed */ + return Collections.unmodifiableList(mRemediationInstructions); + } + + /** + * Disconnect any existing connection and shutdown the daemon, the + * VpnService is not stopped but it is reset so new connections can be + * started. + */ + public void disconnect() + { + /* as soon as the TUN device is created by calling establish() on the + * VpnService.Builder object the system binds to the service and keeps + * bound until the file descriptor of the TUN device is closed. thus + * calling stopService() here would not stop (destroy) the service yet, + * instead we call startService() with an empty Intent which shuts down + * the daemon (and closes the TUN device, if any) */ + Context context = getApplicationContext(); + Intent intent = new Intent(context, CharonVpnService.class); + context.startService(intent); + } + + /** + * Update state and notify all listeners about the change. By using a Handler + * this is done from the main UI thread and not the initial reporter thread. + * Also, in doing the actual state change from the main thread, listeners + * see all changes and none are skipped. + * + * @param change the state update to perform before notifying listeners, returns true if state changed + */ + private void notifyListeners(final Callable change) + { + mHandler.post(new Runnable() { + @Override + public void run() + { + try + { + if (change.call()) + { /* otherwise there is no need to notify the listeners */ + for (VpnStateListener listener : mListeners) + { + listener.stateChanged(); + } + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + } + + /** + * Called when a connection is started. Sets the currently active VPN + * profile, resets IMC and Error state variables, sets the State to + * CONNECTING, increases the connection ID, and notifies all listeners. + * + * May be called from threads other than the main thread. + * + * @param profile current profile + */ + public void startConnection(final VpnProfile profile) + { + notifyListeners(new Callable() { + @Override + public Boolean call() throws Exception + { + VpnStateService.this.mConnectionID++; + VpnStateService.this.mProfile = profile; + VpnStateService.this.mState = State.CONNECTING; + VpnStateService.this.mError = ErrorState.NO_ERROR; + VpnStateService.this.mImcState = ImcState.UNKNOWN; + VpnStateService.this.mRemediationInstructions.clear(); + return true; + } + }); + } + + /** + * Update the state and notify all listeners, if changed. + * + * May be called from threads other than the main thread. + * + * @param state new state + */ + public void setState(final State state) + { + notifyListeners(new Callable() { + @Override + public Boolean call() throws Exception + { + if (VpnStateService.this.mState != state) + { + VpnStateService.this.mState = state; + return true; + } + return false; + } + }); + } + + /** + * Set the current error state and notify all listeners, if changed. + * + * May be called from threads other than the main thread. + * + * @param error error state + */ + public void setError(final ErrorState error) + { + notifyListeners(new Callable() { + @Override + public Boolean call() throws Exception + { + if (VpnStateService.this.mError != error) + { + VpnStateService.this.mError = error; + return true; + } + return false; + } + }); + } + + /** + * Set the current IMC state and notify all listeners, if changed. + * + * Setting the state to UNKNOWN clears all remediation instructions. + * + * May be called from threads other than the main thread. + * + * @param error error state + */ + public void setImcState(final ImcState state) + { + notifyListeners(new Callable() { + @Override + public Boolean call() throws Exception + { + if (state == ImcState.UNKNOWN) + { + VpnStateService.this.mRemediationInstructions.clear(); + } + if (VpnStateService.this.mImcState != state) + { + VpnStateService.this.mImcState = state; + return true; + } + return false; + } + }); + } + + /** + * Add the given remediation instruction to the internal list. Listeners + * are not notified. + * + * Instructions are cleared if the IMC state is set to UNKNOWN. + * + * May be called from threads other than the main thread. + * + * @param instruction remediation instruction + */ + public void addRemediationInstruction(final RemediationInstruction instruction) + { + mHandler.post(new Runnable() { + @Override + public void run() + { + VpnStateService.this.mRemediationInstructions.add(instruction); + } + }); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/AndroidImc.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/AndroidImc.java new file mode 100644 index 000000000..351fab801 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/AndroidImc.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2013 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.logic.imc; + +import org.strongswan.android.logic.imc.attributes.Attribute; +import org.strongswan.android.logic.imc.attributes.AttributeType; +import org.strongswan.android.logic.imc.collectors.Collector; +import org.strongswan.android.logic.imc.collectors.DeviceIdCollector; +import org.strongswan.android.logic.imc.collectors.InstalledPackagesCollector; +import org.strongswan.android.logic.imc.collectors.PortFilterCollector; +import org.strongswan.android.logic.imc.collectors.ProductInformationCollector; +import org.strongswan.android.logic.imc.collectors.SettingsCollector; +import org.strongswan.android.logic.imc.collectors.StringVersionCollector; + +import android.content.Context; + +public class AndroidImc +{ + private final Context mContext; + + public AndroidImc(Context context) + { + mContext = context; + } + + /** + * Get a measurement (the binary encoding of the requested attribute) for + * the given vendor specific attribute type. + * + * @param vendor vendor ID + * @param type vendor specific attribute type + * @return encoded attribute, or null if not available or failed + */ + public byte[] getMeasurement(int vendor, int type) + { + return getMeasurement(vendor, type, null); + } + + /** + * Get a measurement (the binary encoding of the requested attribute) for + * the given vendor specific attribute type. + * + * @param vendor vendor ID + * @param type vendor specific attribute type + * @param args optional arguments for a measurement + * @return encoded attribute, or null if not available or failed + */ + public byte[] getMeasurement(int vendor, int type, String[] args) + { + AttributeType attributeType = AttributeType.fromValues(vendor, type); + Collector collector = null; + + switch (attributeType) + { + case IETF_PRODUCT_INFORMATION: + collector = new ProductInformationCollector(); + break; + case IETF_STRING_VERSION: + collector = new StringVersionCollector(); + break; + case IETF_PORT_FILTER: + collector = new PortFilterCollector(); + break; + case IETF_INSTALLED_PACKAGES: + collector = new InstalledPackagesCollector(mContext); + break; + case ITA_SETTINGS: + collector = new SettingsCollector(mContext, args); + break; + case ITA_DEVICE_ID: + collector = new DeviceIdCollector(mContext); + break; + default: + break; + } + if (collector != null) + { + Attribute attribute = collector.getMeasurement(); + if (attribute != null) + { + return attribute.getEncoding(); + } + } + return null; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/ImcState.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/ImcState.java new file mode 100644 index 000000000..4fc3834f9 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/ImcState.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013 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.logic.imc; + +public enum ImcState +{ + UNKNOWN(0), + ALLOW(1), + BLOCK(2), + ISOLATE(3); + + private final int mValue; + + private ImcState(int value) + { + mValue = value; + } + + /** + * Get the numeric value of the IMC state. + * @return numeric value + */ + public int getValue() + { + return mValue; + } + + /** + * Get the enum entry from a numeric value, if defined + * + * @param value numeric value + * @return the enum entry or null + */ + public static ImcState fromValue(int value) + { + for (ImcState state : ImcState.values()) + { + if (state.mValue == value) + { + return state; + } + } + return null; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/RemediationInstruction.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/RemediationInstruction.java new file mode 100644 index 000000000..5435ad88c --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/RemediationInstruction.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2013 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.logic.imc; + +import java.io.IOException; +import java.io.StringReader; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Xml; + +public class RemediationInstruction implements Parcelable +{ + private String mTitle; + private String mDescription; + private String mHeader; + private final List mItems = new LinkedList(); + + @Override + public int describeContents() + { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) + { + dest.writeString(mTitle); + dest.writeString(mDescription); + dest.writeString(mHeader); + dest.writeStringList(mItems); + } + + public static final Parcelable.Creator CREATOR = new Creator() { + + @Override + public RemediationInstruction[] newArray(int size) + { + return new RemediationInstruction[size]; + } + + @Override + public RemediationInstruction createFromParcel(Parcel source) + { + return new RemediationInstruction(source); + } + }; + + private RemediationInstruction() + { + } + + private RemediationInstruction(Parcel source) + { + mTitle = source.readString(); + mDescription = source.readString(); + mHeader = source.readString(); + source.readStringList(mItems); + } + + public String getTitle() + { + return mTitle; + } + + private void setTitle(String title) + { + mTitle = title; + } + + public String getDescription() + { + return mDescription; + } + + private void setDescription(String description) + { + mDescription = description; + } + + public String getHeader() + { + return mHeader; + } + + private void setHeader(String header) + { + mHeader = header; + } + + public List getItems() + { + return Collections.unmodifiableList(mItems); + } + + private void addItem(String item) + { + mItems.add(item); + } + + /** + * Create a list of RemediationInstruction objects from the given XML data. + * + * @param xml XML data + * @return list of RemediationInstruction objects + */ + public static List fromXml(String xml) + { + List instructions = new LinkedList(); + XmlPullParser parser = Xml.newPullParser(); + try + { + parser.setInput(new StringReader(xml)); + parser.nextTag(); + readInstructions(parser, instructions); + } + catch (XmlPullParserException e) + { + e.printStackTrace(); + } + catch (IOException e) + { + e.printStackTrace(); + } + return instructions; + } + + /** + * Read a <remediationinstructions> element and store the extracted + * RemediationInstruction objects in the given list. + * + * @param parser + * @param instructions + * @throws XmlPullParserException + * @throws IOException + */ + private static void readInstructions(XmlPullParser parser, List instructions) throws XmlPullParserException, IOException + { + parser.require(XmlPullParser.START_TAG, null, "remediationinstructions"); + while (parser.next() != XmlPullParser.END_TAG) + { + if (parser.getEventType() != XmlPullParser.START_TAG) + { + continue; + } + if (parser.getName().equals("instruction")) + { + RemediationInstruction instruction = new RemediationInstruction(); + readInstruction(parser, instruction); + instructions.add(instruction); + } + else + { + skipTag(parser); + } + } + } + + /** + * Read an <instruction> element and store the information in the + * given RemediationInstruction object. + * + * @param parser + * @param instruction + * @throws XmlPullParserException + * @throws IOException + */ + private static void readInstruction(XmlPullParser parser, RemediationInstruction instruction) throws XmlPullParserException, IOException + { + parser.require(XmlPullParser.START_TAG, null, "instruction"); + while (parser.next() != XmlPullParser.END_TAG) + { + if (parser.getEventType() != XmlPullParser.START_TAG) + { + continue; + } + String name = parser.getName(); + if (name.equals("title")) + { + instruction.setTitle(parser.nextText()); + } + else if (name.equals("description")) + { + instruction.setDescription(parser.nextText()); + } + else if (name.equals("itemsheader")) + { + instruction.setHeader(parser.nextText()); + } + else if (name.equals("items")) + { + readItems(parser, instruction); + } + else + { + skipTag(parser); + } + } + } + + /** + * Read all items of an <items> node and add them to the given + * RemediationInstruction object. + * + * @param parser + * @param instruction + * @throws XmlPullParserException + * @throws IOException + */ + private static void readItems(XmlPullParser parser, RemediationInstruction instruction) throws XmlPullParserException, IOException + { + while (parser.next() != XmlPullParser.END_TAG) + { + if (parser.getEventType() != XmlPullParser.START_TAG) + { + continue; + } + if (parser.getName().equals("item")) + { + instruction.addItem(parser.nextText()); + } + else + { + skipTag(parser); + } + } + } + + /** + * Skip the current tag and all child elements. + * + * @param parser + * @throws XmlPullParserException + * @throws IOException + */ + private static void skipTag(XmlPullParser parser) throws XmlPullParserException, IOException + { + int depth = 1; + + parser.require(XmlPullParser.START_TAG, null, null); + while (depth != 0) + { + switch (parser.next()) + { + case XmlPullParser.END_TAG: + depth--; + break; + case XmlPullParser.START_TAG: + depth++; + break; + } + } + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/Attribute.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/Attribute.java new file mode 100644 index 000000000..ca759000f --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/Attribute.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2013 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.logic.imc.attributes; + +/** + * Interface to be implemented by attribute classes + */ +public interface Attribute +{ + /** + * Returns the binary encoding of the attribute + * @return binary encoding + */ + public byte[] getEncoding(); +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/AttributeType.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/AttributeType.java new file mode 100644 index 000000000..11f1c61da --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/AttributeType.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2013 Tobias Brunner + * Copyright (C) 2012 Christoph Buehler + * Copyright (C) 2012 Patrick Loetscher + * 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.logic.imc.attributes; + +public enum AttributeType +{ + /* IETF standard PA-TNC attribute types defined by RFC 5792 */ + IETF_TESTING(PrivateEnterpriseNumber.IETF, 0), + IETF_ATTRIBUTE_REQUEST(PrivateEnterpriseNumber.IETF, 1), + IETF_PRODUCT_INFORMATION(PrivateEnterpriseNumber.IETF, 2), + IETF_NUMERIC_VERSION(PrivateEnterpriseNumber.IETF, 3), + IETF_STRING_VERSION(PrivateEnterpriseNumber.IETF, 4), + IETF_OPERATIONAL_STATUS(PrivateEnterpriseNumber.IETF, 5), + IETF_PORT_FILTER(PrivateEnterpriseNumber.IETF, 6), + IETF_INSTALLED_PACKAGES(PrivateEnterpriseNumber.IETF, 7), + IETF_PA_TNC_ERROR(PrivateEnterpriseNumber.IETF, 8), + IETF_ASSESSMENT_RESULT(PrivateEnterpriseNumber.IETF, 9), + IETF_REMEDIATION_INSTRUCTIONS(PrivateEnterpriseNumber.IETF, 10), + IETF_FORWARDING_ENABLED(PrivateEnterpriseNumber.IETF, 11), + IETF_FACTORY_DEFAULT_PWD_ENABLED(PrivateEnterpriseNumber.IETF, 12), + IETF_RESERVED(PrivateEnterpriseNumber.IETF, 0xffffffff), + /* ITA attributes */ + ITA_SETTINGS(PrivateEnterpriseNumber.ITA, 4), + ITA_DEVICE_ID(PrivateEnterpriseNumber.ITA, 8); + + private PrivateEnterpriseNumber mVendor; + private int mType; + + /** + * Enum type for vendor specific attributes (defined in their namespace) + * + * @param vendor private enterprise number of vendor + * @param type vendor specific attribute type + */ + private AttributeType(PrivateEnterpriseNumber vendor, int type) + { + mVendor = vendor; + mType = type; + } + + /** + * Get private enterprise number of vendor + * + * @return PEN + */ + public PrivateEnterpriseNumber getVendor() + { + return mVendor; + } + + /** + * Get vendor specific type + * + * @return type + */ + public int getType() + { + return mType; + } + + /** + * Get the enum entry from the given numeric values, if defined + * + * @param vendor vendor id + * @param type vendor specific type + * @return enum entry or null + */ + public static AttributeType fromValues(int vendor, int type) + { + PrivateEnterpriseNumber pen = PrivateEnterpriseNumber.fromValue(vendor); + + if (pen == null) + { + return null; + } + for (AttributeType attr : AttributeType.values()) + { + if (attr.mVendor == pen && attr.mType == type) + { + return attr; + } + } + return null; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/DeviceIdAttribute.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/DeviceIdAttribute.java new file mode 100644 index 000000000..ecab7db24 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/DeviceIdAttribute.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 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.logic.imc.attributes; + +/** + * ITA Device ID attribute + * + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Device ID (Variable Length) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class DeviceIdAttribute implements Attribute +{ + private String mDeviceId; + + /** + * Set the device ID + * @param version version number + */ + public void setDeviceId(String deviceId) + { + this.mDeviceId = deviceId; + } + + @Override + public byte[] getEncoding() + { + return mDeviceId.getBytes(); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/InstalledPackagesAttribute.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/InstalledPackagesAttribute.java new file mode 100644 index 000000000..dd1ad7292 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/InstalledPackagesAttribute.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2013 Tobias Brunner + * Copyright (C) 2012 Christoph Buehler + * Copyright (C) 2012 Patrick Loetscher + * 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.logic.imc.attributes; + +import java.util.LinkedList; + +import org.strongswan.android.utils.BufferedByteWriter; + +import android.util.Pair; + +/** + * PA-TNC Installed Packages attribute (see section 4.2.7 of RFC 5792) + * + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | Package Count | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Pkg Name Len | Package Name (Variable Length) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Version Len | Package Version Number (Variable Length) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class InstalledPackagesAttribute implements Attribute +{ + private final short RESERVED = 0; + private final LinkedList> mPackages = new LinkedList>(); + + /** + * Add an installed package to this attribute. + * @param name name of the package + * @param version version number of the package + */ + public void addPackage(String name, String version) + { + mPackages.add(new Pair(name, version)); + } + + @Override + public byte[] getEncoding() + { + BufferedByteWriter writer = new BufferedByteWriter(); + writer.put16(RESERVED); + writer.put16((short)mPackages.size()); + for (Pair pair : mPackages) + { + writer.putLen8(pair.first.getBytes()); + writer.putLen8(pair.second.getBytes()); + } + return writer.toByteArray(); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/PortFilterAttribute.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/PortFilterAttribute.java new file mode 100644 index 000000000..191690b94 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/PortFilterAttribute.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2013 Tobias Brunner + * Copyright (C) 2012 Christoph Buehler + * Copyright (C) 2012 Patrick Loetscher + * 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.logic.imc.attributes; + +import java.util.LinkedList; + +import org.strongswan.android.logic.imc.collectors.Protocol; +import org.strongswan.android.utils.BufferedByteWriter; + +import android.util.Pair; + +/** + * PA-TNC Port Filter attribute (see section 4.2.6 of RFC 5792) + * + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved |B| Protocol | Port Number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved |B| Protocol | Port Number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class PortFilterAttribute implements Attribute +{ + private final LinkedList> mPorts = new LinkedList>(); + + /** + * Add an open port with the given protocol and port number + * @param protocol transport protocol + * @param port port number + */ + public void addPort(Protocol protocol, short port) + { + mPorts.add(new Pair(protocol, port)); + } + + @Override + public byte[] getEncoding() + { + BufferedByteWriter writer = new BufferedByteWriter(); + for (Pair port : mPorts) + { + /* we report open ports, so the BLOCKED flag is not set */ + writer.put((byte)0); + writer.put(port.first.getValue()); + writer.put16(port.second); + } + return writer.toByteArray(); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/PrivateEnterpriseNumber.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/PrivateEnterpriseNumber.java new file mode 100644 index 000000000..9db702ec0 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/PrivateEnterpriseNumber.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2013 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.logic.imc.attributes; + +public enum PrivateEnterpriseNumber +{ + IETF(0x000000), + GOOGLE(0x002B79), + ITA(0x00902a), + UNASSIGNED(0xfffffe), + RESERVED(0xffffff); + + private int mValue; + + /** + * Enum for private enterprise numbers (PEN) as allocated by IANA + * + * @param value numeric value + */ + private PrivateEnterpriseNumber(int value) + { + mValue = value; + } + + /** + * Get the numeric value of a PEN + * + * @return numeric value + */ + public int getValue() + { + return mValue; + } + + /** + * Get the enum entry from a numeric value, if defined + * + * @param value numeric value + * @return the enum entry or null + */ + public static PrivateEnterpriseNumber fromValue(int value) + { + for (PrivateEnterpriseNumber pen : PrivateEnterpriseNumber.values()) + { + if (pen.mValue == value) + { + return pen; + } + } + return null; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/ProductInformationAttribute.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/ProductInformationAttribute.java new file mode 100644 index 000000000..cace18d55 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/ProductInformationAttribute.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013 Tobias Brunner + * Copyright (C) 2012 Christoph Buehler + * Copyright (C) 2012 Patrick Loetscher + * 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.logic.imc.attributes; + +import org.strongswan.android.utils.BufferedByteWriter; + +/** + * PA-TNC Product Information attribute (see section 4.2.2 of RFC 5792) + * + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Product Vendor ID | Product ID | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Product ID | Product Name (Variable Length) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class ProductInformationAttribute implements Attribute +{ + private final String PRODUCT_NAME = "Android"; + private final short PRODUCT_ID = 0; + + @Override + public byte[] getEncoding() + { + BufferedByteWriter writer = new BufferedByteWriter(); + writer.put24(PrivateEnterpriseNumber.GOOGLE.getValue()); + writer.put16(PRODUCT_ID); + writer.put(PRODUCT_NAME.getBytes()); + return writer.toByteArray(); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/SettingsAttribute.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/SettingsAttribute.java new file mode 100644 index 000000000..37d820168 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/SettingsAttribute.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2013 Tobias Brunner + * Copyright (C) 2012 Christoph Buehler + * Copyright (C) 2012 Patrick Loetscher + * 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.logic.imc.attributes; + +import java.util.LinkedList; + +import org.strongswan.android.utils.BufferedByteWriter; + +import android.util.Pair; + +/** + * ITA Settings attribute + * + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Settings Count | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Name Length | Name (Variable Length) ~ + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ~ Name (Variable Length) ~ + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Value Length | Value (Variable Length) ~ + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ~ Value (Variable Length) ~ + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Name Length | Name (Variable Length) ~ + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ~ Name (Variable Length) ~ + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Value Length | Value (Variable Length) ~ + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ~ Value (Variable Length) ~ + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * ........................... + */ +public class SettingsAttribute implements Attribute +{ + private final LinkedList> mSettings = new LinkedList>(); + + /** + * Add a setting to this attribute. + * @param name name of the setting + * @param value value of the setting + */ + public void addSetting(String name, String value) + { + mSettings.add(new Pair(name, value)); + } + + @Override + public byte[] getEncoding() + { + BufferedByteWriter writer = new BufferedByteWriter(); + writer.put32(mSettings.size()); + for (Pair pair : mSettings) + { + writer.putLen16(pair.first.getBytes()); + writer.putLen16(pair.second.getBytes()); + } + return writer.toByteArray(); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/StringVersionAttribute.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/StringVersionAttribute.java new file mode 100644 index 000000000..4b6f2bc37 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/attributes/StringVersionAttribute.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2013 Tobias Brunner + * Copyright (C) 2012 Christoph Buehler + * Copyright (C) 2012 Patrick Loetscher + * 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.logic.imc.attributes; + +import org.strongswan.android.utils.BufferedByteWriter; + +/** + * PA-TNC String Version attribute (see section 4.2.4 of RFC 5792) + * + * 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Version Len | Product Version Number (Variable Length) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Build Num Len | Internal Build Number (Variable Length) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Config. Len | Configuration Version Number (Variable Length)| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +public class StringVersionAttribute implements Attribute +{ + private String mVersionNumber; + private String mBuildNumber; + + /** + * Set the product version number + * @param version version number + */ + public void setProductVersionNumber(String version) + { + this.mVersionNumber = version; + } + + /** + * Set the internal build number + * @param build build number + */ + public void setInternalBuildNumber(String build) + { + this.mBuildNumber = build; + } + + @Override + public byte[] getEncoding() + { + BufferedByteWriter writer = new BufferedByteWriter(); + writer.putLen8(mVersionNumber.getBytes()); + writer.putLen8(mBuildNumber.getBytes()); + /* we don't provide a configuration number */ + writer.put((byte)0); + return writer.toByteArray(); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/Collector.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/Collector.java new file mode 100644 index 000000000..a686f13a1 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/Collector.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 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.logic.imc.collectors; + +import org.strongswan.android.logic.imc.attributes.Attribute; + +/** + * Interface for measurement collectors + */ +public interface Collector +{ + /** + * This method shall return the result of a measurement, if available + * @return attribute or null + */ + public abstract Attribute getMeasurement(); +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/DeviceIdCollector.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/DeviceIdCollector.java new file mode 100644 index 000000000..ebe9e10b0 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/DeviceIdCollector.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 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.logic.imc.collectors; + +import org.strongswan.android.logic.imc.attributes.Attribute; +import org.strongswan.android.logic.imc.attributes.DeviceIdAttribute; + +import android.content.ContentResolver; +import android.content.Context; + +public class DeviceIdCollector implements Collector +{ + private final ContentResolver mContentResolver; + + public DeviceIdCollector(Context context) + { + mContentResolver = context.getContentResolver(); + } + + @Override + public Attribute getMeasurement() + { + String id = android.provider.Settings.Secure.getString(mContentResolver, "android_id"); + if (id != null) + { + DeviceIdAttribute attribute = new DeviceIdAttribute(); + attribute.setDeviceId(id); + return attribute; + } + return null; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/InstalledPackagesCollector.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/InstalledPackagesCollector.java new file mode 100644 index 000000000..caa5170ec --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/InstalledPackagesCollector.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2013 Tobias Brunner + * Copyright (C) 2012 Christoph Buehler + * Copyright (C) 2012 Patrick Loetscher + * 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.logic.imc.collectors; + +import java.util.List; + +import org.strongswan.android.logic.imc.attributes.Attribute; +import org.strongswan.android.logic.imc.attributes.InstalledPackagesAttribute; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +public class InstalledPackagesCollector implements Collector +{ + private final PackageManager mPackageManager; + + public InstalledPackagesCollector(Context context) + { + mPackageManager = context.getPackageManager(); + } + + @Override + public Attribute getMeasurement() + { + InstalledPackagesAttribute attribute = new InstalledPackagesAttribute(); + List packages = mPackageManager.getInstalledPackages(0); + for (PackageInfo info : packages) + { + if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 || + info.packageName == null || info.versionName == null) + { /* ignore packages installed in the system image */ + continue; + } + attribute.addPackage(info.packageName, info.versionName); + } + return attribute; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/PortFilterCollector.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/PortFilterCollector.java new file mode 100644 index 000000000..ed86686d8 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/PortFilterCollector.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2013 Tobias Brunner + * Copyright (C) 2012 Christoph Buehler + * Copyright (C) 2012 Patrick Loetscher + * 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.logic.imc.collectors; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.strongswan.android.logic.imc.attributes.Attribute; +import org.strongswan.android.logic.imc.attributes.PortFilterAttribute; + +public class PortFilterCollector implements Collector +{ + private static Pattern LISTEN = Pattern.compile("\\bLISTEN\\b"); + private static Pattern PROTOCOL = Pattern.compile("\\b(tcp|udp)6?\\b"); + private static Pattern PORT = Pattern.compile("[:]{1,3}(\\d{1,5})\\b(?!\\.)"); + + @Override + public Attribute getMeasurement() + { + PortFilterAttribute attribute = null; + try + { + Process netstat = Runtime.getRuntime().exec("netstat -n"); + try + { + BufferedReader reader = new BufferedReader(new InputStreamReader(netstat.getInputStream())); + String line; + attribute = new PortFilterAttribute(); + while ((line = reader.readLine()) != null) + { + if (!LISTEN.matcher(line).find()) + { + continue; + } + Matcher protocolMatcher = PROTOCOL.matcher(line); + Matcher portMatcher = PORT.matcher(line); + if (protocolMatcher.find() && portMatcher.find()) + { + Protocol protocol = Protocol.fromName(protocolMatcher.group()); + if (protocol == null) + { + continue; + } + int port = Integer.parseInt(portMatcher.group(1)); + attribute.addPort(protocol, (short)port); + } + } + } + finally + { + netstat.destroy(); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + return attribute; + } + +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/ProductInformationCollector.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/ProductInformationCollector.java new file mode 100644 index 000000000..c377e9041 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/ProductInformationCollector.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 Tobias Brunner + * Copyright (C) 2012 Christoph Buehler + * Copyright (C) 2012 Patrick Loetscher + * 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.logic.imc.collectors; + +import org.strongswan.android.logic.imc.attributes.Attribute; +import org.strongswan.android.logic.imc.attributes.ProductInformationAttribute; + +public class ProductInformationCollector implements Collector +{ + @Override + public Attribute getMeasurement() + { /* this is currently hardcoded in the attribute */ + return new ProductInformationAttribute(); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/Protocol.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/Protocol.java new file mode 100644 index 000000000..7320652a1 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/Protocol.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 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.logic.imc.collectors; + +public enum Protocol +{ + TCP((byte)6, "tcp", "tcp6"), + UDP((byte)17, "udp", "udp6"); + + private final byte mValue; + private String[] mNames; + + private Protocol(byte value, String... names) + { + mValue = value; + mNames = names; + } + + /** + * Get the numeric value of the protocol. + * @return numeric value + */ + public byte getValue() + { + return mValue; + } + + /** + * Get the protocol from the given protocol name, if found. + * @param name protocol name (e.g. "udp" or "tcp") + * @return enum entry or null + */ + public static Protocol fromName(String name) + { + for (Protocol protocol : Protocol.values()) + { + for (String keyword : protocol.mNames) + { + if (keyword.equalsIgnoreCase(name)) + { + return protocol; + } + } + } + return null; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/SettingsCollector.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/SettingsCollector.java new file mode 100644 index 000000000..658c2daea --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/SettingsCollector.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013 Tobias Brunner + * Copyright (C) 2012 Christoph Buehler + * Copyright (C) 2012 Patrick Loetscher + * 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.logic.imc.collectors; + +import java.util.Locale; + +import org.strongswan.android.logic.imc.attributes.Attribute; +import org.strongswan.android.logic.imc.attributes.SettingsAttribute; + +import android.content.ContentResolver; +import android.content.Context; + +public class SettingsCollector implements Collector +{ + private final ContentResolver mContentResolver; + private final String[] mSettings; + + public SettingsCollector(Context context, String[] args) + { + mContentResolver = context.getContentResolver(); + mSettings = args; + } + + @Override + public Attribute getMeasurement() + { + if (mSettings == null || mSettings.length == 0) + { + return null; + } + SettingsAttribute attribute = new SettingsAttribute(); + for (String name : mSettings) + { + String value = android.provider.Settings.Secure.getString(mContentResolver, name.toLowerCase(Locale.US)); + if (value == null) + { + value = android.provider.Settings.System.getString(mContentResolver, name.toLowerCase(Locale.US)); + } + if (value != null) + { + attribute.addSetting(name, value); + } + } + return attribute; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/StringVersionCollector.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/StringVersionCollector.java new file mode 100644 index 000000000..6e0df94a5 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/imc/collectors/StringVersionCollector.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2013 Tobias Brunner + * Copyright (C) 2012 Christoph Buehler + * Copyright (C) 2012 Patrick Loetscher + * 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.logic.imc.collectors; + +import org.strongswan.android.logic.imc.attributes.Attribute; +import org.strongswan.android.logic.imc.attributes.StringVersionAttribute; + +public class StringVersionCollector implements Collector +{ + @Override + public Attribute getMeasurement() + { + StringVersionAttribute attribute = new StringVersionAttribute(); + attribute.setProductVersionNumber(android.os.Build.VERSION.RELEASE); + attribute.setInternalBuildNumber(android.os.Build.DISPLAY); + return attribute; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/security/LocalCertificateKeyStoreProvider.java b/src/frontends/android/app/src/main/java/org/strongswan/android/security/LocalCertificateKeyStoreProvider.java new file mode 100644 index 000000000..c49b1044f --- /dev/null +++ b/src/frontends/android/app/src/main/java/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()); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/security/LocalCertificateKeyStoreSpi.java b/src/frontends/android/app/src/main/java/org/strongswan/android/security/LocalCertificateKeyStoreSpi.java new file mode 100644 index 000000000..64a48a9bb --- /dev/null +++ b/src/frontends/android/app/src/main/java/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(); + } + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/security/LocalCertificateStore.java b/src/frontends/android/app/src/main/java/org/strongswan/android/security/LocalCertificateStore.java new file mode 100644 index 000000000..cec5c603d --- /dev/null +++ b/src/frontends/android/app/src/main/java/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; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java b/src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java new file mode 100644 index 000000000..143741faf --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2012 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.cert.X509Certificate; + +import android.net.http.SslCertificate; + +public class TrustedCertificateEntry implements Comparable +{ + private final X509Certificate mCert; + private final String mAlias; + private String mSubjectPrimary; + private String mSubjectSecondary = ""; + private String mString; + + /** + * Create an entry for certificate lists. + * + * @param alias alias of the certificate (as used in the KeyStore) + * @param cert certificate associated with that alias + */ + public TrustedCertificateEntry(String alias, X509Certificate cert) + { + mCert = cert; + mAlias = alias; + + SslCertificate ssl = new SslCertificate(mCert); + String o = ssl.getIssuedTo().getOName(); + String ou = ssl.getIssuedTo().getUName(); + String cn = ssl.getIssuedTo().getCName(); + if (!o.isEmpty()) + { + mSubjectPrimary = o; + if (!cn.isEmpty()) + { + mSubjectSecondary = cn; + } + else if (!ou.isEmpty()) + { + mSubjectSecondary = ou; + } + } + else if (!cn.isEmpty()) + { + mSubjectPrimary = cn; + } + else + { + mSubjectPrimary = ssl.getIssuedTo().getDName(); + } + } + + /** + * The main subject of this certificate (O, CN or the complete DN, whatever + * is found first). + * + * @return the main subject + */ + public String getSubjectPrimary() + { + return mSubjectPrimary; + } + + /** + * Get the secondary subject of this certificate (either CN or OU if primary + * subject is O, empty otherwise) + * + * @return the secondary subject + */ + public String getSubjectSecondary() + { + return mSubjectSecondary; + } + + /** + * The alias associated with this certificate. + * + * @return KeyStore alias of this certificate + */ + public String getAlias() + { + return mAlias; + } + + /** + * The certificate. + * + * @return certificate + */ + public X509Certificate getCertificate() + { + return mCert; + } + + @Override + public String toString() + { /* combination of both subject lines, used for filtering lists */ + if (mString == null) + { + mString = mSubjectPrimary; + if (!mSubjectSecondary.isEmpty()) + { + mString += ", " + mSubjectSecondary; + } + } + return mString; + } + + @Override + public int compareTo(TrustedCertificateEntry another) + { + int diff = mSubjectPrimary.compareToIgnoreCase(another.mSubjectPrimary); + if (diff == 0) + { + diff = mSubjectSecondary.compareToIgnoreCase(another.mSubjectSecondary); + } + return diff; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java new file mode 100644 index 000000000..c381900c6 --- /dev/null +++ b/src/frontends/android/app/src/main/java/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/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java new file mode 100644 index 000000000..5b1799744 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2013 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.util.ArrayList; + +import org.strongswan.android.R; +import org.strongswan.android.logic.VpnStateService; +import org.strongswan.android.logic.VpnStateService.VpnStateListener; +import org.strongswan.android.logic.imc.ImcState; +import org.strongswan.android.logic.imc.RemediationInstruction; + +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnTouchListener; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class ImcStateFragment extends Fragment implements VpnStateListener +{ + private TextView mStateView; + private TextView mAction; + private LinearLayout mButton; + private VpnStateService mService; + private final ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceDisconnected(ComponentName name) + { + mService = null; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) + { + mService = ((VpnStateService.LocalBinder)service).getService(); + mService.registerListener(ImcStateFragment.this); + updateView(); + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + /* bind to the service only seems to work from the ApplicationContext */ + Context context = getActivity().getApplicationContext(); + context.bindService(new Intent(context, VpnStateService.class), + mServiceConnection, Service.BIND_AUTO_CREATE); + /* hide it initially */ + getFragmentManager().beginTransaction().hide(this).commit(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) + { + View view = inflater.inflate(R.layout.imc_state_fragment, container, false); + + mButton = (LinearLayout)view.findViewById(R.id.imc_state_button); + mButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) + { + Intent intent; + if (mService != null && !mService.getRemediationInstructions().isEmpty()) + { + intent = new Intent(getActivity(), RemediationInstructionsActivity.class); + intent.putParcelableArrayListExtra(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS, + new ArrayList(mService.getRemediationInstructions())); + } + else + { + intent = new Intent(getActivity(), LogActivity.class); + } + startActivity(intent); + } + }); + final GestureDetector gestures = new GestureDetector(getActivity(), new GestureDetector.SimpleOnGestureListener() { + /* a better value would be getScaledTouchExplorationTapSlop() but that is hidden */ + private final int mMinDistance = ViewConfiguration.get(getActivity()).getScaledTouchSlop() * 4; + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) + { + if (Math.abs(e1.getX() - e2.getX()) >= mMinDistance) + { /* only if the user swiped a minimum horizontal distance */ + if (mService != null) + { + mService.setImcState(ImcState.UNKNOWN); + } + return true; + } + return false; + } + }); + mButton.setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) + { + return gestures.onTouchEvent(event); + } + }); + + mStateView = (TextView)view.findViewById(R.id.imc_state); + mAction = (TextView)view.findViewById(R.id.action); + + return view; + } + + @Override + public void onStart() + { + super.onStart(); + if (mService != null) + { + mService.registerListener(this); + updateView(); + } + } + + @Override + public void onStop() + { + super.onStop(); + if (mService != null) + { + mService.unregisterListener(this); + } + } + + @Override + public void onDestroy() + { + super.onDestroy(); + if (mService != null) + { + getActivity().getApplicationContext().unbindService(mServiceConnection); + } + } + + @Override + public void stateChanged() + { + updateView(); + } + + public void updateView() + { + FragmentManager fm = getFragmentManager(); + if (fm == null) + { + return; + } + FragmentTransaction ft = fm.beginTransaction(); + + switch (mService.getImcState()) + { + case UNKNOWN: + case ALLOW: + ft.hide(this); + break; + case ISOLATE: + mStateView.setText(R.string.imc_state_isolate); + mStateView.setTextColor(getResources().getColor(R.color.warning_text)); + ft.show(this); + break; + case BLOCK: + mStateView.setText(R.string.imc_state_block); + mStateView.setTextColor(getResources().getColor(R.color.error_text)); + ft.show(this); + break; + } + ft.commit(); + + mAction.setText(mService.getRemediationInstructions().isEmpty() ? R.string.show_log + : R.string.show_remediation_instructions); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogActivity.java new file mode 100644 index 000000000..a5efecc09 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogActivity.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2012 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.File; + +import org.strongswan.android.R; +import org.strongswan.android.data.LogContentProvider; +import org.strongswan.android.logic.CharonVpnService; + +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Toast; + +public class LogActivity extends Activity +{ + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.log_activity); + + getActionBar().setDisplayHomeAsUpEnabled(true); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + getMenuInflater().inflate(R.menu.log, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case android.R.id.home: + finish(); + return true; + case R.id.menu_send_log: + File logfile = new File(getFilesDir(), CharonVpnService.LOG_FILE); + if (!logfile.exists() || logfile.length() == 0) + { + Toast.makeText(this, getString(R.string.empty_log), Toast.LENGTH_SHORT).show(); + return true; + } + + String version = ""; + try + { + version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; + } + catch (NameNotFoundException e) + { + e.printStackTrace(); + } + + Intent intent = new Intent(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_EMAIL, new String[] { MainActivity.CONTACT_EMAIL }); + intent.putExtra(Intent.EXTRA_SUBJECT, String.format(getString(R.string.log_mail_subject), version)); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_STREAM, LogContentProvider.createContentUri()); + startActivity(Intent.createChooser(intent, getString(R.string.send_log))); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java new file mode 100644 index 000000000..8740e0c46 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2012 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.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.StringReader; + +import org.strongswan.android.R; +import org.strongswan.android.logic.CharonVpnService; + +import android.app.Fragment; +import android.os.Bundle; +import android.os.FileObserver; +import android.os.Handler; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +public class LogFragment extends Fragment implements Runnable +{ + private String mLogFilePath; + private Handler mLogHandler; + private TextView mLogView; + private LogScrollView mScrollView; + private BufferedReader mReader; + private Thread mThread; + private volatile boolean mRunning; + private FileObserver mDirectoryObserver; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + mLogFilePath = getActivity().getFilesDir() + File.separator + CharonVpnService.LOG_FILE; + /* use a handler to update the log view */ + mLogHandler = new Handler(); + + mDirectoryObserver = new LogDirectoryObserver(getActivity().getFilesDir().getAbsolutePath()); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + { + View view = inflater.inflate(R.layout.log_fragment, null); + mLogView = (TextView)view.findViewById(R.id.log_view); + mScrollView = (LogScrollView)view.findViewById(R.id.scroll_view); + return view; + } + + @Override + public void onStart() + { + super.onStart(); + startLogReader(); + mDirectoryObserver.startWatching(); + } + + @Override + public void onStop() + { + super.onStop(); + mDirectoryObserver.stopWatching(); + stopLogReader(); + } + + /** + * Start reading from the log file + */ + private void startLogReader() + { + try + { + mReader = new BufferedReader(new FileReader(mLogFilePath)); + } + catch (FileNotFoundException e) + { + mReader = new BufferedReader(new StringReader("")); + } + + mLogView.setText(""); + mRunning = true; + mThread = new Thread(this); + mThread.start(); + } + + /** + * Stop reading from the log file + */ + private void stopLogReader() + { + try + { + mRunning = false; + mThread.interrupt(); + mThread.join(); + } + catch (InterruptedException e) + { + } + } + + /** + * Write the given log line to the TextView. We strip the prefix off to save + * some space (it is not that helpful for regular users anyway). + * + * @param line log line to log + */ + public void logLine(final String line) + { + mLogHandler.post(new Runnable() { + @Override + public void run() + { + /* strip off prefix (month=3, day=2, time=8, thread=2, spaces=3) */ + mLogView.append((line.length() > 18 ? line.substring(18) : line) + '\n'); + /* calling autoScroll() directly does not work, probably because content + * is not yet updated, so we post this to be done later */ + mScrollView.post(new Runnable() { + @Override + public void run() + { + mScrollView.autoScroll(); + } + }); + } + }); + } + + @Override + public void run() + { + while (mRunning) + { + try + { /* this works as long as the file is not truncated */ + String line = mReader.readLine(); + if (line == null) + { /* wait until there is more to log */ + Thread.sleep(1000); + } + else + { + logLine(line); + } + } + catch (Exception e) + { + break; + } + } + } + + /** + * FileObserver that checks for changes regarding the log file. Since charon + * truncates it (for which there is no explicit event) we check for any modification + * to the file, keep track of the file size and reopen it if it got smaller. + */ + private class LogDirectoryObserver extends FileObserver + { + private final File mFile; + private long mSize; + + public LogDirectoryObserver(String path) + { + super(path, FileObserver.CREATE | FileObserver.MODIFY | FileObserver.DELETE); + mFile = new File(mLogFilePath); + mSize = mFile.length(); + } + + @Override + public void onEvent(int event, String path) + { + if (path == null || !path.equals(CharonVpnService.LOG_FILE)) + { + return; + } + switch (event) + { /* even though we only subscribed for these we check them, + * as strange events are sometimes received */ + case FileObserver.CREATE: + case FileObserver.DELETE: + restartLogReader(); + break; + case FileObserver.MODIFY: + /* if the size got smaller reopen the log file, as it was probably truncated */ + long size = mFile.length(); + if (size < mSize) + { + restartLogReader(); + } + mSize = size; + break; + } + } + + private void restartLogReader() + { + /* we are called from a separate thread, so we use the handler */ + mLogHandler.post(new Runnable() { + @Override + public void run() + { + stopLogReader(); + startLogReader(); + } + }); + } + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogScrollView.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogScrollView.java new file mode 100644 index 000000000..7eee820ce --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogScrollView.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ScrollView; + +public class LogScrollView extends ScrollView +{ + private boolean mAutoScroll = true; + + public LogScrollView(Context context) + { + super(context); + } + + public LogScrollView(Context context, AttributeSet attrs) + { + super(context, attrs); + } + + public LogScrollView(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) + { + /* disable auto-scrolling when the user starts scrolling around */ + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) + { + mAutoScroll = false; + } + return super.onTouchEvent(ev); + } + + /** + * Call this to move newly added content into view by scrolling to the bottom. + * Nothing happens if auto-scrolling is disabled. + */ + public void autoScroll() + { + if (mAutoScroll) + { + fullScroll(View.FOCUS_DOWN); + } + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) + { + super.onScrollChanged(l, t, oldl, oldt); + /* if the user scrolls to the bottom we enable auto-scrolling again */ + if (t == getChildAt(getChildCount() - 1).getHeight() - getHeight()) + { + mAutoScroll = true; + } + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java new file mode 100644 index 000000000..e1b3e0783 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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 org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.data.VpnType.VpnTypeFeature; +import org.strongswan.android.logic.CharonVpnService; +import org.strongswan.android.logic.TrustedCertificateManager; +import org.strongswan.android.logic.VpnStateService; +import org.strongswan.android.logic.VpnStateService.State; +import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener; + +import android.app.ActionBar; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.app.Service; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.net.VpnService; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.IBinder; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.widget.EditText; +import android.widget.Toast; + +public class MainActivity extends Activity implements OnVpnProfileSelectedListener +{ + public static final String CONTACT_EMAIL = "android@strongswan.org"; + public static final String START_PROFILE = "org.strongswan.android.action.START_PROFILE"; + public static final String EXTRA_VPN_PROFILE_ID = "org.strongswan.android.VPN_PROFILE_ID"; + /** Use "bring your own device" (BYOD) features */ + public static final boolean USE_BYOD = true; + private static final int PREPARE_VPN_SERVICE = 0; + private static final String PROFILE_NAME = "org.strongswan.android.MainActivity.PROFILE_NAME"; + private static final String PROFILE_REQUIRES_PASSWORD = "org.strongswan.android.MainActivity.REQUIRES_PASSWORD"; + private static final String PROFILE_RECONNECT = "org.strongswan.android.MainActivity.RECONNECT"; + private static final String DIALOG_TAG = "Dialog"; + + private Bundle mProfileInfo; + private VpnStateService mService; + private final ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceDisconnected(ComponentName name) + { + mService = null; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) + { + mService = ((VpnStateService.LocalBinder)service).getService(); + + if (START_PROFILE.equals(getIntent().getAction())) + { + startVpnProfile(getIntent()); + } + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + setContentView(R.layout.main); + + this.bindService(new Intent(this, VpnStateService.class), + mServiceConnection, Service.BIND_AUTO_CREATE); + + ActionBar bar = getActionBar(); + bar.setDisplayShowTitleEnabled(false); + + /* load CA certificates in a background task */ + new LoadCertificatesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + @Override + protected void onDestroy() + { + super.onDestroy(); + if (mService != null) + { + this.unbindService(mServiceConnection); + } + } + + /** + * Due to launchMode=singleTop this is called if the Activity already exists + */ + @Override + protected void onNewIntent(Intent intent) + { + super.onNewIntent(intent); + + if (START_PROFILE.equals(intent.getAction())) + { + startVpnProfile(intent); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case R.id.menu_manage_certs: + Intent certIntent = new Intent(this, TrustedCertificatesActivity.class); + startActivity(certIntent); + return true; + case R.id.menu_show_log: + Intent logIntent = new Intent(this, LogActivity.class); + startActivity(logIntent); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + /** + * Prepare the VpnService. If this succeeds the current VPN profile is + * started. + * @param profileInfo a bundle containing the information about the profile to be started + */ + protected void prepareVpnService(Bundle profileInfo) + { + Intent intent; + try + { + intent = VpnService.prepare(this); + } + catch (IllegalStateException ex) + { + /* this happens if the always-on VPN feature (Android 4.2+) is activated */ + VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_during_lockdown); + return; + } + /* store profile info until the user grants us permission */ + mProfileInfo = profileInfo; + if (intent != null) + { + try + { + startActivityForResult(intent, PREPARE_VPN_SERVICE); + } + catch (ActivityNotFoundException ex) + { + /* it seems some devices, even though they come with Android 4, + * don't have the VPN components built into the system image. + * com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog + * will not be found then */ + VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported); + } + } + else + { /* user already granted permission to use VpnService */ + onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + switch (requestCode) + { + case PREPARE_VPN_SERVICE: + if (resultCode == RESULT_OK && mProfileInfo != null) + { + Intent intent = new Intent(this, CharonVpnService.class); + intent.putExtras(mProfileInfo); + this.startService(intent); + } + break; + default: + super.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public void onVpnProfileSelected(VpnProfile profile) + { + Bundle profileInfo = new Bundle(); + profileInfo.putLong(VpnProfileDataSource.KEY_ID, profile.getId()); + profileInfo.putString(VpnProfileDataSource.KEY_USERNAME, profile.getUsername()); + profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, profile.getPassword()); + profileInfo.putBoolean(PROFILE_REQUIRES_PASSWORD, profile.getVpnType().has(VpnTypeFeature.USER_PASS)); + profileInfo.putString(PROFILE_NAME, profile.getName()); + + removeFragmentByTag(DIALOG_TAG); + + if (mService != null && mService.getState() == State.CONNECTED) + { + profileInfo.putBoolean(PROFILE_RECONNECT, mService.getProfile().getId() == profile.getId()); + + ConfirmationDialog dialog = new ConfirmationDialog(); + dialog.setArguments(profileInfo); + dialog.show(this.getFragmentManager(), DIALOG_TAG); + return; + } + startVpnProfile(profileInfo); + } + + /** + * Start the given VPN profile asking the user for a password if required. + * @param profileInfo data about the profile + */ + private void startVpnProfile(Bundle profileInfo) + { + if (profileInfo.getBoolean(PROFILE_REQUIRES_PASSWORD) && + profileInfo.getString(VpnProfileDataSource.KEY_PASSWORD) == null) + { + LoginDialog login = new LoginDialog(); + login.setArguments(profileInfo); + login.show(getFragmentManager(), DIALOG_TAG); + return; + } + prepareVpnService(profileInfo); + } + + /** + * Start the VPN profile referred to by the given intent. Displays an error + * if the profile doesn't exist. + * @param intent Intent that caused us to start this + */ + private void startVpnProfile(Intent intent) + { + long profileId = intent.getLongExtra(EXTRA_VPN_PROFILE_ID, 0); + if (profileId <= 0) + { /* invalid invocation */ + return; + } + VpnProfileDataSource dataSource = new VpnProfileDataSource(this); + dataSource.open(); + VpnProfile profile = dataSource.getVpnProfile(profileId); + dataSource.close(); + + if (profile != null) + { + onVpnProfileSelected(profile); + } + else + { + Toast.makeText(this, R.string.profile_not_found, Toast.LENGTH_LONG).show(); + } + } + + /** + * Class that loads the cached CA certificates. + */ + private class LoadCertificatesTask extends AsyncTask + { + @Override + protected void onPreExecute() + { + setProgressBarIndeterminateVisibility(true); + } + @Override + protected TrustedCertificateManager doInBackground(Void... params) + { + return TrustedCertificateManager.getInstance().load(); + } + @Override + protected void onPostExecute(TrustedCertificateManager result) + { + setProgressBarIndeterminateVisibility(false); + } + } + + /** + * Dismiss dialog if shown + */ + public void removeFragmentByTag(String tag) + { + FragmentManager fm = getFragmentManager(); + Fragment login = fm.findFragmentByTag(tag); + if (login != null) + { + FragmentTransaction ft = fm.beginTransaction(); + ft.remove(login); + ft.commit(); + } + } + + /** + * Class that displays a confirmation dialog if a VPN profile is already connected + * and then initiates the selected VPN profile if the user confirms the dialog. + */ + public static class ConfirmationDialog extends DialogFragment + { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + final Bundle profileInfo = getArguments(); + int icon = android.R.drawable.ic_dialog_alert; + int title = R.string.connect_profile_question; + int message = R.string.replaces_active_connection; + int button = R.string.connect; + + if (profileInfo.getBoolean(PROFILE_RECONNECT)) + { + icon = android.R.drawable.ic_dialog_info; + title = R.string.vpn_connected; + message = R.string.vpn_profile_connected; + button = R.string.reconnect; + } + + return new AlertDialog.Builder(getActivity()) + .setIcon(icon) + .setTitle(String.format(getString(title), profileInfo.getString(PROFILE_NAME))) + .setMessage(message) + .setPositiveButton(button, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) + { + MainActivity activity = (MainActivity)getActivity(); + activity.startVpnProfile(profileInfo); + } + }) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + dismiss(); + } + }).create(); + } + } + + /** + * Class that displays a login dialog and initiates the selected VPN + * profile if the user confirms the dialog. + */ + public static class LoginDialog extends DialogFragment + { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + final Bundle profileInfo = getArguments(); + LayoutInflater inflater = getActivity().getLayoutInflater(); + View view = inflater.inflate(R.layout.login_dialog, null); + EditText username = (EditText)view.findViewById(R.id.username); + username.setText(profileInfo.getString(VpnProfileDataSource.KEY_USERNAME)); + final EditText password = (EditText)view.findViewById(R.id.password); + + Builder adb = new AlertDialog.Builder(getActivity()); + adb.setView(view); + adb.setTitle(getString(R.string.login_title)); + adb.setPositiveButton(R.string.login_confirm, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) + { + MainActivity activity = (MainActivity)getActivity(); + profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, password.getText().toString().trim()); + activity.prepareVpnService(profileInfo); + } + }); + adb.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + dismiss(); + } + }); + return adb.create(); + } + } + + /** + * Class representing an error message which is displayed if VpnService is + * not supported on the current device. + */ + public static class VpnNotSupportedError extends DialogFragment + { + static final String ERROR_MESSAGE_ID = "org.strongswan.android.VpnNotSupportedError.MessageId"; + + public static void showWithMessage(Activity activity, int messageId) + { + Bundle bundle = new Bundle(); + bundle.putInt(ERROR_MESSAGE_ID, messageId); + VpnNotSupportedError dialog = new VpnNotSupportedError(); + dialog.setArguments(bundle); + dialog.show(activity.getFragmentManager(), DIALOG_TAG); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + final Bundle arguments = getArguments(); + final int messageId = arguments.getInt(ERROR_MESSAGE_ID); + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.vpn_not_supported_title) + .setMessage(messageId) + .setCancelable(false) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) + { + dialog.dismiss(); + } + }).create(); + } + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java new file mode 100644 index 000000000..04c288bcf --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2013 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 org.strongswan.android.logic.imc.RemediationInstruction; + +import android.app.ListFragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +public class RemediationInstructionFragment extends ListFragment +{ + public static final String ARG_REMEDIATION_INSTRUCTION = "instruction"; + private RemediationInstruction mInstruction = null; + private TextView mTitle; + private TextView mDescription; + private TextView mHeader; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + { + return inflater.inflate(R.layout.remediation_instruction, container, false); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) + { + super.onActivityCreated(savedInstanceState); + + if (savedInstanceState != null) + { + mInstruction = savedInstanceState.getParcelable(ARG_REMEDIATION_INSTRUCTION); + } + /* show dividers only between list items */ + getListView().setHeaderDividersEnabled(false); + getListView().setFooterDividersEnabled(false); + /* don't show loader while adapter is not set */ + setListShown(true); + mTitle = (TextView)getView().findViewById(R.id.title); + mDescription = (TextView)getView().findViewById(R.id.description); + mHeader = (TextView)getView().findViewById(R.id.list_header); + } + + @Override + public void onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + outState.putParcelable(ARG_REMEDIATION_INSTRUCTION, mInstruction); + } + + @Override + public void onStart() + { + super.onStart(); + + Bundle args = getArguments(); + if (args != null) + { + mInstruction = args.getParcelable(ARG_REMEDIATION_INSTRUCTION); + } + updateView(mInstruction); + } + + public void updateView(RemediationInstruction instruction) + { + mInstruction = instruction; + if (mInstruction != null) + { + mTitle.setText(mInstruction.getTitle()); + mDescription.setText(mInstruction.getDescription()); + if (mInstruction.getHeader() != null) + { + mHeader.setText(mInstruction.getHeader()); + setListAdapter(new ArrayAdapter(getActivity(), + android.R.layout.simple_list_item_1, mInstruction.getItems())); + } + else + { + mHeader.setText(""); + setListAdapter(null); + } + } + else + { + mTitle.setText(""); + mDescription.setText(""); + mHeader.setText(""); + setListAdapter(null); + } + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java new file mode 100644 index 000000000..66d0de261 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2013 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.util.ArrayList; + +import org.strongswan.android.R; +import org.strongswan.android.logic.imc.RemediationInstruction; +import org.strongswan.android.ui.RemediationInstructionsFragment.OnRemediationInstructionSelectedListener; + +import android.app.Activity; +import android.os.Bundle; +import android.view.MenuItem; + +public class RemediationInstructionsActivity extends Activity implements OnRemediationInstructionSelectedListener +{ + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.remediation_instructions); + getActionBar().setDisplayHomeAsUpEnabled(true); + + if (savedInstanceState != null) + { /* only update if we're not restoring */ + return; + } + RemediationInstructionsFragment frag = (RemediationInstructionsFragment)getFragmentManager().findFragmentById(R.id.remediation_instructions_fragment); + if (frag != null) + { /* two-pane layout, update fragment */ + Bundle extras = getIntent().getExtras(); + ArrayList list = extras.getParcelableArrayList(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS); + frag.updateView(list); + } + else + { /* one-pane layout, create fragment */ + frag = new RemediationInstructionsFragment(); + frag.setArguments(getIntent().getExtras()); + getFragmentManager().beginTransaction().add(R.id.fragment_container, frag).commit(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case android.R.id.home: + /* one-pane layout, pop possible fragment from stack, finish otherwise */ + if (!getFragmentManager().popBackStackImmediate()) + { + finish(); + } + getActionBar().setTitle(getTitle()); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onRemediationInstructionSelected(RemediationInstruction instruction) + { + RemediationInstructionFragment frag = (RemediationInstructionFragment)getFragmentManager().findFragmentById(R.id.remediation_instruction_fragment); + + if (frag != null) + { /* two-pane layout, update directly */ + frag.updateView(instruction); + } + else + { /* one-pane layout, replace fragment */ + frag = new RemediationInstructionFragment(); + Bundle args = new Bundle(); + args.putParcelable(RemediationInstructionFragment.ARG_REMEDIATION_INSTRUCTION, instruction); + frag.setArguments(args); + + getFragmentManager().beginTransaction().replace(R.id.fragment_container, frag).addToBackStack(null).commit(); + getActionBar().setTitle(instruction.getTitle()); + } + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java new file mode 100644 index 000000000..86467dc35 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2013 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.util.ArrayList; + +import org.strongswan.android.R; +import org.strongswan.android.logic.imc.RemediationInstruction; +import org.strongswan.android.ui.adapter.RemediationInstructionAdapter; + +import android.app.Activity; +import android.app.ListFragment; +import android.os.Bundle; +import android.view.View; +import android.widget.ListView; + +public class RemediationInstructionsFragment extends ListFragment +{ + public static final String EXTRA_REMEDIATION_INSTRUCTIONS = "instructions"; + private static final String KEY_POSITION = "position"; + private ArrayList mInstructions = null; + private OnRemediationInstructionSelectedListener mListener; + private RemediationInstructionAdapter mAdapter; + private int mCurrentPosition = -1; + + /** + * The activity containing this fragment should implement this interface + */ + public interface OnRemediationInstructionSelectedListener + { + public void onRemediationInstructionSelected(RemediationInstruction instruction); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) + { + super.onActivityCreated(savedInstanceState); + + if (savedInstanceState != null) + { + mInstructions = savedInstanceState.getParcelableArrayList(EXTRA_REMEDIATION_INSTRUCTIONS); + mCurrentPosition = savedInstanceState.getInt(KEY_POSITION); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + outState.putParcelableArrayList(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS, mInstructions); + outState.putInt(KEY_POSITION, mCurrentPosition); + } + + @Override + public void onAttach(Activity activity) + { + super.onAttach(activity); + + if (activity instanceof OnRemediationInstructionSelectedListener) + { + mListener = (OnRemediationInstructionSelectedListener)activity; + } + } + + @Override + public void onStart() + { + super.onStart(); + + boolean two_pane = getFragmentManager().findFragmentById(R.id.remediation_instruction_fragment) != null; + if (two_pane) + { /* two-pane layout, make list items selectable */ + getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); + } + + Bundle args = getArguments(); + if (mInstructions == null && args != null) + { + mInstructions = args.getParcelableArrayList(EXTRA_REMEDIATION_INSTRUCTIONS); + } + updateView(mInstructions); + + if (two_pane && mCurrentPosition == -1 && mInstructions.size() > 0) + { /* two-pane layout, select first instruction */ + mCurrentPosition = 0; + mListener.onRemediationInstructionSelected(mInstructions.get(0)); + } + getListView().setItemChecked(mCurrentPosition, true); + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) + { + mCurrentPosition = position; + mListener.onRemediationInstructionSelected(mInstructions.get(position)); + getListView().setItemChecked(position, true); + } + + public void updateView(ArrayList instructions) + { + if (mAdapter == null) + { + mAdapter = new RemediationInstructionAdapter(getActivity()); + setListAdapter(mAdapter); + } + mInstructions = instructions; + mAdapter.setData(mInstructions); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java new file mode 100644 index 000000000..61bd2c9a2 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateImportActivity.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2014 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.ui; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.logic.TrustedCertificateManager; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentTransaction; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.widget.Toast; + +public class TrustedCertificateImportActivity extends Activity +{ + private static final int OPEN_DOCUMENT = 0; + private static final String DIALOG_TAG = "Dialog"; + + /* same as those listed in the manifest */ + private static final String[] ACCEPTED_MIME_TYPES = { + "application/x-x509-ca-cert", + "application/x-x509-server-cert", + "application/x-pem-file", + "application/pkix-cert" + }; + + @TargetApi(Build.VERSION_CODES.KITKAT) + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + if (savedInstanceState != null) + { /* do nothing when we are restoring */ + return; + } + + Intent intent = getIntent(); + String action = intent.getAction(); + if (Intent.ACTION_VIEW.equals(action)) + { + importCertificate(intent.getData()); + } + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + { + Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + openIntent.setType("*/*"); + openIntent.putExtra(Intent.EXTRA_MIME_TYPES, ACCEPTED_MIME_TYPES); + startActivityForResult(openIntent, OPEN_DOCUMENT); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + switch (requestCode) + { + case OPEN_DOCUMENT: + if (resultCode == Activity.RESULT_OK && data != null) + { + importCertificate(data.getData()); + return; + } + finish(); + return; + } + super.onActivityResult(requestCode, resultCode, data); + } + + /** + * Import the file pointed to by the given URI as a certificate. + * @param uri + */ + private void importCertificate(Uri uri) + { + X509Certificate certificate = parseCertificate(uri); + if (certificate == null) + { + Toast.makeText(this, R.string.cert_import_failed, Toast.LENGTH_LONG).show(); + finish(); + return; + } + /* Ask the user whether to import the certificate. This is particularly + * necessary because the import activity can be triggered by any app on + * the system. Also, if our app is the only one that is registered to + * open certificate files by MIME type the user would have no idea really + * where the file was imported just by reading the Toast we display. */ + ConfirmImportDialog dialog = new ConfirmImportDialog(); + Bundle args = new Bundle(); + args.putSerializable(VpnProfileDataSource.KEY_CERTIFICATE, certificate); + dialog.setArguments(args); + FragmentTransaction ft = getFragmentManager().beginTransaction(); + ft.add(dialog, DIALOG_TAG); + ft.commit(); + } + + /** + * Load the file from the given URI and try to parse it as X.509 certificate. + * @param uri + * @return certificate or null + */ + private X509Certificate parseCertificate(Uri uri) + { + X509Certificate certificate = null; + try + { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + InputStream in = getContentResolver().openInputStream(uri); + certificate = (X509Certificate)factory.generateCertificate(in); + /* we don't check whether it's actually a CA certificate or not */ + } + catch (CertificateException e) + { + e.printStackTrace(); + } + catch (FileNotFoundException e) + { + e.printStackTrace(); + } + return certificate; + } + + + /** + * Try to store the given certificate in the KeyStore. + * @param certificate + * @return whether it was successfully stored + */ + private boolean storeCertificate(X509Certificate certificate) + { + try + { + KeyStore store = KeyStore.getInstance("LocalCertificateStore"); + store.load(null, null); + store.setCertificateEntry(null, certificate); + TrustedCertificateManager.getInstance().reset(); + return true; + } + catch (Exception e) + { + e.printStackTrace(); + return false; + } + } + + /** + * Class that displays a confirmation dialog when a certificate should get + * imported. If the user confirms the import we try to store it. + */ + public static class ConfirmImportDialog extends DialogFragment + { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + final X509Certificate certificate; + + certificate = (X509Certificate)getArguments().getSerializable(VpnProfileDataSource.KEY_CERTIFICATE); + + return new AlertDialog.Builder(getActivity()) + .setIcon(R.drawable.ic_launcher) + .setTitle(R.string.import_certificate) + .setMessage(certificate.getSubjectDN().toString()) + .setPositiveButton(R.string.import_certificate, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) + { + TrustedCertificateImportActivity activity = (TrustedCertificateImportActivity)getActivity(); + if (activity.storeCertificate(certificate)) + { + Toast.makeText(getActivity(), R.string.cert_imported_successfully, Toast.LENGTH_LONG).show(); + getActivity().setResult(Activity.RESULT_OK); + } + else + { + Toast.makeText(getActivity(), R.string.cert_import_failed, Toast.LENGTH_LONG).show(); + } + getActivity().finish(); + } + }) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + getActivity().finish(); + } + }).create(); + } + + @Override + public void onCancel(DialogInterface dialog) + { + getActivity().finish(); + } + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java new file mode 100644 index 000000000..8bd39c435 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificateListFragment.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2012-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.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Hashtable; +import java.util.List; +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; + +import android.app.Activity; +import android.app.ListFragment; +import android.app.LoaderManager.LoaderCallbacks; +import android.content.AsyncTaskLoader; +import android.content.Context; +import android.content.Loader; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.ListView; +import android.widget.SearchView; +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 TrustedCertificateSource mSource = TrustedCertificateSource.SYSTEM; + + /** + * The activity containing this fragment should implement this interface + */ + public interface OnTrustedCertificateSelectedListener { + public void onTrustedCertificateSelected(TrustedCertificateEntry selected); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) + { + super.onActivityCreated(savedInstanceState); + setHasOptionsMenu(true); + + setEmptyText(getString(R.string.no_certificates)); + + mAdapter = new TrustedCertificateAdapter(getActivity()); + setListAdapter(mAdapter); + + setListShown(false); + + Bundle arguments = getArguments(); + if (arguments != null) + { + mSource = (TrustedCertificateSource)arguments.getSerializable(EXTRA_CERTIFICATE_SOURCE); + } + + getLoaderManager().initLoader(0, null, this); + } + + @Override + public void onDestroy() + { + super.onDestroy(); + } + + @Override + public void onAttach(Activity activity) + { + super.onAttach(activity); + + if (activity instanceof OnTrustedCertificateSelectedListener) + { + mListener = (OnTrustedCertificateSelectedListener)activity; + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) + { + MenuItem item = menu.add(R.string.search); + item.setIcon(android.R.drawable.ic_menu_search); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + + SearchView sv = new SearchView(getActivity()); + sv.setOnQueryTextListener(this); + item.setActionView(sv); + } + + @Override + public boolean onQueryTextSubmit(String query) + { /* already handled when the text changes */ + return true; + } + + @Override + public boolean onQueryTextChange(String newText) + { + String search = TextUtils.isEmpty(newText) ? null : newText; + mAdapter.getFilter().filter(search); + 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 */ + return new CertificateListLoader(getActivity(), mSource); + } + + @Override + public void onLoadFinished(Loader> loader, List data) + { + mAdapter.setData(data); + + if (isResumed()) + { + setListShown(true); + } + else + { + setListShownNoAnimation(true); + } + } + + @Override + public void onLoaderReset(Loader> loader) + { + mAdapter.setData(null); + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) + { + if (mListener != null) + { + mListener.onTrustedCertificateSelected(mAdapter.getItem(position)); + } + } + + public static class CertificateListLoader extends AsyncTaskLoader> + { + private List mData; + private final TrustedCertificateSource mSource; + + public CertificateListLoader(Context context, TrustedCertificateSource source) + { + super(context); + mSource = source; + } + + @Override + public List loadInBackground() + { + TrustedCertificateManager certman = TrustedCertificateManager.getInstance().load(); + Hashtable certificates = certman.getCACertificates(mSource); + List selected; + + selected = new ArrayList(); + for (Entry entry : certificates.entrySet()) + { + selected.add(new TrustedCertificateEntry(entry.getKey(), entry.getValue())); + } + Collections.sort(selected); + return selected; + } + + @Override + protected void onStartLoading() + { + if (mData != null) + { /* if we have data ready, deliver it directly */ + deliverResult(mData); + return; + } + forceLoad(); + } + + @Override + public void deliverResult(List data) + { + if (isReset()) + { + return; + } + mData = data; + if (isStarted()) + { /* if it is started we deliver the data directly, + * otherwise this is handled in onStartLoading */ + super.deliverResult(data); + } + } + + @Override + protected void onReset() + { + mData = null; + } + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java new file mode 100644 index 000000000..663950c16 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/TrustedCertificatesActivity.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2012-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.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; +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; + +public class TrustedCertificatesActivity extends Activity implements TrustedCertificateListFragment.OnTrustedCertificateSelectedListener, OnCertificateDeleteListener +{ + public static final String SELECT_CERTIFICATE = "org.strongswan.android.action.SELECT_CERTIFICATE"; + private static final String DIALOG_TAG = "Dialog"; + private static final int IMPORT_CERTIFICATE = 0; + private boolean mSelect; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + 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) + .setTag(listener) + .setTabListener(listener)); + listener = new TrustedCertificatesTabListener(this, "user", TrustedCertificateSource.USER); + actionBar.addTab(actionBar + .newTab() + .setText(R.string.user_tab) + .setTag(listener) + .setTabListener(listener)); + listener = new TrustedCertificatesTabListener(this, "local", TrustedCertificateSource.LOCAL); + actionBar.addTab(actionBar + .newTab() + .setText(R.string.local_tab) + .setTag(listener) + .setTabListener(listener)); + + if (savedInstanceState != null) + { + actionBar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); + } + mSelect = SELECT_CERTIFICATE.equals(getIntent().getAction()); + } + + @Override + protected void onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + outState.putInt("tab", getActionBar().getSelectedNavigationIndex()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + getMenuInflater().inflate(R.menu.certificates, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + { + menu.removeItem(R.id.menu_import_certificate); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case android.R.id.home: + finish(); + return true; + case R.id.menu_reload_certs: + reloadCertificates(); + return true; + case R.id.menu_import_certificate: + Intent intent = new Intent(this, TrustedCertificateImportActivity.class); + startActivityForResult(intent, IMPORT_CERTIFICATE); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + switch (requestCode) + { + case IMPORT_CERTIFICATE: + if (resultCode == Activity.RESULT_OK) + { + reloadCertificates(); + } + return; + } + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + public void onTrustedCertificateSelected(TrustedCertificateEntry selected) + { + if (mSelect) + { + /* the user selected a certificate, return to calling activity */ + Intent intent = new Intent(); + intent.putExtra(VpnProfileDataSource.KEY_CERTIFICATE, selected.getAlias()); + setResult(Activity.RESULT_OK, intent); + finish(); + } + else + { + TrustedCertificatesTabListener listener; + listener = (TrustedCertificatesTabListener)getActionBar().getSelectedTab().getTag(); + if (listener.mTag == "local") + { + Bundle args = new Bundle(); + args.putString(CertificateDeleteConfirmationDialog.ALIAS, selected.getAlias()); + CertificateDeleteConfirmationDialog dialog = new CertificateDeleteConfirmationDialog(); + dialog.setArguments(args); + dialog.show(this.getFragmentManager(), DIALOG_TAG); + } + } + } + + @Override + public void onDelete(String alias) + { + try + { + KeyStore store = KeyStore.getInstance("LocalCertificateStore"); + store.load(null, null); + store.deleteEntry(alias); + reloadCertificates(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + private void reloadCertificates() + { + TrustedCertificateManager.getInstance().reset(); + for (int i = 0; i < getActionBar().getTabCount(); i++) + { + Tab tab = getActionBar().getTabAt(i); + TrustedCertificatesTabListener listener = (TrustedCertificatesTabListener)tab.getTag(); + listener.reset(); + } + } + + public static class TrustedCertificatesTabListener implements ActionBar.TabListener + { + private final String mTag; + private final TrustedCertificateSource mSource; + private Fragment mFragment; + + public TrustedCertificatesTabListener(Activity activity, String tag, TrustedCertificateSource source) + { + mTag = tag; + 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 */ + mFragment = activity.getFragmentManager().findFragmentByTag(mTag); + if (mFragment != null && !mFragment.isDetached()) + { + FragmentTransaction ft = activity.getFragmentManager().beginTransaction(); + ft.detach(mFragment); + ft.commit(); + } + } + + @Override + public void onTabSelected(Tab tab, FragmentTransaction ft) + { + if (mFragment == null) + { + mFragment = new TrustedCertificateListFragment(); + Bundle args = new Bundle(); + args.putSerializable(TrustedCertificateListFragment.EXTRA_CERTIFICATE_SOURCE, mSource); + mFragment.setArguments(args); + ft.add(android.R.id.content, mFragment, mTag); + } + else + { + ft.attach(mFragment); + } + } + + @Override + public void onTabUnselected(Tab tab, FragmentTransaction ft) + { + if (mFragment != null) + { + ft.detach(mFragment); + } + } + + @Override + public void onTabReselected(Tab tab, FragmentTransaction ft) + { + /* nothing to be done */ + } + + public void reset() + { + if (mFragment != null) + { + ((TrustedCertificateListFragment)mFragment).reset(); + } + } + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java new file mode 100644 index 000000000..a8b3daa06 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -0,0 +1,653 @@ +/* + * Copyright (C) 2012-2014 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.security.cert.X509Certificate; + +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.data.VpnType; +import org.strongswan.android.data.VpnType.VpnTypeFeature; +import org.strongswan.android.logic.TrustedCertificateManager; +import org.strongswan.android.security.TrustedCertificateEntry; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.security.KeyChain; +import android.security.KeyChainAliasCallback; +import android.security.KeyChainException; +import android.text.Html; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +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.TextView; + +public class VpnProfileDetailActivity extends Activity +{ + private static final int SELECT_TRUSTED_CERTIFICATE = 0; + private static final int MTU_MIN = 1280; + private static final int MTU_MAX = 1500; + + private VpnProfileDataSource mDataSource; + private Long mId; + private TrustedCertificateEntry mCertEntry; + private String mUserCertLoading; + private TrustedCertificateEntry mUserCertEntry; + private VpnType mVpnType = VpnType.IKEV2_EAP; + private VpnProfile mProfile; + private EditText mName; + private EditText mGateway; + private Spinner mSelectVpnType; + private ViewGroup mUsernamePassword; + private EditText mUsername; + private EditText mPassword; + private ViewGroup mUserCertificate; + private RelativeLayout mSelectUserCert; + private CheckBox mCheckAuto; + private RelativeLayout mSelectCert; + private RelativeLayout mTncNotice; + private CheckBox mShowAdvanced; + private ViewGroup mAdvancedSettings; + private EditText mMTU; + private EditText mPort; + private CheckBox mBlockIPv4; + private CheckBox mBlockIPv6; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + /* the title is set when we load the profile, if any */ + getActionBar().setDisplayHomeAsUpEnabled(true); + + mDataSource = new VpnProfileDataSource(this); + mDataSource.open(); + + setContentView(R.layout.profile_detail_view); + + mName = (EditText)findViewById(R.id.name); + mGateway = (EditText)findViewById(R.id.gateway); + mSelectVpnType = (Spinner)findViewById(R.id.vpn_type); + 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 = (RelativeLayout)findViewById(R.id.select_user_certificate); + + mCheckAuto = (CheckBox)findViewById(R.id.ca_auto); + mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate); + + mShowAdvanced = (CheckBox)findViewById(R.id.show_advanced); + mAdvancedSettings = (ViewGroup)findViewById(R.id.advanced_settings); + + mMTU = (EditText)findViewById(R.id.mtu); + mPort = (EditText)findViewById(R.id.port); + mBlockIPv4 = (CheckBox)findViewById(R.id.split_tunneling_v4); + mBlockIPv6 = (CheckBox)findViewById(R.id.split_tunneling_v6); + + mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) + { + mVpnType = VpnType.values()[position]; + updateCredentialView(); + } + + @Override + public void onNothingSelected(AdapterView parent) + { /* should not happen */ + mVpnType = VpnType.IKEV2_EAP; + updateCredentialView(); + } + }); + + ((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) + { + new TncNoticeDialog().show(VpnProfileDetailActivity.this.getFragmentManager(), "TncNotice"); + } + }); + + mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener()); + + mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + updateCertificateSelector(); + } + }); + + mSelectCert.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) + { + Intent intent = new Intent(VpnProfileDetailActivity.this, TrustedCertificatesActivity.class); + intent.setAction(TrustedCertificatesActivity.SELECT_CERTIFICATE); + startActivityForResult(intent, SELECT_TRUSTED_CERTIFICATE); + } + }); + + mShowAdvanced.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + updateAdvancedSettings(); + } + }); + + mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID); + if (mId == null) + { + Bundle extras = getIntent().getExtras(); + mId = extras == null ? null : extras.getLong(VpnProfileDataSource.KEY_ID); + } + + loadProfileData(savedInstanceState); + + updateCredentialView(); + updateCertificateSelector(); + updateAdvancedSettings(); + } + + @Override + protected void onDestroy() + { + super.onDestroy(); + mDataSource.close(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + if (mId != null) + { + outState.putLong(VpnProfileDataSource.KEY_ID, mId); + } + if (mUserCertEntry != null) + { + outState.putString(VpnProfileDataSource.KEY_USER_CERTIFICATE, mUserCertEntry.getAlias()); + } + if (mCertEntry != null) + { + outState.putString(VpnProfileDataSource.KEY_CERTIFICATE, mCertEntry.getAlias()); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.profile_edit, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case android.R.id.home: + case R.id.menu_cancel: + finish(); + return true; + case R.id.menu_accept: + saveProfile(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + switch (requestCode) + { + case SELECT_TRUSTED_CERTIFICATE: + if (resultCode == RESULT_OK) + { + String alias = data.getStringExtra(VpnProfileDataSource.KEY_CERTIFICATE); + X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias); + mCertEntry = certificate == null ? null : new TrustedCertificateEntry(alias, certificate); + updateCertificateSelector(); + } + break; + default: + super.onActivityResult(requestCode, resultCode, data); + } + } + + /** + * Update the UI to enter credentials depending on the type of VPN currently selected + */ + private void updateCredentialView() + { + mUsernamePassword.setVisibility(mVpnType.has(VpnTypeFeature.USER_PASS) ? View.VISIBLE : View.GONE); + mUserCertificate.setVisibility(mVpnType.has(VpnTypeFeature.CERTIFICATE) ? View.VISIBLE : View.GONE); + mTncNotice.setVisibility(mVpnType.has(VpnTypeFeature.BYOD) ? View.VISIBLE : View.GONE); + + if (mVpnType.has(VpnTypeFeature.CERTIFICATE)) + { + if (mUserCertLoading != null) + { + ((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 */ + ((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 + { + ((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); + } + } + } + + /** + * Show an alert in case the previously selected certificate is not found anymore + * or the user did not select a certificate in the spinner. + */ + private void showCertificateAlert() + { + AlertDialog.Builder adb = new AlertDialog.Builder(VpnProfileDetailActivity.this); + adb.setTitle(R.string.alert_text_nocertfound_title); + adb.setMessage(R.string.alert_text_nocertfound); + adb.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) + { + dialog.cancel(); + } + }); + adb.show(); + } + + /** + * Update the CA certificate selection UI depending on whether the + * certificate should be automatically selected or not. + */ + private void updateCertificateSelector() + { + if (!mCheckAuto.isChecked()) + { + mSelectCert.setEnabled(true); + mSelectCert.setVisibility(View.VISIBLE); + + if (mCertEntry != null) + { + ((TextView)mSelectCert.findViewById(android.R.id.text1)).setText(mCertEntry.getSubjectPrimary()); + ((TextView)mSelectCert.findViewById(android.R.id.text2)).setText(mCertEntry.getSubjectSecondary()); + } + else + { + ((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 + { + mSelectCert.setEnabled(false); + mSelectCert.setVisibility(View.GONE); + } + } + + /** + * Update the advanced settings UI depending on whether any advanced + * settings have already been made. + */ + private void updateAdvancedSettings() + { + boolean show = mShowAdvanced.isChecked(); + if (!show && mProfile != null) + { + Integer st = mProfile.getSplitTunneling(); + show = mProfile.getMTU() != null || mProfile.getPort() != null || (st != null && st != 0); + } + mShowAdvanced.setVisibility(!show ? View.VISIBLE : View.GONE); + mAdvancedSettings.setVisibility(show ? View.VISIBLE : View.GONE); + } + + /** + * Save or update the profile depending on whether we actually have a + * profile object or not (this was created in updateProfileData) + */ + private void saveProfile() + { + if (verifyInput()) + { + if (mProfile != null) + { + updateProfileData(); + mDataSource.updateVpnProfile(mProfile); + } + else + { + mProfile = new VpnProfile(); + updateProfileData(); + mDataSource.insertProfile(mProfile); + } + setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_ID, mProfile.getId())); + finish(); + } + } + + /** + * Verify the user input and display error messages. + * @return true if the input is valid + */ + private boolean verifyInput() + { + boolean valid = true; + if (mGateway.getText().toString().trim().isEmpty()) + { + mGateway.setError(getString(R.string.alert_text_no_input_gateway)); + valid = false; + } + if (mVpnType.has(VpnTypeFeature.USER_PASS)) + { + if (mUsername.getText().toString().trim().isEmpty()) + { + mUsername.setError(getString(R.string.alert_text_no_input_username)); + valid = false; + } + } + if (mVpnType.has(VpnTypeFeature.CERTIFICATE) && mUserCertEntry == null) + { /* let's show an error icon */ + ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError(""); + valid = false; + } + if (!mCheckAuto.isChecked() && mCertEntry == null) + { + showCertificateAlert(); + valid = false; + } + Integer mtu = getInteger(mMTU); + if (mtu != null && (mtu < MTU_MIN || mtu > MTU_MAX)) + { + mMTU.setError(String.format(getString(R.string.alert_text_out_of_range), MTU_MIN, MTU_MAX)); + valid = false; + } + Integer port = getInteger(mPort); + if (port != null && (port < 1 || port > 65535)) + { + mPort.setError(String.format(getString(R.string.alert_text_out_of_range), 1, 65535)); + valid = false; + } + return valid; + } + + /** + * Update the profile object with the data entered by the user + */ + private void updateProfileData() + { + /* the name is optional, we default to the gateway if none is given */ + String name = mName.getText().toString().trim(); + String gateway = mGateway.getText().toString().trim(); + mProfile.setName(name.isEmpty() ? gateway : name); + mProfile.setGateway(gateway); + mProfile.setVpnType(mVpnType); + if (mVpnType.has(VpnTypeFeature.USER_PASS)) + { + mProfile.setUsername(mUsername.getText().toString().trim()); + String password = mPassword.getText().toString().trim(); + password = password.isEmpty() ? null : password; + mProfile.setPassword(password); + } + if (mVpnType.has(VpnTypeFeature.CERTIFICATE)) + { + mProfile.setUserCertificateAlias(mUserCertEntry.getAlias()); + } + String certAlias = mCheckAuto.isChecked() ? null : mCertEntry.getAlias(); + mProfile.setCertificateAlias(certAlias); + mProfile.setMTU(getInteger(mMTU)); + mProfile.setPort(getInteger(mPort)); + int st = 0; + st |= mBlockIPv4.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 : 0; + st |= mBlockIPv6.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 : 0; + mProfile.setSplitTunneling(st == 0 ? null : st); + } + + /** + * Load an existing profile if we got an ID + * + * @param savedInstanceState previously saved state + */ + private void loadProfileData(Bundle savedInstanceState) + { + String useralias = null, alias = null; + + getActionBar().setTitle(R.string.add_profile); + if (mId != null && mId != 0) + { + mProfile = mDataSource.getVpnProfile(mId); + if (mProfile != null) + { + mName.setText(mProfile.getName()); + mGateway.setText(mProfile.getGateway()); + mVpnType = mProfile.getVpnType(); + mUsername.setText(mProfile.getUsername()); + mPassword.setText(mProfile.getPassword()); + mMTU.setText(mProfile.getMTU() != null ? mProfile.getMTU().toString() : null); + mPort.setText(mProfile.getPort() != null ? mProfile.getPort().toString() : null); + mBlockIPv4.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) != 0 : false); + mBlockIPv6.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) != 0 : false); + useralias = mProfile.getUserCertificateAlias(); + alias = mProfile.getCertificateAlias(); + getActionBar().setTitle(mProfile.getName()); + } + else + { + Log.e(VpnProfileDetailActivity.class.getSimpleName(), + "VPN profile with id " + mId + " not found"); + finish(); + } + } + + mSelectVpnType.setSelection(mVpnType.ordinal()); + + /* check if the user selected a user certificate previously */ + useralias = savedInstanceState == null ? useralias: savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE); + if (useralias != null) + { + UserCertificateLoader loader = new UserCertificateLoader(this, useralias); + mUserCertLoading = useralias; + loader.execute(); + } + + /* check if the user selected a CA certificate previously */ + alias = savedInstanceState == null ? alias : savedInstanceState.getString(VpnProfileDataSource.KEY_CERTIFICATE); + mCheckAuto.setChecked(alias == null); + if (alias != null) + { + X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias); + if (certificate != null) + { + mCertEntry = new TrustedCertificateEntry(alias, certificate); + } + else + { /* previously selected certificate is not here anymore */ + showCertificateAlert(); + mCertEntry = null; + } + } + } + + /** + * Get the integer value in the given text box or null if empty + * + * @param view text box (numeric entry assumed) + */ + private Integer getInteger(EditText view) + { + String value = view.getText().toString().trim(); + return value.isEmpty() ? null : Integer.valueOf(value); + } + + private class SelectUserCertOnClickListener implements OnClickListener, KeyChainAliasCallback + { + @Override + public void onClick(View v) + { + String useralias = mUserCertEntry != null ? mUserCertEntry.getAlias() : null; + KeyChain.choosePrivateKeyAlias(VpnProfileDetailActivity.this, this, new String[] { "RSA" }, null, null, -1, useralias); + } + + @Override + public void alias(final String alias) + { + if (alias != null) + { /* otherwise the dialog was canceled, the request denied */ + try + { + final X509Certificate[] chain = KeyChain.getCertificateChain(VpnProfileDetailActivity.this, alias); + /* alias() is not called from our main thread */ + runOnUiThread(new Runnable() { + @Override + public void run() + { + if (chain != null && chain.length > 0) + { + mUserCertEntry = new TrustedCertificateEntry(alias, chain[0]); + } + updateCredentialView(); + } + }); + } + catch (KeyChainException e) + { + e.printStackTrace(); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + } + } + + /** + * Load the selected user certificate asynchronously. This cannot be done + * from the main thread as getCertificateChain() calls back to our main + * thread to bind to the KeyChain service resulting in a deadlock. + */ + private class UserCertificateLoader extends AsyncTask + { + private final Context mContext; + private final String mAlias; + + public UserCertificateLoader(Context context, String alias) + { + mContext = context; + mAlias = alias; + } + + @Override + protected X509Certificate doInBackground(Void... params) + { + X509Certificate[] chain = null; + try + { + chain = KeyChain.getCertificateChain(mContext, mAlias); + } + catch (KeyChainException e) + { + e.printStackTrace(); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + if (chain != null && chain.length > 0) + { + return chain[0]; + } + return null; + } + + @Override + protected void onPostExecute(X509Certificate result) + { + if (result != null) + { + mUserCertEntry = new TrustedCertificateEntry(mAlias, result); + } + else + { /* previously selected certificate is not here anymore */ + ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError(""); + mUserCertEntry = null; + } + mUserCertLoading = null; + updateCredentialView(); + } + } + + /** + * Dialog with notification message if EAP-TNC is used. + */ + public static class TncNoticeDialog extends DialogFragment + { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.tnc_notice_title) + .setMessage(Html.fromHtml(getString(R.string.tnc_notice_details))) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) + { + dialog.dismiss(); + } + }).create(); + } + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java new file mode 100644 index 000000000..fb684b53d --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.ui.adapter.VpnProfileAdapter; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.os.Bundle; +import android.util.AttributeSet; +import android.view.ActionMode; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView.MultiChoiceModeListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; +import android.widget.Toast; + +public class VpnProfileListFragment extends Fragment +{ + private static final int ADD_REQUEST = 1; + private static final int EDIT_REQUEST = 2; + + private List mVpnProfiles; + private VpnProfileDataSource mDataSource; + private VpnProfileAdapter mListAdapter; + private ListView mListView; + private OnVpnProfileSelectedListener mListener; + private boolean mReadOnly; + + /** + * The activity containing this fragment should implement this interface + */ + public interface OnVpnProfileSelectedListener { + public void onVpnProfileSelected(VpnProfile profile); + } + + @Override + public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) + { + super.onInflate(activity, attrs, savedInstanceState); + TypedArray a = activity.obtainStyledAttributes(attrs, R.styleable.Fragment); + mReadOnly = a.getBoolean(R.styleable.Fragment_read_only, false); + a.recycle(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) + { + View view = inflater.inflate(R.layout.profile_list_fragment, null); + + mListView = (ListView)view.findViewById(R.id.profile_list); + mListView.setAdapter(mListAdapter); + mListView.setEmptyView(view.findViewById(R.id.profile_list_empty)); + mListView.setOnItemClickListener(mVpnProfileClicked); + + if (!mReadOnly) + { + mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); + mListView.setMultiChoiceModeListener(mVpnProfileSelected); + } + return view; + } + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + Bundle args = getArguments(); + if (args != null) + { + mReadOnly = args.getBoolean("read_only", mReadOnly); + } + + if (!mReadOnly) + { + setHasOptionsMenu(true); + } + + Context context = getActivity().getApplicationContext(); + + mDataSource = new VpnProfileDataSource(this.getActivity()); + mDataSource.open(); + + /* cached list of profiles used as backend for the ListView */ + mVpnProfiles = mDataSource.getAllVpnProfiles(); + + mListAdapter = new VpnProfileAdapter(context, R.layout.profile_list_item, mVpnProfiles); + } + + @Override + public void onDestroy() + { + super.onDestroy(); + mDataSource.close(); + } + + @Override + public void onAttach(Activity activity) + { + super.onAttach(activity); + + if (activity instanceof OnVpnProfileSelectedListener) + { + mListener = (OnVpnProfileSelectedListener)activity; + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) + { + inflater.inflate(R.menu.profile_list, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case R.id.add_profile: + Intent connectionIntent = new Intent(getActivity(), + VpnProfileDetailActivity.class); + startActivityForResult(connectionIntent, ADD_REQUEST); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) + { + switch (requestCode) + { + case ADD_REQUEST: + case EDIT_REQUEST: + if (resultCode != Activity.RESULT_OK) + { + return; + } + long id = data.getLongExtra(VpnProfileDataSource.KEY_ID, 0); + VpnProfile profile = mDataSource.getVpnProfile(id); + if (profile != null) + { /* in case this was an edit, we remove it first */ + mVpnProfiles.remove(profile); + mVpnProfiles.add(profile); + mListAdapter.notifyDataSetChanged(); + } + return; + } + super.onActivityResult(requestCode, resultCode, data); + } + + private final OnItemClickListener mVpnProfileClicked = new OnItemClickListener() { + @Override + public void onItemClick(AdapterView a, View v, int position, long id) + { + if (mListener != null) + { + mListener.onVpnProfileSelected((VpnProfile)a.getItemAtPosition(position)); + } + } + }; + + private final MultiChoiceModeListener mVpnProfileSelected = new MultiChoiceModeListener() { + private HashSet mSelected; + private MenuItem mEditProfile; + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) + { + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) + { + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) + { + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.profile_list_context, menu); + mEditProfile = menu.findItem(R.id.edit_profile); + mSelected = new HashSet(); + mode.setTitle(R.string.select_profiles); + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) + { + switch (item.getItemId()) + { + case R.id.edit_profile: + { + int position = mSelected.iterator().next(); + VpnProfile profile = (VpnProfile)mListView.getItemAtPosition(position); + Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class); + connectionIntent.putExtra(VpnProfileDataSource.KEY_ID, profile.getId()); + startActivityForResult(connectionIntent, EDIT_REQUEST); + break; + } + case R.id.delete_profile: + { + ArrayList profiles = new ArrayList(); + for (int position : mSelected) + { + profiles.add((VpnProfile)mListView.getItemAtPosition(position)); + } + for (VpnProfile profile : profiles) + { + mDataSource.deleteVpnProfile(profile); + mVpnProfiles.remove(profile); + } + mListAdapter.notifyDataSetChanged(); + Toast.makeText(VpnProfileListFragment.this.getActivity(), + R.string.profiles_deleted, Toast.LENGTH_SHORT).show(); + break; + } + default: + return false; + } + mode.finish(); + return true; + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, + long id, boolean checked) + { + if (checked) + { + mSelected.add(position); + } + else + { + mSelected.remove(position); + } + final int checkedCount = mSelected.size(); + mEditProfile.setEnabled(checkedCount == 1); + switch (checkedCount) + { + case 0: + mode.setSubtitle(R.string.no_profile_selected); + break; + case 1: + mode.setSubtitle(R.string.one_profile_selected); + break; + default: + mode.setSubtitle(String.format(getString(R.string.x_profiles_selected), checkedCount)); + break; + } + } + }; +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java new file mode 100644 index 000000000..b4d34f5a5 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileSelectActivity.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012 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 org.strongswan.android.data.VpnProfile; +import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +public class VpnProfileSelectActivity extends Activity implements OnVpnProfileSelectedListener +{ + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.vpn_profile_select); + + /* we should probably return a result also if the user clicks the back + * button before selecting a profile */ + setResult(RESULT_CANCELED); + } + + @Override + public void onVpnProfileSelected(VpnProfile profile) + { + Intent shortcut = new Intent(MainActivity.START_PROFILE); + shortcut.putExtra(MainActivity.EXTRA_VPN_PROFILE_ID, profile.getId()); + + Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcut); + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, profile.getName()); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(this, R.drawable.ic_launcher)); + setResult(RESULT_OK, intent); + finish(); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java new file mode 100644 index 000000000..160ba951b --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.util.ArrayList; +import java.util.List; + +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.logic.VpnStateService; +import org.strongswan.android.logic.VpnStateService.ErrorState; +import org.strongswan.android.logic.VpnStateService.State; +import org.strongswan.android.logic.VpnStateService.VpnStateListener; +import org.strongswan.android.logic.imc.ImcState; +import org.strongswan.android.logic.imc.RemediationInstruction; + +import android.app.AlertDialog; +import android.app.Fragment; +import android.app.ProgressDialog; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +public class VpnStateFragment extends Fragment implements VpnStateListener +{ + private static final String KEY_ERROR_CONNECTION_ID = "error_connection_id"; + private static final String KEY_DISMISSED_CONNECTION_ID = "dismissed_connection_id"; + + private TextView mProfileNameView; + private TextView mProfileView; + private TextView mStateView; + private int stateBaseColor; + private Button mActionButton; + private ProgressDialog mConnectDialog; + private ProgressDialog mDisconnectDialog; + private ProgressDialog mProgressDialog; + private AlertDialog mErrorDialog; + private long mErrorConnectionID; + private long mDismissedConnectionID; + private VpnStateService mService; + private final ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceDisconnected(ComponentName name) + { + mService = null; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) + { + mService = ((VpnStateService.LocalBinder)service).getService(); + mService.registerListener(VpnStateFragment.this); + updateView(); + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + /* bind to the service only seems to work from the ApplicationContext */ + Context context = getActivity().getApplicationContext(); + context.bindService(new Intent(context, VpnStateService.class), + mServiceConnection, Service.BIND_AUTO_CREATE); + + mErrorConnectionID = 0; + mDismissedConnectionID = 0; + if (savedInstanceState != null && savedInstanceState.containsKey(KEY_ERROR_CONNECTION_ID)) + { + mErrorConnectionID = (Long)savedInstanceState.getSerializable(KEY_ERROR_CONNECTION_ID); + mDismissedConnectionID = (Long)savedInstanceState.getSerializable(KEY_DISMISSED_CONNECTION_ID); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + + outState.putSerializable(KEY_ERROR_CONNECTION_ID, mErrorConnectionID); + outState.putSerializable(KEY_DISMISSED_CONNECTION_ID, mDismissedConnectionID); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) + { + View view = inflater.inflate(R.layout.vpn_state_fragment, null); + + mActionButton = (Button)view.findViewById(R.id.action); + mActionButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) + { + if (mService != null) + { + mService.disconnect(); + } + } + }); + enableActionButton(false); + + mStateView = (TextView)view.findViewById(R.id.vpn_state); + stateBaseColor = mStateView.getCurrentTextColor(); + mProfileView = (TextView)view.findViewById(R.id.vpn_profile_label); + mProfileNameView = (TextView)view.findViewById(R.id.vpn_profile_name); + + return view; + } + + @Override + public void onStart() + { + super.onStart(); + if (mService != null) + { + mService.registerListener(this); + updateView(); + } + } + + @Override + public void onStop() + { + super.onStop(); + if (mService != null) + { + mService.unregisterListener(this); + } + hideErrorDialog(); + hideProgressDialog(); + } + + @Override + public void onDestroy() + { + super.onDestroy(); + if (mService != null) + { + getActivity().getApplicationContext().unbindService(mServiceConnection); + } + } + + @Override + public void stateChanged() + { + updateView(); + } + + public void updateView() + { + long connectionID = mService.getConnectionID(); + VpnProfile profile = mService.getProfile(); + State state = mService.getState(); + ErrorState error = mService.getErrorState(); + ImcState imcState = mService.getImcState(); + String name = "", gateway = ""; + + if (profile != null) + { + name = profile.getName(); + gateway = profile.getGateway(); + } + + if (reportError(connectionID, name, error, imcState)) + { + return; + } + + enableActionButton(false); + mProfileNameView.setText(name); + + switch (state) + { + case DISABLED: + showProfile(false); + hideProgressDialog(); + mStateView.setText(R.string.state_disabled); + mStateView.setTextColor(stateBaseColor); + break; + case CONNECTING: + showProfile(true); + showConnectDialog(name, gateway); + mStateView.setText(R.string.state_connecting); + mStateView.setTextColor(stateBaseColor); + break; + case CONNECTED: + showProfile(true); + hideProgressDialog(); + enableActionButton(true); + mStateView.setText(R.string.state_connected); + mStateView.setTextColor(getResources().getColor(R.color.success_text)); + break; + case DISCONNECTING: + showProfile(true); + showDisconnectDialog(name); + mStateView.setText(R.string.state_disconnecting); + mStateView.setTextColor(stateBaseColor); + break; + } + } + + private boolean reportError(long connectionID, String name, ErrorState error, ImcState imcState) + { + if (connectionID > mDismissedConnectionID) + { /* report error if it hasn't been dismissed yet */ + mErrorConnectionID = connectionID; + } + else + { /* ignore all other errors */ + error = ErrorState.NO_ERROR; + } + if (error == ErrorState.NO_ERROR) + { + hideErrorDialog(); + return false; + } + else if (mErrorDialog != null) + { /* we already show the dialog */ + return true; + } + hideProgressDialog(); + mProfileNameView.setText(name); + showProfile(true); + enableActionButton(false); + mStateView.setText(R.string.state_error); + mStateView.setTextColor(getResources().getColor(R.color.error_text)); + switch (error) + { + case AUTH_FAILED: + if (imcState == ImcState.BLOCK) + { + showErrorDialog(R.string.error_assessment_failed); + } + else + { + showErrorDialog(R.string.error_auth_failed); + } + break; + case PEER_AUTH_FAILED: + showErrorDialog(R.string.error_peer_auth_failed); + break; + case LOOKUP_FAILED: + showErrorDialog(R.string.error_lookup_failed); + break; + case UNREACHABLE: + showErrorDialog(R.string.error_unreachable); + break; + default: + showErrorDialog(R.string.error_generic); + break; + } + return true; + } + + private void showProfile(boolean show) + { + mProfileView.setVisibility(show ? View.VISIBLE : View.GONE); + mProfileNameView.setVisibility(show ? View.VISIBLE : View.GONE); + } + + private void enableActionButton(boolean enable) + { + mActionButton.setEnabled(enable); + mActionButton.setVisibility(enable ? View.VISIBLE : View.GONE); + } + + private void hideProgressDialog() + { + if (mProgressDialog != null) + { + mProgressDialog.dismiss(); + mProgressDialog = null; + mDisconnectDialog = mConnectDialog = null; + } + } + + private void hideErrorDialog() + { + if (mErrorDialog != null) + { + mErrorDialog.dismiss(); + mErrorDialog = null; + } + } + + private void clearError() + { + if (mService != null) + { + mService.disconnect(); + } + mDismissedConnectionID = mErrorConnectionID; + updateView(); + } + + private void showConnectDialog(String profile, String gateway) + { + if (mConnectDialog != null) + { /* already showing the dialog */ + return; + } + hideProgressDialog(); + mConnectDialog = new ProgressDialog(getActivity()); + mConnectDialog.setTitle(String.format(getString(R.string.connecting_title), profile)); + mConnectDialog.setMessage(String.format(getString(R.string.connecting_message), gateway)); + mConnectDialog.setIndeterminate(true); + mConnectDialog.setCancelable(false); + mConnectDialog.setButton(DialogInterface.BUTTON_NEGATIVE, + getString(android.R.string.cancel), + new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + if (mService != null) + { + mService.disconnect(); + } + } + }); + mConnectDialog.show(); + mProgressDialog = mConnectDialog; + } + + private void showDisconnectDialog(String profile) + { + if (mDisconnectDialog != null) + { /* already showing the dialog */ + return; + } + hideProgressDialog(); + mDisconnectDialog = new ProgressDialog(getActivity()); + mDisconnectDialog.setMessage(getString(R.string.state_disconnecting)); + mDisconnectDialog.setIndeterminate(true); + mDisconnectDialog.setCancelable(false); + mDisconnectDialog.show(); + mProgressDialog = mDisconnectDialog; + } + + private void showErrorDialog(int textid) + { + final List instructions = mService.getRemediationInstructions(); + final boolean show_instructions = mService.getImcState() == ImcState.BLOCK && !instructions.isEmpty(); + int text = show_instructions ? R.string.show_remediation_instructions : R.string.show_log; + + mErrorDialog = new AlertDialog.Builder(getActivity()) + .setMessage(getString(R.string.error_introduction) + " " + getString(textid)) + .setCancelable(false) + .setNeutralButton(text, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + clearError(); + dialog.dismiss(); + Intent intent; + if (show_instructions) + { + intent = new Intent(getActivity(), RemediationInstructionsActivity.class); + intent.putParcelableArrayListExtra(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS, + new ArrayList(instructions)); + } + else + { + intent = new Intent(getActivity(), LogActivity.class); + } + startActivity(intent); + } + }) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) + { + clearError(); + dialog.dismiss(); + } + }).create(); + mErrorDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) + { + mErrorDialog = null; + } + }); + mErrorDialog.show(); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/RemediationInstructionAdapter.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/RemediationInstructionAdapter.java new file mode 100644 index 000000000..e9ab52287 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/RemediationInstructionAdapter.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2013 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.adapter; + +import java.util.List; + +import org.strongswan.android.R; +import org.strongswan.android.logic.imc.RemediationInstruction; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +public class RemediationInstructionAdapter extends ArrayAdapter +{ + public RemediationInstructionAdapter(Context context) + { + super(context, 0); + } + + /** + * Set new data for this adapter. + * + * @param data the new data (null to clear) + */ + public void setData(List data) + { + clear(); + if (data != null) + { + addAll(data); + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + View view; + if (convertView != null) + { + view = convertView; + } + else + { + LayoutInflater inflater = LayoutInflater.from(getContext()); + view = inflater.inflate(R.layout.remediation_instruction_item, parent, false); + } + RemediationInstruction item = getItem(position); + TextView text = (TextView)view.findViewById(android.R.id.text1); + text.setText(item.getTitle()); + text = (TextView)view.findViewById(android.R.id.text2); + text.setText(item.getDescription()); + return view; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java new file mode 100644 index 000000000..3795bb199 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012 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.adapter; + +import java.util.List; + +import org.strongswan.android.R; +import org.strongswan.android.security.TrustedCertificateEntry; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +public class TrustedCertificateAdapter extends ArrayAdapter +{ + public TrustedCertificateAdapter(Context context) + { + super(context, R.layout.trusted_certificates_item); + } + + /** + * Set new data for this adapter. + * + * @param data the new data (null to clear) + */ + public void setData(List data) + { + clear(); + if (data != null) + { + addAll(data); + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + View view; + if (convertView != null) + { + view = convertView; + } + else + { + LayoutInflater inflater = LayoutInflater.from(getContext()); + view = inflater.inflate(R.layout.trusted_certificates_item, parent, false); + } + TrustedCertificateEntry item = getItem(position); + TextView text = (TextView)view.findViewById(R.id.subject_primary); + text.setText(item.getSubjectPrimary()); + text = (TextView)view.findViewById(R.id.subject_secondary); + text.setText(item.getSubjectSecondary()); + return view; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java new file mode 100644 index 000000000..f3bb271bc --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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.adapter; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnType.VpnTypeFeature; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +public class VpnProfileAdapter extends ArrayAdapter +{ + private final int resource; + private final List items; + + public VpnProfileAdapter(Context context, int resource, + List items) + { + super(context, resource, items); + this.resource = resource; + this.items = items; + sortItems(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + View vpnProfileView; + if (convertView != null) + { + vpnProfileView = convertView; + } + else + { + LayoutInflater inflater = LayoutInflater.from(getContext()); + vpnProfileView = inflater.inflate(resource, null); + } + VpnProfile profile = getItem(position); + TextView tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_name); + tv.setText(profile.getName()); + tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_gateway); + tv.setText(getContext().getString(R.string.profile_gateway_label) + " " + profile.getGateway()); + tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_username); + if (profile.getVpnType().has(VpnTypeFeature.USER_PASS)) + { /* if the view is reused we make sure it is visible */ + tv.setVisibility(View.VISIBLE); + tv.setText(getContext().getString(R.string.profile_username_label) + " " + profile.getUsername()); + } + else + { + tv.setVisibility(View.GONE); + } + tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_certificate); + if (profile.getVpnType().has(VpnTypeFeature.CERTIFICATE)) + { + tv.setText(getContext().getString(R.string.profile_user_certificate_label) + " " + profile.getUserCertificateAlias()); + tv.setVisibility(View.VISIBLE); + } + else + { + tv.setVisibility(View.GONE); + } + return vpnProfileView; + } + + @Override + public void notifyDataSetChanged() + { + sortItems(); + super.notifyDataSetChanged(); + } + + private void sortItems() + { + Collections.sort(this.items, new Comparator() { + @Override + public int compare(VpnProfile lhs, VpnProfile rhs) + { + return lhs.getName().compareToIgnoreCase(rhs.getName()); + } + }); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/BufferedByteWriter.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/BufferedByteWriter.java new file mode 100644 index 000000000..efc728377 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/BufferedByteWriter.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2013 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; + +import java.nio.ByteBuffer; + +/** + * Very similar to ByteBuffer (although with a stripped interface) but it + * automatically resizes the underlying buffer. + */ +public class BufferedByteWriter +{ + /** + * The underlying byte buffer + */ + private byte[] mBuffer; + + /** + * ByteBuffer used as wrapper around the buffer to easily convert values + */ + private ByteBuffer mWriter; + + /** + * Create a writer with a default initial capacity + */ + public BufferedByteWriter() + { + this(0); + } + + /** + * Create a writer with the given initial capacity (helps avoid expensive + * resizing if known). + * @param capacity initial capacity + */ + public BufferedByteWriter(int capacity) + { + capacity = capacity > 4 ? capacity : 32; + mBuffer = new byte[capacity]; + mWriter = ByteBuffer.wrap(mBuffer); + } + + /** + * Ensure that there is enough space available to write the requested + * number of bytes. If necessary the internal buffer is resized. + * @param required required number of bytes + */ + private void ensureCapacity(int required) + { + if (mWriter.remaining() >= required) + { + return; + } + byte[] buffer = new byte[(mBuffer.length + required) * 2]; + System.arraycopy(mBuffer, 0, buffer, 0, mWriter.position()); + mBuffer = buffer; + ByteBuffer writer = ByteBuffer.wrap(buffer); + writer.position(mWriter.position()); + mWriter = writer; + } + + /** + * Write the given byte array to the buffer + * @param value + * @return the writer + */ + public BufferedByteWriter put(byte[] value) + { + ensureCapacity(value.length); + mWriter.put(value); + return this; + } + + /** + * Write the given byte to the buffer + * @param value + * @return the writer + */ + public BufferedByteWriter put(byte value) + { + ensureCapacity(1); + mWriter.put(value); + return this; + } + + /** + * Write the 8-bit length of the given data followed by the data itself + * @param value + * @return the writer + */ + public BufferedByteWriter putLen8(byte[] value) + { + ensureCapacity(1 + value.length); + mWriter.put((byte)value.length); + mWriter.put(value); + return this; + } + + /** + * Write the 16-bit length of the given data followed by the data itself + * @param value + * @return the writer + */ + public BufferedByteWriter putLen16(byte[] value) + { + ensureCapacity(2 + value.length); + mWriter.putShort((short)value.length); + mWriter.put(value); + return this; + } + + /** + * Write the given short value (16-bit) in big-endian order to the buffer + * @param value + * @return the writer + */ + public BufferedByteWriter put16(short value) + { + ensureCapacity(2); + mWriter.putShort(value); + return this; + } + + /** + * Write 24-bit of the given value in big-endian order to the buffer + * @param value + * @return the writer + */ + public BufferedByteWriter put24(int value) + { + ensureCapacity(3); + mWriter.put((byte)(value >> 16)); + mWriter.putShort((short)value); + return this; + } + + /** + * Write the given int value (32-bit) in big-endian order to the buffer + * @param value + * @return the writer + */ + public BufferedByteWriter put32(int value) + { + ensureCapacity(4); + mWriter.putInt(value); + return this; + } + + /** + * Write the given long value (64-bit) in big-endian order to the buffer + * @param value + * @return the writer + */ + public BufferedByteWriter put64(long value) + { + ensureCapacity(8); + mWriter.putLong(value); + return this; + } + + /** + * Convert the internal buffer to a new byte array. + * @return byte array + */ + public byte[] toByteArray() + { + int length = mWriter.position(); + byte[] bytes = new byte[length]; + System.arraycopy(mBuffer, 0, bytes, 0, length); + return bytes; + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/SettingsWriter.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/SettingsWriter.java new file mode 100644 index 000000000..01c0ab8a8 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/SettingsWriter.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2015 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; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import java.util.regex.Pattern; + + +/** + * Simple generator for data/files that may be parsed by libstrongswan's + * settings_t class. + */ +public class SettingsWriter +{ + /** + * Top-level section + */ + private final SettingsSection mTop = new SettingsSection(); + + /** + * Set a string value + * @param key + * @param value + * @return the writer + */ + public SettingsWriter setValue(String key, String value) + { + Pattern pattern = Pattern.compile("[^#{}=\"\\n\\t ]+"); + if (key == null || !pattern.matcher(key).matches()) + { + return this; + } + String[] keys = key.split("\\."); + SettingsSection section = mTop; + section = findOrCreateSection(Arrays.copyOfRange(keys, 0, keys.length-1)); + section.Settings.put(keys[keys.length-1], value); + return this; + } + + /** + * Set an integer value + * @param key + * @param value + * @return the writer + */ + public SettingsWriter setValue(String key, Integer value) + { + return setValue(key, value == null ? null : value.toString()); + } + + /** + * Set a boolean value + * @param key + * @param value + * @return the writer + */ + public SettingsWriter setValue(String key, Boolean value) + { + return setValue(key, value == null ? null : value ? "1" : "0"); + } + + /** + * Serializes the settings to a string in the format understood by + * libstrongswan's settings_t parser. + * @return serialized settings + */ + public String serialize() + { + StringBuilder builder = new StringBuilder(); + serializeSection(mTop, builder); + return builder.toString(); + } + + /** + * Serialize the settings in a section and recursively serialize sub-sections + * @param section + * @param builder + */ + private void serializeSection(SettingsSection section, StringBuilder builder) + { + for (Entry setting : section.Settings.entrySet()) + { + builder.append(setting.getKey()).append('='); + if (setting.getValue() != null) + { + builder.append("\"").append(escapeValue(setting.getValue())).append("\""); + } + builder.append('\n'); + } + + for (Entry subsection : section.Sections.entrySet()) + { + builder.append(subsection.getKey()).append(" {\n"); + serializeSection(subsection.getValue(), builder); + builder.append("}\n"); + } + } + + /** + * Escape value so it may be wrapped in " + * @param value + * @return + */ + private String escapeValue(String value) + { + return value.replace("\"", "\\\""); + } + + /** + * Find or create the nested sections with the given names + * @param sections list of section names + * @return final section + */ + private SettingsSection findOrCreateSection(String[] sections) + { + SettingsSection section = mTop; + for (String name : sections) + { + SettingsSection subsection = section.Sections.get(name); + if (subsection == null) + { + subsection = new SettingsSection(); + section.Sections.put(name, subsection); + } + section = subsection; + } + return section; + } + + /** + * A section containing sub-sections and settings. + */ + private class SettingsSection + { + /** + * Assigned key/value pairs + */ + LinkedHashMap Settings = new LinkedHashMap(); + + /** + * Assigned sub-sections + */ + LinkedHashMap Sections = new LinkedHashMap(); + } +} diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Utils.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Utils.java new file mode 100644 index 000000000..b5c447f31 --- /dev/null +++ b/src/frontends/android/app/src/main/java/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); + } +} diff --git a/src/frontends/android/app/src/main/jni/.gitignore b/src/frontends/android/app/src/main/jni/.gitignore new file mode 100644 index 000000000..ca5cf16ed --- /dev/null +++ b/src/frontends/android/app/src/main/jni/.gitignore @@ -0,0 +1,2 @@ +openssl +strongswan diff --git a/src/frontends/android/app/src/main/jni/Android.mk b/src/frontends/android/app/src/main/jni/Android.mk new file mode 100644 index 000000000..1fb233b48 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/Android.mk @@ -0,0 +1,84 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +# use "bring your own device" (BYOD) features (also see USE_BYOD in +# MainActivity.java) +strongswan_USE_BYOD := true + +strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \ + pkcs1 pkcs8 pem xcbc hmac socket-default \ + eap-identity eap-mschapv2 eap-md5 eap-gtc eap-tls + +ifneq ($(strongswan_USE_BYOD),) +strongswan_BYOD_PLUGINS := eap-ttls eap-tnc tnc-imc tnc-tnccs tnccs-20 +endif + +strongswan_PLUGINS := $(strongswan_CHARON_PLUGINS) \ + $(strongswan_BYOD_PLUGINS) + +include $(LOCAL_PATH)/strongswan/Android.common.mk + +# includes +strongswan_PATH := $(LOCAL_PATH)/strongswan +openssl_PATH := $(LOCAL_PATH)/openssl/include + +# CFLAGS (partially from a configure run using droid-gcc) +strongswan_CFLAGS := \ + -Wall \ + -Wextra \ + -Wno-format \ + -Wno-pointer-sign \ + -Wno-pointer-arith \ + -Wno-sign-compare \ + -Wno-strict-aliasing \ + -Wno-unused-parameter \ + -DHAVE___BOOL \ + -DHAVE_STDBOOL_H \ + -DHAVE_ALLOCA_H \ + -DHAVE_ALLOCA \ + -DHAVE_CLOCK_GETTIME \ + -DHAVE_DLADDR \ + -DHAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC \ + -DHAVE_PRCTL \ + -DHAVE_LINUX_UDP_H \ + -DHAVE_STRUCT_SADB_X_POLICY_SADB_X_POLICY_PRIORITY \ + -DHAVE_IPSEC_MODE_BEET \ + -DHAVE_IPSEC_DIR_FWD \ + -DHAVE_IN6ADDR_ANY \ + -DHAVE_NETINET_IP6_H \ + -DOPENSSL_NO_ENGINE \ + -DCONFIG_H_INCLUDED \ + -DCAPABILITIES \ + -DCAPABILITIES_NATIVE \ + -DMONOLITHIC \ + -DUSE_IKEV1 \ + -DUSE_IKEV2 \ + -DUSE_BUILTIN_PRINTF \ + -DDEBUG \ + -DCHARON_UDP_PORT=0 \ + -DCHARON_NATT_PORT=0 \ + -DVERSION=\"$(strongswan_VERSION)\" \ + -DDEV_RANDOM=\"/dev/random\" \ + -DDEV_URANDOM=\"/dev/urandom\" + +ifneq ($(strongswan_USE_BYOD),) +strongswan_CFLAGS += -DUSE_BYOD +endif + +strongswan_BUILD := \ + openssl \ + libandroidbridge \ + strongswan/src/libipsec \ + strongswan/src/libcharon \ + strongswan/src/libhydra \ + strongswan/src/libstrongswan + +ifneq ($(strongswan_USE_BYOD),) +strongswan_BUILD += \ + strongswan/src/libtnccs \ + strongswan/src/libtncif \ + strongswan/src/libimcv +endif + +include $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \ + $(strongswan_BUILD))) diff --git a/src/frontends/android/app/src/main/jni/Application.mk b/src/frontends/android/app/src/main/jni/Application.mk new file mode 100644 index 000000000..9fa668354 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/Application.mk @@ -0,0 +1,3 @@ +# select the ABI(s) to build for (see CPU-ARCH-ABIS.html in the NDK docs). +APP_ABI := armeabi x86 mips +APP_PLATFORM := android-19 diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/Android.mk b/src/frontends/android/app/src/main/jni/libandroidbridge/Android.mk new file mode 100644 index 000000000..c56b8d5f0 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/Android.mk @@ -0,0 +1,63 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +# copy-n-paste from Makefile.am +LOCAL_SRC_FILES := \ +android_jni.c \ +backend/android_attr.c \ +backend/android_creds.c \ +backend/android_dns_proxy.c \ +backend/android_private_key.c \ +backend/android_service.c \ +charonservice.c \ +kernel/android_ipsec.c \ +kernel/android_net.c \ +kernel/network_manager.c \ +vpnservice_builder.c + +ifneq ($(strongswan_USE_BYOD),) +LOCAL_SRC_FILES += \ +byod/imc_android_state.c \ +byod/imc_android.c +endif + +# build libandroidbridge ------------------------------------------------------- + +LOCAL_C_INCLUDES += \ + $(strongswan_PATH)/src/libipsec \ + $(strongswan_PATH)/src/libhydra \ + $(strongswan_PATH)/src/libcharon \ + $(strongswan_PATH)/src/libstrongswan + +ifneq ($(strongswan_USE_BYOD),) +LOCAL_C_INCLUDES += \ + $(strongswan_PATH)/src/libimcv \ + $(strongswan_PATH)/src/libtncif \ + $(strongswan_PATH)/src/libtnccs \ + $(strongswan_PATH)/src/libtls +endif + +LOCAL_CFLAGS := $(strongswan_CFLAGS) \ + -DPLUGINS='"$(strongswan_CHARON_PLUGINS)"' + +ifneq ($(strongswan_USE_BYOD),) +LOCAL_CFLAGS += -DPLUGINS_BYOD='"$(strongswan_BYOD_PLUGINS)"' +endif + +LOCAL_MODULE := libandroidbridge + +LOCAL_MODULE_TAGS := optional + +LOCAL_ARM_MODE := arm + +LOCAL_PRELINK_MODULE := false + +LOCAL_LDLIBS := -llog + +LOCAL_SHARED_LIBRARIES := libstrongswan libhydra libipsec libcharon + +ifneq ($(strongswan_USE_BYOD),) +LOCAL_SHARED_LIBRARIES += libimcv libtncif libtnccs +endif + +include $(BUILD_SHARED_LIBRARY) diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.c b/src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.c new file mode 100644 index 000000000..a6412bdf7 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.c @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2012-2015 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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. + */ + +#include + +#include "android_jni.h" + +#include +#include + +/** + * JVM + */ +static JavaVM *android_jvm; + +static struct { + char name[32]; + void *handle; +} libs[] = { + { "libstrongswan.so", NULL }, +#ifdef USE_BYOD + { "libtncif.so", NULL }, + { "libtnccs.so", NULL }, + { "libimcv.so", NULL }, +#endif + { "libhydra.so", NULL }, + { "libcharon.so", NULL }, + { "libipsec.so", NULL }, +}; + +jclass *android_charonvpnservice_class; +jclass *android_charonvpnservice_builder_class; +android_sdk_version_t android_sdk_version; + +/** + * Thread-local variable. Only used because of the destructor + */ +static thread_value_t *androidjni_threadlocal; + +/** + * Thread-local destructor to ensure that a native thread is detached + * from the JVM even if androidjni_detach_thread() is not called. + */ +static void attached_thread_cleanup(void *arg) +{ + (*android_jvm)->DetachCurrentThread(android_jvm); +} + +/* + * Described in header + */ +void androidjni_attach_thread(JNIEnv **env) +{ + if ((*android_jvm)->GetEnv(android_jvm, (void**)env, + JNI_VERSION_1_6) == JNI_OK) + { /* already attached or even a Java thread */ + return; + } + (*android_jvm)->AttachCurrentThread(android_jvm, env, NULL); + /* use a thread-local value with a destructor that automatically detaches + * the thread from the JVM before it terminates, if not done manually */ + androidjni_threadlocal->set(androidjni_threadlocal, (void*)*env); +} + +/* + * Described in header + */ +void androidjni_detach_thread() +{ + if (androidjni_threadlocal->get(androidjni_threadlocal)) + { /* only do this if we actually attached this thread */ + androidjni_threadlocal->set(androidjni_threadlocal, NULL); + (*android_jvm)->DetachCurrentThread(android_jvm); + } +} + +/** + * Called when this library is loaded by the JVM + */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) +{ + JNIEnv *env; + jclass jversion; + jfieldID jsdk_int; + int i; + + android_jvm = vm; + + if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) + { + return -1; + } + + for (i = 0; i < countof(libs); i++) + { + libs[i].handle = dlopen(libs[i].name, RTLD_GLOBAL); + if (!libs[i].handle) + { + return -1; + } + } + + androidjni_threadlocal = thread_value_create(attached_thread_cleanup); + + android_charonvpnservice_class = + (*env)->NewGlobalRef(env, (*env)->FindClass(env, + JNI_PACKAGE_STRING "/CharonVpnService")); + android_charonvpnservice_builder_class = + (*env)->NewGlobalRef(env, (*env)->FindClass(env, + JNI_PACKAGE_STRING "/CharonVpnService$BuilderAdapter")); + + jversion = (*env)->FindClass(env, "android/os/Build$VERSION"); + jsdk_int = (*env)->GetStaticFieldID(env, jversion, "SDK_INT", "I"); + android_sdk_version = (*env)->GetStaticIntField(env, jversion, jsdk_int); + + return JNI_VERSION_1_6; +} + +/** + * Called when this library is unloaded by the JVM (which never happens on + * Android) + */ +void JNI_OnUnload(JavaVM *vm, void *reserved) +{ + int i; + + androidjni_threadlocal->destroy(androidjni_threadlocal); + + for (i = countof(libs) - 1; i >= 0; i--) + { + if (libs[i].handle) + { + dlclose(libs[i].handle); + } + } +} + diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.h b/src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.h new file mode 100644 index 000000000..b08670f7e --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/android_jni.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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. + */ + +/** + * @defgroup android_jni android_jni + * @{ @ingroup libandroidbridge + */ + +#ifndef ANDROID_JNI_H_ +#define ANDROID_JNI_H_ + +#include +#include + +#define JNI_PACKAGE org_strongswan_android_logic +#define JNI_PACKAGE_STRING "org/strongswan/android/logic" + +#define JNI_METHOD_PP(pack, klass, name, ret, ...) \ + ret Java_##pack##_##klass##_##name(JNIEnv *env, jobject this, ##__VA_ARGS__) + +#define JNI_METHOD_P(pack, klass, name, ret, ...) \ + JNI_METHOD_PP(pack, klass, name, ret, ##__VA_ARGS__) + +#define JNI_METHOD(klass, name, ret, ...) \ + JNI_METHOD_P(JNI_PACKAGE, klass, name, ret, ##__VA_ARGS__) + +/** + * Java classes + * Initialized in JNI_OnLoad() + */ +extern jclass *android_charonvpnservice_class; +extern jclass *android_charonvpnservice_builder_class; + +/** + * Currently known (supported) SDK versions + * + * see android.os.Build.VERSION_CODES for definitions + */ +typedef enum { + ANDROID_ICE_CREAM_SANDWICH = 14, + ANDROID_ICE_CREAM_SANDWICH_MR1 = 15, + ANDROID_JELLY_BEAN = 16, + ANDROID_JELLY_BEAN_MR1 = 17, + ANDROID_JELLY_BEAN_MR2 = 18, +} android_sdk_version_t; + +/** + * The current SDK version of the Android framework + * + * see android.os.Build.VERSION.SDK_INT + */ +extern android_sdk_version_t android_sdk_version; + +/** + * Attach the current thread to the JVM + * + * As local JNI references are not freed until the thread detaches + * androidjni_detach_thread() should be called as soon as possible. + * If it is not called a thread-local destructor ensures that the + * thread is at least detached as soon as it terminates. + * + * @param env JNIEnv + */ +void androidjni_attach_thread(JNIEnv **env); + +/** + * Detach the current thread from the JVM + * + * Call this as soon as possible to ensure that local JNI references are freed. + */ +void androidjni_detach_thread(); + +/** + * Handle exceptions thrown by a JNI call + * + * @param env JNIEnv + * @return TRUE if an exception was thrown + */ +static inline bool androidjni_exception_occurred(JNIEnv *env) +{ + if ((*env)->ExceptionOccurred(env)) + { /* clear any exception, otherwise the VM is terminated */ + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + return TRUE; + } + return FALSE; +} + +/** + * Convert a Java string to a C string. Memory is allocated. + * + * @param env JNIEnv + * @param jstr Java string + * @return native C string (allocated) + */ +static inline char *androidjni_convert_jstring(JNIEnv *env, jstring jstr) +{ + char *str = NULL; + jsize bytes, chars; + + if (jstr) + { + chars = (*env)->GetStringLength(env, jstr); + bytes = (*env)->GetStringUTFLength(env, jstr); + str = malloc(bytes + 1); + (*env)->GetStringUTFRegion(env, jstr, 0, chars, str); + str[bytes] = '\0'; + } + return str; +} + +/** + * Converts the given Java byte array to a chunk + * + * @param env JNIEnv + * @param jbytearray Java byte array + * @return allocated chunk + */ +static inline chunk_t chunk_from_byte_array(JNIEnv *env, jbyteArray jbytearray) +{ + chunk_t chunk; + + chunk = chunk_alloc((*env)->GetArrayLength(env, jbytearray)); + (*env)->GetByteArrayRegion(env, jbytearray, 0, chunk.len, chunk.ptr); + return chunk; +} + +/** + * Converts the given chunk to a Java byte array + * + * @param env JNIEnv + * @param chunk native chunk + * @return allocated Java byte array + */ +static inline jbyteArray byte_array_from_chunk(JNIEnv *env, chunk_t chunk) +{ + jbyteArray jbytearray; + + jbytearray = (*env)->NewByteArray(env, chunk.len); + (*env)->SetByteArrayRegion(env, jbytearray, 0, chunk.len, chunk.ptr); + return jbytearray; +} + +#endif /** ANDROID_JNI_H_ @}*/ diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_attr.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_attr.c new file mode 100644 index 000000000..645b3fa9b --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_attr.c @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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. + */ + +#include "android_attr.h" +#include "../charonservice.h" + +#include +#include +#include + +typedef struct private_android_attr_t private_android_attr_t; + +/** + * Private data of an android_attr_t object. + */ +struct private_android_attr_t { + + /** + * Public interface. + */ + android_attr_t public; +}; + +METHOD(attribute_handler_t, handle, bool, + private_android_attr_t *this, ike_sa_t *ike_sa, + configuration_attribute_type_t type, chunk_t data) +{ + vpnservice_builder_t *builder; + host_t *dns; + + switch (type) + { + case INTERNAL_IP4_DNS: + dns = host_create_from_chunk(AF_INET, data, 0); + break; + case INTERNAL_IP6_DNS: + dns = host_create_from_chunk(AF_INET6, data, 0); + break; + default: + return FALSE; + } + + if (!dns || dns->is_anyaddr(dns)) + { + DESTROY_IF(dns); + return FALSE; + } + + builder = charonservice->get_vpnservice_builder(charonservice); + builder->add_dns(builder, dns); + dns->destroy(dns); + return TRUE; +} + +METHOD(attribute_handler_t, release, void, + private_android_attr_t *this, ike_sa_t *ike_sa, + configuration_attribute_type_t type, chunk_t data) +{ + /* DNS servers cannot be removed from an existing TUN device */ +} + +METHOD(enumerator_t, enumerate_dns6, bool, + enumerator_t *this, configuration_attribute_type_t *type, chunk_t *data) +{ + *type = INTERNAL_IP6_DNS; + *data = chunk_empty; + this->enumerate = (void*)return_false; + return TRUE; +} + +METHOD(enumerator_t, enumerate_dns4, bool, + enumerator_t *this, configuration_attribute_type_t *type, chunk_t *data) +{ + *type = INTERNAL_IP4_DNS; + *data = chunk_empty; + this->enumerate = (void*)_enumerate_dns6; + return TRUE; +} + +METHOD(attribute_handler_t, create_attribute_enumerator, enumerator_t*, + private_android_attr_t *this, ike_sa_t *ike_sa, linked_list_t *vips) +{ + enumerator_t *enumerator; + + INIT(enumerator, + .enumerate = (void*)_enumerate_dns4, + .destroy = (void*)free, + ); + return enumerator; +} + +METHOD(android_attr_t, destroy, void, + private_android_attr_t *this) +{ + free(this); +} + +/** + * Described in header + */ +android_attr_t *android_attr_create() +{ + private_android_attr_t *this; + + INIT(this, + .public = { + .handler = { + .handle = _handle, + .release = _release, + .create_attribute_enumerator = _create_attribute_enumerator, + }, + .destroy = _destroy, + }, + ); + + return &this->public; +} diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_attr.h b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_attr.h new file mode 100644 index 000000000..56b02e1ce --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_attr.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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. + */ + +/** + * @defgroup android_attr android_attr + * @{ @ingroup android_backend + */ + +#ifndef ANDROID_ATTR_H_ +#define ANDROID_ATTR_H_ + +#include +#include + +typedef struct android_attr_t android_attr_t; + +/** + * Handler for DNS configuration + */ +struct android_attr_t { + + /** + * implements the attribute_handler_t interface + */ + attribute_handler_t handler; + + /** + * Destroy a android_attr_t + */ + void (*destroy)(android_attr_t *this); +}; + +/** + * Create a android_attr_t instance. + */ +android_attr_t *android_attr_create(void); + +#endif /** ANDROID_ATTR_H_ @}*/ + diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.c new file mode 100644 index 000000000..ddc032638 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.c @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2012 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. + */ + +#include "android_creds.h" +#include "../charonservice.h" + +#include +#include +#include +#include + +typedef struct private_android_creds_t private_android_creds_t; + +/** + * Private data of an android_creds_t object + */ +struct private_android_creds_t { + + /** + * Public interface + */ + android_creds_t public; + + /** + * Credential set storing trusted certificates and user credentials + */ + mem_cred_t *creds; + + /** + * read/write lock to make sure certificates are only loaded once + */ + rwlock_t *lock; + + /** + * TRUE if certificates have been loaded via JNI + */ + bool loaded; +}; + +/** + * Free allocated DER encoding + */ +static void free_encoding(chunk_t *chunk) +{ + chunk_free(chunk); + free(chunk); +} + +/** + * Load trusted certificates via charonservice (JNI). + */ +static void load_trusted_certificates(private_android_creds_t *this) +{ + linked_list_t *certs; + certificate_t *cert; + chunk_t *current; + + certs = charonservice->get_trusted_certificates(charonservice); + if (certs) + { + while (certs->remove_first(certs, (void**)¤t) == SUCCESS) + { + cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_BLOB_ASN1_DER, *current, BUILD_END); + if (cert) + { + DBG2(DBG_CFG, "loaded CA certificate '%Y'", + cert->get_subject(cert)); + this->creds->add_cert(this->creds, TRUE, cert); + } + free_encoding(current); + } + certs->destroy(certs); + } +} + +METHOD(credential_set_t, create_cert_enumerator, enumerator_t*, + private_android_creds_t *this, certificate_type_t cert, key_type_t key, + identification_t *id, bool trusted) +{ + enumerator_t *enumerator; + + if (cert != CERT_ANY && cert != CERT_X509) + { + return NULL; + } + this->lock->read_lock(this->lock); + if (!this->loaded) + { + this->lock->unlock(this->lock); + this->lock->write_lock(this->lock); + /* check again after acquiring the write lock */ + if (!this->loaded) + { + load_trusted_certificates(this); + this->loaded = TRUE; + } + this->lock->unlock(this->lock); + this->lock->read_lock(this->lock); + } + enumerator = this->creds->set.create_cert_enumerator(&this->creds->set, + cert, key, id, trusted); + return enumerator_create_cleaner(enumerator, (void*)this->lock->unlock, + this->lock); +} + +METHOD(android_creds_t, add_username_password, void, + private_android_creds_t *this, char *username, char *password) +{ + shared_key_t *shared_key; + identification_t *id; + chunk_t secret; + + secret = chunk_create(password, strlen(password)); + shared_key = shared_key_create(SHARED_EAP, chunk_clone(secret)); + id = identification_create_from_string(username); + + this->creds->add_shared(this->creds, shared_key, id, NULL); +} + +METHOD(credential_set_t, create_shared_enumerator, enumerator_t*, + private_android_creds_t *this, shared_key_type_t type, + identification_t *me, identification_t *other) +{ + return this->creds->set.create_shared_enumerator(&this->creds->set, + type, me, other); +} + +METHOD(android_creds_t, load_user_certificate, certificate_t*, + private_android_creds_t *this) +{ + linked_list_t *encodings; + certificate_t *cert = NULL, *ca_cert; + chunk_t *current; + + encodings = charonservice->get_user_certificate(charonservice); + if (!encodings) + { + return NULL; + } + + while (encodings->remove_first(encodings, (void**)¤t) == SUCCESS) + { + if (!cert) + { /* the first element is the user certificate */ + cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_BLOB_ASN1_DER, *current, BUILD_END); + if (!cert) + { + DBG1(DBG_CFG, "failed to load user certificate"); + free_encoding(current); + break; + } + DBG1(DBG_CFG, "loaded user certificate '%Y' and private key", + cert->get_subject(cert)); + cert = this->creds->add_cert_ref(this->creds, TRUE, cert); + free_encoding(current); + continue; + } + /* the rest are CA certificates, we ignore failures */ + ca_cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_BLOB_ASN1_DER, *current, BUILD_END); + if (ca_cert) + { + DBG1(DBG_CFG, "loaded CA certificate '%Y'", + ca_cert->get_subject(ca_cert)); + this->creds->add_cert(this->creds, TRUE, ca_cert); + } + free_encoding(current); + } + encodings->destroy_function(encodings, (void*)free_encoding); + + if (cert) + { + private_key_t *key; + + key = charonservice->get_user_key(charonservice, + cert->get_public_key(cert)); + if (key) + { + this->creds->add_key(this->creds, key); + } + else + { + DBG1(DBG_CFG, "failed to load private key"); + cert->destroy(cert); + cert = NULL; + } + } + return cert; +} + +METHOD(credential_set_t, create_private_enumerator, enumerator_t*, + private_android_creds_t *this, key_type_t type, identification_t *id) +{ + return this->creds->set.create_private_enumerator(&this->creds->set, + type, id); +} + +METHOD(android_creds_t, clear, void, + private_android_creds_t *this) +{ + this->lock->write_lock(this->lock); + this->creds->clear(this->creds); + this->loaded = FALSE; + this->lock->unlock(this->lock); +} + +METHOD(android_creds_t, destroy, void, + private_android_creds_t *this) +{ + clear(this); + this->creds->destroy(this->creds); + this->lock->destroy(this->lock); + free(this); +} + +/** + * Described in header. + */ +android_creds_t *android_creds_create() +{ + private_android_creds_t *this; + + INIT(this, + .public = { + .set = { + .create_cert_enumerator = _create_cert_enumerator, + .create_shared_enumerator = _create_shared_enumerator, + .create_private_enumerator = _create_private_enumerator, + .create_cdp_enumerator = (void*)return_null, + .cache_cert = (void*)nop, + }, + .add_username_password = _add_username_password, + .load_user_certificate = _load_user_certificate, + .clear = _clear, + .destroy = _destroy, + }, + .creds = mem_cred_create(), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + ); + + return &this->public; +} diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.h b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.h new file mode 100644 index 000000000..918708f14 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_creds.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2012 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. + */ + +/** + * @defgroup android_creds android_creds + * @{ @ingroup android_backend + */ + +#ifndef ANDROID_CREDS_H_ +#define ANDROID_CREDS_H_ + +#include +#include + +typedef struct android_creds_t android_creds_t; + +/** + * Android credential set that provides CA certificates via JNI and supplied + * user credentials. + */ +struct android_creds_t { + + /** + * Implements credential_set_t + */ + credential_set_t set; + + /** + * Add user name and password for EAP authentication + * + * @param username user name + * @param password password + */ + void (*add_username_password)(android_creds_t *this, char *username, + char *password); + + /** + * Load the user certificate and private key + * + * @return loaded client certificate, NULL on failure + */ + certificate_t *(*load_user_certificate)(android_creds_t *this); + + /** + * Clear the cached certificates and stored credentials. + */ + void (*clear)(android_creds_t *this); + + /** + * Destroy a android_creds instance. + */ + void (*destroy)(android_creds_t *this); + +}; + +/** + * Create an android_creds instance. + */ +android_creds_t *android_creds_create(); + +#endif /** ANDROID_CREDS_H_ @}*/ + diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_dns_proxy.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_dns_proxy.c new file mode 100644 index 000000000..908e37238 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_dns_proxy.c @@ -0,0 +1,419 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "android_dns_proxy.h" + +#include +#include +#include +#include + +/** + * Timeout in seconds for sockets (i.e. not used for x seconds -> delete) + */ +#define SOCKET_TIMEOUT 30 + +typedef struct private_android_dns_proxy_t private_android_dns_proxy_t; + +struct private_android_dns_proxy_t { + + /** + * Public interface + */ + android_dns_proxy_t public; + + /** + * Mapping from source address to sockets + */ + hashtable_t *sockets; + + /** + * Registered callback + */ + dns_proxy_response_cb_t cb; + + /** + * Data passed to callback + */ + void *data; + + /** + * Lock used to synchronize access to the private members + */ + rwlock_t *lock; + + /** + * Hostnames to filter queries by + */ + hashtable_t *hostnames; +}; + +/** + * Data for proxy sockets + */ +typedef struct { + private_android_dns_proxy_t *proxy; + time_t last_use; + host_t *src; + int fd; +} proxy_socket_t; + +/** + * Destroy a socket + */ +static void socket_destroy(proxy_socket_t *this) +{ + this->src->destroy(this->src); + if (this->fd != -1) + { + close(this->fd); + } + free(this); +} + +/** + * Hash a proxy socket by src address + */ +static u_int socket_hash(host_t *src) +{ + u_int16_t port = src->get_port(src); + return chunk_hash_inc(src->get_address(src), + chunk_hash(chunk_from_thing(port))); +} + +/** + * Compare proxy sockets by src address + */ +static bool socket_equals(host_t *a, host_t *b) +{ + return a->equals(a, b); +} + +/** + * Opens a UDP socket for the given address family + */ +static int open_socket(int family) +{ + int skt; + + skt = socket(family, SOCK_DGRAM, IPPROTO_UDP); + if (skt < 0) + { + DBG1(DBG_NET, "could not open proxy socket: %s", strerror(errno)); + return -1; + } + if (!hydra->kernel_interface->bypass_socket(hydra->kernel_interface, + skt, family)) + { + DBG1(DBG_NET, "installing bypass policy for proxy socket failed"); + close(skt); + return -1; + } + return skt; +} + +/** + * Create a proxy socket for the given source + */ +static proxy_socket_t *create_socket(private_android_dns_proxy_t *this, + host_t *src) +{ + proxy_socket_t *skt; + + INIT(skt, + .proxy = this, + .src = src->clone(src), + .fd = open_socket(src->get_family(src)), + ); + if (skt->fd == -1) + { + socket_destroy(skt); + return NULL; + } + return skt; +} + +CALLBACK(handle_response, bool, + proxy_socket_t *this, int fd, watcher_event_t event) +{ + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + char buf[4096]; + ssize_t len; + host_t *src; + + len = recvfrom(fd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr*)&addr, + &addr_len); + if (len > 0) + { + ip_packet_t *packet; + + src = host_create_from_sockaddr((sockaddr_t*)&addr); + if (!src) + { + DBG1(DBG_NET, "failed to parse source address"); + return TRUE; + } + packet = ip_packet_create_udp_from_data(src, this->src, + chunk_create(buf, len)); + if (!packet) + { + DBG1(DBG_NET, "failed to parse DNS response"); + return TRUE; + } + this->proxy->lock->read_lock(this->proxy->lock); + this->last_use = time_monotonic(NULL); + if (this->proxy->cb) + { + this->proxy->cb(this->proxy->data, packet); + } + else + { + packet->destroy(packet); + } + this->proxy->lock->unlock(this->proxy->lock); + } + else if (errno != EWOULDBLOCK) + { + DBG1(DBG_NET, "receiving DNS response failed: %s", strerror(errno)); + } + return TRUE; +} + +CALLBACK(handle_timeout, job_requeue_t, + proxy_socket_t *this) +{ + time_t now, diff; + + now = time_monotonic(NULL); + this->proxy->lock->write_lock(this->proxy->lock); + diff = now - this->last_use; + if (diff >= SOCKET_TIMEOUT) + { + this->proxy->sockets->remove(this->proxy->sockets, this->src); + lib->watcher->remove(lib->watcher, this->fd); + this->proxy->lock->unlock(this->proxy->lock); + socket_destroy(this); + return JOB_REQUEUE_NONE; + } + this->proxy->lock->unlock(this->proxy->lock); + return JOB_RESCHEDULE(SOCKET_TIMEOUT - diff); +} + +/** + * DNS header and masks to access flags + */ +typedef struct __attribute__((packed)) { + u_int16_t id; + u_int16_t flags; +#define DNS_QR_MASK 0x8000 +#define DNS_OPCODE_MASK 0x7800 + u_int16_t qdcount; + u_int16_t ancount; + u_int16_t nscount; + u_int16_t arcount; +} dns_header_t; + +/** + * Extract the hostname in the question section data points to. + * Hostnames can be at most 255 characters (including dots separating labels), + * each label must be between 1 and 63 characters. + * The format is [len][label][len][label], followed by a null byte to indicate + * the null label of the root. + */ +static char *extract_hostname(chunk_t data) +{ + char *hostname, *pos, *end; + u_int8_t label; + + if (!data.len || data.len > 255) + { + return NULL; + } + label = *data.ptr; + data = chunk_skip(data, 1); + hostname = strndup(data.ptr, data.len); + /* replace all label lengths with dots */ + pos = hostname + label; + end = hostname + strlen(hostname); + while (pos < end) + { + label = *pos; + *pos++ = '.'; + pos += label; + } + return hostname; +} + +/** + * Check if the DNS query is for one of the allowed hostnames + */ +static bool check_hostname(private_android_dns_proxy_t *this, chunk_t data) +{ + dns_header_t *dns; + char *hostname; + bool success = FALSE; + + this->lock->read_lock(this->lock); + if (!this->hostnames->get_count(this->hostnames)) + { + this->lock->unlock(this->lock); + return TRUE; + } + if (data.len < sizeof(dns_header_t)) + { + this->lock->unlock(this->lock); + return FALSE; + } + dns = (dns_header_t*)data.ptr; + if ((ntohs(dns->flags) & DNS_QR_MASK) == 0 && + (ntohs(dns->flags) & DNS_OPCODE_MASK) == 0 && + dns->qdcount) + { + data = chunk_skip(data, sizeof(dns_header_t)); + hostname = extract_hostname(data); + if (hostname && this->hostnames->get(this->hostnames, hostname)) + { + success = TRUE; + } + free(hostname); + } + this->lock->unlock(this->lock); + return success; +} + +METHOD(android_dns_proxy_t, handle, bool, + private_android_dns_proxy_t *this, ip_packet_t *packet) +{ + proxy_socket_t *skt; + host_t *dst, *src; + chunk_t data; + + if (packet->get_next_header(packet) != IPPROTO_UDP) + { + return FALSE; + } + dst = packet->get_destination(packet); + if (dst->get_port(dst) != 53) + { /* no DNS packet */ + return FALSE; + } + data = packet->get_payload(packet); + /* remove UDP header */ + data = chunk_skip(data, 8); + if (!check_hostname(this, data)) + { + return FALSE; + } + src = packet->get_source(packet); + this->lock->write_lock(this->lock); + skt = this->sockets->get(this->sockets, src); + if (!skt) + { + skt = create_socket(this, src); + if (!skt) + { + this->lock->unlock(this->lock); + return FALSE; + } + this->sockets->put(this->sockets, skt->src, skt); + lib->watcher->add(lib->watcher, skt->fd, WATCHER_READ, handle_response, + skt); + lib->scheduler->schedule_job(lib->scheduler, + (job_t*)callback_job_create(handle_timeout, skt, + NULL, (callback_job_cancel_t)return_false), SOCKET_TIMEOUT); + } + skt->last_use = time_monotonic(NULL); + if (sendto(skt->fd, data.ptr, data.len, 0, dst->get_sockaddr(dst), + *dst->get_sockaddr_len(dst)) != data.len) + { + DBG1(DBG_NET, "sending DNS request failed: %s", strerror(errno)); + } + this->lock->unlock(this->lock); + return TRUE; +} + +METHOD(android_dns_proxy_t, register_cb, void, + private_android_dns_proxy_t *this, dns_proxy_response_cb_t cb, void *data) +{ + this->lock->write_lock(this->lock); + this->cb = cb; + this->data = data; + this->lock->unlock(this->lock); +} + +METHOD(android_dns_proxy_t, unregister_cb, void, + private_android_dns_proxy_t *this, dns_proxy_response_cb_t cb) +{ + this->lock->write_lock(this->lock); + if (this->cb == cb) + { + this->cb = NULL; + } + this->lock->unlock(this->lock); +} + +METHOD(android_dns_proxy_t, add_hostname, void, + private_android_dns_proxy_t *this, char *hostname) +{ + char *existing; + + hostname = strdup(hostname); + this->lock->write_lock(this->lock); + existing = this->hostnames->put(this->hostnames, hostname, hostname); + this->lock->unlock(this->lock); + free(existing); +} + +METHOD(android_dns_proxy_t, destroy, void, + private_android_dns_proxy_t *this) +{ + this->hostnames->destroy_function(this->hostnames, (void*)free); + this->sockets->destroy_function(this->sockets, (void*)socket_destroy); + this->lock->destroy(this->lock); + free(this); +} + +/** + * Described in header. + */ +android_dns_proxy_t *android_dns_proxy_create() +{ + private_android_dns_proxy_t *this; + + INIT(this, + .public = { + .handle = _handle, + .register_cb = _register_cb, + .unregister_cb = _unregister_cb, + .add_hostname = _add_hostname, + .destroy = _destroy, + }, + .sockets = hashtable_create((hashtable_hash_t)socket_hash, + (hashtable_equals_t)socket_equals, 4), + .hostnames = hashtable_create(hashtable_hash_str, + hashtable_equals_str, 4), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + ); + + return &this->public; +} diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_dns_proxy.h b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_dns_proxy.h new file mode 100644 index 000000000..481b060cf --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_dns_proxy.h @@ -0,0 +1,86 @@ +/* + * 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. + */ + +/** + * @defgroup android_dns_proxy android_dns_proxy + * @{ @ingroup android_backend + */ + +#ifndef ANDROID_DNS_PROXY_H_ +#define ANDROID_DNS_PROXY_H_ + +#include + +typedef struct android_dns_proxy_t android_dns_proxy_t; + +/** + * Callback called to deliver a DNS response packet. + * + * @param data data supplied during registration of the callback + * @param packet DNS response packet (has to be destroyed) + */ +typedef void (*dns_proxy_response_cb_t)(void *data, ip_packet_t *packet); + +/** + * DNS proxy class + */ +struct android_dns_proxy_t { + + /** + * Handle an outbound DNS packet (if the packet is one) + * + * @param packet packet to handle + * @return TRUE if handled, FALSE otherwise (no DNS) + */ + bool (*handle)(android_dns_proxy_t *this, ip_packet_t *packet); + + /** + * Register the callback used to deliver DNS response packets. + * + * @param cb the callback function + * @param data optional data provided to callback + */ + void (*register_cb)(android_dns_proxy_t *this, dns_proxy_response_cb_t cb, + void *data); + + /** + * Unregister the callback used to deliver DNS response packets. + * + * @param cb the callback function + * @param data optional data provided to callback + */ + void (*unregister_cb)(android_dns_proxy_t *this, dns_proxy_response_cb_t cb); + + /** + * Add a hostname for which queries are proxied. If at least one hostname + * is configured DNS queries for others will not be handled. + * + * @param hostname hostname to add (gets cloned) + */ + void (*add_hostname)(android_dns_proxy_t *this, char *hostname); + + /** + * Destroy an instance. + */ + void (*destroy)(android_dns_proxy_t *this); +}; + +/** + * Create an instance. + */ +android_dns_proxy_t *android_dns_proxy_create(); + +#endif /** ANDROID_DNS_PROXY_H_ @}*/ + diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_private_key.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_private_key.c new file mode 100644 index 000000000..769ea3f31 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_private_key.c @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2012-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. + */ + +#include "android_private_key.h" + +#include "../android_jni.h" +#include +#include + +typedef struct private_private_key_t private_private_key_t; + +/** + * Private data of a android_private_key_t object. + */ +struct private_private_key_t { + + /** + * Public interface + */ + private_key_t public; + + /** + * reference to the Java PrivateKey object + */ + jobject key; + + /** + * Java class used to build signatures + */ + jclass signature_class; + + /** + * public key that belongs to this private key + */ + public_key_t *pubkey; + + /** + * reference count + */ + refcount_t ref; +}; + +METHOD(private_key_t, sign, bool, + private_private_key_t *this, signature_scheme_t scheme, + chunk_t data, chunk_t *signature) +{ + JNIEnv *env; + jmethodID method_id; + const char *method = NULL; + jstring jmethod; + jobject jsignature; + jbyteArray jdata, jsigarray; + + switch (this->pubkey->get_type(this->pubkey)) + { + case KEY_RSA: + switch (scheme) + { + case SIGN_RSA_EMSA_PKCS1_NULL: + method = "NONEwithRSA"; + break; + 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 KEY_ECDSA: + switch (scheme) + { + case SIGN_ECDSA_WITH_SHA1_DER: + method = "SHA1withECDSA"; + break; + case SIGN_ECDSA_WITH_SHA256_DER: + case SIGN_ECDSA_256: + method = "SHA256withECDSA"; + break; + case SIGN_ECDSA_WITH_SHA384_DER: + case SIGN_ECDSA_384: + method = "SHA384withECDSA"; + break; + case SIGN_ECDSA_WITH_SHA512_DER: + case SIGN_ECDSA_521: + method = "SHA512withECDSA"; + break; + default: + break; + } + break; + default: + break; + } + if (!method) + { + DBG1(DBG_LIB, "signature scheme %N not supported via JNI", + signature_scheme_names, scheme); + return FALSE; + } + + androidjni_attach_thread(&env); + /* we use java.security.Signature to create the signature without requiring + * access to the actual private key */ + method_id = (*env)->GetStaticMethodID(env, this->signature_class, + "getInstance", "(Ljava/lang/String;)Ljava/security/Signature;"); + if (!method_id) + { + goto failed; + } + jmethod = (*env)->NewStringUTF(env, method); + if (!jmethod) + { + goto failed; + } + jsignature = (*env)->CallStaticObjectMethod(env, this->signature_class, + method_id, jmethod); + if (!jsignature) + { + goto failed; + } + method_id = (*env)->GetMethodID(env, this->signature_class, "initSign", + "(Ljava/security/PrivateKey;)V"); + if (!method_id) + { + goto failed; + } + (*env)->CallVoidMethod(env, jsignature, method_id, this->key); + if (androidjni_exception_occurred(env)) + { + goto failed; + } + method_id = (*env)->GetMethodID(env, this->signature_class, "update", + "([B)V"); + if (!method_id) + { + goto failed; + } + jdata = byte_array_from_chunk(env, data); + (*env)->CallVoidMethod(env, jsignature, method_id, jdata); + if (androidjni_exception_occurred(env)) + { + goto failed; + } + method_id = (*env)->GetMethodID(env, this->signature_class, "sign", + "()[B"); + if (!method_id) + { + goto failed; + } + jsigarray = (*env)->CallObjectMethod(env, jsignature, method_id); + if (!jsigarray) + { + goto failed; + } + 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; + } + if (len) + { + /* 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); + } + } + else + { + *signature = chunk_from_byte_array(env, jsigarray); + } + androidjni_detach_thread(); + return TRUE; + +failed: + DBG1(DBG_LIB, "failed to build %N signature via JNI", + signature_scheme_names, scheme); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +METHOD(private_key_t, get_type, key_type_t, + private_private_key_t *this) +{ + return this->pubkey->get_type(this->pubkey); +} + +METHOD(private_key_t, decrypt, bool, + private_private_key_t *this, encryption_scheme_t scheme, + chunk_t crypto, chunk_t *plain) +{ + DBG1(DBG_LIB, "private key decryption is currently not supported via JNI"); + return FALSE; +} + +METHOD(private_key_t, get_keysize, int, + private_private_key_t *this) +{ + return this->pubkey->get_keysize(this->pubkey); +} + +METHOD(private_key_t, get_public_key, public_key_t*, + private_private_key_t *this) +{ + return this->pubkey->get_ref(this->pubkey); +} + +METHOD(private_key_t, get_encoding, bool, + private_private_key_t *this, cred_encoding_type_t type, + chunk_t *encoding) +{ + return FALSE; +} + +METHOD(private_key_t, get_fingerprint, bool, + private_private_key_t *this, cred_encoding_type_t type, chunk_t *fp) +{ + return this->pubkey->get_fingerprint(this->pubkey, type, fp); +} + +METHOD(private_key_t, get_ref, private_key_t*, + private_private_key_t *this) +{ + ref_get(&this->ref); + return &this->public; +} + +METHOD(private_key_t, destroy, void, + private_private_key_t *this) +{ + if (ref_put(&this->ref)) + { + JNIEnv *env; + + androidjni_attach_thread(&env); + if (android_sdk_version == ANDROID_JELLY_BEAN) + { /* there is a bug in JB that causes a SIGSEGV if the key object is + * garbage collected so we intentionally leak the reference to it */ + DBG1(DBG_LIB, "intentionally leaking private key reference due to " + "a bug in the framework"); + } + else + { + (*env)->DeleteGlobalRef(env, this->key); + } + (*env)->DeleteGlobalRef(env, this->signature_class); + androidjni_detach_thread(); + this->pubkey->destroy(this->pubkey); + free(this); + } +} + +/* + * See header + */ +private_key_t *android_private_key_create(jobject key, public_key_t *pubkey) +{ + JNIEnv *env; + private_private_key_t *this; + + INIT(this, + .public = { + .get_type = _get_type, + .sign = _sign, + .decrypt = _decrypt, + .get_keysize = _get_keysize, + .get_public_key = _get_public_key, + .belongs_to = private_key_belongs_to, + .equals = private_key_equals, + .get_fingerprint = _get_fingerprint, + .has_fingerprint = private_key_has_fingerprint, + .get_encoding = _get_encoding, + .get_ref = _get_ref, + .destroy = _destroy, + }, + .ref = 1, + .pubkey = pubkey, + ); + + if (!pubkey) + { + free(this); + return NULL; + } + + /* in ICS we could simply call getEncoded and use the PKCS#8/DER encoded + * private key, since JB that's not possible as there is no direct access + * to private keys anymore (as these could now be hardware backed) */ + androidjni_attach_thread(&env); + this->key = (*env)->NewGlobalRef(env, key); + this->signature_class = (*env)->NewGlobalRef(env, (*env)->FindClass(env, + "java/security/Signature")); + androidjni_detach_thread(); + return &this->public; +} diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_private_key.h b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_private_key.h new file mode 100644 index 000000000..9848657de --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_private_key.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012 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. + */ + +/** + * @defgroup android_private_key android_private_key + * @{ @ingroup android_backend + */ + +#ifndef ANDROID_PRIVATE_KEY_H_ +#define ANDROID_PRIVATE_KEY_H_ + +#include + +#include + +/** + * Create a JNI backed key, stored in the Android KeyChain + * + * @param key PrivateKey instance + * @param pubkey public key as extracted from the certificate (gets adopted) + * @return private_key_t instance + */ +private_key_t *android_private_key_create(jobject key, public_key_t *pubkey); + +#endif /** ANDROID_PRIVATE_KEY_H_ @}*/ + diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c new file mode 100644 index 000000000..7ef3913f7 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c @@ -0,0 +1,848 @@ +/* + * Copyright (C) 2010-2015 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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. + */ + +#include +#include + +#include "android_service.h" +#include "android_dns_proxy.h" +#include "../charonservice.h" +#include "../vpnservice_builder.h" + +#include +#include +#include +#include +#include +#include + +typedef struct private_android_service_t private_android_service_t; + +/** + * private data of Android service + */ +struct private_android_service_t { + + /** + * public interface + */ + android_service_t public; + + /** + * credential set + */ + android_creds_t *creds; + + /** + * current IKE_SA + */ + ike_sa_t *ike_sa; + + /** + * configuration setttings + */ + settings_t *settings; + + /** + * lock to safely access the TUN device fd + */ + rwlock_t *lock; + + /** + * TUN device file descriptor + */ + int tunfd; + + /** + * MTU of TUN device + */ + int mtu; + + /** + * DNS proxy + */ + android_dns_proxy_t *dns_proxy; + + /** + * Whether to use the DNS proxy or not + */ + bool use_dns_proxy; +}; + +/** + * Outbound callback + */ +static void send_esp(void *data, esp_packet_t *packet) +{ + charon->sender->send_no_marker(charon->sender, (packet_t*)packet); +} + +/** + * Inbound callback + */ +static void deliver_plain(private_android_service_t *this, + ip_packet_t *packet) +{ + chunk_t encoding; + ssize_t len; + + encoding = packet->get_encoding(packet); + + this->lock->read_lock(this->lock); + if (this->tunfd < 0) + { /* the TUN device is already closed */ + this->lock->unlock(this->lock); + packet->destroy(packet); + return; + } + len = write(this->tunfd, encoding.ptr, encoding.len); + this->lock->unlock(this->lock); + + if (len < 0 || len != encoding.len) + { + DBG1(DBG_DMN, "failed to write packet to TUN device: %s", + strerror(errno)); + } + packet->destroy(packet); +} + +/** + * Receiver callback + */ +static void receiver_esp_cb(void *data, packet_t *packet) +{ + esp_packet_t *esp_packet; + + esp_packet = esp_packet_create_from_packet(packet); + ipsec->processor->queue_inbound(ipsec->processor, esp_packet); +} + +/** + * Job handling outbound plaintext packets + */ +static job_requeue_t handle_plain(private_android_service_t *this) +{ + ip_packet_t *packet; + chunk_t raw; + fd_set set; + ssize_t len; + int tunfd; + bool old, dns_proxy; + timeval_t tv = { + /* check every second if tunfd is still valid */ + .tv_sec = 1, + }; + + FD_ZERO(&set); + + this->lock->read_lock(this->lock); + if (this->tunfd < 0) + { /* the TUN device is already closed */ + this->lock->unlock(this->lock); + return JOB_REQUEUE_NONE; + } + tunfd = this->tunfd; + FD_SET(tunfd, &set); + /* cache this while we have the lock */ + dns_proxy = this->use_dns_proxy; + this->lock->unlock(this->lock); + + old = thread_cancelability(TRUE); + len = select(tunfd + 1, &set, NULL, NULL, &tv); + thread_cancelability(old); + + if (len < 0) + { + if (errno == EBADF) + { /* the TUN device got closed just before calling select(), retry */ + return JOB_REQUEUE_FAIR; + } + DBG1(DBG_DMN, "select on TUN device failed: %s", strerror(errno)); + return JOB_REQUEUE_NONE; + } + else if (len == 0) + { /* timeout, check again right away */ + return JOB_REQUEUE_DIRECT; + } + + raw = chunk_alloc(this->mtu); + len = read(tunfd, raw.ptr, raw.len); + if (len < 0) + { + DBG1(DBG_DMN, "reading from TUN device failed: %s", strerror(errno)); + chunk_free(&raw); + return JOB_REQUEUE_FAIR; + } + raw.len = len; + + packet = ip_packet_create(raw); + if (packet) + { + if (!dns_proxy || !this->dns_proxy->handle(this->dns_proxy, packet)) + { + ipsec->processor->queue_outbound(ipsec->processor, packet); + } + } + else + { + DBG1(DBG_DMN, "invalid IP packet read from TUN device"); + } + return JOB_REQUEUE_DIRECT; +} + +/** + * Add a route to the TUN device builder + */ +static bool add_route(vpnservice_builder_t *builder, host_t *net, + u_int8_t prefix) +{ + /* if route is 0.0.0.0/0, split it into two routes 0.0.0.0/1 and + * 128.0.0.0/1 because otherwise it would conflict with the current default + * route. likewise for IPv6 with ::/0. */ + if (net->is_anyaddr(net) && prefix == 0) + { + bool success; + + success = add_route(builder, net, 1); + if (net->get_family(net) == AF_INET) + { + net = host_create_from_string("128.0.0.0", 0); + } + else + { + net = host_create_from_string("8000::", 0); + } + success = success && add_route(builder, net, 1); + net->destroy(net); + return success; + } + return builder->add_route(builder, net, prefix); +} + +/** + * Generate and set routes from installed IPsec policies + */ +static bool add_routes(vpnservice_builder_t *builder, child_sa_t *child_sa) +{ + traffic_selector_t *src_ts, *dst_ts; + enumerator_t *enumerator; + bool success = TRUE; + + enumerator = child_sa->create_policy_enumerator(child_sa); + while (success && enumerator->enumerate(enumerator, &src_ts, &dst_ts)) + { + host_t *net; + u_int8_t prefix; + + dst_ts->to_subnet(dst_ts, &net, &prefix); + success = add_route(builder, net, prefix); + net->destroy(net); + } + enumerator->destroy(enumerator); + return success; +} + +/** + * Setup a new TUN device for the supplied SAs, also queues a job that + * reads packets from this device. + * Additional information such as DNS servers are gathered in appropriate + * listeners asynchronously. To be sure every required bit of information is + * available this should be called after the CHILD_SA has been established. + */ +static bool setup_tun_device(private_android_service_t *this, + ike_sa_t *ike_sa, child_sa_t *child_sa) +{ + vpnservice_builder_t *builder; + enumerator_t *enumerator; + bool vip_found = FALSE, already_registered = FALSE; + host_t *vip; + int tunfd; + + DBG1(DBG_DMN, "setting up TUN device for CHILD_SA %s{%u}", + child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa)); + + builder = charonservice->get_vpnservice_builder(charonservice); + + enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, TRUE); + while (enumerator->enumerate(enumerator, &vip)) + { + if (!vip->is_anyaddr(vip)) + { + if (!builder->add_address(builder, vip)) + { + break; + } + vip_found = TRUE; + } + } + enumerator->destroy(enumerator); + + if (!vip_found) + { + DBG1(DBG_DMN, "setting up TUN device failed, no virtual IP found"); + return FALSE; + } + if (!add_routes(builder, child_sa) || + !builder->set_mtu(builder, this->mtu)) + { + return FALSE; + } + + tunfd = builder->establish(builder); + if (tunfd == -1) + { + return FALSE; + } + + this->lock->write_lock(this->lock); + if (this->tunfd > 0) + { /* close previously opened TUN device */ + close(this->tunfd); + already_registered = true; + } + this->tunfd = tunfd; + this->lock->unlock(this->lock); + + DBG1(DBG_DMN, "successfully created TUN device"); + + if (!already_registered) + { + charon->receiver->add_esp_cb(charon->receiver, + (receiver_esp_cb_t)receiver_esp_cb, NULL); + ipsec->processor->register_inbound(ipsec->processor, + (ipsec_inbound_cb_t)deliver_plain, this); + ipsec->processor->register_outbound(ipsec->processor, + (ipsec_outbound_cb_t)send_esp, NULL); + this->dns_proxy->register_cb(this->dns_proxy, + (dns_proxy_response_cb_t)deliver_plain, this); + + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create((callback_job_cb_t)handle_plain, this, + NULL, (callback_job_cancel_t)return_false)); + } + return TRUE; +} + +/** + * Setup a new TUN device based on the existing one, but without DNS server. + */ +static bool setup_tun_device_without_dns(private_android_service_t *this) +{ + vpnservice_builder_t *builder; + int tunfd; + + DBG1(DBG_DMN, "setting up TUN device without DNS"); + + builder = charonservice->get_vpnservice_builder(charonservice); + + tunfd = builder->establish_no_dns(builder); + if (tunfd == -1) + { + return FALSE; + } + + this->lock->write_lock(this->lock); + if (this->tunfd > 0) + { /* close previously opened TUN device, this should always be the case */ + close(this->tunfd); + } + this->tunfd = tunfd; + this->lock->unlock(this->lock); + + DBG1(DBG_DMN, "successfully created TUN device without DNS"); + return TRUE; +} + +/** + * Close the current tun device + */ +static void close_tun_device(private_android_service_t *this) +{ + int tunfd; + + this->lock->write_lock(this->lock); + if (this->tunfd < 0) + { /* already closed (or never created) */ + this->lock->unlock(this->lock); + return; + } + tunfd = this->tunfd; + this->tunfd = -1; + this->lock->unlock(this->lock); + + this->dns_proxy->unregister_cb(this->dns_proxy, + (dns_proxy_response_cb_t)deliver_plain); + ipsec->processor->unregister_outbound(ipsec->processor, + (ipsec_outbound_cb_t)send_esp); + ipsec->processor->unregister_inbound(ipsec->processor, + (ipsec_inbound_cb_t)deliver_plain); + charon->receiver->del_esp_cb(charon->receiver, + (receiver_esp_cb_t)receiver_esp_cb); + close(tunfd); +} + +/** + * Terminate the IKE_SA with the given unique ID + */ +CALLBACK(terminate, job_requeue_t, + u_int32_t *id) +{ + charon->controller->terminate_ike(charon->controller, *id, + controller_cb_empty, NULL, 0); + return JOB_REQUEUE_NONE; +} + +/** + * Reestablish the IKE_SA with the given unique ID + */ +CALLBACK(reestablish, job_requeue_t, + u_int32_t *id) +{ + ike_sa_t *ike_sa; + + ike_sa = charon->ike_sa_manager->checkout_by_id(charon->ike_sa_manager, *id); + if (ike_sa) + { + if (ike_sa->reauth(ike_sa) == DESTROY_ME) + { + charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, + ike_sa); + } + else + { + charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); + } + } + return JOB_REQUEUE_NONE; +} + +METHOD(listener_t, child_updown, bool, + private_android_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, + bool up) +{ + if (this->ike_sa == ike_sa) + { + if (up) + { + /* disable the hooks registered to catch initiation failures */ + this->public.listener.ike_updown = NULL; + /* CHILD_SA is up so we can disable the DNS proxy we enabled to + * reestablish the SA */ + this->lock->write_lock(this->lock); + this->use_dns_proxy = FALSE; + this->lock->unlock(this->lock); + if (!setup_tun_device(this, ike_sa, child_sa)) + { + DBG1(DBG_DMN, "failed to setup TUN device"); + charonservice->update_status(charonservice, + CHARONSERVICE_GENERIC_ERROR); + return FALSE; + + } + charonservice->update_status(charonservice, + CHARONSERVICE_CHILD_STATE_UP); + } + else + { + charonservice->update_status(charonservice, + CHARONSERVICE_CHILD_STATE_DOWN); + } + } + return TRUE; +} + +METHOD(listener_t, ike_updown, bool, + private_android_service_t *this, ike_sa_t *ike_sa, bool up) +{ + /* this callback is only registered during initiation, so if the IKE_SA + * goes down we assume some kind of authentication error, more specific + * errors are catched in the alert() handler */ + if (this->ike_sa == ike_sa && !up) + { + charonservice->update_status(charonservice, + CHARONSERVICE_AUTH_ERROR); + return FALSE; + } + return TRUE; +} + +METHOD(listener_t, alert, bool, + private_android_service_t *this, ike_sa_t *ike_sa, alert_t alert, + va_list args) +{ + if (this->ike_sa == ike_sa) + { + switch (alert) + { + case ALERT_PEER_ADDR_FAILED: + charonservice->update_status(charonservice, + CHARONSERVICE_LOOKUP_ERROR); + break; + case ALERT_PEER_AUTH_FAILED: + charonservice->update_status(charonservice, + CHARONSERVICE_PEER_AUTH_ERROR); + break; + case ALERT_KEEP_ON_CHILD_SA_FAILURE: + { + u_int32_t *id = malloc_thing(u_int32_t); + + /* because close_ike_on_child_failure is set this is only + * triggered when CHILD_SA rekeying failed. reestablish it in + * the hope that the initial setup works again. */ + *id = ike_sa->get_unique_id(ike_sa); + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create_with_prio( + (callback_job_cb_t)reestablish, id, free, + (callback_job_cancel_t)return_false, JOB_PRIO_HIGH)); + break; + } + case ALERT_PEER_INIT_UNREACHABLE: + this->lock->read_lock(this->lock); + if (this->tunfd < 0) + { + u_int32_t *id = malloc_thing(u_int32_t); + + /* always fail if we are not able to initiate the IKE_SA + * initially */ + charonservice->update_status(charonservice, + CHARONSERVICE_UNREACHABLE_ERROR); + /* terminate the IKE_SA so no further keying tries are + * attempted */ + *id = ike_sa->get_unique_id(ike_sa); + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create_with_prio( + (callback_job_cb_t)terminate, id, free, + (callback_job_cancel_t)return_false, JOB_PRIO_HIGH)); + } + else + { + peer_cfg_t *peer_cfg; + u_int32_t tries, try; + + /* when reestablishing and if keyingtries is not %forever + * the IKE_SA is destroyed after the set number of tries, + * so notify the GUI */ + peer_cfg = ike_sa->get_peer_cfg(ike_sa); + tries = peer_cfg->get_keyingtries(peer_cfg); + try = va_arg(args, u_int32_t); + if (tries != 0 && try == tries-1) + { + charonservice->update_status(charonservice, + CHARONSERVICE_UNREACHABLE_ERROR); + } + } + this->lock->unlock(this->lock); + break; + default: + break; + } + } + return TRUE; +} + +METHOD(listener_t, ike_rekey, bool, + private_android_service_t *this, ike_sa_t *old, ike_sa_t *new) +{ + if (this->ike_sa == old) + { + this->ike_sa = new; + } + return TRUE; +} + +METHOD(listener_t, ike_reestablish_pre, bool, + private_android_service_t *this, ike_sa_t *old, ike_sa_t *new) +{ + if (this->ike_sa == old) + { + /* enable DNS proxy so hosts are properly resolved while the TUN device + * is still active */ + this->lock->write_lock(this->lock); + this->use_dns_proxy = TRUE; + this->lock->unlock(this->lock); + /* if DNS servers are installed that are only reachable through the VPN + * the DNS proxy doesn't help, so uninstall DNS servers */ + if (!setup_tun_device_without_dns(this)) + { + DBG1(DBG_DMN, "failed to setup TUN device without DNS"); + charonservice->update_status(charonservice, + CHARONSERVICE_GENERIC_ERROR); + } + } + return TRUE; +} + +METHOD(listener_t, ike_reestablish_post, bool, + private_android_service_t *this, ike_sa_t *old, ike_sa_t *new, + bool initiated) +{ + if (this->ike_sa == old && initiated) + { + this->ike_sa = new; + /* re-register hook to detect initiation failures */ + this->public.listener.ike_updown = _ike_updown; + /* if the IKE_SA got deleted by the responder we get the child_down() + * event on the old IKE_SA after this hook has been called, so they + * get ignored and thus we trigger the event here */ + charonservice->update_status(charonservice, + CHARONSERVICE_CHILD_STATE_DOWN); + } + return TRUE; +} + +static void add_auth_cfg_pw(private_android_service_t *this, + peer_cfg_t *peer_cfg, bool byod) +{ + identification_t *user; + auth_cfg_t *auth; + char *username, *password; + + auth = auth_cfg_create(); + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); + if (byod) + { /* use EAP-TTLS if BYOD is enabled */ + auth->add(auth, AUTH_RULE_EAP_TYPE, EAP_TTLS); + } + + username = this->settings->get_str(this->settings, "connection.username", + NULL); + password = this->settings->get_str(this->settings, "connection.password", + NULL); + user = identification_create_from_string(username); + auth->add(auth, AUTH_RULE_IDENTITY, user); + + this->creds->add_username_password(this->creds, username, password); + peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE); +} + +static bool add_auth_cfg_cert(private_android_service_t *this, + peer_cfg_t *peer_cfg) +{ + certificate_t *cert; + identification_t *id; + auth_cfg_t *auth; + char *type; + + cert = this->creds->load_user_certificate(this->creds); + if (!cert) + { + return FALSE; + } + + type = this->settings->get_str(this->settings, "connection.type", NULL); + auth = auth_cfg_create(); + if (strpfx("ikev2-eap-tls", type)) + { + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); + auth->add(auth, AUTH_RULE_EAP_TYPE, EAP_TLS); + id = identification_create_from_string("%any"); + auth->add(auth, AUTH_RULE_AAA_IDENTITY, id); + } + else + { + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); + } + auth->add(auth, AUTH_RULE_SUBJECT_CERT, cert); + + id = cert->get_subject(cert); + auth->add(auth, AUTH_RULE_IDENTITY, id->clone(id)); + peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE); + return TRUE; +} + +static job_requeue_t initiate(private_android_service_t *this) +{ + identification_t *gateway; + ike_cfg_t *ike_cfg; + peer_cfg_t *peer_cfg; + child_cfg_t *child_cfg; + traffic_selector_t *ts; + ike_sa_t *ike_sa; + auth_cfg_t *auth; + lifetime_cfg_t lifetime = { + .time = { + .life = 3600, /* 1h */ + .rekey = 3000, /* 50min */ + .jitter = 300 /* 5min */ + } + }; + char *type, *server; + int port; + + server = this->settings->get_str(this->settings, "connection.server", NULL); + port = this->settings->get_int(this->settings, "connection.port", + IKEV2_UDP_PORT); + ike_cfg = ike_cfg_create(IKEV2, TRUE, TRUE, "0.0.0.0", + charon->socket->get_port(charon->socket, FALSE), + server, port, FRAGMENTATION_YES, 0); + ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE)); + ike_cfg->add_proposal(ike_cfg, proposal_create_default_aead(PROTO_IKE)); + + peer_cfg = peer_cfg_create("android", ike_cfg, CERT_SEND_IF_ASKED, + UNIQUE_REPLACE, 0, /* keyingtries */ + 36000, 0, /* rekey 10h, reauth none */ + 600, 600, /* jitter, over 10min */ + TRUE, FALSE, TRUE, /* mobike, aggressive, pull */ + 0, 0, /* DPD delay, timeout */ + FALSE, NULL, NULL); /* mediation */ + peer_cfg->add_virtual_ip(peer_cfg, host_create_any(AF_INET)); + peer_cfg->add_virtual_ip(peer_cfg, host_create_any(AF_INET6)); + + type = this->settings->get_str(this->settings, "connection.type", NULL); + /* local auth config */ + if (streq("ikev2-cert", type) || + streq("ikev2-cert-eap", type) || + streq("ikev2-eap-tls", type)) + { + if (!add_auth_cfg_cert(this, peer_cfg)) + { + peer_cfg->destroy(peer_cfg); + charonservice->update_status(charonservice, + CHARONSERVICE_GENERIC_ERROR); + return JOB_REQUEUE_NONE; + } + } + if (streq("ikev2-eap", type) || + streq("ikev2-cert-eap", type) || + streq("ikev2-byod-eap", type)) + { + add_auth_cfg_pw(this, peer_cfg, strpfx(type, "ikev2-byod")); + } + + /* remote auth config */ + auth = auth_cfg_create(); + gateway = identification_create_from_string(server); + auth->add(auth, AUTH_RULE_IDENTITY, gateway); + auth->add(auth, AUTH_RULE_IDENTITY_LOOSE, TRUE); + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); + peer_cfg->add_auth_cfg(peer_cfg, auth, FALSE); + + child_cfg = child_cfg_create("android", &lifetime, NULL, TRUE, MODE_TUNNEL, + ACTION_NONE, ACTION_RESTART, ACTION_RESTART, + FALSE, 0, 0, NULL, NULL, 0); + /* create ESP proposals with and without DH groups, let responder decide + * if PFS is used */ + child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, + "aes128gcm16-aes256gcm16-ecp256")); + child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, + "aes128-sha256-ecp256-modp3072")); + child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, + "aes256-sha384-ecp521-modp8192")); + child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, + "aes128-aes192-aes256-sha1-sha256-sha384-sha512-" + "ecp256-ecp384-ecp521-" + "modp2048-modp3072-modp4096-modp1024")); + child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, + "aes128gcm16-aes256gcm16")); + child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, + "aes128-sha256")); + child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, + "aes256-sha384")); + child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, + "aes128-aes192-aes256-sha1-sha256-sha384-sha512")); + ts = traffic_selector_create_from_cidr("0.0.0.0/0", 0, 0, 65535); + child_cfg->add_traffic_selector(child_cfg, TRUE, ts); + ts = traffic_selector_create_from_cidr("0.0.0.0/0", 0, 0, 65535); + child_cfg->add_traffic_selector(child_cfg, FALSE, ts); + ts = traffic_selector_create_from_cidr("::/0", 0, 0, 65535); + child_cfg->add_traffic_selector(child_cfg, TRUE, ts); + ts = traffic_selector_create_from_cidr("::/0", 0, 0, 65535); + child_cfg->add_traffic_selector(child_cfg, FALSE, ts); + peer_cfg->add_child_cfg(peer_cfg, child_cfg); + + /* get us an IKE_SA */ + ike_sa = charon->ike_sa_manager->checkout_by_config(charon->ike_sa_manager, + peer_cfg); + if (!ike_sa) + { + peer_cfg->destroy(peer_cfg); + charonservice->update_status(charonservice, + CHARONSERVICE_GENERIC_ERROR); + return JOB_REQUEUE_NONE; + } + if (!ike_sa->get_peer_cfg(ike_sa)) + { + ike_sa->set_peer_cfg(ike_sa, peer_cfg); + } + peer_cfg->destroy(peer_cfg); + + /* store the IKE_SA so we can track its progress */ + this->ike_sa = ike_sa; + + /* get an additional reference because initiate consumes one */ + child_cfg->get_ref(child_cfg); + if (ike_sa->initiate(ike_sa, child_cfg, 0, NULL, NULL) != SUCCESS) + { + DBG1(DBG_CFG, "failed to initiate tunnel"); + charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, + ike_sa); + return JOB_REQUEUE_NONE; + } + charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); + return JOB_REQUEUE_NONE; +} + +METHOD(android_service_t, destroy, void, + private_android_service_t *this) +{ + charon->bus->remove_listener(charon->bus, &this->public.listener); + /* make sure the tun device is actually closed */ + close_tun_device(this); + this->dns_proxy->destroy(this->dns_proxy); + this->lock->destroy(this->lock); + this->settings->destroy(this->settings); + free(this); +} + +/** + * See header + */ +android_service_t *android_service_create(android_creds_t *creds, + settings_t *settings) +{ + private_android_service_t *this; + + INIT(this, + .public = { + .listener = { + .ike_rekey = _ike_rekey, + .ike_reestablish_pre = _ike_reestablish_pre, + .ike_reestablish_post = _ike_reestablish_post, + .ike_updown = _ike_updown, + .child_updown = _child_updown, + .alert = _alert, + }, + .destroy = _destroy, + }, + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + .dns_proxy = android_dns_proxy_create(), + .settings = settings, + .creds = creds, + .tunfd = -1, + .mtu = settings->get_int(settings, "global.mtu", ANDROID_DEFAULT_MTU), + ); + /* only allow queries for the VPN gateway */ + this->dns_proxy->add_hostname(this->dns_proxy, + this->settings->get_str(this->settings, "connection.server", NULL)); + + charon->bus->add_listener(charon->bus, &this->public.listener); + + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create((callback_job_cb_t)initiate, this, + NULL, NULL)); + return &this->public; +} diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.h b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.h new file mode 100644 index 000000000..1a5175774 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2010-2015 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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. + */ + +/** + * @defgroup android_service android_service + * @{ @ingroup android_backend + */ + +#ifndef ANDROID_SERVICE_H_ +#define ANDROID_SERVICE_H_ + +#include "android_creds.h" + +#include +#include + +typedef struct android_service_t android_service_t; + +/** + * Service that sets up an IKE_SA/CHILD_SA and handles events + */ +struct android_service_t { + + /** + * Implements listener_t. + */ + listener_t listener; + + /** + * Destroy a android_service_t. + */ + void (*destroy)(android_service_t *this); + +}; + +/** + * Create an Android service instance. Queues a job that starts initiation of a + * new IKE SA. + * + * @param creds Android specific credential set + * @param settings configuration settings (gets adopted) + */ +android_service_t *android_service_create(android_creds_t *creds, + settings_t *settings); + +#endif /** ANDROID_SERVICE_H_ @}*/ diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android.c b/src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android.c new file mode 100644 index 000000000..6432b957f --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android.c @@ -0,0 +1,722 @@ +/* + * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012 Christoph Buehler + * Copyright (C) 2012 Patrick Loetscher + * Copyright (C) 2011-2012 Andreas Steffen + * 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. + */ + +#include "imc_android_state.h" +#include "../android_jni.h" +#include "../charonservice.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +/* IMC definitions */ + +static const char imc_name[] = "Android"; + +static pen_type_t msg_types[] = { + { PEN_IETF, PA_SUBTYPE_IETF_OPERATING_SYSTEM }, + { PEN_IETF, PA_SUBTYPE_IETF_VPN }, + { PEN_TCG, PA_SUBTYPE_TCG_PTS }, +}; + +static imc_agent_t *imc_android; + +/** + * AndroidImc object accessed via JNI + */ +static jobject android_imc; + +/** + * AndroidImc class object + */ +static jclass android_imc_cls; + +/** + * see section 3.8.1 of TCG TNC IF-IMC Specification 1.3 + */ +static TNC_Result tnc_imc_initialize(TNC_IMCID imc_id, + TNC_Version min_version, + TNC_Version max_version, + TNC_Version *actual_version) +{ + if (imc_android) + { + DBG1(DBG_IMC, "IMC \"%s\" has already been initialized", imc_name); + return TNC_RESULT_ALREADY_INITIALIZED; + } + imc_android = imc_agent_create(imc_name, msg_types, countof(msg_types), + imc_id, actual_version); + if (!imc_android) + { + return TNC_RESULT_FATAL; + } + + if (min_version > TNC_IFIMC_VERSION_1 || max_version < TNC_IFIMC_VERSION_1) + { + DBG1(DBG_IMC, "no common IF-IMC version"); + return TNC_RESULT_NO_COMMON_VERSION; + } + return TNC_RESULT_SUCCESS; +} + +/** + * Update the state in the GUI. + */ +static void update_imc_state(TNC_ConnectionState state) +{ + android_imc_state_t imc_state = ANDROID_IMC_STATE_UNKNOWN; + + switch (state) + { /* map connection states to the values used by the GUI */ + case TNC_CONNECTION_STATE_ACCESS_ALLOWED: + imc_state = ANDROID_IMC_STATE_ALLOW; + break; + case TNC_CONNECTION_STATE_ACCESS_ISOLATED: + imc_state = ANDROID_IMC_STATE_ISOLATE; + break; + case TNC_CONNECTION_STATE_ACCESS_NONE: + imc_state = ANDROID_IMC_STATE_BLOCK; + break; + } + + charonservice->update_imc_state(charonservice, imc_state); +} + +/** + * see section 3.8.2 of TCG TNC IF-IMC Specification 1.3 + */ +static TNC_Result tnc_imc_notifyconnectionchange(TNC_IMCID imc_id, + TNC_ConnectionID connection_id, + TNC_ConnectionState new_state) +{ + imc_state_t *state; + + if (!imc_android) + { + DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name); + return TNC_RESULT_NOT_INITIALIZED; + } + switch (new_state) + { + case TNC_CONNECTION_STATE_CREATE: + state = imc_android_state_create(connection_id); + return imc_android->create_state(imc_android, state); + case TNC_CONNECTION_STATE_HANDSHAKE: + if (imc_android->change_state(imc_android, connection_id, new_state, + &state) != TNC_RESULT_SUCCESS) + { + return TNC_RESULT_FATAL; + } + state->set_result(state, imc_id, + TNC_IMV_EVALUATION_RESULT_DONT_KNOW); + return TNC_RESULT_SUCCESS; + case TNC_CONNECTION_STATE_DELETE: + return imc_android->delete_state(imc_android, connection_id); + case TNC_CONNECTION_STATE_ACCESS_ALLOWED: + case TNC_CONNECTION_STATE_ACCESS_ISOLATED: + case TNC_CONNECTION_STATE_ACCESS_NONE: + update_imc_state(new_state); + /* fall-through */ + default: + return imc_android->change_state(imc_android, connection_id, + new_state, NULL); + } +} + +/** + * Convert the native C strings in the enumerator to a Java String array. + * The given enumerator gets destroyed. + */ +static jobjectArray string_array_create(JNIEnv *env, enumerator_t *enumerator) +{ + linked_list_t *list; + jobjectArray jarray; + jstring jstring; + char *native; + jclass cls; + int i = 0; + + cls = (*env)->FindClass(env, "java/lang/String"); + list = linked_list_create_from_enumerator(enumerator); + jarray = (*env)->NewObjectArray(env, list->get_count(list), cls, NULL); + if (!jarray) + { + goto failed; + } + enumerator = list->create_enumerator(list); + while (enumerator->enumerate(enumerator, (void**)&native)) + { + jstring = (*env)->NewStringUTF(env, native); + if (!jstring) + { + enumerator->destroy(enumerator); + goto failed; + } + (*env)->SetObjectArrayElement(env, jarray, i++, jstring); + } + enumerator->destroy(enumerator); + list->destroy(list); + return jarray; + +failed: + androidjni_exception_occurred(env); + list->destroy(list); + return NULL; +} + +/** + * Get a measurement for the given attribute type from the Android IMC. + * NULL is returned if no measurement is available or an error occurred. + * + * The optional args is an enumerator over char* (gets destroyed). + */ +static pa_tnc_attr_t *get_measurement(pen_type_t attr_type, enumerator_t *args) +{ + JNIEnv *env; + pa_tnc_attr_t *attr; + jmethodID method_id; + jbyteArray jmeasurement; + jobjectArray jargs = NULL; + chunk_t data; + + androidjni_attach_thread(&env); + if (args) + { + jargs = string_array_create(env, args); + if (!jargs) + { + goto failed; + } + method_id = (*env)->GetMethodID(env, android_imc_cls, "getMeasurement", + "(II[Ljava/lang/String;)[B"); + } + else + { + method_id = (*env)->GetMethodID(env, android_imc_cls, "getMeasurement", + "(II)[B"); + } + if (!method_id) + { + goto failed; + } + jmeasurement = (*env)->CallObjectMethod(env, android_imc, method_id, + attr_type.vendor_id, attr_type.type, + jargs); + if (!jmeasurement || androidjni_exception_occurred(env)) + { + goto failed; + } + data = chunk_create((*env)->GetByteArrayElements(env, jmeasurement, NULL), + (*env)->GetArrayLength(env, jmeasurement)); + if (!data.ptr) + { + goto failed; + } + attr = imcv_pa_tnc_attributes->construct(imcv_pa_tnc_attributes, + attr_type.vendor_id, attr_type.type, data); + (*env)->ReleaseByteArrayElements(env, jmeasurement, data.ptr, JNI_ABORT); + androidjni_detach_thread(); + return attr; + +failed: + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return NULL; +} + +/** + * Add the measurement for the requested attribute type with optional + * arguments (enumerator over char*, gets destroyed). + */ +static void add_measurement(pen_type_t attr_type, imc_msg_t *msg, + enumerator_t *args) +{ + pa_tnc_attr_t *attr; + enum_name_t *pa_attr_names; + + attr = get_measurement(attr_type, args); + if (attr) + { + msg->add_attribute(msg, attr); + return; + } + pa_attr_names = imcv_pa_tnc_attributes->get_names(imcv_pa_tnc_attributes, + attr_type.vendor_id); + if (pa_attr_names) + { + DBG1(DBG_IMC, "no measurement available for PA-TNC attribute type " + "'%N/%N' 0x%06x/0x%08x", pen_names, attr_type.vendor_id, + pa_attr_names, attr_type.type, attr_type.vendor_id, attr_type.type); + } + else + { + DBG1(DBG_IMC, "no measurement available for PA-TNC attribute type '%N' " + "0x%06x/0x%08x", pen_names, attr_type.vendor_id, + attr_type.vendor_id, attr_type.type); + } +} + +/** + * Handle an IETF attribute + */ +static void handle_ietf_attribute(pen_type_t attr_type, pa_tnc_attr_t *attr, + imc_msg_t *out_msg) +{ + if (attr_type.type == IETF_ATTR_ATTRIBUTE_REQUEST) + { + ietf_attr_attr_request_t *attr_cast; + pen_type_t *entry; + enumerator_t *enumerator; + + attr_cast = (ietf_attr_attr_request_t*)attr; + enumerator = attr_cast->create_enumerator(attr_cast); + while (enumerator->enumerate(enumerator, &entry)) + { + add_measurement(*entry, out_msg, NULL); + } + enumerator->destroy(enumerator); + } + else if (attr_type.type == IETF_ATTR_REMEDIATION_INSTRUCTIONS) + { + ietf_attr_remediation_instr_t *attr_cast; + pen_type_t param; + chunk_t str; + char *instr; + + attr_cast = (ietf_attr_remediation_instr_t*)attr; + param = attr_cast->get_parameters_type(attr_cast); + + if (pen_type_is(param, PEN_IETF, IETF_REMEDIATION_PARAMETERS_STRING)) + { + str = attr_cast->get_string(attr_cast, NULL); + instr = strndup(str.ptr, str.len); + charonservice->add_remediation_instr(charonservice, instr); + free (instr); + } + } +} + +/** + * Handle an ITA attribute + */ +static void handle_ita_attribute(pen_type_t attr_type, pa_tnc_attr_t *attr, + imc_msg_t *out_msg) +{ + if (attr_type.type == ITA_ATTR_GET_SETTINGS) + { + ita_attr_get_settings_t *attr_cast; + + attr_cast = (ita_attr_get_settings_t*)attr; + add_measurement((pen_type_t){ PEN_ITA, ITA_ATTR_SETTINGS }, + out_msg, attr_cast->create_enumerator(attr_cast)); + } +} + +/** + * Handle a TCG attribute + */ +static void handle_tcg_attribute(imc_android_state_t *state, + pen_type_t attr_type, pa_tnc_attr_t *attr, + imc_msg_t *out_msg) +{ + pts_t *pts; + + pts = state->get_pts(state); + switch (attr_type.type) + { + case TCG_PTS_REQ_PROTO_CAPS: + { + tcg_pts_attr_proto_caps_t *attr_cast; + pts_proto_caps_flag_t caps; + + attr_cast = (tcg_pts_attr_proto_caps_t*)attr; + caps = attr_cast->get_flags(attr_cast) & pts->get_proto_caps(pts); + pts->set_proto_caps(pts, caps); + attr = tcg_pts_attr_proto_caps_create(caps, FALSE); + out_msg->add_attribute(out_msg, attr); + break; + } + case TCG_PTS_MEAS_ALGO: + { + tcg_pts_attr_meas_algo_t *attr_cast; + pts_meas_algorithms_t supported, algo; + + if (!pts_meas_algo_probe(&supported)) + { + attr = pts_hash_alg_error_create(PTS_MEAS_ALGO_NONE); + out_msg->add_attribute(out_msg, attr); + break; + } + attr_cast = (tcg_pts_attr_meas_algo_t*)attr; + algo = pts_meas_algo_select(supported, + attr_cast->get_algorithms(attr_cast)); + if (algo == PTS_MEAS_ALGO_NONE) + { + attr = pts_hash_alg_error_create(supported); + out_msg->add_attribute(out_msg, attr); + break; + } + pts->set_meas_algorithm(pts, algo); + attr = tcg_pts_attr_meas_algo_create(algo, TRUE); + out_msg->add_attribute(out_msg, attr); + break; + } + case TCG_PTS_REQ_FILE_MEAS: + { + tcg_pts_attr_req_file_meas_t *attr_cast; + pts_file_meas_t *measurements; + pts_error_code_t pts_error; + u_int32_t delim; + u_int16_t req_id; + bool is_dir; + char *path; + + attr_cast = (tcg_pts_attr_req_file_meas_t*)attr; + path = attr_cast->get_pathname(attr_cast); + if (!pts->is_path_valid(pts, path, &pts_error)) + { /* silently ignore internal errors */ + break; + } + else if (pts_error) + { + attr = ietf_attr_pa_tnc_error_create(pen_type_create(PEN_TCG, + pts_error), attr->get_value(attr)); + out_msg->add_attribute(out_msg, attr); + break; + } + delim = attr_cast->get_delimiter(attr_cast); + if (delim != SOLIDUS_UTF && delim != REVERSE_SOLIDUS_UTF) + { + attr = ietf_attr_pa_tnc_error_create(pen_type_create(PEN_TCG, + TCG_PTS_INVALID_DELIMITER), attr->get_value(attr)); + out_msg->add_attribute(out_msg, attr); + break; + } + req_id = attr_cast->get_request_id(attr_cast); + is_dir = attr_cast->get_directory_flag(attr_cast); + + DBG1(DBG_IMC, "measurement request %d for %s '%s'", req_id, + is_dir ? "directory" : "file", path); + measurements = pts_file_meas_create_from_path(req_id, path, is_dir, + TRUE, pts->get_meas_algorithm(pts)); + if (!measurements) + { + attr = ietf_attr_pa_tnc_error_create(pen_type_create(PEN_TCG, + TCG_PTS_FILE_NOT_FOUND), attr->get_value(attr)); + out_msg->add_attribute(out_msg, attr); + break; + } + attr = tcg_pts_attr_file_meas_create(measurements); + attr->set_noskip_flag(attr, TRUE); + out_msg->add_attribute(out_msg, attr); + break; + } + default: + DBG1(DBG_IMC, "received unsupported TCG attribute '%N'", + tcg_attr_names, attr_type.type); + break; + } +} + +/** + * see section 3.8.3 of TCG TNC IF-IMC Specification 1.3 + */ +static TNC_Result tnc_imc_beginhandshake(TNC_IMCID imc_id, + TNC_ConnectionID connection_id) +{ + imc_state_t *state; + imc_msg_t *out_msg; + TNC_Result result = TNC_RESULT_SUCCESS; + + if (!imc_android) + { + DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name); + return TNC_RESULT_NOT_INITIALIZED; + } + if (!imc_android->get_state(imc_android, connection_id, &state)) + { + return TNC_RESULT_FATAL; + } + if (lib->settings->get_bool(lib->settings, + "android.imc.send_os_info", TRUE)) + { + out_msg = imc_msg_create(imc_android, state, connection_id, imc_id, + TNC_IMVID_ANY, msg_types[0]); + add_measurement((pen_type_t){ PEN_IETF, IETF_ATTR_PRODUCT_INFORMATION }, + out_msg, NULL); + add_measurement((pen_type_t){ PEN_IETF, IETF_ATTR_STRING_VERSION }, + out_msg, NULL); + add_measurement((pen_type_t){ PEN_ITA, ITA_ATTR_DEVICE_ID }, + out_msg, NULL); + /* send PA-TNC message with the excl flag not set */ + result = out_msg->send(out_msg, FALSE); + out_msg->destroy(out_msg); + } + + return result; +} + +static TNC_Result receive_message(imc_android_state_t *state, imc_msg_t *in_msg) +{ + imc_msg_t *out_msg; + enumerator_t *enumerator; + pa_tnc_attr_t *attr; + pen_type_t attr_type; + TNC_Result result; + bool fatal_error = FALSE; + + out_msg = imc_msg_create_as_reply(in_msg); + + /* parse received PA-TNC message and handle local and remote errors */ + result = in_msg->receive(in_msg, out_msg, &fatal_error); + if (result != TNC_RESULT_SUCCESS) + { + out_msg->destroy(out_msg); + return result; + } + + /* analyze PA-TNC attributes */ + enumerator = in_msg->create_attribute_enumerator(in_msg); + while (enumerator->enumerate(enumerator, &attr)) + { + attr_type = attr->get_type(attr); + + switch (attr_type.vendor_id) + { + case PEN_IETF: + handle_ietf_attribute(attr_type, attr, out_msg); + continue; + case PEN_ITA: + handle_ita_attribute(attr_type, attr, out_msg); + continue; + case PEN_TCG: + handle_tcg_attribute(state, attr_type, attr, out_msg); + continue; + default: + continue; + } + } + enumerator->destroy(enumerator); + + if (fatal_error) + { + result = TNC_RESULT_FATAL; + } + else + { + result = out_msg->send(out_msg, TRUE); + } + out_msg->destroy(out_msg); + + return result; +} + +/** + * see section 3.8.4 of TCG TNC IF-IMC Specification 1.3 + + */ +static TNC_Result tnc_imc_receivemessage(TNC_IMCID imc_id, + TNC_ConnectionID connection_id, + TNC_BufferReference msg, + TNC_UInt32 msg_len, + TNC_MessageType msg_type) +{ + imc_state_t *state; + imc_msg_t *in_msg; + TNC_Result result; + + if (!imc_android) + { + DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name); + return TNC_RESULT_NOT_INITIALIZED; + } + if (!imc_android->get_state(imc_android, connection_id, &state)) + { + return TNC_RESULT_FATAL; + } + in_msg = imc_msg_create_from_data(imc_android, state, connection_id, + msg_type, chunk_create(msg, msg_len)); + result = receive_message((imc_android_state_t*)state, in_msg); + in_msg->destroy(in_msg); + + return result; +} + +/** + * see section 3.8.6 of TCG TNC IF-IMV Specification 1.3 + */ +static TNC_Result tnc_imc_receivemessagelong(TNC_IMCID imc_id, + TNC_ConnectionID connection_id, + TNC_UInt32 msg_flags, + TNC_BufferReference msg, + TNC_UInt32 msg_len, + TNC_VendorID msg_vid, + TNC_MessageSubtype msg_subtype, + TNC_UInt32 src_imv_id, + TNC_UInt32 dst_imc_id) +{ + imc_state_t *state; + imc_msg_t *in_msg; + TNC_Result result; + + if (!imc_android) + { + DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name); + return TNC_RESULT_NOT_INITIALIZED; + } + if (!imc_android->get_state(imc_android, connection_id, &state)) + { + return TNC_RESULT_FATAL; + } + in_msg = imc_msg_create_from_long_data(imc_android, state, connection_id, + src_imv_id, dst_imc_id,msg_vid, msg_subtype, + chunk_create(msg, msg_len)); + result = receive_message((imc_android_state_t*)state, in_msg); + in_msg->destroy(in_msg); + + return result; +} + +/** + * see section 3.8.7 of TCG TNC IF-IMC Specification 1.3 + */ +static TNC_Result tnc_imc_batchending(TNC_IMCID imc_id, + TNC_ConnectionID connection_id) +{ + if (!imc_android) + { + DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name); + return TNC_RESULT_NOT_INITIALIZED; + } + return TNC_RESULT_SUCCESS; +} + +/** + * see section 3.8.8 of TCG TNC IF-IMC Specification 1.3 + */ +static TNC_Result tnc_imc_terminate(TNC_IMCID imc_id) +{ + if (!imc_android) + { + DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name); + return TNC_RESULT_NOT_INITIALIZED; + } + imc_android->destroy(imc_android); + imc_android = NULL; + return TNC_RESULT_SUCCESS; +} + +/** + * see section 4.2.8.1 of TCG TNC IF-IMC Specification 1.3 + */ +static TNC_Result tnc_imc_providebindfunction(TNC_IMCID imc_id, + TNC_TNCC_BindFunctionPointer bind_function) +{ + if (!imc_android) + { + DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name); + return TNC_RESULT_NOT_INITIALIZED; + } + return imc_android->bind_functions(imc_android, bind_function); +} + +/* + * Described in header + */ +bool imc_android_register(plugin_t *plugin, plugin_feature_t *feature, + bool reg, void *data) +{ + JNIEnv *env; + jmethodID method_id; + jobject obj, context = (jobject)data; + jclass cls; + bool success = TRUE; + + androidjni_attach_thread(&env); + if (reg) + { + cls = (*env)->FindClass(env, JNI_PACKAGE_STRING "/imc/AndroidImc"); + if (!cls) + { + goto failed; + } + android_imc_cls = (*env)->NewGlobalRef(env, cls); + method_id = (*env)->GetMethodID(env, cls, "", + "(Landroid/content/Context;)V"); + if (!method_id) + { + goto failed; + } + obj = (*env)->NewObject(env, cls, method_id, context); + if (!obj) + { + goto failed; + } + android_imc = (*env)->NewGlobalRef(env, obj); + androidjni_detach_thread(); + + if (tnc->imcs->load_from_functions(tnc->imcs, "Android", + tnc_imc_initialize, tnc_imc_notifyconnectionchange, + tnc_imc_beginhandshake, tnc_imc_receivemessage, + tnc_imc_receivemessagelong, tnc_imc_batchending, + tnc_imc_terminate, tnc_imc_providebindfunction)) + { + return TRUE; + } +failed: + DBG1(DBG_IMC, "initialization of Android IMC failed"); + androidjni_exception_occurred(env); + success = FALSE; + } + + if (android_imc) + { + (*env)->DeleteGlobalRef(env, android_imc); + android_imc = NULL; + } + if (android_imc_cls) + { + (*env)->DeleteGlobalRef(env, android_imc_cls); + android_imc_cls = NULL; + } + androidjni_detach_thread(); + return success; +} diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android.h b/src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android.h new file mode 100644 index 000000000..3bfc6de40 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 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. + */ + +/** + * @defgroup android_imc android_imc + * @{ @ingroup android_byod + */ + +#ifndef ANDROID_IMC_H_ +#define ANDROID_IMC_H_ + +/** + * Callback for the Android IMC plugin + */ +bool imc_android_register(plugin_t *plugin, plugin_feature_t *feature, + bool reg, void *data); + +#endif /** ANDROID_IMC_H_ @}*/ diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android_state.c b/src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android_state.c new file mode 100644 index 000000000..ea4f92def --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android_state.c @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Andreas Steffen + * 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. + */ + +#include "imc_android_state.h" + +#include + +#include + +typedef struct private_imc_android_state_t private_imc_android_state_t; + +/** + * Private data of an imc_state_t object. + */ +struct private_imc_android_state_t { + + /** + * Public interface + */ + imc_android_state_t public; + + /** + * TNCCS connection ID + */ + TNC_ConnectionID connection_id; + + /** + * TNCCS connection state + */ + TNC_ConnectionState state; + + /** + * Assessment/Evaluation Result + */ + TNC_IMV_Evaluation_Result result; + + /** + * Does the TNCCS connection support long message types? + */ + bool has_long; + + /** + * Does the TNCCS connection support exclusive delivery? + */ + bool has_excl; + + /** + * Maximum PA-TNC message size for this TNCCS connection + */ + u_int32_t max_msg_len; + + /** + * PA-TNC attribute segmentation contracts associated with TNCCS connection + */ + seg_contract_manager_t *contracts; + + /** + * TCG Platform Trust Service (PTS) + */ + pts_t *pts; +}; + +METHOD(imc_state_t, get_connection_id, TNC_ConnectionID, + private_imc_android_state_t *this) +{ + return this->connection_id; +} + +METHOD(imc_state_t, has_long, bool, + private_imc_android_state_t *this) +{ + return this->has_long; +} + +METHOD(imc_state_t, has_excl, bool, + private_imc_android_state_t *this) +{ + return this->has_excl; +} + +METHOD(imc_state_t, set_flags, void, + private_imc_android_state_t *this, bool has_long, bool has_excl) +{ + this->has_long = has_long; + this->has_excl = has_excl; +} + +METHOD(imc_state_t, set_max_msg_len, void, + private_imc_android_state_t *this, u_int32_t max_msg_len) +{ + this->max_msg_len = max_msg_len; +} + +METHOD(imc_state_t, get_max_msg_len, u_int32_t, + private_imc_android_state_t *this) +{ + return this->max_msg_len; +} + +METHOD(imc_state_t, get_contracts, seg_contract_manager_t*, + private_imc_android_state_t *this) +{ + return this->contracts; +} + +METHOD(imc_state_t, change_state, void, + private_imc_android_state_t *this, TNC_ConnectionState new_state) +{ + this->state = new_state; +} + +METHOD(imc_state_t, set_result, void, + private_imc_android_state_t *this, TNC_IMCID id, TNC_IMV_Evaluation_Result result) +{ + this->result = result; +} + +METHOD(imc_state_t, get_result, bool, + private_imc_android_state_t *this, TNC_IMCID id, TNC_IMV_Evaluation_Result *result) +{ + if (result) + { + *result = this->result; + } + return this->result != TNC_IMV_EVALUATION_RESULT_DONT_KNOW; +} + +METHOD(imc_state_t, destroy, void, + private_imc_android_state_t *this) +{ + this->contracts->destroy(this->contracts); + this->pts->destroy(this->pts); + free(this); +} + +METHOD(imc_android_state_t, get_pts, pts_t*, + private_imc_android_state_t *this) +{ + return this->pts; +} + +/** + * Described in header. + */ +imc_state_t *imc_android_state_create(TNC_ConnectionID connection_id) +{ + private_imc_android_state_t *this; + + INIT(this, + .public = { + .interface = { + .get_connection_id = _get_connection_id, + .has_long = _has_long, + .has_excl = _has_excl, + .set_flags = _set_flags, + .set_max_msg_len = _set_max_msg_len, + .get_max_msg_len = _get_max_msg_len, + .get_contracts = _get_contracts, + .change_state = _change_state, + .set_result = _set_result, + .get_result = _get_result, + .destroy = _destroy, + }, + .get_pts = _get_pts, + }, + .state = TNC_CONNECTION_STATE_CREATE, + .result = TNC_IMV_EVALUATION_RESULT_DONT_KNOW, + .connection_id = connection_id, + .contracts = seg_contract_manager_create(), + .pts = pts_create(TRUE), + ); + + return &this->public.interface; +} diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android_state.h b/src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android_state.h new file mode 100644 index 000000000..68197f331 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/byod/imc_android_state.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012 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. + */ + +/** + * @defgroup imc_android_state imc_android_state + * @{ @ingroup android_byod + */ + +#ifndef IMC_ANDROID_STATE_H_ +#define IMC_ANDROID_STATE_H_ + +#include +#include + +typedef struct imc_android_state_t imc_android_state_t; + +/** + * Internal state of an imc_android_t connection instance + */ +struct imc_android_state_t { + + /** + * imc_state_t interface + */ + imc_state_t interface; + + /** + * Get TCG Platform Trust Service (PTS) object + */ + pts_t *(*get_pts)(imc_android_state_t *this); +}; + +/** + * Create an imc_android_state_t instance + * + * @param id connection ID + */ +imc_state_t* imc_android_state_create(TNC_ConnectionID id); + +#endif /** IMC_ANDROID_STATE_H_ @}*/ diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c new file mode 100644 index 000000000..2655f7361 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.c @@ -0,0 +1,706 @@ +/* + * Copyright (C) 2012-2015 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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. + */ + +#include +#include +#include +#include +#include + +#include "charonservice.h" +#include "android_jni.h" +#include "backend/android_attr.h" +#include "backend/android_creds.h" +#include "backend/android_private_key.h" +#include "backend/android_service.h" +#include "kernel/android_ipsec.h" +#include "kernel/android_net.h" + +#ifdef USE_BYOD +#include "byod/imc_android.h" +#endif + +#include +#include +#include +#include +#include + +#define ANDROID_DEBUG_LEVEL 1 +#define ANDROID_RETRASNMIT_TRIES 3 +#define ANDROID_RETRANSMIT_TIMEOUT 2.0 +#define ANDROID_RETRANSMIT_BASE 1.4 + +typedef struct private_charonservice_t private_charonservice_t; + +/** + * private data of charonservice + */ +struct private_charonservice_t { + + /** + * public interface + */ + charonservice_t public; + + /** + * android_attr instance + */ + android_attr_t *attr; + + /** + * android_creds instance + */ + android_creds_t *creds; + + /** + * android_service instance + */ + android_service_t *service; + + /** + * VpnService builder (accessed via JNI) + */ + vpnservice_builder_t *builder; + + /** + * NetworkManager instance (accessed via JNI) + */ + network_manager_t *network_manager; + + /** + * CharonVpnService reference + */ + jobject vpn_service; + + /** + * Sockets that were bypassed and we keep track for + */ + linked_list_t *sockets; +}; + +/** + * Single instance of charonservice_t. + */ +charonservice_t *charonservice; + +/** + * hook in library for debugging messages + */ +extern void (*dbg)(debug_t group, level_t level, char *fmt, ...); + +/** + * Logging hook for library logs, using android specific logging + */ +static void dbg_android(debug_t group, level_t level, char *fmt, ...) +{ + va_list args; + + if (level <= ANDROID_DEBUG_LEVEL) + { + char sgroup[16], buffer[8192]; + char *current = buffer, *next; + + snprintf(sgroup, sizeof(sgroup), "%N", debug_names, group); + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + while (current) + { /* log each line separately */ + next = strchr(current, '\n'); + if (next) + { + *(next++) = '\0'; + } + __android_log_print(ANDROID_LOG_INFO, "charon", "00[%s] %s\n", + sgroup, current); + current = next; + } + } +} + +METHOD(charonservice_t, update_status, bool, + private_charonservice_t *this, android_vpn_state_t code) +{ + JNIEnv *env; + jmethodID method_id; + bool success = FALSE; + + androidjni_attach_thread(&env); + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_class, + "updateStatus", "(I)V"); + if (!method_id) + { + goto failed; + } + (*env)->CallVoidMethod(env, this->vpn_service, method_id, (jint)code); + success = !androidjni_exception_occurred(env); + +failed: + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return success; +} + +METHOD(charonservice_t, update_imc_state, bool, + private_charonservice_t *this, android_imc_state_t state) +{ + JNIEnv *env; + jmethodID method_id; + bool success = FALSE; + + androidjni_attach_thread(&env); + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_class, + "updateImcState", "(I)V"); + if (!method_id) + { + goto failed; + } + (*env)->CallVoidMethod(env, this->vpn_service, method_id, (jint)state); + success = !androidjni_exception_occurred(env); + +failed: + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return success; +} + +METHOD(charonservice_t, add_remediation_instr, bool, + private_charonservice_t *this, char *instr) +{ + JNIEnv *env; + jmethodID method_id; + jstring jinstr; + bool success = FALSE; + + androidjni_attach_thread(&env); + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_class, + "addRemediationInstruction", + "(Ljava/lang/String;)V"); + if (!method_id) + { + goto failed; + } + jinstr = (*env)->NewStringUTF(env, instr); + if (!jinstr) + { + goto failed; + } + (*env)->CallVoidMethod(env, this->vpn_service, method_id, jinstr); + success = !androidjni_exception_occurred(env); + +failed: + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return success; +} + +/** + * Bypass a single socket + */ +static bool bypass_single_socket(intptr_t fd, private_charonservice_t *this) +{ + JNIEnv *env; + jmethodID method_id; + + androidjni_attach_thread(&env); + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_class, + "protect", "(I)Z"); + if (!method_id) + { + goto failed; + } + if (!(*env)->CallBooleanMethod(env, this->vpn_service, method_id, fd)) + { + DBG2(DBG_KNL, "VpnService.protect() failed"); + goto failed; + } + androidjni_detach_thread(); + return TRUE; + +failed: + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +METHOD(charonservice_t, bypass_socket, bool, + private_charonservice_t *this, int fd, int family) +{ + if (fd >= 0) + { + this->sockets->insert_last(this->sockets, (void*)(intptr_t)fd); + return bypass_single_socket((intptr_t)fd, this); + } + this->sockets->invoke_function(this->sockets, (void*)bypass_single_socket, + this); + return TRUE; +} + +/** + * Converts the given Java array of byte arrays (byte[][]) to a linked list + * of chunk_t objects. + */ +static linked_list_t *convert_array_of_byte_arrays(JNIEnv *env, + jobjectArray jarray) +{ + linked_list_t *list; + jsize i; + + list = linked_list_create(); + for (i = 0; i < (*env)->GetArrayLength(env, jarray); ++i) + { + chunk_t *chunk; + jbyteArray jbytearray; + + chunk = malloc_thing(chunk_t); + list->insert_last(list, chunk); + + jbytearray = (*env)->GetObjectArrayElement(env, jarray, i); + *chunk = chunk_alloc((*env)->GetArrayLength(env, jbytearray)); + (*env)->GetByteArrayRegion(env, jbytearray, 0, chunk->len, chunk->ptr); + (*env)->DeleteLocalRef(env, jbytearray); + } + return list; +} + +METHOD(charonservice_t, get_trusted_certificates, linked_list_t*, + private_charonservice_t *this) +{ + JNIEnv *env; + jmethodID method_id; + jobjectArray jcerts; + linked_list_t *list; + + androidjni_attach_thread(&env); + + method_id = (*env)->GetMethodID(env, + android_charonvpnservice_class, + "getTrustedCertificates", "()[[B"); + if (!method_id) + { + goto failed; + } + jcerts = (*env)->CallObjectMethod(env, this->vpn_service, method_id); + if (!jcerts || androidjni_exception_occurred(env)) + { + goto failed; + } + list = convert_array_of_byte_arrays(env, jcerts); + androidjni_detach_thread(); + return list; + +failed: + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return NULL; +} + +METHOD(charonservice_t, get_user_certificate, linked_list_t*, + private_charonservice_t *this) +{ + JNIEnv *env; + jmethodID method_id; + jobjectArray jencodings; + linked_list_t *list; + + androidjni_attach_thread(&env); + + method_id = (*env)->GetMethodID(env, + android_charonvpnservice_class, + "getUserCertificate", "()[[B"); + if (!method_id) + { + goto failed; + } + jencodings = (*env)->CallObjectMethod(env, this->vpn_service, method_id); + if (!jencodings || androidjni_exception_occurred(env)) + { + goto failed; + } + list = convert_array_of_byte_arrays(env, jencodings); + androidjni_detach_thread(); + return list; + +failed: + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return NULL; +} + +METHOD(charonservice_t, get_user_key, private_key_t*, + private_charonservice_t *this, public_key_t *pubkey) +{ + JNIEnv *env; + jmethodID method_id; + private_key_t *key; + jobject jkey; + + androidjni_attach_thread(&env); + + method_id = (*env)->GetMethodID(env, + android_charonvpnservice_class, + "getUserKey", "()Ljava/security/PrivateKey;"); + if (!method_id) + { + goto failed; + } + jkey = (*env)->CallObjectMethod(env, this->vpn_service, method_id); + if (!jkey || androidjni_exception_occurred(env)) + { + goto failed; + } + key = android_private_key_create(jkey, pubkey); + androidjni_detach_thread(); + return key; + +failed: + DESTROY_IF(pubkey); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return NULL; +} + +METHOD(charonservice_t, get_vpnservice_builder, vpnservice_builder_t*, + private_charonservice_t *this) +{ + return this->builder; +} + +METHOD(charonservice_t, get_network_manager, network_manager_t*, + private_charonservice_t *this) +{ + return this->network_manager; +} + +/** + * Initiate a new connection + * + * @param settings configuration settings (gets owned) + */ +static void initiate(settings_t *settings) +{ + private_charonservice_t *this = (private_charonservice_t*)charonservice; + + lib->settings->set_str(lib->settings, + "charon.plugins.tnc-imc.preferred_language", + settings->get_str(settings, "global.language", "en")); + /* this is actually the size of the complete IKE/IP packet, so if the MTU + * for the TUN devices has to be reduced to pass traffic the IKE packets + * will be a bit smaller than necessary as there is no IPsec overhead like + * for the tunneled traffic (but compensating that seems like overkill) */ + lib->settings->set_int(lib->settings, + "charon.fragment_size", + settings->get_int(settings, "global.mtu", + ANDROID_DEFAULT_MTU)); + + this->creds->clear(this->creds); + DESTROY_IF(this->service); + this->service = android_service_create(this->creds, settings); +} + +/** + * Initialize/deinitialize Android backend + */ +static bool charonservice_register(plugin_t *plugin, plugin_feature_t *feature, + bool reg, void *data) +{ + private_charonservice_t *this = (private_charonservice_t*)charonservice; + if (reg) + { + lib->credmgr->add_set(lib->credmgr, &this->creds->set); + charon->attributes->add_handler(charon->attributes, + &this->attr->handler); + } + else + { + lib->credmgr->remove_set(lib->credmgr, &this->creds->set); + charon->attributes->remove_handler(charon->attributes, + &this->attr->handler); + if (this->service) + { + this->service->destroy(this->service); + this->service = NULL; + } + } + return TRUE; +} + +/** + * Set strongswan.conf options + */ +static void set_options(char *logfile) +{ + lib->settings->set_int(lib->settings, + "charon.plugins.android_log.loglevel", ANDROID_DEBUG_LEVEL); + /* setup file logger */ + lib->settings->set_str(lib->settings, + "charon.filelog.%s.time_format", "%b %e %T", logfile); + lib->settings->set_bool(lib->settings, + "charon.filelog.%s.append", FALSE, logfile); + lib->settings->set_bool(lib->settings, + "charon.filelog.%s.flush_line", TRUE, logfile); + lib->settings->set_int(lib->settings, + "charon.filelog.%s.default", ANDROID_DEBUG_LEVEL, logfile); + + lib->settings->set_int(lib->settings, + "charon.retransmit_tries", ANDROID_RETRASNMIT_TRIES); + lib->settings->set_double(lib->settings, + "charon.retransmit_timeout", ANDROID_RETRANSMIT_TIMEOUT); + lib->settings->set_double(lib->settings, + "charon.retransmit_base", ANDROID_RETRANSMIT_BASE); + lib->settings->set_bool(lib->settings, + "charon.initiator_only", TRUE); + lib->settings->set_bool(lib->settings, + "charon.close_ike_on_child_failure", TRUE); + /* setting the source address breaks the VpnService.protect() function which + * uses SO_BINDTODEVICE internally. the addresses provided to the kernel as + * auxiliary data have precedence over this option causing a routing loop if + * the gateway is contained in the VPN routes. alternatively, providing an + * explicit device (in addition or instead of the source address) in the + * auxiliary data would also work, but we currently don't have that + * information */ + lib->settings->set_bool(lib->settings, + "charon.plugins.socket-default.set_source", FALSE); + /* the Linux kernel does currently not support UDP encaspulation for IPv6 + * so lets disable IPv6 for now to avoid issues with dual-stack gateways */ + lib->settings->set_bool(lib->settings, + "charon.plugins.socket-default.use_ipv6", FALSE); + +#ifdef USE_BYOD + lib->settings->set_str(lib->settings, + "charon.plugins.eap-tnc.protocol", "tnccs-2.0"); + lib->settings->set_int(lib->settings, + "charon.plugins.eap-ttls.max_message_count", 0); + lib->settings->set_bool(lib->settings, + "android.imc.send_os_info", TRUE); + lib->settings->set_str(lib->settings, + "libtnccs.tnc_config", ""); +#endif +} + +/** + * Initialize the charonservice object + */ +static void charonservice_init(JNIEnv *env, jobject service, jobject builder, + jboolean byod) +{ + private_charonservice_t *this; + static plugin_feature_t features[] = { + PLUGIN_CALLBACK(kernel_ipsec_register, kernel_android_ipsec_create), + PLUGIN_PROVIDE(CUSTOM, "kernel-ipsec"), + PLUGIN_CALLBACK(kernel_net_register, kernel_android_net_create), + PLUGIN_PROVIDE(CUSTOM, "kernel-net"), + PLUGIN_CALLBACK(charonservice_register, NULL), + PLUGIN_PROVIDE(CUSTOM, "android-backend"), + PLUGIN_DEPENDS(CUSTOM, "libcharon"), + }; + + INIT(this, + .public = { + .update_status = _update_status, + .update_imc_state = _update_imc_state, + .add_remediation_instr = _add_remediation_instr, + .bypass_socket = _bypass_socket, + .get_trusted_certificates = _get_trusted_certificates, + .get_user_certificate = _get_user_certificate, + .get_user_key = _get_user_key, + .get_vpnservice_builder = _get_vpnservice_builder, + .get_network_manager = _get_network_manager, + }, + .attr = android_attr_create(), + .creds = android_creds_create(), + .builder = vpnservice_builder_create(builder), + .network_manager = network_manager_create(service), + .sockets = linked_list_create(), + .vpn_service = (*env)->NewGlobalRef(env, service), + ); + charonservice = &this->public; + + lib->plugins->add_static_features(lib->plugins, "androidbridge", features, + countof(features), TRUE, NULL, NULL); + +#ifdef USE_BYOD + if (byod) + { + plugin_feature_t byod_features[] = { + PLUGIN_CALLBACK(imc_android_register, this->vpn_service), + PLUGIN_PROVIDE(CUSTOM, "android-imc"), + PLUGIN_DEPENDS(CUSTOM, "android-backend"), + PLUGIN_DEPENDS(CUSTOM, "imc-manager"), + }; + + lib->plugins->add_static_features(lib->plugins, "android-byod", + byod_features, countof(byod_features), TRUE, NULL, NULL); + } +#endif +} + +/** + * Deinitialize the charonservice object + */ +static void charonservice_deinit(JNIEnv *env) +{ + private_charonservice_t *this = (private_charonservice_t*)charonservice; + + this->network_manager->destroy(this->network_manager); + this->sockets->destroy(this->sockets); + this->builder->destroy(this->builder); + this->creds->destroy(this->creds); + this->attr->destroy(this->attr); + (*env)->DeleteGlobalRef(env, this->vpn_service); + free(this); + charonservice = NULL; +} + +/** + * Handle SIGSEGV/SIGILL signals raised by threads + */ +static void segv_handler(int signal) +{ + dbg_android(DBG_DMN, 1, "thread %u received %d", thread_current_id(), + signal); + exit(1); +} + +/** + * Initialize charon and the libraries via JNI + */ +JNI_METHOD(CharonVpnService, initializeCharon, jboolean, + jobject builder, jstring jlogfile, jboolean byod) +{ + struct sigaction action; + struct utsname utsname; + char *logfile, *plugins; + + /* logging for library during initialization, as we have no bus yet */ + dbg = dbg_android; + + /* initialize library */ + if (!library_init(NULL, "charon")) + { + library_deinit(); + return FALSE; + } + + /* set options before initializing other libraries that might read them */ + logfile = androidjni_convert_jstring(env, jlogfile); + set_options(logfile); + free(logfile); + + if (!libhydra_init()) + { + libhydra_deinit(); + library_deinit(); + return FALSE; + } + + if (!libipsec_init()) + { + libipsec_deinit(); + libhydra_deinit(); + library_deinit(); + return FALSE; + } + + if (!libcharon_init()) + { + libcharon_deinit(); + libipsec_deinit(); + libhydra_deinit(); + library_deinit(); + return FALSE; + } + + charon->load_loggers(charon, NULL, FALSE); + + charonservice_init(env, this, builder, byod); + + if (uname(&utsname) != 0) + { + memset(&utsname, 0, sizeof(utsname)); + } + DBG1(DBG_DMN, "Starting IKE charon daemon (strongSwan "VERSION", %s %s, %s)", + utsname.sysname, utsname.release, utsname.machine); + +#ifdef PLUGINS_BYOD + if (byod) + { + plugins = PLUGINS " " PLUGINS_BYOD; + } + else +#endif + { + plugins = PLUGINS; + } + + if (!charon->initialize(charon, plugins)) + { + libcharon_deinit(); + charonservice_deinit(env); + libipsec_deinit(); + libhydra_deinit(); + library_deinit(); + return FALSE; + } + lib->plugins->status(lib->plugins, LEVEL_CTRL); + + /* add handler for SEGV and ILL etc. */ + action.sa_handler = segv_handler; + action.sa_flags = 0; + sigemptyset(&action.sa_mask); + sigaction(SIGSEGV, &action, NULL); + sigaction(SIGILL, &action, NULL); + sigaction(SIGBUS, &action, NULL); + action.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &action, NULL); + + /* start daemon (i.e. the threads in the thread-pool) */ + charon->start(charon); + return TRUE; +} + +/** + * Deinitialize charon and all libraries + */ +JNI_METHOD(CharonVpnService, deinitializeCharon, void) +{ + /* deinitialize charon before we destroy our own objects */ + libcharon_deinit(); + charonservice_deinit(env); + libipsec_deinit(); + libhydra_deinit(); + library_deinit(); +} + +/** + * Initiate SA + */ +JNI_METHOD(CharonVpnService, initiate, void, + jstring jconfig) +{ + settings_t *settings; + char *config; + + config = androidjni_convert_jstring(env, jconfig); + settings = settings_create_string(config); + initiate(settings); +} diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.h b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.h new file mode 100644 index 000000000..8cb68e099 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/charonservice.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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. + */ + +/** + * @defgroup libandroidbridge libandroidbridge + * + * @defgroup android_backend backend + * @ingroup libandroidbridge + * + * @defgroup android_byod byod + * @ingroup libandroidbridge + * + * @defgroup android_kernel kernel + * @ingroup libandroidbridge + * + * @defgroup charonservice charonservice + * @{ @ingroup libandroidbridge + */ + +#ifndef CHARONSERVICE_H_ +#define CHARONSERVICE_H_ + +#include "vpnservice_builder.h" +#include "kernel/network_manager.h" + +#include +#include + +typedef enum android_vpn_state_t android_vpn_state_t; +typedef enum android_imc_state_t android_imc_state_t; +typedef struct charonservice_t charonservice_t; + +/** + * Default value for the MTU of TUN device and the size of IKE fragments + */ +#define ANDROID_DEFAULT_MTU 1400 + +/** + * VPN status codes. As defined in CharonVpnService.java + */ +enum android_vpn_state_t { + CHARONSERVICE_CHILD_STATE_UP = 1, + CHARONSERVICE_CHILD_STATE_DOWN, + CHARONSERVICE_AUTH_ERROR, + CHARONSERVICE_PEER_AUTH_ERROR, + CHARONSERVICE_LOOKUP_ERROR, + CHARONSERVICE_UNREACHABLE_ERROR, + CHARONSERVICE_GENERIC_ERROR, +}; + +/** + * Final IMC state as defined in ImcState.java + */ +enum android_imc_state_t { + ANDROID_IMC_STATE_UNKNOWN = 0, + ANDROID_IMC_STATE_ALLOW = 1, + ANDROID_IMC_STATE_BLOCK = 2, + ANDROID_IMC_STATE_ISOLATE = 3, +}; + +/** + * Public interface of charonservice. + * + * Used to communicate with CharonVpnService via JNI + */ +struct charonservice_t { + + /** + * Update the status in the Java domain (UI) + * + * @param code status code + * @return TRUE on success + */ + bool (*update_status)(charonservice_t *this, android_vpn_state_t code); + + /** + * Update final IMC state in the Java domain (UI) + * + * @param state IMC state + * @return TRUE on success + */ + bool (*update_imc_state)(charonservice_t *this, android_imc_state_t state); + + /** + * Add a remediation instruction via JNI + * + * @param instr remediation instruction + * @return TRUE on success + */ + bool (*add_remediation_instr)(charonservice_t *this, char *instr); + + /** + * Install a bypass policy for the given socket using the protect() Method + * of the Android VpnService interface. + * + * Use -1 as fd to re-bypass previously bypassed sockets. + * + * @param fd socket file descriptor + * @param family socket protocol family + * @return TRUE if operation successful + */ + bool (*bypass_socket)(charonservice_t *this, int fd, int family); + + /** + * Get a list of trusted certificates via JNI + * + * @return list of DER encoded certificates (as chunk_t*), + * NULL on failure + */ + linked_list_t *(*get_trusted_certificates)(charonservice_t *this); + + /** + * Get the configured user certificate chain via JNI + * + * The first item in the returned list is the user certificate followed + * by any remaining elements of the certificate chain. + * + * @return list of DER encoded certificates (as chunk_t*), + * NULL on failure + */ + linked_list_t *(*get_user_certificate)(charonservice_t *this); + + /** + * Get the configured private key via JNI + * + * @param pubkey the public key as extracted from the certificate + * @return PrivateKey object, NULL on failure + */ + private_key_t *(*get_user_key)(charonservice_t *this, public_key_t *pubkey); + + /** + * Get the current vpnservice_builder_t object + * + * @return VpnService.Builder instance + */ + vpnservice_builder_t *(*get_vpnservice_builder)(charonservice_t *this); + + /** + * Get the current network_manager_t object + * + * @return NetworkManager instance + */ + network_manager_t *(*get_network_manager)(charonservice_t *this); +}; + +/** + * The single instance of charonservice_t. + * + * Set between JNI calls to initializeCharon() and deinitializeCharon(). + */ +extern charonservice_t *charonservice; + +#endif /** CHARONSERVICE_H_ @}*/ diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_ipsec.c b/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_ipsec.c new file mode 100644 index 000000000..2eef49f91 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_ipsec.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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. + */ + +#include "android_ipsec.h" +#include "../charonservice.h" + +#include +#include +#include +#include + +typedef struct private_kernel_android_ipsec_t private_kernel_android_ipsec_t; + +struct private_kernel_android_ipsec_t { + + /** + * Public kernel interface + */ + kernel_android_ipsec_t public; + + /** + * Listener for lifetime expire events + */ + ipsec_event_listener_t ipsec_listener; +}; + +/** + * Callback registrered with libipsec. + */ +static void expire(u_int8_t protocol, u_int32_t spi, host_t *dst, bool hard) +{ + hydra->kernel_interface->expire(hydra->kernel_interface, protocol, + spi, dst, hard); +} + +METHOD(kernel_ipsec_t, get_spi, status_t, + private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, + u_int8_t protocol, u_int32_t *spi) +{ + return ipsec->sas->get_spi(ipsec->sas, src, dst, protocol, spi); +} + +METHOD(kernel_ipsec_t, get_cpi, status_t, + private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, + u_int16_t *cpi) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, add_sa, status_t, + private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, + u_int32_t spi, u_int8_t protocol, u_int32_t reqid, mark_t mark, + u_int32_t tfc, lifetime_cfg_t *lifetime, u_int16_t enc_alg, chunk_t enc_key, + u_int16_t int_alg, chunk_t int_key, ipsec_mode_t mode, + u_int16_t ipcomp, u_int16_t cpi, u_int32_t replay_window, + bool initiator, bool encap, bool esn, bool inbound, bool update, + linked_list_t *src_ts, linked_list_t *dst_ts) +{ + return ipsec->sas->add_sa(ipsec->sas, src, dst, spi, protocol, reqid, mark, + tfc, lifetime, enc_alg, enc_key, int_alg, int_key, + mode, ipcomp, cpi, initiator, encap, esn, + inbound, update); +} + +METHOD(kernel_ipsec_t, update_sa, status_t, + private_kernel_android_ipsec_t *this, u_int32_t spi, u_int8_t protocol, + u_int16_t cpi, host_t *src, host_t *dst, host_t *new_src, host_t *new_dst, + bool encap, bool new_encap, mark_t mark) +{ + return ipsec->sas->update_sa(ipsec->sas, spi, protocol, cpi, src, dst, + new_src, new_dst, encap, new_encap, mark); +} + +METHOD(kernel_ipsec_t, query_sa, status_t, + private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, + u_int32_t spi, u_int8_t protocol, mark_t mark, + u_int64_t *bytes, u_int64_t *packets, time_t *time) +{ + return ipsec->sas->query_sa(ipsec->sas, src, dst, spi, protocol, mark, + bytes, packets, time); +} + +METHOD(kernel_ipsec_t, del_sa, status_t, + private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, + u_int32_t spi, u_int8_t protocol, u_int16_t cpi, mark_t mark) +{ + return ipsec->sas->del_sa(ipsec->sas, src, dst, spi, protocol, cpi, mark); +} + +METHOD(kernel_ipsec_t, flush_sas, status_t, + private_kernel_android_ipsec_t *this) +{ + return ipsec->sas->flush_sas(ipsec->sas); +} + +METHOD(kernel_ipsec_t, add_policy, status_t, + private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts, + policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, mark_t mark, + policy_priority_t priority) +{ + return ipsec->policies->add_policy(ipsec->policies, src, dst, src_ts, + dst_ts, direction, type, sa, mark, + priority); +} + +METHOD(kernel_ipsec_t, query_policy, status_t, + private_kernel_android_ipsec_t *this, traffic_selector_t *src_ts, + traffic_selector_t *dst_ts, policy_dir_t direction, mark_t mark, + time_t *use_time) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, del_policy, status_t, + private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts, + policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, + mark_t mark, policy_priority_t priority) +{ + return ipsec->policies->del_policy(ipsec->policies, src_ts, dst_ts, + direction, sa->reqid, mark, priority); +} + +METHOD(kernel_ipsec_t, flush_policies, status_t, + private_kernel_android_ipsec_t *this) +{ + ipsec->policies->flush_policies(ipsec->policies); + return SUCCESS; +} + +METHOD(kernel_ipsec_t, bypass_socket, bool, + private_kernel_android_ipsec_t *this, int fd, int family) +{ + return charonservice->bypass_socket(charonservice, fd, family); +} + +METHOD(kernel_ipsec_t, enable_udp_decap, bool, + private_kernel_android_ipsec_t *this, int fd, int family, u_int16_t port) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, destroy, void, + private_kernel_android_ipsec_t *this) +{ + ipsec->events->unregister_listener(ipsec->events, &this->ipsec_listener); + free(this); +} + +/* + * Described in header. + */ +kernel_android_ipsec_t *kernel_android_ipsec_create() +{ + private_kernel_android_ipsec_t *this; + + INIT(this, + .public = { + .interface = { + .get_spi = _get_spi, + .get_cpi = _get_cpi, + .add_sa = _add_sa, + .update_sa = _update_sa, + .query_sa = _query_sa, + .del_sa = _del_sa, + .flush_sas = _flush_sas, + .add_policy = _add_policy, + .query_policy = _query_policy, + .del_policy = _del_policy, + .flush_policies = _flush_policies, + .bypass_socket = _bypass_socket, + .enable_udp_decap = _enable_udp_decap, + .destroy = _destroy, + }, + }, + .ipsec_listener = { + .expire = expire, + }, + ); + + ipsec->events->register_listener(ipsec->events, &this->ipsec_listener); + + return &this->public; +} diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_ipsec.h b/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_ipsec.h new file mode 100644 index 000000000..b68c8b2a9 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_ipsec.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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. + */ + +/** + * @defgroup kernel_android_ipsec kernel_android_ipsec + * @{ @ingroup android_kernel + */ + +#ifndef KERNEL_ANDROID_IPSEC_H_ +#define KERNEL_ANDROID_IPSEC_H_ + +#include +#include + +typedef struct kernel_android_ipsec_t kernel_android_ipsec_t; + +/** + * Implementation of the ipsec interface using libipsec on Android + */ +struct kernel_android_ipsec_t { + + /** + * Implements kernel_ipsec_t interface + */ + kernel_ipsec_t interface; +}; + +/** + * Create a android ipsec interface instance. + * + * @return kernel_android_ipsec_t instance + */ +kernel_android_ipsec_t *kernel_android_ipsec_create(); + +#endif /** KERNEL_ANDROID_IPSEC_H_ @}*/ diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_net.c b/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_net.c new file mode 100644 index 000000000..2ce1bdfac --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_net.c @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2012-2015 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. + */ +#include +#include +#include +#include + +#include "android_net.h" + +#include "../android_jni.h" +#include "../charonservice.h" +#include +#include +#include + +/** delay before firing roam events (ms) */ +#define ROAM_DELAY 100 +#define ROAM_DELAY_RECHECK 1000 + +typedef struct private_android_net_t private_android_net_t; + +struct private_android_net_t { + + /** + * Public kernel interface + */ + kernel_net_t public; + + /** + * Reference to NetworkManager object + */ + network_manager_t *network_manager; + + /** + * Earliest time of the next roam event + */ + timeval_t next_roam; + + /** + * Mutex to check and update roam event time, and other private members + */ + mutex_t *mutex; + + /** + * List of virtual IPs + */ + linked_list_t *vips; + + /** + * Socket used to determine source address + */ + int socket_v4; + + /** + * Whether the device is currently connected + */ + bool connected; +}; + +/** + * callback function that raises the delayed roam event + */ +static job_requeue_t roam_event() +{ + /* this will fail if no connection is up */ + charonservice->bypass_socket(charonservice, -1, 0); + hydra->kernel_interface->roam(hydra->kernel_interface, TRUE); + return JOB_REQUEUE_NONE; +} + +/** + * Listen for connectivity change events and queue a roam event + */ +static void connectivity_cb(private_android_net_t *this, + bool disconnected) +{ + timeval_t now; + job_t *job; + + time_monotonic(&now); + this->mutex->lock(this->mutex); + this->connected = !disconnected; + if (!timercmp(&now, &this->next_roam, >)) + { + this->mutex->unlock(this->mutex); + return; + } + timeval_add_ms(&now, ROAM_DELAY); + this->next_roam = now; + this->mutex->unlock(this->mutex); + + job = (job_t*)callback_job_create((callback_job_cb_t)roam_event, NULL, + NULL, NULL); + lib->scheduler->schedule_job_ms(lib->scheduler, job, ROAM_DELAY); +} + +METHOD(kernel_net_t, get_source_addr, host_t*, + private_android_net_t *this, host_t *dest, host_t *src) +{ + union { + struct sockaddr sockaddr; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } addr; + socklen_t addrlen; + timeval_t now; + job_t *job; + + addrlen = *dest->get_sockaddr_len(dest); + addr.sockaddr.sa_family = AF_UNSPEC; + if (connect(this->socket_v4, &addr.sockaddr, addrlen) < 0) + { + DBG1(DBG_KNL, "failed to disconnect socket: %s", strerror(errno)); + return NULL; + } + if (android_sdk_version <= ANDROID_JELLY_BEAN_MR2) + { /* this seems to help avoiding the VIP, unless there is no connectivity + * at all */ + charonservice->bypass_socket(charonservice, -1, 0); + } + if (connect(this->socket_v4, dest->get_sockaddr(dest), addrlen) < 0) + { + /* don't report an error if we are not connected (ENETUNREACH) */ + if (errno != ENETUNREACH) + { + DBG1(DBG_KNL, "failed to connect socket: %s", strerror(errno)); + } + else + { + time_monotonic(&now); + this->mutex->lock(this->mutex); + if (this->connected && timercmp(&now, &this->next_roam, >)) + { /* we were not able to find a source address but reportedly are + * connected, trigger a recheck in case an IP address appears + * delayed but the callback is not triggered again */ + timeval_add_ms(&now, ROAM_DELAY_RECHECK); + this->next_roam = now; + this->mutex->unlock(this->mutex); + job = (job_t*)callback_job_create((callback_job_cb_t)roam_event, + NULL, NULL, NULL); + lib->scheduler->schedule_job_ms(lib->scheduler, job, + ROAM_DELAY_RECHECK); + } + else + { + this->mutex->unlock(this->mutex); + } + } + return NULL; + } + if (getsockname(this->socket_v4, &addr.sockaddr, &addrlen) < 0) + { + DBG1(DBG_KNL, "failed to determine src address: %s", strerror(errno)); + return NULL; + } + return host_create_from_sockaddr((sockaddr_t*)&addr); +} + +METHOD(kernel_net_t, get_source_addr_old, host_t*, + private_android_net_t *this, host_t *dest, host_t *src) +{ + host_t *host; + + /* on older Android versions we might get the virtual IP back because + * the protect() implementation there and connect() don't properly work + * together, on newer releases (using fwmarks) that's not a problem */ + host = get_source_addr(this, dest, src); + if (host) + { + this->mutex->lock(this->mutex); + if (this->vips->find_first(this->vips, (void*)host->ip_equals, + NULL, host) == SUCCESS) + { + host->destroy(host); + host = NULL; + } + this->mutex->unlock(this->mutex); + } + return host; +} + +METHOD(kernel_net_t, get_nexthop, host_t*, + private_android_net_t *this, host_t *dest, int prefix, host_t *src) +{ + return NULL; +} + +METHOD(kernel_net_t, get_interface, bool, + private_android_net_t *this, host_t *host, char **name) +{ + if (name) + { /* the actual name does not matter in our case */ + *name = strdup("strongswan"); + } + return TRUE; +} + +METHOD(kernel_net_t, create_address_enumerator, enumerator_t*, + private_android_net_t *this, kernel_address_type_t which) +{ + /* return virtual IPs if requested, nothing otherwise */ + if (which & ADDR_TYPE_VIRTUAL) + { + this->mutex->lock(this->mutex); + return enumerator_create_cleaner( + this->vips->create_enumerator(this->vips), + (void*)this->mutex->unlock, this->mutex); + } + return enumerator_create_empty(); +} + +METHOD(kernel_net_t, add_ip, status_t, + private_android_net_t *this, host_t *virtual_ip, int prefix, char *iface) +{ + this->mutex->lock(this->mutex); + this->vips->insert_last(this->vips, virtual_ip->clone(virtual_ip)); + this->mutex->unlock(this->mutex); + return SUCCESS; +} + +METHOD(kernel_net_t, del_ip, status_t, + private_android_net_t *this, host_t *virtual_ip, int prefix, bool wait) +{ + host_t *vip; + + this->mutex->lock(this->mutex); + if (this->vips->find_first(this->vips, (void*)virtual_ip->ip_equals, + (void**)&vip, virtual_ip) == SUCCESS) + { + this->vips->remove(this->vips, vip, NULL); + vip->destroy(vip); + } + this->mutex->unlock(this->mutex); + return SUCCESS; +} + +METHOD(kernel_net_t, add_route, status_t, + private_android_net_t *this, chunk_t dst_net, u_int8_t prefixlen, + host_t *gateway, host_t *src_ip, char *if_name) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_net_t, del_route, status_t, + private_android_net_t *this, chunk_t dst_net, u_int8_t prefixlen, + host_t *gateway, host_t *src_ip, char *if_name) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_net_t, destroy, void, + private_android_net_t *this) +{ + this->network_manager->remove_connectivity_cb(this->network_manager, + (void*)connectivity_cb); + this->mutex->destroy(this->mutex); + this->vips->destroy(this->vips); + close(this->socket_v4); + free(this); +} + +kernel_net_t *kernel_android_net_create() +{ + private_android_net_t *this; + + INIT(this, + .public = { + .get_source_addr = _get_source_addr, + .get_nexthop = _get_nexthop, + .get_interface = _get_interface, + .create_address_enumerator = _create_address_enumerator, + .add_ip = _add_ip, + .del_ip = _del_ip, + .add_route = _add_route, + .del_route = _del_route, + .destroy = _destroy, + }, + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .vips = linked_list_create(), + .network_manager = charonservice->get_network_manager(charonservice), + ); + timerclear(&this->next_roam); + + if (android_sdk_version <= ANDROID_JELLY_BEAN_MR2) + { + this->public.get_source_addr = _get_source_addr_old; + } + + this->socket_v4 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (this->socket_v4 < 0) + { + DBG1(DBG_KNL, "failed to create socket to lookup src addresses: %s", + strerror(errno)); + } + charonservice->bypass_socket(charonservice, this->socket_v4, AF_INET); + + this->mutex->lock(this->mutex); + this->network_manager->add_connectivity_cb( + this->network_manager, (void*)connectivity_cb, this); + this->connected = this->network_manager->is_connected(this->network_manager); + this->mutex->unlock(this->mutex); + return &this->public; +} diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_net.h b/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_net.h new file mode 100644 index 000000000..761fa21bc --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_net.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2012-2015 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. + */ + +/** + * @defgroup android_net android_net + * @{ @ingroup android_kernel + */ + +#ifndef ANDROID_NET_H_ +#define ANDROID_NET_H_ + +#include +#include + +/** + * Create an Android-specific kernel_net_t instance. + * + * @return kernel_net_t instance + */ +kernel_net_t *kernel_android_net_create(); + + +#endif /** ANDROID_NET_H_ @}*/ diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/network_manager.c b/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/network_manager.c new file mode 100644 index 000000000..372b25c55 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/network_manager.c @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2012-2015 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. + */ + +#include "network_manager.h" + +#include "../android_jni.h" +#include "../charonservice.h" +#include +#include + +typedef struct private_network_manager_t private_network_manager_t; + +struct private_network_manager_t { + + /** + * Public interface + */ + network_manager_t public; + + /** + * Reference to NetworkManager object + */ + jobject obj; + + /** + * Java class for NetworkManager + */ + jclass cls; + + /** + * Registered callback + */ + struct { + connectivity_cb_t cb; + void *data; + } connectivity_cb; + + /** + * Mutex to access callback + */ + mutex_t *mutex; +}; + +JNI_METHOD(NetworkManager, networkChanged, void, + bool disconnected) +{ + private_network_manager_t *nm; + + nm = (private_network_manager_t*)charonservice->get_network_manager( + charonservice); + nm->mutex->lock(nm->mutex); + if (nm->connectivity_cb.cb) + { + nm->connectivity_cb.cb(nm->connectivity_cb.data, disconnected); + } + nm->mutex->unlock(nm->mutex); +} + +METHOD(network_manager_t, add_connectivity_cb, void, + private_network_manager_t *this, connectivity_cb_t cb, void *data) +{ + this->mutex->lock(this->mutex); + if (!this->connectivity_cb.cb) + { + JNIEnv *env; + jmethodID method_id; + + androidjni_attach_thread(&env); + method_id = (*env)->GetMethodID(env, this->cls, "Register", "()V"); + if (!method_id) + { + androidjni_exception_occurred(env); + } + else + { + (*env)->CallVoidMethod(env, this->obj, method_id); + if (!androidjni_exception_occurred(env)) + { + this->connectivity_cb.cb = cb; + this->connectivity_cb.data = data; + } + } + androidjni_detach_thread(); + } + this->mutex->unlock(this->mutex); +} + +/** + * Unregister the NetworkManager via JNI. + * + * this->mutex has to be locked + */ +static void unregister_network_manager(private_network_manager_t *this) +{ + JNIEnv *env; + jmethodID method_id; + + androidjni_attach_thread(&env); + method_id = (*env)->GetMethodID(env, this->cls, "Unregister", "()V"); + if (!method_id) + { + androidjni_exception_occurred(env); + } + else + { + (*env)->CallVoidMethod(env, this->obj, method_id); + androidjni_exception_occurred(env); + } + androidjni_detach_thread(); +} + +METHOD(network_manager_t, remove_connectivity_cb, void, + private_network_manager_t *this, connectivity_cb_t cb) +{ + this->mutex->lock(this->mutex); + if (this->connectivity_cb.cb == cb) + { + this->connectivity_cb.cb = NULL; + unregister_network_manager(this); + } + this->mutex->unlock(this->mutex); +} + +METHOD(network_manager_t, is_connected, bool, + private_network_manager_t *this) +{ + JNIEnv *env; + jmethodID method_id; + bool connected = FALSE; + + androidjni_attach_thread(&env); + method_id = (*env)->GetMethodID(env, this->cls, "isConnected", "()Z"); + if (!method_id) + { + androidjni_exception_occurred(env); + } + else + { + connected = (*env)->CallBooleanMethod(env, this->obj, method_id); + connected = !androidjni_exception_occurred(env) && connected; + } + androidjni_detach_thread(); + return connected; +} + +METHOD(network_manager_t, destroy, void, + private_network_manager_t *this) +{ + JNIEnv *env; + + this->mutex->lock(this->mutex); + if (this->connectivity_cb.cb) + { + this->connectivity_cb.cb = NULL; + unregister_network_manager(this); + } + this->mutex->unlock(this->mutex); + + androidjni_attach_thread(&env); + if (this->obj) + { + (*env)->DeleteGlobalRef(env, this->obj); + } + if (this->cls) + { + (*env)->DeleteGlobalRef(env, this->cls); + } + androidjni_detach_thread(); + this->mutex->destroy(this->mutex); + free(this); +} + +/* + * Described in header. + */ +network_manager_t *network_manager_create(jobject context) +{ + private_network_manager_t *this; + JNIEnv *env; + jmethodID method_id; + jobject obj; + jclass cls; + + INIT(this, + .public = { + .add_connectivity_cb = _add_connectivity_cb, + .remove_connectivity_cb = _remove_connectivity_cb, + .is_connected = _is_connected, + .destroy = _destroy, + }, + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + ); + + androidjni_attach_thread(&env); + cls = (*env)->FindClass(env, JNI_PACKAGE_STRING "/NetworkManager"); + if (!cls) + { + goto failed; + } + this->cls = (*env)->NewGlobalRef(env, cls); + method_id = (*env)->GetMethodID(env, cls, "", + "(Landroid/content/Context;)V"); + if (!method_id) + { + goto failed; + } + obj = (*env)->NewObject(env, cls, method_id, context); + if (!obj) + { + goto failed; + } + this->obj = (*env)->NewGlobalRef(env, obj); + androidjni_detach_thread(); + return &this->public; + +failed: + DBG1(DBG_KNL, "failed to build NetworkManager object"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + destroy(this); + return NULL; +}; diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/network_manager.h b/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/network_manager.h new file mode 100644 index 000000000..9a6a715b7 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/network_manager.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012-2015 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. + */ + +/** + * @defgroup network_manager network_manager + * @{ @ingroup android_kernel + */ + +#ifndef NETWORK_MANAGER_H_ +#define NETWORK_MANAGER_H_ + +#include + +#include +#include + +typedef struct network_manager_t network_manager_t; + +/** + * Callback called if connectivity changes somehow. + * + * Implementation should be quick as the call is made by the Java apps main + * thread. + * + * @param data data supplied during registration + * @param disconnected TRUE if currently disconnected + */ +typedef void (*connectivity_cb_t)(void *data, bool disconnected); + +/** + * NetworkManager, used to listen for network changes. + * + * Communicates with NetworkManager via JNI + */ +struct network_manager_t { + + /** + * Register a callback that is called if connectivity changes + * + * @note Only the first registered callback is currently used + * + * @param cb callback to register + * @param data data provided to callback + */ + void (*add_connectivity_cb)(network_manager_t *this, connectivity_cb_t cb, + void *data); + + /** + * Unregister a previously registered callback for connectivity changes + * + * @param cb previously registered callback + */ + void (*remove_connectivity_cb)(network_manager_t *this, + connectivity_cb_t cb); + + /** + * Check whether we currently have connectivity + * + * @return TRUE if currently connected + */ + bool (*is_connected)(network_manager_t *this); + + /** + * Destroy a network_manager_t instance + */ + void (*destroy)(network_manager_t *this); +}; + +/** + * Create a network_manager_t instance + * + * @param context Context object + * @return network_manager_t instance + */ +network_manager_t *network_manager_create(jobject context); + +#endif /** NETWORK_MANAGER_H_ @}*/ diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/vpnservice_builder.c b/src/frontends/android/app/src/main/jni/libandroidbridge/vpnservice_builder.c new file mode 100644 index 000000000..c7a6eb6da --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/vpnservice_builder.c @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2012-2014 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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. + */ + +#include "vpnservice_builder.h" +#include "android_jni.h" + +#include +#include + +typedef struct private_vpnservice_builder_t private_vpnservice_builder_t; + +/** + * private data of vpnservice_builder + */ +struct private_vpnservice_builder_t { + + /** + * public interface + */ + vpnservice_builder_t public; + + /** + * Java object + */ + jobject builder; +}; + +METHOD(vpnservice_builder_t, add_address, bool, + private_vpnservice_builder_t *this, host_t *addr) +{ + JNIEnv *env; + jmethodID method_id; + jstring str; + char buf[INET6_ADDRSTRLEN]; + int prefix; + + androidjni_attach_thread(&env); + + DBG2(DBG_LIB, "builder: adding interface address %H", addr); + + prefix = addr->get_family(addr) == AF_INET ? 32 : 128; + if (snprintf(buf, sizeof(buf), "%H", addr) >= sizeof(buf)) + { + goto failed; + } + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, + "addAddress", "(Ljava/lang/String;I)Z"); + if (!method_id) + { + goto failed; + } + str = (*env)->NewStringUTF(env, buf); + if (!str) + { + goto failed; + } + if (!(*env)->CallBooleanMethod(env, this->builder, method_id, str, prefix)) + { + goto failed; + } + androidjni_detach_thread(); + return TRUE; + +failed: + DBG1(DBG_LIB, "builder: failed to add address"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +METHOD(vpnservice_builder_t, set_mtu, bool, + private_vpnservice_builder_t *this, int mtu) +{ + JNIEnv *env; + jmethodID method_id; + + androidjni_attach_thread(&env); + + DBG2(DBG_LIB, "builder: setting MTU to %d", mtu); + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, + "setMtu", "(I)Z"); + if (!method_id) + { + goto failed; + } + if (!(*env)->CallBooleanMethod(env, this->builder, method_id, mtu)) + { + goto failed; + } + androidjni_detach_thread(); + return TRUE; + +failed: + DBG1(DBG_LIB, "builder: failed to set MTU"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +METHOD(vpnservice_builder_t, add_route, bool, + private_vpnservice_builder_t *this, host_t *net, int prefix) +{ + JNIEnv *env; + jmethodID method_id; + jstring str; + char buf[INET6_ADDRSTRLEN]; + + androidjni_attach_thread(&env); + + DBG2(DBG_LIB, "builder: adding route %+H/%d", net, prefix); + + if (snprintf(buf, sizeof(buf), "%+H", net) >= sizeof(buf)) + { + goto failed; + } + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, + "addRoute", "(Ljava/lang/String;I)Z"); + if (!method_id) + { + goto failed; + } + str = (*env)->NewStringUTF(env, buf); + if (!str) + { + goto failed; + } + if (!(*env)->CallBooleanMethod(env, this->builder, method_id, str, prefix)) + { + goto failed; + } + androidjni_detach_thread(); + return TRUE; + +failed: + DBG1(DBG_LIB, "builder: failed to add route"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +METHOD(vpnservice_builder_t, add_dns, bool, + private_vpnservice_builder_t *this, host_t *dns) +{ + JNIEnv *env; + jmethodID method_id; + jstring str; + char buf[INET6_ADDRSTRLEN]; + + androidjni_attach_thread(&env); + + DBG2(DBG_LIB, "builder: adding DNS server %H", dns); + + if (snprintf(buf, sizeof(buf), "%H", dns) >= sizeof(buf)) + { + goto failed; + } + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, + "addDnsServer", "(Ljava/lang/String;)Z"); + if (!method_id) + { + goto failed; + } + str = (*env)->NewStringUTF(env, buf); + if (!str) + { + goto failed; + } + if (!(*env)->CallBooleanMethod(env, this->builder, method_id, str)) + { + goto failed; + } + androidjni_detach_thread(); + return TRUE; + +failed: + DBG1(DBG_LIB, "builder: failed to add DNS server"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +/** + * Establish or reestablish the TUN device + */ +static int establish_internal(private_vpnservice_builder_t *this, char *method) +{ + JNIEnv *env; + jmethodID method_id; + int fd; + + androidjni_attach_thread(&env); + + DBG2(DBG_LIB, "builder: building TUN device"); + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, + method, "()I"); + if (!method_id) + { + goto failed; + } + fd = (*env)->CallIntMethod(env, this->builder, method_id); + if (fd == -1) + { + goto failed; + } + androidjni_detach_thread(); + return fd; + +failed: + DBG1(DBG_LIB, "builder: failed to build TUN device"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return -1; +} + +METHOD(vpnservice_builder_t, establish, int, + private_vpnservice_builder_t *this) +{ + return establish_internal(this, "establish"); +} + +METHOD(vpnservice_builder_t, establish_no_dns, int, + private_vpnservice_builder_t *this) +{ + return establish_internal(this, "establishNoDns"); +} + +METHOD(vpnservice_builder_t, destroy, void, + private_vpnservice_builder_t *this) +{ + JNIEnv *env; + + androidjni_attach_thread(&env); + (*env)->DeleteGlobalRef(env, this->builder); + androidjni_detach_thread(); + free(this); +} + +vpnservice_builder_t *vpnservice_builder_create(jobject builder) +{ + JNIEnv *env; + private_vpnservice_builder_t *this; + + INIT(this, + .public = { + .add_address = _add_address, + .add_route = _add_route, + .add_dns = _add_dns, + .set_mtu = _set_mtu, + .establish = _establish, + .establish_no_dns = _establish_no_dns, + .destroy = _destroy, + }, + ); + + androidjni_attach_thread(&env); + this->builder = (*env)->NewGlobalRef(env, builder); + androidjni_detach_thread(); + + return &this->public; +} diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/vpnservice_builder.h b/src/frontends/android/app/src/main/jni/libandroidbridge/vpnservice_builder.h new file mode 100644 index 000000000..08c436da6 --- /dev/null +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/vpnservice_builder.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012-2014 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * 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. + */ + +/** + * @defgroup vpnservice_builder vpnservice_builder + * @{ @ingroup libandroidbridge + */ + +#ifndef VPNSERVICE_BUILDER_H_ +#define VPNSERVICE_BUILDER_H_ + +#include + +#include +#include + +typedef struct vpnservice_builder_t vpnservice_builder_t; + +/** + * VpnService.Builder, used to build a TUN device. + * + * Communicates with CharonVpnService.BuilderAdapter via JNI + */ +struct vpnservice_builder_t { + + /** + * Add an interface address + * + * @param addr the desired interface address + * @return TRUE on success + */ + bool (*add_address)(vpnservice_builder_t *this, host_t *addr); + + /** + * Add a route + * + * @param net the network address + * @param prefix_length the prefix length + * @return TRUE on success + */ + bool (*add_route)(vpnservice_builder_t *this, host_t *net, int prefix); + + /** + * Add a DNS server + * + * @param dns the address of the DNS server + * @return TRUE on success + */ + bool (*add_dns)(vpnservice_builder_t *this, host_t *dns); + + /** + * Set the MTU for the TUN device + * + * @param mtu the MTU to set + * @return TRUE on success + */ + bool (*set_mtu)(vpnservice_builder_t *this, int mtu); + + /** + * Build the TUN device + * + * @return the TUN file descriptor, -1 if failed + */ + int (*establish)(vpnservice_builder_t *this); + + /** + * Build the TUN device without DNS related data + * + * @return the TUN file descriptor, -1 if failed + */ + int (*establish_no_dns)(vpnservice_builder_t *this); + + /** + * Destroy a vpnservice_builder + */ + void (*destroy)(vpnservice_builder_t *this); + +}; + +/** + * Create a vpnservice_builder instance + * + * @param builder CharonVpnService.BuilderAdapter object + * @return vpnservice_builder_t instance + */ +vpnservice_builder_t *vpnservice_builder_create(jobject builder); + +#endif /** VPNSERVICE_BUILDER_H_ @}*/ diff --git a/src/frontends/android/app/src/main/res/drawable-hdpi/ic_launcher.png b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..7cd1df4ee Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/src/frontends/android/app/src/main/res/drawable-mdpi/ic_launcher.png b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..200ee9677 Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_launcher.png b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..2eb6db1b6 Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/src/frontends/android/app/src/main/res/drawable/remediation_instruction_background_large.xml b/src/frontends/android/app/src/main/res/drawable/remediation_instruction_background_large.xml new file mode 100644 index 000000000..470fecb12 --- /dev/null +++ b/src/frontends/android/app/src/main/res/drawable/remediation_instruction_background_large.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/drawable/state_background.xml b/src/frontends/android/app/src/main/res/drawable/state_background.xml new file mode 100644 index 000000000..ee36325cc --- /dev/null +++ b/src/frontends/android/app/src/main/res/drawable/state_background.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/layout-large/remediation_instructions.xml b/src/frontends/android/app/src/main/res/layout-large/remediation_instructions.xml new file mode 100644 index 000000000..5a28dd654 --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout-large/remediation_instructions.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/layout/imc_state_fragment.xml b/src/frontends/android/app/src/main/res/layout/imc_state_fragment.xml new file mode 100644 index 000000000..171c88d2d --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/imc_state_fragment.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/layout/log_activity.xml b/src/frontends/android/app/src/main/res/layout/log_activity.xml new file mode 100644 index 000000000..80fee09fb --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/log_activity.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/src/frontends/android/app/src/main/res/layout/log_fragment.xml b/src/frontends/android/app/src/main/res/layout/log_fragment.xml new file mode 100644 index 000000000..c2e187a66 --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/log_fragment.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/layout/login_dialog.xml b/src/frontends/android/app/src/main/res/layout/login_dialog.xml new file mode 100644 index 000000000..0262af0a3 --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/login_dialog.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/layout/main.xml b/src/frontends/android/app/src/main/res/layout/main.xml new file mode 100644 index 000000000..ab03e72bc --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/main.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml new file mode 100644 index 000000000..57d5606ff --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/frontends/android/app/src/main/res/layout/profile_list_fragment.xml b/src/frontends/android/app/src/main/res/layout/profile_list_fragment.xml new file mode 100644 index 000000000..50d628bfa --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/profile_list_fragment.xml @@ -0,0 +1,38 @@ + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/layout/profile_list_item.xml b/src/frontends/android/app/src/main/res/layout/profile_list_item.xml new file mode 100644 index 000000000..93df7b649 --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/profile_list_item.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/layout/remediation_instruction.xml b/src/frontends/android/app/src/main/res/layout/remediation_instruction.xml new file mode 100644 index 000000000..09c0d43a3 --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/remediation_instruction.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/layout/remediation_instruction_item.xml b/src/frontends/android/app/src/main/res/layout/remediation_instruction_item.xml new file mode 100644 index 000000000..c25e6c123 --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/remediation_instruction_item.xml @@ -0,0 +1,47 @@ + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/layout/remediation_instructions.xml b/src/frontends/android/app/src/main/res/layout/remediation_instructions.xml new file mode 100644 index 000000000..84143b575 --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/remediation_instructions.xml @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/src/frontends/android/app/src/main/res/layout/trusted_certificates_activity.xml b/src/frontends/android/app/src/main/res/layout/trusted_certificates_activity.xml new file mode 100644 index 000000000..966ecf25e --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/trusted_certificates_activity.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/src/frontends/android/app/src/main/res/layout/trusted_certificates_item.xml b/src/frontends/android/app/src/main/res/layout/trusted_certificates_item.xml new file mode 100644 index 000000000..609d06a7a --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/trusted_certificates_item.xml @@ -0,0 +1,36 @@ + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/layout/two_line_button.xml b/src/frontends/android/app/src/main/res/layout/two_line_button.xml new file mode 100644 index 000000000..89d095295 --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/two_line_button.xml @@ -0,0 +1,39 @@ + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/layout/vpn_profile_select.xml b/src/frontends/android/app/src/main/res/layout/vpn_profile_select.xml new file mode 100644 index 000000000..7f49aa4e7 --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/vpn_profile_select.xml @@ -0,0 +1,30 @@ + + + + + + + \ No newline at end of file diff --git a/src/frontends/android/app/src/main/res/layout/vpn_state_fragment.xml b/src/frontends/android/app/src/main/res/layout/vpn_state_fragment.xml new file mode 100644 index 000000000..e347c4c4b --- /dev/null +++ b/src/frontends/android/app/src/main/res/layout/vpn_state_fragment.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/menu/certificates.xml b/src/frontends/android/app/src/main/res/menu/certificates.xml new file mode 100644 index 000000000..6066cab60 --- /dev/null +++ b/src/frontends/android/app/src/main/res/menu/certificates.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/menu/log.xml b/src/frontends/android/app/src/main/res/menu/log.xml new file mode 100644 index 000000000..1af5bd397 --- /dev/null +++ b/src/frontends/android/app/src/main/res/menu/log.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/src/frontends/android/app/src/main/res/menu/main.xml b/src/frontends/android/app/src/main/res/menu/main.xml new file mode 100644 index 000000000..3dde5227e --- /dev/null +++ b/src/frontends/android/app/src/main/res/menu/main.xml @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/frontends/android/app/src/main/res/menu/profile_edit.xml b/src/frontends/android/app/src/main/res/menu/profile_edit.xml new file mode 100644 index 000000000..e69020ed0 --- /dev/null +++ b/src/frontends/android/app/src/main/res/menu/profile_edit.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/src/frontends/android/app/src/main/res/menu/profile_list.xml b/src/frontends/android/app/src/main/res/menu/profile_list.xml new file mode 100644 index 000000000..57c9a86a4 --- /dev/null +++ b/src/frontends/android/app/src/main/res/menu/profile_list.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/src/frontends/android/app/src/main/res/menu/profile_list_context.xml b/src/frontends/android/app/src/main/res/menu/profile_list_context.xml new file mode 100644 index 000000000..e674ae856 --- /dev/null +++ b/src/frontends/android/app/src/main/res/menu/profile_list_context.xml @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/frontends/android/app/src/main/res/values-de/arrays.xml b/src/frontends/android/app/src/main/res/values-de/arrays.xml new file mode 100644 index 000000000..d05140165 --- /dev/null +++ b/src/frontends/android/app/src/main/res/values-de/arrays.xml @@ -0,0 +1,25 @@ + + + + + + IKEv2 EAP (Benutzername/Passwort) + IKEv2 Zertifikat + IKEv2 Zertifikat + EAP (Benutzername/Passwort) + IKEv2 EAP-TLS (Zertifikat) + IKEv2 EAP-TNC (Benutzername/Passwort) + + \ No newline at end of file diff --git a/src/frontends/android/app/src/main/res/values-de/strings.xml b/src/frontends/android/app/src/main/res/values-de/strings.xml new file mode 100644 index 000000000..6cd5ba50a --- /dev/null +++ b/src/frontends/android/app/src/main/res/values-de/strings.xml @@ -0,0 +1,134 @@ + + + + + + strongSwan VPN Client + strongSwan + Log anzeigen + Suchen + VPN nicht unterstützt + Ihr Gerät unterstützt keine VPN Anwendungen.\nBitte kontaktieren Sie den Hersteller. + VPN Verbindungen sind nicht möglich im abgeriegelten Modus. + Laden… + Profil nicht gefunden + strongSwan-Verknüpfung + + + Log + Logdatei senden + Logdatei ist leer + strongSwan %1$s Logdatei + + + Keine VPN Profile vorhanden. + Profil hinzufügen + Bearbeiten + Löschen + Profile auswählen + Ausgewählte Profile gelöscht + Kein Profil ausgewählt + Ein Profil ausgewählt + %1$d Profile ausgewählt + + + Speichern + Abbrechen + Profilname: + (Gateway-Adresse verwenden) + Gateway: + Typ: + Benutzername: + Passwort: + (anfordern wenn benötigt) + Benutzer-Zertifikat: + Benutzer-Zertifikat auswählen + Wählen Sie ein bestimmtes Benutzer-Zertifikat + CA-Zertifikat: + Automatisch wählen + CA-Zertifikat auswählen + Wählen Sie ein bestimmtes CA-Zertifikat + Erweiterte Einstellungen anzeigen + MTU: + Server Port: + (Standardwert verwenden) + Split-Tunneling: + Blockiere IPv4 Verkehr der nicht für das VPN bestimmt ist + Blockiere IPv6 Verkehr der nicht für das VPN bestimmt ist + + Bitte geben Sie hier die Gateway-Adresse ein + Bitte geben Sie hier Ihren Benutzernamen ein + Kein CA-Zertifikat ausgewählt + Bitte wählen Sie eines aus oder aktivieren Sie Automatisch wählen + Bitte geben Sie eine Nummer von %1$d - %2$d ein + EAP-TNC kann Ihre Privatsphäre beeinträchtigen + Gerätedaten werden an den Gateway-Betreiber gesendet + <p>Trusted Network Connect (TNC) erlaubt Gateway-Betreibern den Gesundheitszustand von Endgeräten zu prüfen.</p><p>Dazu kann der Betreiber Daten verlangen, wie etwa eine eindeutige Identifikationsnummer, eine Liste der installierten Pakete, Systemeinstellungen oder kryptografische Prüfsummen von Dateien.</p><b>Solche Daten werden nur übermittelt nachdem die Identität des Gateways geprüft wurde.</b> + + + CA-Zertifikate + Keine Zertifikate + CA-Zertifikate neu laden + System + Benutzer + Importiert + Zertifikat löschen? + Das Zertifikat wird permanent entfernt! + Zertifikat importieren + Zertifikat erfolgreich importiert + Zertifikat-Import fehlgeschlagen + + + Status: + Profil: + Trennen + Verbinden… + Verbunden + Trennen… + Kein aktives Profil + Fehler + + + Assessment: + Eingeschränkt + Fehlgeschlagen + Korrekturanweisungen anzeigen + + + Korrekturanweisungen + + + Passwort eingeben um zu verbinden + Verbinden + Fehler beim Aufsetzen des VPN: + Gateway-Adresse konnte nicht aufgelöst werden. + Gateway ist nicht erreichbar. + Authentifizierung des Gateway ist fehlgeschlagen. + Benutzerauthentifizierung ist fehlgeschlagen. + Sicherheitsassessment ist fehlgeschlagen. + Unbekannter Fehler während des Verbindens. + Verbinden: %1$s + Verbinde mit \""%1$s\". + VPN verbunden + Dieses VPN Profil ist momentan verbunden! + Neu verbinden + Verbinde %1$s? + Dies ersetzt die aktuelle VPN Verbindung! + Verbinden + + diff --git a/src/frontends/android/app/src/main/res/values-pl/arrays.xml b/src/frontends/android/app/src/main/res/values-pl/arrays.xml new file mode 100644 index 000000000..30e43f1fb --- /dev/null +++ b/src/frontends/android/app/src/main/res/values-pl/arrays.xml @@ -0,0 +1,25 @@ + + + + + + IKEv2 EAP (użytkownik/hasło) + IKEv2 certyfikat + IKEv2 certyfikat + EAP (użytkownik/hasło) + IKEv2 EAP-TLS (certyfikat) + IKEv2 EAP-TNC (użytkownik/hasło) + + \ No newline at end of file diff --git a/src/frontends/android/app/src/main/res/values-pl/strings.xml b/src/frontends/android/app/src/main/res/values-pl/strings.xml new file mode 100644 index 000000000..fb2aba003 --- /dev/null +++ b/src/frontends/android/app/src/main/res/values-pl/strings.xml @@ -0,0 +1,134 @@ + + + + + + strongSwan klient VPN + strongSwan + Pokaż log + Szukaj + Nie obsługiwany VPN + Urządzenie nie obsługuje aplikacji VPN.\nProszę skontaktować się z producentem. + Polączenia nie sa możliwe w trybie zamkniętym + Wczytywanie… + Nie znaleziono profilu + Skrót strongSwan + + + Log + Prześlij log + Log jest pusty + strongSwan %1$s Log + + + Brak profilu VPN + Dodaj profil VPN + Edytuj + Usuń + Wybierz profile + Wybrane profile usunięte + Nie wybrano profilu + Wybrano jeden profil + Wzbrano %1$d profile" + + + Zapisz + Anuluj + Nazwa profilu: + (użyj adresu bramki) + Bramka: + Typ: + Użytkownik: + Hasło: + (w razie potrzeby zapromptuj) + Certyfikat użytkownika: + Wybierz certyfikat użytkownika + >Wybierz określony certyfikat użytkownika + Certyfikat CA: + Wybierz automatycznie + Wybierz certyfikat CA + Wybierz określony certyfikat CA + Show advanced settings + MTU: + Server port: + (use default) + Split tunneling: + Block IPv4 traffic not destined for the VPN + Block IPv6 traffic not destined for the VPN + + Wprowadź adres bramki + Wprowadź swoją nazwę użytkownika + Nie wybrano żadnego certyfikatu CA + Wybierz lub uaktywnij jeden Wybierz automatycznie + Please enter a number in the range from %1$d - %2$d + EAP-TNC may affect your privacy + Device data is sent to the gateway operator + <p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b> + + + Certyfikaty CA + Brak certyfikatów + Przeładuj certyfikaty CA + System + Użytkownik + Imported + Delete certificate? + The certificate will be permanently removed! + Import certificate + Certificate successfully imported + Failed to import certificate + + + Status: + Profil: + Rozłącz + Łączenie… + Połączony + Przerywam połączenie… + Brak aktywnego VPN + Błąd + + + Assessment: + Restricted + Failed + View remediation instructions + + + Remediation instructions + + + Wprowadż hasło + Połącz + Nie udało się utworzyć tunelu VPN: + Nie znaleziono adresu bramki + Bramka jest nieosiągalna + Błąd przy weryfikacji bramki + Błąd przy autoryzacji użytkownika + Security assessment failed. + Nieznany błąd w czasie połączenia + Łączenie: %1$s + Tworzenie tunelu VPN z \""%1$s\". + Połączenie z VPN + Ten profil VPN jest obecnie połaczony! + Połączyć ponownie + Połącz %1$s? + To zastąpi aktywne połączenie VPN! + Połącz + + diff --git a/src/frontends/android/app/src/main/res/values-ru/arrays.xml b/src/frontends/android/app/src/main/res/values-ru/arrays.xml new file mode 100644 index 000000000..5fbd43168 --- /dev/null +++ b/src/frontends/android/app/src/main/res/values-ru/arrays.xml @@ -0,0 +1,24 @@ + + + + + + IKEv2 EAP (Логин/Пароль) + IKEv2 Сертификат + IKEv2 Сертификат + EAP (Логин/Пароль) + IKEv2 EAP-TLS (Сертификат) + IKEv2 EAP-TNC (Логин/Пароль) + + diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml new file mode 100644 index 000000000..eabfc084b --- /dev/null +++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml @@ -0,0 +1,132 @@ + + + + + + Клиент strongSwan VPN + strongSwan + Журнал + Поиск + VPN не поддерживается + Ваше устройство не поддерживат VPN приложение.\nПожалуйста свяжитесь с производителем. + VPN соединения не поддерживаются в режиме lockdown. + Загрузка… + Профиль не найден + Ссылка на strongSwan + + + Журнал + Отправить журнал + Журнал пуст + strongSwan %1$s журнал + + + VPN профили не обнаружены. + Добавить VPN профиль + Редактировать + Удалить + Выбрать профили + Выбранные профили удалены + Профили не выбраны + Выбран профиль + %1$d прифиля(ей) выбрано" + + + Сохранить + Отмена + Название профиля: + (адрес шлюза) + Шлюз: + Тип: + Логин: + Пароль: + (спросить если нужно) + Сертификат пользователя: + Выбрать сертификат пользователя + Выбрать сертификат пользователя + Сертификат CA: + Выбрать автоматически + Выбрать сертификат CA + Выбрать CA сертификат + Show advanced settings + MTU: + Server port: + (use default) + Split tunneling: + Block IPv4 traffic not destined for the VPN + Block IPv6 traffic not destined for the VPN + + Пожалуйста введите адрес шлюза + Пожалуйста введите имя пользователя + Не выбран сертификат CA + Пожалуйста выберите один Выбрать автоматически + Please enter a number in the range from %1$d - %2$d + EAP-TNC may affect your privacy + Device data is sent to the gateway operator + <p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b> + + + Сертификаты CA + Нет доступных сертификатов + Обновить сертификат CA + Система + Пользователь + Imported + Delete certificate? + The certificate will be permanently removed! + Import certificate + Certificate successfully imported + Failed to import certificate + + + Статус: + Профиль: + Отключить + Соединение… + Соединен + Отключение… + Нет активных VPN + Ошибка + + + Assessment: + Restricted + Failed + View remediation instructions + + + Remediation instructions + + + Введите пароль для соединения + Соединить + Ошибка подключения к VPN: + Не найден адрес шлюза. + Шлюз недоступен. + Ошибка авторизаци при подключении к шлюзу. + Ошибка авторизации пользователя. + Security assessment failed. + Неизвестная ошибка. + Подключение: %1$s + Подключение к VPN с \""%1$s\". + Соединение с VPN установлено + Подключение к этому профилю VPN уже существует! + Переподключить + Подключить %1$s? + Это заменит ваше текущее VPN соединение! + Соединить + + + diff --git a/src/frontends/android/app/src/main/res/values-ua/arrays.xml b/src/frontends/android/app/src/main/res/values-ua/arrays.xml new file mode 100644 index 000000000..1acc0d769 --- /dev/null +++ b/src/frontends/android/app/src/main/res/values-ua/arrays.xml @@ -0,0 +1,24 @@ + + + + + + IKEv2 EAP (Логін/Пароль) + IKEv2 Сертифікати + IKEv2 Сертифікати + EAP (Логін/Пароль) + IKEv2 EAP-TLS (Сертифікати) + IKEv2 EAP-TNC (Логін/Пароль) + + diff --git a/src/frontends/android/app/src/main/res/values-ua/strings.xml b/src/frontends/android/app/src/main/res/values-ua/strings.xml new file mode 100644 index 000000000..d7c238370 --- /dev/null +++ b/src/frontends/android/app/src/main/res/values-ua/strings.xml @@ -0,0 +1,133 @@ + + + + + + strongSwan VPN клієнт + strongSwan + Перегляд журналу + Пошук + VPN не підтримуеться + Ваш пристрій не підтримує VPN.\nЗв\'яжіться з виробником. + VPN з\'єднання не пітримується у режимі lockdown. + Завантаження… + Профіль не знайдено + strongSwan посилання + + + Журнал + Відправити файл журналу + Журнал порожній + strongSwan %1$s файл журналу + + + Немає VPN профілів + Додати VPN профіль + Редагувати + Видалити + Обрати профіль + Обрані профілі видалено + Профіль не обрано + Один профіль обрано + %1$d профілів обрано" + + + Зберегти + Відміна + Назва профілю: + (використовувати адресу шлюза) + Шлюз: + Тип: + Логін: + Пароль: + (запитати якщо потрібно) + Сертифікат користувача: + Виберіть сертифікат користувача + Вибрати спеціальний сертифікат користувача + Сертифікат CA: + Вибрати автоматично + Вибрати сертифікат CA + Вибрати спеціальний сертифікат CA + Show advanced settings + MTU: + Server port: + (use default) + Split tunneling: + Block IPv4 traffic not destined for the VPN + Block IPv6 traffic not destined for the VPN + + Введіть адресу шлюза тут + Введіть ім\'я користувача тут + Не вибрано сертифікат CA + Будь ласка виберіть один Вибрати автоматично + Please enter a number in the range from %1$d - %2$d + EAP-TNC may affect your privacy + Device data is sent to the gateway operator + <p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b> + + + Сертифікати CA + Немає сертифікатів + Перезавантажити CA сертифікати + Система + Користувач + Imported + Delete certificate? + The certificate will be permanently removed! + Import certificate + Certificate successfully imported + Failed to import certificate + + + Статус: + Профіль: + Роз\'єднати + Підключення… + Підключений + Роз\'єднання… + Немає активних VPN + Помилка + + + Assessment: + Restricted + Failed + View remediation instructions + + + Remediation instructions + + + Введіть пароль для з\'єднання + Підключити + Помилка підлючення VPN: + Помилка пошуку адреси шлюза. + Немає зв\'язку зі шлюзом. + Помилка перевірки данних аутентифікації шлюза. + Помилка аутентифікації користувача. + Security assessment failed. + Невідома помилка під час підключення. + Підключення: %1$s + Підключення VPN з \""%1$s\". + VPN підключено + Цей VPN профіль зараз підключений! + Перепідключитися + Підключити %1$s? + Ця дія замінить ваше поточне VPN з\'єднання! + Підключити + + + diff --git a/src/frontends/android/app/src/main/res/values/arrays.xml b/src/frontends/android/app/src/main/res/values/arrays.xml new file mode 100644 index 000000000..b324b594f --- /dev/null +++ b/src/frontends/android/app/src/main/res/values/arrays.xml @@ -0,0 +1,25 @@ + + + + + + IKEv2 EAP (Username/Password) + IKEv2 Certificate + IKEv2 Certificate + EAP (Username/Password) + IKEv2 EAP-TLS (Certificate) + IKEv2 EAP-TNC (Username/Password) + + \ No newline at end of file diff --git a/src/frontends/android/app/src/main/res/values/attrs.xml b/src/frontends/android/app/src/main/res/values/attrs.xml new file mode 100644 index 000000000..6c3480b0e --- /dev/null +++ b/src/frontends/android/app/src/main/res/values/attrs.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/src/frontends/android/app/src/main/res/values/colors.xml b/src/frontends/android/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..4af28b4d4 --- /dev/null +++ b/src/frontends/android/app/src/main/res/values/colors.xml @@ -0,0 +1,33 @@ + + + + + #D9192C + + #FF9909 + + #99CC00 + + #333333 + + #5a5a5a + + diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..5c8ebab57 --- /dev/null +++ b/src/frontends/android/app/src/main/res/values/strings.xml @@ -0,0 +1,134 @@ + + + + + + strongSwan VPN Client + strongSwan + View log + Search + VPN not supported + Your device does not support VPN applications.\nPlease contact the manufacturer. + VPN connections are not supported in lockdown mode. + Loading… + Profile not found + strongSwan shortcut + + + Log + Send log file + Log file is empty + strongSwan %1$s Log File + + + No VPN profiles. + Add VPN profile + Edit + Delete + Select profiles + Selected profiles deleted + No profile selected + One profile selected + %1$d profiles selected" + + + Save + Cancel + Profile Name: + (use gateway address) + Gateway: + Type: + Username: + Password: + (prompt when needed) + User certificate: + Select user certificate + Select a specific user certificate + CA certificate: + Select automatically + Select CA certificate + Select a specific CA certificate + Show advanced settings + MTU: + Server port: + (use default) + Split tunneling: + Block IPv4 traffic not destined for the VPN + Block IPv6 traffic not destined for the VPN + + Please enter the gateway address here + Please enter your username here + No CA certificate selected + Please select one or activate Select automatically + Please enter a number in the range from %1$d - %2$d + EAP-TNC may affect your privacy + Device data is sent to the gateway operator + <p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b> + + + CA certificates + No certificates + Reload CA certificates + System + User + Imported + Delete certificate? + The certificate will be permanently removed! + Import certificate + Certificate successfully imported + Failed to import certificate + + + Status: + Profile: + Disconnect + Connecting… + Connected + Disconnecting… + No active VPN + Error + + + Assessment: + Restricted + Failed + View remediation instructions + + + Remediation instructions + + + Enter password to connect + Connect + Failed to establish VPN: + Gateway address lookup failed. + Gateway is unreachable. + Verifying gateway authentication failed. + User authentication failed. + Security assessment failed. + Unspecified failure while connecting. + Connecting: %1$s + Establishing VPN with \""%1$s\". + VPN connected + This VPN profile is currently connected! + Reconnect + Connect %1$s? + This will replace your active VPN connection! + Connect + + diff --git a/src/frontends/android/app/src/main/res/values/styles.xml b/src/frontends/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..739ba7000 --- /dev/null +++ b/src/frontends/android/app/src/main/res/values/styles.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/src/frontends/android/build.gradle b/src/frontends/android/build.gradle new file mode 100644 index 000000000..9b18c6e90 --- /dev/null +++ b/src/frontends/android/build.gradle @@ -0,0 +1,14 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.3.0' + } +} + +allprojects { + repositories { + jcenter() + } +} diff --git a/src/frontends/android/gradle/wrapper/gradle-wrapper.jar b/src/frontends/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..8c0fb64a8 Binary files /dev/null and b/src/frontends/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src/frontends/android/gradle/wrapper/gradle-wrapper.properties b/src/frontends/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..0c71e760d --- /dev/null +++ b/src/frontends/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 10 15:27:10 PDT 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip diff --git a/src/frontends/android/gradlew b/src/frontends/android/gradlew new file mode 100755 index 000000000..91a7e269e --- /dev/null +++ b/src/frontends/android/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/src/frontends/android/gradlew.bat b/src/frontends/android/gradlew.bat new file mode 100644 index 000000000..aec99730b --- /dev/null +++ b/src/frontends/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/frontends/android/jni/.gitignore b/src/frontends/android/jni/.gitignore deleted file mode 100644 index ca5cf16ed..000000000 --- a/src/frontends/android/jni/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -openssl -strongswan diff --git a/src/frontends/android/jni/Android.mk b/src/frontends/android/jni/Android.mk deleted file mode 100644 index 1fb233b48..000000000 --- a/src/frontends/android/jni/Android.mk +++ /dev/null @@ -1,84 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -# use "bring your own device" (BYOD) features (also see USE_BYOD in -# MainActivity.java) -strongswan_USE_BYOD := true - -strongswan_CHARON_PLUGINS := android-log openssl fips-prf random nonce pubkey \ - pkcs1 pkcs8 pem xcbc hmac socket-default \ - eap-identity eap-mschapv2 eap-md5 eap-gtc eap-tls - -ifneq ($(strongswan_USE_BYOD),) -strongswan_BYOD_PLUGINS := eap-ttls eap-tnc tnc-imc tnc-tnccs tnccs-20 -endif - -strongswan_PLUGINS := $(strongswan_CHARON_PLUGINS) \ - $(strongswan_BYOD_PLUGINS) - -include $(LOCAL_PATH)/strongswan/Android.common.mk - -# includes -strongswan_PATH := $(LOCAL_PATH)/strongswan -openssl_PATH := $(LOCAL_PATH)/openssl/include - -# CFLAGS (partially from a configure run using droid-gcc) -strongswan_CFLAGS := \ - -Wall \ - -Wextra \ - -Wno-format \ - -Wno-pointer-sign \ - -Wno-pointer-arith \ - -Wno-sign-compare \ - -Wno-strict-aliasing \ - -Wno-unused-parameter \ - -DHAVE___BOOL \ - -DHAVE_STDBOOL_H \ - -DHAVE_ALLOCA_H \ - -DHAVE_ALLOCA \ - -DHAVE_CLOCK_GETTIME \ - -DHAVE_DLADDR \ - -DHAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC \ - -DHAVE_PRCTL \ - -DHAVE_LINUX_UDP_H \ - -DHAVE_STRUCT_SADB_X_POLICY_SADB_X_POLICY_PRIORITY \ - -DHAVE_IPSEC_MODE_BEET \ - -DHAVE_IPSEC_DIR_FWD \ - -DHAVE_IN6ADDR_ANY \ - -DHAVE_NETINET_IP6_H \ - -DOPENSSL_NO_ENGINE \ - -DCONFIG_H_INCLUDED \ - -DCAPABILITIES \ - -DCAPABILITIES_NATIVE \ - -DMONOLITHIC \ - -DUSE_IKEV1 \ - -DUSE_IKEV2 \ - -DUSE_BUILTIN_PRINTF \ - -DDEBUG \ - -DCHARON_UDP_PORT=0 \ - -DCHARON_NATT_PORT=0 \ - -DVERSION=\"$(strongswan_VERSION)\" \ - -DDEV_RANDOM=\"/dev/random\" \ - -DDEV_URANDOM=\"/dev/urandom\" - -ifneq ($(strongswan_USE_BYOD),) -strongswan_CFLAGS += -DUSE_BYOD -endif - -strongswan_BUILD := \ - openssl \ - libandroidbridge \ - strongswan/src/libipsec \ - strongswan/src/libcharon \ - strongswan/src/libhydra \ - strongswan/src/libstrongswan - -ifneq ($(strongswan_USE_BYOD),) -strongswan_BUILD += \ - strongswan/src/libtnccs \ - strongswan/src/libtncif \ - strongswan/src/libimcv -endif - -include $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \ - $(strongswan_BUILD))) diff --git a/src/frontends/android/jni/Application.mk b/src/frontends/android/jni/Application.mk deleted file mode 100644 index 9fa668354..000000000 --- a/src/frontends/android/jni/Application.mk +++ /dev/null @@ -1,3 +0,0 @@ -# select the ABI(s) to build for (see CPU-ARCH-ABIS.html in the NDK docs). -APP_ABI := armeabi x86 mips -APP_PLATFORM := android-19 diff --git a/src/frontends/android/jni/libandroidbridge/Android.mk b/src/frontends/android/jni/libandroidbridge/Android.mk deleted file mode 100644 index c56b8d5f0..000000000 --- a/src/frontends/android/jni/libandroidbridge/Android.mk +++ /dev/null @@ -1,63 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -# copy-n-paste from Makefile.am -LOCAL_SRC_FILES := \ -android_jni.c \ -backend/android_attr.c \ -backend/android_creds.c \ -backend/android_dns_proxy.c \ -backend/android_private_key.c \ -backend/android_service.c \ -charonservice.c \ -kernel/android_ipsec.c \ -kernel/android_net.c \ -kernel/network_manager.c \ -vpnservice_builder.c - -ifneq ($(strongswan_USE_BYOD),) -LOCAL_SRC_FILES += \ -byod/imc_android_state.c \ -byod/imc_android.c -endif - -# build libandroidbridge ------------------------------------------------------- - -LOCAL_C_INCLUDES += \ - $(strongswan_PATH)/src/libipsec \ - $(strongswan_PATH)/src/libhydra \ - $(strongswan_PATH)/src/libcharon \ - $(strongswan_PATH)/src/libstrongswan - -ifneq ($(strongswan_USE_BYOD),) -LOCAL_C_INCLUDES += \ - $(strongswan_PATH)/src/libimcv \ - $(strongswan_PATH)/src/libtncif \ - $(strongswan_PATH)/src/libtnccs \ - $(strongswan_PATH)/src/libtls -endif - -LOCAL_CFLAGS := $(strongswan_CFLAGS) \ - -DPLUGINS='"$(strongswan_CHARON_PLUGINS)"' - -ifneq ($(strongswan_USE_BYOD),) -LOCAL_CFLAGS += -DPLUGINS_BYOD='"$(strongswan_BYOD_PLUGINS)"' -endif - -LOCAL_MODULE := libandroidbridge - -LOCAL_MODULE_TAGS := optional - -LOCAL_ARM_MODE := arm - -LOCAL_PRELINK_MODULE := false - -LOCAL_LDLIBS := -llog - -LOCAL_SHARED_LIBRARIES := libstrongswan libhydra libipsec libcharon - -ifneq ($(strongswan_USE_BYOD),) -LOCAL_SHARED_LIBRARIES += libimcv libtncif libtnccs -endif - -include $(BUILD_SHARED_LIBRARY) diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.c b/src/frontends/android/jni/libandroidbridge/android_jni.c deleted file mode 100644 index a6412bdf7..000000000 --- a/src/frontends/android/jni/libandroidbridge/android_jni.c +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2012-2015 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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. - */ - -#include - -#include "android_jni.h" - -#include -#include - -/** - * JVM - */ -static JavaVM *android_jvm; - -static struct { - char name[32]; - void *handle; -} libs[] = { - { "libstrongswan.so", NULL }, -#ifdef USE_BYOD - { "libtncif.so", NULL }, - { "libtnccs.so", NULL }, - { "libimcv.so", NULL }, -#endif - { "libhydra.so", NULL }, - { "libcharon.so", NULL }, - { "libipsec.so", NULL }, -}; - -jclass *android_charonvpnservice_class; -jclass *android_charonvpnservice_builder_class; -android_sdk_version_t android_sdk_version; - -/** - * Thread-local variable. Only used because of the destructor - */ -static thread_value_t *androidjni_threadlocal; - -/** - * Thread-local destructor to ensure that a native thread is detached - * from the JVM even if androidjni_detach_thread() is not called. - */ -static void attached_thread_cleanup(void *arg) -{ - (*android_jvm)->DetachCurrentThread(android_jvm); -} - -/* - * Described in header - */ -void androidjni_attach_thread(JNIEnv **env) -{ - if ((*android_jvm)->GetEnv(android_jvm, (void**)env, - JNI_VERSION_1_6) == JNI_OK) - { /* already attached or even a Java thread */ - return; - } - (*android_jvm)->AttachCurrentThread(android_jvm, env, NULL); - /* use a thread-local value with a destructor that automatically detaches - * the thread from the JVM before it terminates, if not done manually */ - androidjni_threadlocal->set(androidjni_threadlocal, (void*)*env); -} - -/* - * Described in header - */ -void androidjni_detach_thread() -{ - if (androidjni_threadlocal->get(androidjni_threadlocal)) - { /* only do this if we actually attached this thread */ - androidjni_threadlocal->set(androidjni_threadlocal, NULL); - (*android_jvm)->DetachCurrentThread(android_jvm); - } -} - -/** - * Called when this library is loaded by the JVM - */ -jint JNI_OnLoad(JavaVM *vm, void *reserved) -{ - JNIEnv *env; - jclass jversion; - jfieldID jsdk_int; - int i; - - android_jvm = vm; - - if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) - { - return -1; - } - - for (i = 0; i < countof(libs); i++) - { - libs[i].handle = dlopen(libs[i].name, RTLD_GLOBAL); - if (!libs[i].handle) - { - return -1; - } - } - - androidjni_threadlocal = thread_value_create(attached_thread_cleanup); - - android_charonvpnservice_class = - (*env)->NewGlobalRef(env, (*env)->FindClass(env, - JNI_PACKAGE_STRING "/CharonVpnService")); - android_charonvpnservice_builder_class = - (*env)->NewGlobalRef(env, (*env)->FindClass(env, - JNI_PACKAGE_STRING "/CharonVpnService$BuilderAdapter")); - - jversion = (*env)->FindClass(env, "android/os/Build$VERSION"); - jsdk_int = (*env)->GetStaticFieldID(env, jversion, "SDK_INT", "I"); - android_sdk_version = (*env)->GetStaticIntField(env, jversion, jsdk_int); - - return JNI_VERSION_1_6; -} - -/** - * Called when this library is unloaded by the JVM (which never happens on - * Android) - */ -void JNI_OnUnload(JavaVM *vm, void *reserved) -{ - int i; - - androidjni_threadlocal->destroy(androidjni_threadlocal); - - for (i = countof(libs) - 1; i >= 0; i--) - { - if (libs[i].handle) - { - dlclose(libs[i].handle); - } - } -} - diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.h b/src/frontends/android/jni/libandroidbridge/android_jni.h deleted file mode 100644 index b08670f7e..000000000 --- a/src/frontends/android/jni/libandroidbridge/android_jni.h +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2012 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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. - */ - -/** - * @defgroup android_jni android_jni - * @{ @ingroup libandroidbridge - */ - -#ifndef ANDROID_JNI_H_ -#define ANDROID_JNI_H_ - -#include -#include - -#define JNI_PACKAGE org_strongswan_android_logic -#define JNI_PACKAGE_STRING "org/strongswan/android/logic" - -#define JNI_METHOD_PP(pack, klass, name, ret, ...) \ - ret Java_##pack##_##klass##_##name(JNIEnv *env, jobject this, ##__VA_ARGS__) - -#define JNI_METHOD_P(pack, klass, name, ret, ...) \ - JNI_METHOD_PP(pack, klass, name, ret, ##__VA_ARGS__) - -#define JNI_METHOD(klass, name, ret, ...) \ - JNI_METHOD_P(JNI_PACKAGE, klass, name, ret, ##__VA_ARGS__) - -/** - * Java classes - * Initialized in JNI_OnLoad() - */ -extern jclass *android_charonvpnservice_class; -extern jclass *android_charonvpnservice_builder_class; - -/** - * Currently known (supported) SDK versions - * - * see android.os.Build.VERSION_CODES for definitions - */ -typedef enum { - ANDROID_ICE_CREAM_SANDWICH = 14, - ANDROID_ICE_CREAM_SANDWICH_MR1 = 15, - ANDROID_JELLY_BEAN = 16, - ANDROID_JELLY_BEAN_MR1 = 17, - ANDROID_JELLY_BEAN_MR2 = 18, -} android_sdk_version_t; - -/** - * The current SDK version of the Android framework - * - * see android.os.Build.VERSION.SDK_INT - */ -extern android_sdk_version_t android_sdk_version; - -/** - * Attach the current thread to the JVM - * - * As local JNI references are not freed until the thread detaches - * androidjni_detach_thread() should be called as soon as possible. - * If it is not called a thread-local destructor ensures that the - * thread is at least detached as soon as it terminates. - * - * @param env JNIEnv - */ -void androidjni_attach_thread(JNIEnv **env); - -/** - * Detach the current thread from the JVM - * - * Call this as soon as possible to ensure that local JNI references are freed. - */ -void androidjni_detach_thread(); - -/** - * Handle exceptions thrown by a JNI call - * - * @param env JNIEnv - * @return TRUE if an exception was thrown - */ -static inline bool androidjni_exception_occurred(JNIEnv *env) -{ - if ((*env)->ExceptionOccurred(env)) - { /* clear any exception, otherwise the VM is terminated */ - (*env)->ExceptionDescribe(env); - (*env)->ExceptionClear(env); - return TRUE; - } - return FALSE; -} - -/** - * Convert a Java string to a C string. Memory is allocated. - * - * @param env JNIEnv - * @param jstr Java string - * @return native C string (allocated) - */ -static inline char *androidjni_convert_jstring(JNIEnv *env, jstring jstr) -{ - char *str = NULL; - jsize bytes, chars; - - if (jstr) - { - chars = (*env)->GetStringLength(env, jstr); - bytes = (*env)->GetStringUTFLength(env, jstr); - str = malloc(bytes + 1); - (*env)->GetStringUTFRegion(env, jstr, 0, chars, str); - str[bytes] = '\0'; - } - return str; -} - -/** - * Converts the given Java byte array to a chunk - * - * @param env JNIEnv - * @param jbytearray Java byte array - * @return allocated chunk - */ -static inline chunk_t chunk_from_byte_array(JNIEnv *env, jbyteArray jbytearray) -{ - chunk_t chunk; - - chunk = chunk_alloc((*env)->GetArrayLength(env, jbytearray)); - (*env)->GetByteArrayRegion(env, jbytearray, 0, chunk.len, chunk.ptr); - return chunk; -} - -/** - * Converts the given chunk to a Java byte array - * - * @param env JNIEnv - * @param chunk native chunk - * @return allocated Java byte array - */ -static inline jbyteArray byte_array_from_chunk(JNIEnv *env, chunk_t chunk) -{ - jbyteArray jbytearray; - - jbytearray = (*env)->NewByteArray(env, chunk.len); - (*env)->SetByteArrayRegion(env, jbytearray, 0, chunk.len, chunk.ptr); - return jbytearray; -} - -#endif /** ANDROID_JNI_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_attr.c b/src/frontends/android/jni/libandroidbridge/backend/android_attr.c deleted file mode 100644 index 645b3fa9b..000000000 --- a/src/frontends/android/jni/libandroidbridge/backend/android_attr.c +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2012-2013 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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. - */ - -#include "android_attr.h" -#include "../charonservice.h" - -#include -#include -#include - -typedef struct private_android_attr_t private_android_attr_t; - -/** - * Private data of an android_attr_t object. - */ -struct private_android_attr_t { - - /** - * Public interface. - */ - android_attr_t public; -}; - -METHOD(attribute_handler_t, handle, bool, - private_android_attr_t *this, ike_sa_t *ike_sa, - configuration_attribute_type_t type, chunk_t data) -{ - vpnservice_builder_t *builder; - host_t *dns; - - switch (type) - { - case INTERNAL_IP4_DNS: - dns = host_create_from_chunk(AF_INET, data, 0); - break; - case INTERNAL_IP6_DNS: - dns = host_create_from_chunk(AF_INET6, data, 0); - break; - default: - return FALSE; - } - - if (!dns || dns->is_anyaddr(dns)) - { - DESTROY_IF(dns); - return FALSE; - } - - builder = charonservice->get_vpnservice_builder(charonservice); - builder->add_dns(builder, dns); - dns->destroy(dns); - return TRUE; -} - -METHOD(attribute_handler_t, release, void, - private_android_attr_t *this, ike_sa_t *ike_sa, - configuration_attribute_type_t type, chunk_t data) -{ - /* DNS servers cannot be removed from an existing TUN device */ -} - -METHOD(enumerator_t, enumerate_dns6, bool, - enumerator_t *this, configuration_attribute_type_t *type, chunk_t *data) -{ - *type = INTERNAL_IP6_DNS; - *data = chunk_empty; - this->enumerate = (void*)return_false; - return TRUE; -} - -METHOD(enumerator_t, enumerate_dns4, bool, - enumerator_t *this, configuration_attribute_type_t *type, chunk_t *data) -{ - *type = INTERNAL_IP4_DNS; - *data = chunk_empty; - this->enumerate = (void*)_enumerate_dns6; - return TRUE; -} - -METHOD(attribute_handler_t, create_attribute_enumerator, enumerator_t*, - private_android_attr_t *this, ike_sa_t *ike_sa, linked_list_t *vips) -{ - enumerator_t *enumerator; - - INIT(enumerator, - .enumerate = (void*)_enumerate_dns4, - .destroy = (void*)free, - ); - return enumerator; -} - -METHOD(android_attr_t, destroy, void, - private_android_attr_t *this) -{ - free(this); -} - -/** - * Described in header - */ -android_attr_t *android_attr_create() -{ - private_android_attr_t *this; - - INIT(this, - .public = { - .handler = { - .handle = _handle, - .release = _release, - .create_attribute_enumerator = _create_attribute_enumerator, - }, - .destroy = _destroy, - }, - ); - - return &this->public; -} diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_attr.h b/src/frontends/android/jni/libandroidbridge/backend/android_attr.h deleted file mode 100644 index 56b02e1ce..000000000 --- a/src/frontends/android/jni/libandroidbridge/backend/android_attr.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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. - */ - -/** - * @defgroup android_attr android_attr - * @{ @ingroup android_backend - */ - -#ifndef ANDROID_ATTR_H_ -#define ANDROID_ATTR_H_ - -#include -#include - -typedef struct android_attr_t android_attr_t; - -/** - * Handler for DNS configuration - */ -struct android_attr_t { - - /** - * implements the attribute_handler_t interface - */ - attribute_handler_t handler; - - /** - * Destroy a android_attr_t - */ - void (*destroy)(android_attr_t *this); -}; - -/** - * Create a android_attr_t instance. - */ -android_attr_t *android_attr_create(void); - -#endif /** ANDROID_ATTR_H_ @}*/ - diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_creds.c b/src/frontends/android/jni/libandroidbridge/backend/android_creds.c deleted file mode 100644 index ddc032638..000000000 --- a/src/frontends/android/jni/libandroidbridge/backend/android_creds.c +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2012 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. - */ - -#include "android_creds.h" -#include "../charonservice.h" - -#include -#include -#include -#include - -typedef struct private_android_creds_t private_android_creds_t; - -/** - * Private data of an android_creds_t object - */ -struct private_android_creds_t { - - /** - * Public interface - */ - android_creds_t public; - - /** - * Credential set storing trusted certificates and user credentials - */ - mem_cred_t *creds; - - /** - * read/write lock to make sure certificates are only loaded once - */ - rwlock_t *lock; - - /** - * TRUE if certificates have been loaded via JNI - */ - bool loaded; -}; - -/** - * Free allocated DER encoding - */ -static void free_encoding(chunk_t *chunk) -{ - chunk_free(chunk); - free(chunk); -} - -/** - * Load trusted certificates via charonservice (JNI). - */ -static void load_trusted_certificates(private_android_creds_t *this) -{ - linked_list_t *certs; - certificate_t *cert; - chunk_t *current; - - certs = charonservice->get_trusted_certificates(charonservice); - if (certs) - { - while (certs->remove_first(certs, (void**)¤t) == SUCCESS) - { - cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, - BUILD_BLOB_ASN1_DER, *current, BUILD_END); - if (cert) - { - DBG2(DBG_CFG, "loaded CA certificate '%Y'", - cert->get_subject(cert)); - this->creds->add_cert(this->creds, TRUE, cert); - } - free_encoding(current); - } - certs->destroy(certs); - } -} - -METHOD(credential_set_t, create_cert_enumerator, enumerator_t*, - private_android_creds_t *this, certificate_type_t cert, key_type_t key, - identification_t *id, bool trusted) -{ - enumerator_t *enumerator; - - if (cert != CERT_ANY && cert != CERT_X509) - { - return NULL; - } - this->lock->read_lock(this->lock); - if (!this->loaded) - { - this->lock->unlock(this->lock); - this->lock->write_lock(this->lock); - /* check again after acquiring the write lock */ - if (!this->loaded) - { - load_trusted_certificates(this); - this->loaded = TRUE; - } - this->lock->unlock(this->lock); - this->lock->read_lock(this->lock); - } - enumerator = this->creds->set.create_cert_enumerator(&this->creds->set, - cert, key, id, trusted); - return enumerator_create_cleaner(enumerator, (void*)this->lock->unlock, - this->lock); -} - -METHOD(android_creds_t, add_username_password, void, - private_android_creds_t *this, char *username, char *password) -{ - shared_key_t *shared_key; - identification_t *id; - chunk_t secret; - - secret = chunk_create(password, strlen(password)); - shared_key = shared_key_create(SHARED_EAP, chunk_clone(secret)); - id = identification_create_from_string(username); - - this->creds->add_shared(this->creds, shared_key, id, NULL); -} - -METHOD(credential_set_t, create_shared_enumerator, enumerator_t*, - private_android_creds_t *this, shared_key_type_t type, - identification_t *me, identification_t *other) -{ - return this->creds->set.create_shared_enumerator(&this->creds->set, - type, me, other); -} - -METHOD(android_creds_t, load_user_certificate, certificate_t*, - private_android_creds_t *this) -{ - linked_list_t *encodings; - certificate_t *cert = NULL, *ca_cert; - chunk_t *current; - - encodings = charonservice->get_user_certificate(charonservice); - if (!encodings) - { - return NULL; - } - - while (encodings->remove_first(encodings, (void**)¤t) == SUCCESS) - { - if (!cert) - { /* the first element is the user certificate */ - cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, - BUILD_BLOB_ASN1_DER, *current, BUILD_END); - if (!cert) - { - DBG1(DBG_CFG, "failed to load user certificate"); - free_encoding(current); - break; - } - DBG1(DBG_CFG, "loaded user certificate '%Y' and private key", - cert->get_subject(cert)); - cert = this->creds->add_cert_ref(this->creds, TRUE, cert); - free_encoding(current); - continue; - } - /* the rest are CA certificates, we ignore failures */ - ca_cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, - BUILD_BLOB_ASN1_DER, *current, BUILD_END); - if (ca_cert) - { - DBG1(DBG_CFG, "loaded CA certificate '%Y'", - ca_cert->get_subject(ca_cert)); - this->creds->add_cert(this->creds, TRUE, ca_cert); - } - free_encoding(current); - } - encodings->destroy_function(encodings, (void*)free_encoding); - - if (cert) - { - private_key_t *key; - - key = charonservice->get_user_key(charonservice, - cert->get_public_key(cert)); - if (key) - { - this->creds->add_key(this->creds, key); - } - else - { - DBG1(DBG_CFG, "failed to load private key"); - cert->destroy(cert); - cert = NULL; - } - } - return cert; -} - -METHOD(credential_set_t, create_private_enumerator, enumerator_t*, - private_android_creds_t *this, key_type_t type, identification_t *id) -{ - return this->creds->set.create_private_enumerator(&this->creds->set, - type, id); -} - -METHOD(android_creds_t, clear, void, - private_android_creds_t *this) -{ - this->lock->write_lock(this->lock); - this->creds->clear(this->creds); - this->loaded = FALSE; - this->lock->unlock(this->lock); -} - -METHOD(android_creds_t, destroy, void, - private_android_creds_t *this) -{ - clear(this); - this->creds->destroy(this->creds); - this->lock->destroy(this->lock); - free(this); -} - -/** - * Described in header. - */ -android_creds_t *android_creds_create() -{ - private_android_creds_t *this; - - INIT(this, - .public = { - .set = { - .create_cert_enumerator = _create_cert_enumerator, - .create_shared_enumerator = _create_shared_enumerator, - .create_private_enumerator = _create_private_enumerator, - .create_cdp_enumerator = (void*)return_null, - .cache_cert = (void*)nop, - }, - .add_username_password = _add_username_password, - .load_user_certificate = _load_user_certificate, - .clear = _clear, - .destroy = _destroy, - }, - .creds = mem_cred_create(), - .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), - ); - - return &this->public; -} diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_creds.h b/src/frontends/android/jni/libandroidbridge/backend/android_creds.h deleted file mode 100644 index 918708f14..000000000 --- a/src/frontends/android/jni/libandroidbridge/backend/android_creds.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2012 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. - */ - -/** - * @defgroup android_creds android_creds - * @{ @ingroup android_backend - */ - -#ifndef ANDROID_CREDS_H_ -#define ANDROID_CREDS_H_ - -#include -#include - -typedef struct android_creds_t android_creds_t; - -/** - * Android credential set that provides CA certificates via JNI and supplied - * user credentials. - */ -struct android_creds_t { - - /** - * Implements credential_set_t - */ - credential_set_t set; - - /** - * Add user name and password for EAP authentication - * - * @param username user name - * @param password password - */ - void (*add_username_password)(android_creds_t *this, char *username, - char *password); - - /** - * Load the user certificate and private key - * - * @return loaded client certificate, NULL on failure - */ - certificate_t *(*load_user_certificate)(android_creds_t *this); - - /** - * Clear the cached certificates and stored credentials. - */ - void (*clear)(android_creds_t *this); - - /** - * Destroy a android_creds instance. - */ - void (*destroy)(android_creds_t *this); - -}; - -/** - * Create an android_creds instance. - */ -android_creds_t *android_creds_create(); - -#endif /** ANDROID_CREDS_H_ @}*/ - diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c deleted file mode 100644 index 908e37238..000000000 --- a/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c +++ /dev/null @@ -1,419 +0,0 @@ -/* - * 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. - */ - -#include -#include -#include -#include -#include -#include - -#include "android_dns_proxy.h" - -#include -#include -#include -#include - -/** - * Timeout in seconds for sockets (i.e. not used for x seconds -> delete) - */ -#define SOCKET_TIMEOUT 30 - -typedef struct private_android_dns_proxy_t private_android_dns_proxy_t; - -struct private_android_dns_proxy_t { - - /** - * Public interface - */ - android_dns_proxy_t public; - - /** - * Mapping from source address to sockets - */ - hashtable_t *sockets; - - /** - * Registered callback - */ - dns_proxy_response_cb_t cb; - - /** - * Data passed to callback - */ - void *data; - - /** - * Lock used to synchronize access to the private members - */ - rwlock_t *lock; - - /** - * Hostnames to filter queries by - */ - hashtable_t *hostnames; -}; - -/** - * Data for proxy sockets - */ -typedef struct { - private_android_dns_proxy_t *proxy; - time_t last_use; - host_t *src; - int fd; -} proxy_socket_t; - -/** - * Destroy a socket - */ -static void socket_destroy(proxy_socket_t *this) -{ - this->src->destroy(this->src); - if (this->fd != -1) - { - close(this->fd); - } - free(this); -} - -/** - * Hash a proxy socket by src address - */ -static u_int socket_hash(host_t *src) -{ - u_int16_t port = src->get_port(src); - return chunk_hash_inc(src->get_address(src), - chunk_hash(chunk_from_thing(port))); -} - -/** - * Compare proxy sockets by src address - */ -static bool socket_equals(host_t *a, host_t *b) -{ - return a->equals(a, b); -} - -/** - * Opens a UDP socket for the given address family - */ -static int open_socket(int family) -{ - int skt; - - skt = socket(family, SOCK_DGRAM, IPPROTO_UDP); - if (skt < 0) - { - DBG1(DBG_NET, "could not open proxy socket: %s", strerror(errno)); - return -1; - } - if (!hydra->kernel_interface->bypass_socket(hydra->kernel_interface, - skt, family)) - { - DBG1(DBG_NET, "installing bypass policy for proxy socket failed"); - close(skt); - return -1; - } - return skt; -} - -/** - * Create a proxy socket for the given source - */ -static proxy_socket_t *create_socket(private_android_dns_proxy_t *this, - host_t *src) -{ - proxy_socket_t *skt; - - INIT(skt, - .proxy = this, - .src = src->clone(src), - .fd = open_socket(src->get_family(src)), - ); - if (skt->fd == -1) - { - socket_destroy(skt); - return NULL; - } - return skt; -} - -CALLBACK(handle_response, bool, - proxy_socket_t *this, int fd, watcher_event_t event) -{ - struct sockaddr_storage addr; - socklen_t addr_len = sizeof(addr); - char buf[4096]; - ssize_t len; - host_t *src; - - len = recvfrom(fd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr*)&addr, - &addr_len); - if (len > 0) - { - ip_packet_t *packet; - - src = host_create_from_sockaddr((sockaddr_t*)&addr); - if (!src) - { - DBG1(DBG_NET, "failed to parse source address"); - return TRUE; - } - packet = ip_packet_create_udp_from_data(src, this->src, - chunk_create(buf, len)); - if (!packet) - { - DBG1(DBG_NET, "failed to parse DNS response"); - return TRUE; - } - this->proxy->lock->read_lock(this->proxy->lock); - this->last_use = time_monotonic(NULL); - if (this->proxy->cb) - { - this->proxy->cb(this->proxy->data, packet); - } - else - { - packet->destroy(packet); - } - this->proxy->lock->unlock(this->proxy->lock); - } - else if (errno != EWOULDBLOCK) - { - DBG1(DBG_NET, "receiving DNS response failed: %s", strerror(errno)); - } - return TRUE; -} - -CALLBACK(handle_timeout, job_requeue_t, - proxy_socket_t *this) -{ - time_t now, diff; - - now = time_monotonic(NULL); - this->proxy->lock->write_lock(this->proxy->lock); - diff = now - this->last_use; - if (diff >= SOCKET_TIMEOUT) - { - this->proxy->sockets->remove(this->proxy->sockets, this->src); - lib->watcher->remove(lib->watcher, this->fd); - this->proxy->lock->unlock(this->proxy->lock); - socket_destroy(this); - return JOB_REQUEUE_NONE; - } - this->proxy->lock->unlock(this->proxy->lock); - return JOB_RESCHEDULE(SOCKET_TIMEOUT - diff); -} - -/** - * DNS header and masks to access flags - */ -typedef struct __attribute__((packed)) { - u_int16_t id; - u_int16_t flags; -#define DNS_QR_MASK 0x8000 -#define DNS_OPCODE_MASK 0x7800 - u_int16_t qdcount; - u_int16_t ancount; - u_int16_t nscount; - u_int16_t arcount; -} dns_header_t; - -/** - * Extract the hostname in the question section data points to. - * Hostnames can be at most 255 characters (including dots separating labels), - * each label must be between 1 and 63 characters. - * The format is [len][label][len][label], followed by a null byte to indicate - * the null label of the root. - */ -static char *extract_hostname(chunk_t data) -{ - char *hostname, *pos, *end; - u_int8_t label; - - if (!data.len || data.len > 255) - { - return NULL; - } - label = *data.ptr; - data = chunk_skip(data, 1); - hostname = strndup(data.ptr, data.len); - /* replace all label lengths with dots */ - pos = hostname + label; - end = hostname + strlen(hostname); - while (pos < end) - { - label = *pos; - *pos++ = '.'; - pos += label; - } - return hostname; -} - -/** - * Check if the DNS query is for one of the allowed hostnames - */ -static bool check_hostname(private_android_dns_proxy_t *this, chunk_t data) -{ - dns_header_t *dns; - char *hostname; - bool success = FALSE; - - this->lock->read_lock(this->lock); - if (!this->hostnames->get_count(this->hostnames)) - { - this->lock->unlock(this->lock); - return TRUE; - } - if (data.len < sizeof(dns_header_t)) - { - this->lock->unlock(this->lock); - return FALSE; - } - dns = (dns_header_t*)data.ptr; - if ((ntohs(dns->flags) & DNS_QR_MASK) == 0 && - (ntohs(dns->flags) & DNS_OPCODE_MASK) == 0 && - dns->qdcount) - { - data = chunk_skip(data, sizeof(dns_header_t)); - hostname = extract_hostname(data); - if (hostname && this->hostnames->get(this->hostnames, hostname)) - { - success = TRUE; - } - free(hostname); - } - this->lock->unlock(this->lock); - return success; -} - -METHOD(android_dns_proxy_t, handle, bool, - private_android_dns_proxy_t *this, ip_packet_t *packet) -{ - proxy_socket_t *skt; - host_t *dst, *src; - chunk_t data; - - if (packet->get_next_header(packet) != IPPROTO_UDP) - { - return FALSE; - } - dst = packet->get_destination(packet); - if (dst->get_port(dst) != 53) - { /* no DNS packet */ - return FALSE; - } - data = packet->get_payload(packet); - /* remove UDP header */ - data = chunk_skip(data, 8); - if (!check_hostname(this, data)) - { - return FALSE; - } - src = packet->get_source(packet); - this->lock->write_lock(this->lock); - skt = this->sockets->get(this->sockets, src); - if (!skt) - { - skt = create_socket(this, src); - if (!skt) - { - this->lock->unlock(this->lock); - return FALSE; - } - this->sockets->put(this->sockets, skt->src, skt); - lib->watcher->add(lib->watcher, skt->fd, WATCHER_READ, handle_response, - skt); - lib->scheduler->schedule_job(lib->scheduler, - (job_t*)callback_job_create(handle_timeout, skt, - NULL, (callback_job_cancel_t)return_false), SOCKET_TIMEOUT); - } - skt->last_use = time_monotonic(NULL); - if (sendto(skt->fd, data.ptr, data.len, 0, dst->get_sockaddr(dst), - *dst->get_sockaddr_len(dst)) != data.len) - { - DBG1(DBG_NET, "sending DNS request failed: %s", strerror(errno)); - } - this->lock->unlock(this->lock); - return TRUE; -} - -METHOD(android_dns_proxy_t, register_cb, void, - private_android_dns_proxy_t *this, dns_proxy_response_cb_t cb, void *data) -{ - this->lock->write_lock(this->lock); - this->cb = cb; - this->data = data; - this->lock->unlock(this->lock); -} - -METHOD(android_dns_proxy_t, unregister_cb, void, - private_android_dns_proxy_t *this, dns_proxy_response_cb_t cb) -{ - this->lock->write_lock(this->lock); - if (this->cb == cb) - { - this->cb = NULL; - } - this->lock->unlock(this->lock); -} - -METHOD(android_dns_proxy_t, add_hostname, void, - private_android_dns_proxy_t *this, char *hostname) -{ - char *existing; - - hostname = strdup(hostname); - this->lock->write_lock(this->lock); - existing = this->hostnames->put(this->hostnames, hostname, hostname); - this->lock->unlock(this->lock); - free(existing); -} - -METHOD(android_dns_proxy_t, destroy, void, - private_android_dns_proxy_t *this) -{ - this->hostnames->destroy_function(this->hostnames, (void*)free); - this->sockets->destroy_function(this->sockets, (void*)socket_destroy); - this->lock->destroy(this->lock); - free(this); -} - -/** - * Described in header. - */ -android_dns_proxy_t *android_dns_proxy_create() -{ - private_android_dns_proxy_t *this; - - INIT(this, - .public = { - .handle = _handle, - .register_cb = _register_cb, - .unregister_cb = _unregister_cb, - .add_hostname = _add_hostname, - .destroy = _destroy, - }, - .sockets = hashtable_create((hashtable_hash_t)socket_hash, - (hashtable_equals_t)socket_equals, 4), - .hostnames = hashtable_create(hashtable_hash_str, - hashtable_equals_str, 4), - .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), - ); - - return &this->public; -} diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h deleted file mode 100644 index 481b060cf..000000000 --- a/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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. - */ - -/** - * @defgroup android_dns_proxy android_dns_proxy - * @{ @ingroup android_backend - */ - -#ifndef ANDROID_DNS_PROXY_H_ -#define ANDROID_DNS_PROXY_H_ - -#include - -typedef struct android_dns_proxy_t android_dns_proxy_t; - -/** - * Callback called to deliver a DNS response packet. - * - * @param data data supplied during registration of the callback - * @param packet DNS response packet (has to be destroyed) - */ -typedef void (*dns_proxy_response_cb_t)(void *data, ip_packet_t *packet); - -/** - * DNS proxy class - */ -struct android_dns_proxy_t { - - /** - * Handle an outbound DNS packet (if the packet is one) - * - * @param packet packet to handle - * @return TRUE if handled, FALSE otherwise (no DNS) - */ - bool (*handle)(android_dns_proxy_t *this, ip_packet_t *packet); - - /** - * Register the callback used to deliver DNS response packets. - * - * @param cb the callback function - * @param data optional data provided to callback - */ - void (*register_cb)(android_dns_proxy_t *this, dns_proxy_response_cb_t cb, - void *data); - - /** - * Unregister the callback used to deliver DNS response packets. - * - * @param cb the callback function - * @param data optional data provided to callback - */ - void (*unregister_cb)(android_dns_proxy_t *this, dns_proxy_response_cb_t cb); - - /** - * Add a hostname for which queries are proxied. If at least one hostname - * is configured DNS queries for others will not be handled. - * - * @param hostname hostname to add (gets cloned) - */ - void (*add_hostname)(android_dns_proxy_t *this, char *hostname); - - /** - * Destroy an instance. - */ - void (*destroy)(android_dns_proxy_t *this); -}; - -/** - * Create an instance. - */ -android_dns_proxy_t *android_dns_proxy_create(); - -#endif /** ANDROID_DNS_PROXY_H_ @}*/ - diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_private_key.c b/src/frontends/android/jni/libandroidbridge/backend/android_private_key.c deleted file mode 100644 index 769ea3f31..000000000 --- a/src/frontends/android/jni/libandroidbridge/backend/android_private_key.c +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (C) 2012-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. - */ - -#include "android_private_key.h" - -#include "../android_jni.h" -#include -#include - -typedef struct private_private_key_t private_private_key_t; - -/** - * Private data of a android_private_key_t object. - */ -struct private_private_key_t { - - /** - * Public interface - */ - private_key_t public; - - /** - * reference to the Java PrivateKey object - */ - jobject key; - - /** - * Java class used to build signatures - */ - jclass signature_class; - - /** - * public key that belongs to this private key - */ - public_key_t *pubkey; - - /** - * reference count - */ - refcount_t ref; -}; - -METHOD(private_key_t, sign, bool, - private_private_key_t *this, signature_scheme_t scheme, - chunk_t data, chunk_t *signature) -{ - JNIEnv *env; - jmethodID method_id; - const char *method = NULL; - jstring jmethod; - jobject jsignature; - jbyteArray jdata, jsigarray; - - switch (this->pubkey->get_type(this->pubkey)) - { - case KEY_RSA: - switch (scheme) - { - case SIGN_RSA_EMSA_PKCS1_NULL: - method = "NONEwithRSA"; - break; - 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 KEY_ECDSA: - switch (scheme) - { - case SIGN_ECDSA_WITH_SHA1_DER: - method = "SHA1withECDSA"; - break; - case SIGN_ECDSA_WITH_SHA256_DER: - case SIGN_ECDSA_256: - method = "SHA256withECDSA"; - break; - case SIGN_ECDSA_WITH_SHA384_DER: - case SIGN_ECDSA_384: - method = "SHA384withECDSA"; - break; - case SIGN_ECDSA_WITH_SHA512_DER: - case SIGN_ECDSA_521: - method = "SHA512withECDSA"; - break; - default: - break; - } - break; - default: - break; - } - if (!method) - { - DBG1(DBG_LIB, "signature scheme %N not supported via JNI", - signature_scheme_names, scheme); - return FALSE; - } - - androidjni_attach_thread(&env); - /* we use java.security.Signature to create the signature without requiring - * access to the actual private key */ - method_id = (*env)->GetStaticMethodID(env, this->signature_class, - "getInstance", "(Ljava/lang/String;)Ljava/security/Signature;"); - if (!method_id) - { - goto failed; - } - jmethod = (*env)->NewStringUTF(env, method); - if (!jmethod) - { - goto failed; - } - jsignature = (*env)->CallStaticObjectMethod(env, this->signature_class, - method_id, jmethod); - if (!jsignature) - { - goto failed; - } - method_id = (*env)->GetMethodID(env, this->signature_class, "initSign", - "(Ljava/security/PrivateKey;)V"); - if (!method_id) - { - goto failed; - } - (*env)->CallVoidMethod(env, jsignature, method_id, this->key); - if (androidjni_exception_occurred(env)) - { - goto failed; - } - method_id = (*env)->GetMethodID(env, this->signature_class, "update", - "([B)V"); - if (!method_id) - { - goto failed; - } - jdata = byte_array_from_chunk(env, data); - (*env)->CallVoidMethod(env, jsignature, method_id, jdata); - if (androidjni_exception_occurred(env)) - { - goto failed; - } - method_id = (*env)->GetMethodID(env, this->signature_class, "sign", - "()[B"); - if (!method_id) - { - goto failed; - } - jsigarray = (*env)->CallObjectMethod(env, jsignature, method_id); - if (!jsigarray) - { - goto failed; - } - 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; - } - if (len) - { - /* 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); - } - } - else - { - *signature = chunk_from_byte_array(env, jsigarray); - } - androidjni_detach_thread(); - return TRUE; - -failed: - DBG1(DBG_LIB, "failed to build %N signature via JNI", - signature_scheme_names, scheme); - androidjni_exception_occurred(env); - androidjni_detach_thread(); - return FALSE; -} - -METHOD(private_key_t, get_type, key_type_t, - private_private_key_t *this) -{ - return this->pubkey->get_type(this->pubkey); -} - -METHOD(private_key_t, decrypt, bool, - private_private_key_t *this, encryption_scheme_t scheme, - chunk_t crypto, chunk_t *plain) -{ - DBG1(DBG_LIB, "private key decryption is currently not supported via JNI"); - return FALSE; -} - -METHOD(private_key_t, get_keysize, int, - private_private_key_t *this) -{ - return this->pubkey->get_keysize(this->pubkey); -} - -METHOD(private_key_t, get_public_key, public_key_t*, - private_private_key_t *this) -{ - return this->pubkey->get_ref(this->pubkey); -} - -METHOD(private_key_t, get_encoding, bool, - private_private_key_t *this, cred_encoding_type_t type, - chunk_t *encoding) -{ - return FALSE; -} - -METHOD(private_key_t, get_fingerprint, bool, - private_private_key_t *this, cred_encoding_type_t type, chunk_t *fp) -{ - return this->pubkey->get_fingerprint(this->pubkey, type, fp); -} - -METHOD(private_key_t, get_ref, private_key_t*, - private_private_key_t *this) -{ - ref_get(&this->ref); - return &this->public; -} - -METHOD(private_key_t, destroy, void, - private_private_key_t *this) -{ - if (ref_put(&this->ref)) - { - JNIEnv *env; - - androidjni_attach_thread(&env); - if (android_sdk_version == ANDROID_JELLY_BEAN) - { /* there is a bug in JB that causes a SIGSEGV if the key object is - * garbage collected so we intentionally leak the reference to it */ - DBG1(DBG_LIB, "intentionally leaking private key reference due to " - "a bug in the framework"); - } - else - { - (*env)->DeleteGlobalRef(env, this->key); - } - (*env)->DeleteGlobalRef(env, this->signature_class); - androidjni_detach_thread(); - this->pubkey->destroy(this->pubkey); - free(this); - } -} - -/* - * See header - */ -private_key_t *android_private_key_create(jobject key, public_key_t *pubkey) -{ - JNIEnv *env; - private_private_key_t *this; - - INIT(this, - .public = { - .get_type = _get_type, - .sign = _sign, - .decrypt = _decrypt, - .get_keysize = _get_keysize, - .get_public_key = _get_public_key, - .belongs_to = private_key_belongs_to, - .equals = private_key_equals, - .get_fingerprint = _get_fingerprint, - .has_fingerprint = private_key_has_fingerprint, - .get_encoding = _get_encoding, - .get_ref = _get_ref, - .destroy = _destroy, - }, - .ref = 1, - .pubkey = pubkey, - ); - - if (!pubkey) - { - free(this); - return NULL; - } - - /* in ICS we could simply call getEncoded and use the PKCS#8/DER encoded - * private key, since JB that's not possible as there is no direct access - * to private keys anymore (as these could now be hardware backed) */ - androidjni_attach_thread(&env); - this->key = (*env)->NewGlobalRef(env, key); - this->signature_class = (*env)->NewGlobalRef(env, (*env)->FindClass(env, - "java/security/Signature")); - androidjni_detach_thread(); - return &this->public; -} diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_private_key.h b/src/frontends/android/jni/libandroidbridge/backend/android_private_key.h deleted file mode 100644 index 9848657de..000000000 --- a/src/frontends/android/jni/libandroidbridge/backend/android_private_key.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2012 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. - */ - -/** - * @defgroup android_private_key android_private_key - * @{ @ingroup android_backend - */ - -#ifndef ANDROID_PRIVATE_KEY_H_ -#define ANDROID_PRIVATE_KEY_H_ - -#include - -#include - -/** - * Create a JNI backed key, stored in the Android KeyChain - * - * @param key PrivateKey instance - * @param pubkey public key as extracted from the certificate (gets adopted) - * @return private_key_t instance - */ -private_key_t *android_private_key_create(jobject key, public_key_t *pubkey); - -#endif /** ANDROID_PRIVATE_KEY_H_ @}*/ - diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c deleted file mode 100644 index 7ef3913f7..000000000 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ /dev/null @@ -1,848 +0,0 @@ -/* - * Copyright (C) 2010-2015 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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. - */ - -#include -#include - -#include "android_service.h" -#include "android_dns_proxy.h" -#include "../charonservice.h" -#include "../vpnservice_builder.h" - -#include -#include -#include -#include -#include -#include - -typedef struct private_android_service_t private_android_service_t; - -/** - * private data of Android service - */ -struct private_android_service_t { - - /** - * public interface - */ - android_service_t public; - - /** - * credential set - */ - android_creds_t *creds; - - /** - * current IKE_SA - */ - ike_sa_t *ike_sa; - - /** - * configuration setttings - */ - settings_t *settings; - - /** - * lock to safely access the TUN device fd - */ - rwlock_t *lock; - - /** - * TUN device file descriptor - */ - int tunfd; - - /** - * MTU of TUN device - */ - int mtu; - - /** - * DNS proxy - */ - android_dns_proxy_t *dns_proxy; - - /** - * Whether to use the DNS proxy or not - */ - bool use_dns_proxy; -}; - -/** - * Outbound callback - */ -static void send_esp(void *data, esp_packet_t *packet) -{ - charon->sender->send_no_marker(charon->sender, (packet_t*)packet); -} - -/** - * Inbound callback - */ -static void deliver_plain(private_android_service_t *this, - ip_packet_t *packet) -{ - chunk_t encoding; - ssize_t len; - - encoding = packet->get_encoding(packet); - - this->lock->read_lock(this->lock); - if (this->tunfd < 0) - { /* the TUN device is already closed */ - this->lock->unlock(this->lock); - packet->destroy(packet); - return; - } - len = write(this->tunfd, encoding.ptr, encoding.len); - this->lock->unlock(this->lock); - - if (len < 0 || len != encoding.len) - { - DBG1(DBG_DMN, "failed to write packet to TUN device: %s", - strerror(errno)); - } - packet->destroy(packet); -} - -/** - * Receiver callback - */ -static void receiver_esp_cb(void *data, packet_t *packet) -{ - esp_packet_t *esp_packet; - - esp_packet = esp_packet_create_from_packet(packet); - ipsec->processor->queue_inbound(ipsec->processor, esp_packet); -} - -/** - * Job handling outbound plaintext packets - */ -static job_requeue_t handle_plain(private_android_service_t *this) -{ - ip_packet_t *packet; - chunk_t raw; - fd_set set; - ssize_t len; - int tunfd; - bool old, dns_proxy; - timeval_t tv = { - /* check every second if tunfd is still valid */ - .tv_sec = 1, - }; - - FD_ZERO(&set); - - this->lock->read_lock(this->lock); - if (this->tunfd < 0) - { /* the TUN device is already closed */ - this->lock->unlock(this->lock); - return JOB_REQUEUE_NONE; - } - tunfd = this->tunfd; - FD_SET(tunfd, &set); - /* cache this while we have the lock */ - dns_proxy = this->use_dns_proxy; - this->lock->unlock(this->lock); - - old = thread_cancelability(TRUE); - len = select(tunfd + 1, &set, NULL, NULL, &tv); - thread_cancelability(old); - - if (len < 0) - { - if (errno == EBADF) - { /* the TUN device got closed just before calling select(), retry */ - return JOB_REQUEUE_FAIR; - } - DBG1(DBG_DMN, "select on TUN device failed: %s", strerror(errno)); - return JOB_REQUEUE_NONE; - } - else if (len == 0) - { /* timeout, check again right away */ - return JOB_REQUEUE_DIRECT; - } - - raw = chunk_alloc(this->mtu); - len = read(tunfd, raw.ptr, raw.len); - if (len < 0) - { - DBG1(DBG_DMN, "reading from TUN device failed: %s", strerror(errno)); - chunk_free(&raw); - return JOB_REQUEUE_FAIR; - } - raw.len = len; - - packet = ip_packet_create(raw); - if (packet) - { - if (!dns_proxy || !this->dns_proxy->handle(this->dns_proxy, packet)) - { - ipsec->processor->queue_outbound(ipsec->processor, packet); - } - } - else - { - DBG1(DBG_DMN, "invalid IP packet read from TUN device"); - } - return JOB_REQUEUE_DIRECT; -} - -/** - * Add a route to the TUN device builder - */ -static bool add_route(vpnservice_builder_t *builder, host_t *net, - u_int8_t prefix) -{ - /* if route is 0.0.0.0/0, split it into two routes 0.0.0.0/1 and - * 128.0.0.0/1 because otherwise it would conflict with the current default - * route. likewise for IPv6 with ::/0. */ - if (net->is_anyaddr(net) && prefix == 0) - { - bool success; - - success = add_route(builder, net, 1); - if (net->get_family(net) == AF_INET) - { - net = host_create_from_string("128.0.0.0", 0); - } - else - { - net = host_create_from_string("8000::", 0); - } - success = success && add_route(builder, net, 1); - net->destroy(net); - return success; - } - return builder->add_route(builder, net, prefix); -} - -/** - * Generate and set routes from installed IPsec policies - */ -static bool add_routes(vpnservice_builder_t *builder, child_sa_t *child_sa) -{ - traffic_selector_t *src_ts, *dst_ts; - enumerator_t *enumerator; - bool success = TRUE; - - enumerator = child_sa->create_policy_enumerator(child_sa); - while (success && enumerator->enumerate(enumerator, &src_ts, &dst_ts)) - { - host_t *net; - u_int8_t prefix; - - dst_ts->to_subnet(dst_ts, &net, &prefix); - success = add_route(builder, net, prefix); - net->destroy(net); - } - enumerator->destroy(enumerator); - return success; -} - -/** - * Setup a new TUN device for the supplied SAs, also queues a job that - * reads packets from this device. - * Additional information such as DNS servers are gathered in appropriate - * listeners asynchronously. To be sure every required bit of information is - * available this should be called after the CHILD_SA has been established. - */ -static bool setup_tun_device(private_android_service_t *this, - ike_sa_t *ike_sa, child_sa_t *child_sa) -{ - vpnservice_builder_t *builder; - enumerator_t *enumerator; - bool vip_found = FALSE, already_registered = FALSE; - host_t *vip; - int tunfd; - - DBG1(DBG_DMN, "setting up TUN device for CHILD_SA %s{%u}", - child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa)); - - builder = charonservice->get_vpnservice_builder(charonservice); - - enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, TRUE); - while (enumerator->enumerate(enumerator, &vip)) - { - if (!vip->is_anyaddr(vip)) - { - if (!builder->add_address(builder, vip)) - { - break; - } - vip_found = TRUE; - } - } - enumerator->destroy(enumerator); - - if (!vip_found) - { - DBG1(DBG_DMN, "setting up TUN device failed, no virtual IP found"); - return FALSE; - } - if (!add_routes(builder, child_sa) || - !builder->set_mtu(builder, this->mtu)) - { - return FALSE; - } - - tunfd = builder->establish(builder); - if (tunfd == -1) - { - return FALSE; - } - - this->lock->write_lock(this->lock); - if (this->tunfd > 0) - { /* close previously opened TUN device */ - close(this->tunfd); - already_registered = true; - } - this->tunfd = tunfd; - this->lock->unlock(this->lock); - - DBG1(DBG_DMN, "successfully created TUN device"); - - if (!already_registered) - { - charon->receiver->add_esp_cb(charon->receiver, - (receiver_esp_cb_t)receiver_esp_cb, NULL); - ipsec->processor->register_inbound(ipsec->processor, - (ipsec_inbound_cb_t)deliver_plain, this); - ipsec->processor->register_outbound(ipsec->processor, - (ipsec_outbound_cb_t)send_esp, NULL); - this->dns_proxy->register_cb(this->dns_proxy, - (dns_proxy_response_cb_t)deliver_plain, this); - - lib->processor->queue_job(lib->processor, - (job_t*)callback_job_create((callback_job_cb_t)handle_plain, this, - NULL, (callback_job_cancel_t)return_false)); - } - return TRUE; -} - -/** - * Setup a new TUN device based on the existing one, but without DNS server. - */ -static bool setup_tun_device_without_dns(private_android_service_t *this) -{ - vpnservice_builder_t *builder; - int tunfd; - - DBG1(DBG_DMN, "setting up TUN device without DNS"); - - builder = charonservice->get_vpnservice_builder(charonservice); - - tunfd = builder->establish_no_dns(builder); - if (tunfd == -1) - { - return FALSE; - } - - this->lock->write_lock(this->lock); - if (this->tunfd > 0) - { /* close previously opened TUN device, this should always be the case */ - close(this->tunfd); - } - this->tunfd = tunfd; - this->lock->unlock(this->lock); - - DBG1(DBG_DMN, "successfully created TUN device without DNS"); - return TRUE; -} - -/** - * Close the current tun device - */ -static void close_tun_device(private_android_service_t *this) -{ - int tunfd; - - this->lock->write_lock(this->lock); - if (this->tunfd < 0) - { /* already closed (or never created) */ - this->lock->unlock(this->lock); - return; - } - tunfd = this->tunfd; - this->tunfd = -1; - this->lock->unlock(this->lock); - - this->dns_proxy->unregister_cb(this->dns_proxy, - (dns_proxy_response_cb_t)deliver_plain); - ipsec->processor->unregister_outbound(ipsec->processor, - (ipsec_outbound_cb_t)send_esp); - ipsec->processor->unregister_inbound(ipsec->processor, - (ipsec_inbound_cb_t)deliver_plain); - charon->receiver->del_esp_cb(charon->receiver, - (receiver_esp_cb_t)receiver_esp_cb); - close(tunfd); -} - -/** - * Terminate the IKE_SA with the given unique ID - */ -CALLBACK(terminate, job_requeue_t, - u_int32_t *id) -{ - charon->controller->terminate_ike(charon->controller, *id, - controller_cb_empty, NULL, 0); - return JOB_REQUEUE_NONE; -} - -/** - * Reestablish the IKE_SA with the given unique ID - */ -CALLBACK(reestablish, job_requeue_t, - u_int32_t *id) -{ - ike_sa_t *ike_sa; - - ike_sa = charon->ike_sa_manager->checkout_by_id(charon->ike_sa_manager, *id); - if (ike_sa) - { - if (ike_sa->reauth(ike_sa) == DESTROY_ME) - { - charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, - ike_sa); - } - else - { - charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); - } - } - return JOB_REQUEUE_NONE; -} - -METHOD(listener_t, child_updown, bool, - private_android_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, - bool up) -{ - if (this->ike_sa == ike_sa) - { - if (up) - { - /* disable the hooks registered to catch initiation failures */ - this->public.listener.ike_updown = NULL; - /* CHILD_SA is up so we can disable the DNS proxy we enabled to - * reestablish the SA */ - this->lock->write_lock(this->lock); - this->use_dns_proxy = FALSE; - this->lock->unlock(this->lock); - if (!setup_tun_device(this, ike_sa, child_sa)) - { - DBG1(DBG_DMN, "failed to setup TUN device"); - charonservice->update_status(charonservice, - CHARONSERVICE_GENERIC_ERROR); - return FALSE; - - } - charonservice->update_status(charonservice, - CHARONSERVICE_CHILD_STATE_UP); - } - else - { - charonservice->update_status(charonservice, - CHARONSERVICE_CHILD_STATE_DOWN); - } - } - return TRUE; -} - -METHOD(listener_t, ike_updown, bool, - private_android_service_t *this, ike_sa_t *ike_sa, bool up) -{ - /* this callback is only registered during initiation, so if the IKE_SA - * goes down we assume some kind of authentication error, more specific - * errors are catched in the alert() handler */ - if (this->ike_sa == ike_sa && !up) - { - charonservice->update_status(charonservice, - CHARONSERVICE_AUTH_ERROR); - return FALSE; - } - return TRUE; -} - -METHOD(listener_t, alert, bool, - private_android_service_t *this, ike_sa_t *ike_sa, alert_t alert, - va_list args) -{ - if (this->ike_sa == ike_sa) - { - switch (alert) - { - case ALERT_PEER_ADDR_FAILED: - charonservice->update_status(charonservice, - CHARONSERVICE_LOOKUP_ERROR); - break; - case ALERT_PEER_AUTH_FAILED: - charonservice->update_status(charonservice, - CHARONSERVICE_PEER_AUTH_ERROR); - break; - case ALERT_KEEP_ON_CHILD_SA_FAILURE: - { - u_int32_t *id = malloc_thing(u_int32_t); - - /* because close_ike_on_child_failure is set this is only - * triggered when CHILD_SA rekeying failed. reestablish it in - * the hope that the initial setup works again. */ - *id = ike_sa->get_unique_id(ike_sa); - lib->processor->queue_job(lib->processor, - (job_t*)callback_job_create_with_prio( - (callback_job_cb_t)reestablish, id, free, - (callback_job_cancel_t)return_false, JOB_PRIO_HIGH)); - break; - } - case ALERT_PEER_INIT_UNREACHABLE: - this->lock->read_lock(this->lock); - if (this->tunfd < 0) - { - u_int32_t *id = malloc_thing(u_int32_t); - - /* always fail if we are not able to initiate the IKE_SA - * initially */ - charonservice->update_status(charonservice, - CHARONSERVICE_UNREACHABLE_ERROR); - /* terminate the IKE_SA so no further keying tries are - * attempted */ - *id = ike_sa->get_unique_id(ike_sa); - lib->processor->queue_job(lib->processor, - (job_t*)callback_job_create_with_prio( - (callback_job_cb_t)terminate, id, free, - (callback_job_cancel_t)return_false, JOB_PRIO_HIGH)); - } - else - { - peer_cfg_t *peer_cfg; - u_int32_t tries, try; - - /* when reestablishing and if keyingtries is not %forever - * the IKE_SA is destroyed after the set number of tries, - * so notify the GUI */ - peer_cfg = ike_sa->get_peer_cfg(ike_sa); - tries = peer_cfg->get_keyingtries(peer_cfg); - try = va_arg(args, u_int32_t); - if (tries != 0 && try == tries-1) - { - charonservice->update_status(charonservice, - CHARONSERVICE_UNREACHABLE_ERROR); - } - } - this->lock->unlock(this->lock); - break; - default: - break; - } - } - return TRUE; -} - -METHOD(listener_t, ike_rekey, bool, - private_android_service_t *this, ike_sa_t *old, ike_sa_t *new) -{ - if (this->ike_sa == old) - { - this->ike_sa = new; - } - return TRUE; -} - -METHOD(listener_t, ike_reestablish_pre, bool, - private_android_service_t *this, ike_sa_t *old, ike_sa_t *new) -{ - if (this->ike_sa == old) - { - /* enable DNS proxy so hosts are properly resolved while the TUN device - * is still active */ - this->lock->write_lock(this->lock); - this->use_dns_proxy = TRUE; - this->lock->unlock(this->lock); - /* if DNS servers are installed that are only reachable through the VPN - * the DNS proxy doesn't help, so uninstall DNS servers */ - if (!setup_tun_device_without_dns(this)) - { - DBG1(DBG_DMN, "failed to setup TUN device without DNS"); - charonservice->update_status(charonservice, - CHARONSERVICE_GENERIC_ERROR); - } - } - return TRUE; -} - -METHOD(listener_t, ike_reestablish_post, bool, - private_android_service_t *this, ike_sa_t *old, ike_sa_t *new, - bool initiated) -{ - if (this->ike_sa == old && initiated) - { - this->ike_sa = new; - /* re-register hook to detect initiation failures */ - this->public.listener.ike_updown = _ike_updown; - /* if the IKE_SA got deleted by the responder we get the child_down() - * event on the old IKE_SA after this hook has been called, so they - * get ignored and thus we trigger the event here */ - charonservice->update_status(charonservice, - CHARONSERVICE_CHILD_STATE_DOWN); - } - return TRUE; -} - -static void add_auth_cfg_pw(private_android_service_t *this, - peer_cfg_t *peer_cfg, bool byod) -{ - identification_t *user; - auth_cfg_t *auth; - char *username, *password; - - auth = auth_cfg_create(); - auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); - if (byod) - { /* use EAP-TTLS if BYOD is enabled */ - auth->add(auth, AUTH_RULE_EAP_TYPE, EAP_TTLS); - } - - username = this->settings->get_str(this->settings, "connection.username", - NULL); - password = this->settings->get_str(this->settings, "connection.password", - NULL); - user = identification_create_from_string(username); - auth->add(auth, AUTH_RULE_IDENTITY, user); - - this->creds->add_username_password(this->creds, username, password); - peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE); -} - -static bool add_auth_cfg_cert(private_android_service_t *this, - peer_cfg_t *peer_cfg) -{ - certificate_t *cert; - identification_t *id; - auth_cfg_t *auth; - char *type; - - cert = this->creds->load_user_certificate(this->creds); - if (!cert) - { - return FALSE; - } - - type = this->settings->get_str(this->settings, "connection.type", NULL); - auth = auth_cfg_create(); - if (strpfx("ikev2-eap-tls", type)) - { - auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); - auth->add(auth, AUTH_RULE_EAP_TYPE, EAP_TLS); - id = identification_create_from_string("%any"); - auth->add(auth, AUTH_RULE_AAA_IDENTITY, id); - } - else - { - auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); - } - auth->add(auth, AUTH_RULE_SUBJECT_CERT, cert); - - id = cert->get_subject(cert); - auth->add(auth, AUTH_RULE_IDENTITY, id->clone(id)); - peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE); - return TRUE; -} - -static job_requeue_t initiate(private_android_service_t *this) -{ - identification_t *gateway; - ike_cfg_t *ike_cfg; - peer_cfg_t *peer_cfg; - child_cfg_t *child_cfg; - traffic_selector_t *ts; - ike_sa_t *ike_sa; - auth_cfg_t *auth; - lifetime_cfg_t lifetime = { - .time = { - .life = 3600, /* 1h */ - .rekey = 3000, /* 50min */ - .jitter = 300 /* 5min */ - } - }; - char *type, *server; - int port; - - server = this->settings->get_str(this->settings, "connection.server", NULL); - port = this->settings->get_int(this->settings, "connection.port", - IKEV2_UDP_PORT); - ike_cfg = ike_cfg_create(IKEV2, TRUE, TRUE, "0.0.0.0", - charon->socket->get_port(charon->socket, FALSE), - server, port, FRAGMENTATION_YES, 0); - ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE)); - ike_cfg->add_proposal(ike_cfg, proposal_create_default_aead(PROTO_IKE)); - - peer_cfg = peer_cfg_create("android", ike_cfg, CERT_SEND_IF_ASKED, - UNIQUE_REPLACE, 0, /* keyingtries */ - 36000, 0, /* rekey 10h, reauth none */ - 600, 600, /* jitter, over 10min */ - TRUE, FALSE, TRUE, /* mobike, aggressive, pull */ - 0, 0, /* DPD delay, timeout */ - FALSE, NULL, NULL); /* mediation */ - peer_cfg->add_virtual_ip(peer_cfg, host_create_any(AF_INET)); - peer_cfg->add_virtual_ip(peer_cfg, host_create_any(AF_INET6)); - - type = this->settings->get_str(this->settings, "connection.type", NULL); - /* local auth config */ - if (streq("ikev2-cert", type) || - streq("ikev2-cert-eap", type) || - streq("ikev2-eap-tls", type)) - { - if (!add_auth_cfg_cert(this, peer_cfg)) - { - peer_cfg->destroy(peer_cfg); - charonservice->update_status(charonservice, - CHARONSERVICE_GENERIC_ERROR); - return JOB_REQUEUE_NONE; - } - } - if (streq("ikev2-eap", type) || - streq("ikev2-cert-eap", type) || - streq("ikev2-byod-eap", type)) - { - add_auth_cfg_pw(this, peer_cfg, strpfx(type, "ikev2-byod")); - } - - /* remote auth config */ - auth = auth_cfg_create(); - gateway = identification_create_from_string(server); - auth->add(auth, AUTH_RULE_IDENTITY, gateway); - auth->add(auth, AUTH_RULE_IDENTITY_LOOSE, TRUE); - auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); - peer_cfg->add_auth_cfg(peer_cfg, auth, FALSE); - - child_cfg = child_cfg_create("android", &lifetime, NULL, TRUE, MODE_TUNNEL, - ACTION_NONE, ACTION_RESTART, ACTION_RESTART, - FALSE, 0, 0, NULL, NULL, 0); - /* create ESP proposals with and without DH groups, let responder decide - * if PFS is used */ - child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, - "aes128gcm16-aes256gcm16-ecp256")); - child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, - "aes128-sha256-ecp256-modp3072")); - child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, - "aes256-sha384-ecp521-modp8192")); - child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, - "aes128-aes192-aes256-sha1-sha256-sha384-sha512-" - "ecp256-ecp384-ecp521-" - "modp2048-modp3072-modp4096-modp1024")); - child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, - "aes128gcm16-aes256gcm16")); - child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, - "aes128-sha256")); - child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, - "aes256-sha384")); - child_cfg->add_proposal(child_cfg, proposal_create_from_string(PROTO_ESP, - "aes128-aes192-aes256-sha1-sha256-sha384-sha512")); - ts = traffic_selector_create_from_cidr("0.0.0.0/0", 0, 0, 65535); - child_cfg->add_traffic_selector(child_cfg, TRUE, ts); - ts = traffic_selector_create_from_cidr("0.0.0.0/0", 0, 0, 65535); - child_cfg->add_traffic_selector(child_cfg, FALSE, ts); - ts = traffic_selector_create_from_cidr("::/0", 0, 0, 65535); - child_cfg->add_traffic_selector(child_cfg, TRUE, ts); - ts = traffic_selector_create_from_cidr("::/0", 0, 0, 65535); - child_cfg->add_traffic_selector(child_cfg, FALSE, ts); - peer_cfg->add_child_cfg(peer_cfg, child_cfg); - - /* get us an IKE_SA */ - ike_sa = charon->ike_sa_manager->checkout_by_config(charon->ike_sa_manager, - peer_cfg); - if (!ike_sa) - { - peer_cfg->destroy(peer_cfg); - charonservice->update_status(charonservice, - CHARONSERVICE_GENERIC_ERROR); - return JOB_REQUEUE_NONE; - } - if (!ike_sa->get_peer_cfg(ike_sa)) - { - ike_sa->set_peer_cfg(ike_sa, peer_cfg); - } - peer_cfg->destroy(peer_cfg); - - /* store the IKE_SA so we can track its progress */ - this->ike_sa = ike_sa; - - /* get an additional reference because initiate consumes one */ - child_cfg->get_ref(child_cfg); - if (ike_sa->initiate(ike_sa, child_cfg, 0, NULL, NULL) != SUCCESS) - { - DBG1(DBG_CFG, "failed to initiate tunnel"); - charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, - ike_sa); - return JOB_REQUEUE_NONE; - } - charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); - return JOB_REQUEUE_NONE; -} - -METHOD(android_service_t, destroy, void, - private_android_service_t *this) -{ - charon->bus->remove_listener(charon->bus, &this->public.listener); - /* make sure the tun device is actually closed */ - close_tun_device(this); - this->dns_proxy->destroy(this->dns_proxy); - this->lock->destroy(this->lock); - this->settings->destroy(this->settings); - free(this); -} - -/** - * See header - */ -android_service_t *android_service_create(android_creds_t *creds, - settings_t *settings) -{ - private_android_service_t *this; - - INIT(this, - .public = { - .listener = { - .ike_rekey = _ike_rekey, - .ike_reestablish_pre = _ike_reestablish_pre, - .ike_reestablish_post = _ike_reestablish_post, - .ike_updown = _ike_updown, - .child_updown = _child_updown, - .alert = _alert, - }, - .destroy = _destroy, - }, - .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), - .dns_proxy = android_dns_proxy_create(), - .settings = settings, - .creds = creds, - .tunfd = -1, - .mtu = settings->get_int(settings, "global.mtu", ANDROID_DEFAULT_MTU), - ); - /* only allow queries for the VPN gateway */ - this->dns_proxy->add_hostname(this->dns_proxy, - this->settings->get_str(this->settings, "connection.server", NULL)); - - charon->bus->add_listener(charon->bus, &this->public.listener); - - lib->processor->queue_job(lib->processor, - (job_t*)callback_job_create((callback_job_cb_t)initiate, this, - NULL, NULL)); - return &this->public; -} diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.h b/src/frontends/android/jni/libandroidbridge/backend/android_service.h deleted file mode 100644 index 1a5175774..000000000 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2010-2015 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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. - */ - -/** - * @defgroup android_service android_service - * @{ @ingroup android_backend - */ - -#ifndef ANDROID_SERVICE_H_ -#define ANDROID_SERVICE_H_ - -#include "android_creds.h" - -#include -#include - -typedef struct android_service_t android_service_t; - -/** - * Service that sets up an IKE_SA/CHILD_SA and handles events - */ -struct android_service_t { - - /** - * Implements listener_t. - */ - listener_t listener; - - /** - * Destroy a android_service_t. - */ - void (*destroy)(android_service_t *this); - -}; - -/** - * Create an Android service instance. Queues a job that starts initiation of a - * new IKE SA. - * - * @param creds Android specific credential set - * @param settings configuration settings (gets adopted) - */ -android_service_t *android_service_create(android_creds_t *creds, - settings_t *settings); - -#endif /** ANDROID_SERVICE_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/byod/imc_android.c b/src/frontends/android/jni/libandroidbridge/byod/imc_android.c deleted file mode 100644 index 6432b957f..000000000 --- a/src/frontends/android/jni/libandroidbridge/byod/imc_android.c +++ /dev/null @@ -1,722 +0,0 @@ -/* - * Copyright (C) 2012-2013 Tobias Brunner - * Copyright (C) 2012 Christoph Buehler - * Copyright (C) 2012 Patrick Loetscher - * Copyright (C) 2011-2012 Andreas Steffen - * 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. - */ - -#include "imc_android_state.h" -#include "../android_jni.h" -#include "../charonservice.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include - -/* IMC definitions */ - -static const char imc_name[] = "Android"; - -static pen_type_t msg_types[] = { - { PEN_IETF, PA_SUBTYPE_IETF_OPERATING_SYSTEM }, - { PEN_IETF, PA_SUBTYPE_IETF_VPN }, - { PEN_TCG, PA_SUBTYPE_TCG_PTS }, -}; - -static imc_agent_t *imc_android; - -/** - * AndroidImc object accessed via JNI - */ -static jobject android_imc; - -/** - * AndroidImc class object - */ -static jclass android_imc_cls; - -/** - * see section 3.8.1 of TCG TNC IF-IMC Specification 1.3 - */ -static TNC_Result tnc_imc_initialize(TNC_IMCID imc_id, - TNC_Version min_version, - TNC_Version max_version, - TNC_Version *actual_version) -{ - if (imc_android) - { - DBG1(DBG_IMC, "IMC \"%s\" has already been initialized", imc_name); - return TNC_RESULT_ALREADY_INITIALIZED; - } - imc_android = imc_agent_create(imc_name, msg_types, countof(msg_types), - imc_id, actual_version); - if (!imc_android) - { - return TNC_RESULT_FATAL; - } - - if (min_version > TNC_IFIMC_VERSION_1 || max_version < TNC_IFIMC_VERSION_1) - { - DBG1(DBG_IMC, "no common IF-IMC version"); - return TNC_RESULT_NO_COMMON_VERSION; - } - return TNC_RESULT_SUCCESS; -} - -/** - * Update the state in the GUI. - */ -static void update_imc_state(TNC_ConnectionState state) -{ - android_imc_state_t imc_state = ANDROID_IMC_STATE_UNKNOWN; - - switch (state) - { /* map connection states to the values used by the GUI */ - case TNC_CONNECTION_STATE_ACCESS_ALLOWED: - imc_state = ANDROID_IMC_STATE_ALLOW; - break; - case TNC_CONNECTION_STATE_ACCESS_ISOLATED: - imc_state = ANDROID_IMC_STATE_ISOLATE; - break; - case TNC_CONNECTION_STATE_ACCESS_NONE: - imc_state = ANDROID_IMC_STATE_BLOCK; - break; - } - - charonservice->update_imc_state(charonservice, imc_state); -} - -/** - * see section 3.8.2 of TCG TNC IF-IMC Specification 1.3 - */ -static TNC_Result tnc_imc_notifyconnectionchange(TNC_IMCID imc_id, - TNC_ConnectionID connection_id, - TNC_ConnectionState new_state) -{ - imc_state_t *state; - - if (!imc_android) - { - DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name); - return TNC_RESULT_NOT_INITIALIZED; - } - switch (new_state) - { - case TNC_CONNECTION_STATE_CREATE: - state = imc_android_state_create(connection_id); - return imc_android->create_state(imc_android, state); - case TNC_CONNECTION_STATE_HANDSHAKE: - if (imc_android->change_state(imc_android, connection_id, new_state, - &state) != TNC_RESULT_SUCCESS) - { - return TNC_RESULT_FATAL; - } - state->set_result(state, imc_id, - TNC_IMV_EVALUATION_RESULT_DONT_KNOW); - return TNC_RESULT_SUCCESS; - case TNC_CONNECTION_STATE_DELETE: - return imc_android->delete_state(imc_android, connection_id); - case TNC_CONNECTION_STATE_ACCESS_ALLOWED: - case TNC_CONNECTION_STATE_ACCESS_ISOLATED: - case TNC_CONNECTION_STATE_ACCESS_NONE: - update_imc_state(new_state); - /* fall-through */ - default: - return imc_android->change_state(imc_android, connection_id, - new_state, NULL); - } -} - -/** - * Convert the native C strings in the enumerator to a Java String array. - * The given enumerator gets destroyed. - */ -static jobjectArray string_array_create(JNIEnv *env, enumerator_t *enumerator) -{ - linked_list_t *list; - jobjectArray jarray; - jstring jstring; - char *native; - jclass cls; - int i = 0; - - cls = (*env)->FindClass(env, "java/lang/String"); - list = linked_list_create_from_enumerator(enumerator); - jarray = (*env)->NewObjectArray(env, list->get_count(list), cls, NULL); - if (!jarray) - { - goto failed; - } - enumerator = list->create_enumerator(list); - while (enumerator->enumerate(enumerator, (void**)&native)) - { - jstring = (*env)->NewStringUTF(env, native); - if (!jstring) - { - enumerator->destroy(enumerator); - goto failed; - } - (*env)->SetObjectArrayElement(env, jarray, i++, jstring); - } - enumerator->destroy(enumerator); - list->destroy(list); - return jarray; - -failed: - androidjni_exception_occurred(env); - list->destroy(list); - return NULL; -} - -/** - * Get a measurement for the given attribute type from the Android IMC. - * NULL is returned if no measurement is available or an error occurred. - * - * The optional args is an enumerator over char* (gets destroyed). - */ -static pa_tnc_attr_t *get_measurement(pen_type_t attr_type, enumerator_t *args) -{ - JNIEnv *env; - pa_tnc_attr_t *attr; - jmethodID method_id; - jbyteArray jmeasurement; - jobjectArray jargs = NULL; - chunk_t data; - - androidjni_attach_thread(&env); - if (args) - { - jargs = string_array_create(env, args); - if (!jargs) - { - goto failed; - } - method_id = (*env)->GetMethodID(env, android_imc_cls, "getMeasurement", - "(II[Ljava/lang/String;)[B"); - } - else - { - method_id = (*env)->GetMethodID(env, android_imc_cls, "getMeasurement", - "(II)[B"); - } - if (!method_id) - { - goto failed; - } - jmeasurement = (*env)->CallObjectMethod(env, android_imc, method_id, - attr_type.vendor_id, attr_type.type, - jargs); - if (!jmeasurement || androidjni_exception_occurred(env)) - { - goto failed; - } - data = chunk_create((*env)->GetByteArrayElements(env, jmeasurement, NULL), - (*env)->GetArrayLength(env, jmeasurement)); - if (!data.ptr) - { - goto failed; - } - attr = imcv_pa_tnc_attributes->construct(imcv_pa_tnc_attributes, - attr_type.vendor_id, attr_type.type, data); - (*env)->ReleaseByteArrayElements(env, jmeasurement, data.ptr, JNI_ABORT); - androidjni_detach_thread(); - return attr; - -failed: - androidjni_exception_occurred(env); - androidjni_detach_thread(); - return NULL; -} - -/** - * Add the measurement for the requested attribute type with optional - * arguments (enumerator over char*, gets destroyed). - */ -static void add_measurement(pen_type_t attr_type, imc_msg_t *msg, - enumerator_t *args) -{ - pa_tnc_attr_t *attr; - enum_name_t *pa_attr_names; - - attr = get_measurement(attr_type, args); - if (attr) - { - msg->add_attribute(msg, attr); - return; - } - pa_attr_names = imcv_pa_tnc_attributes->get_names(imcv_pa_tnc_attributes, - attr_type.vendor_id); - if (pa_attr_names) - { - DBG1(DBG_IMC, "no measurement available for PA-TNC attribute type " - "'%N/%N' 0x%06x/0x%08x", pen_names, attr_type.vendor_id, - pa_attr_names, attr_type.type, attr_type.vendor_id, attr_type.type); - } - else - { - DBG1(DBG_IMC, "no measurement available for PA-TNC attribute type '%N' " - "0x%06x/0x%08x", pen_names, attr_type.vendor_id, - attr_type.vendor_id, attr_type.type); - } -} - -/** - * Handle an IETF attribute - */ -static void handle_ietf_attribute(pen_type_t attr_type, pa_tnc_attr_t *attr, - imc_msg_t *out_msg) -{ - if (attr_type.type == IETF_ATTR_ATTRIBUTE_REQUEST) - { - ietf_attr_attr_request_t *attr_cast; - pen_type_t *entry; - enumerator_t *enumerator; - - attr_cast = (ietf_attr_attr_request_t*)attr; - enumerator = attr_cast->create_enumerator(attr_cast); - while (enumerator->enumerate(enumerator, &entry)) - { - add_measurement(*entry, out_msg, NULL); - } - enumerator->destroy(enumerator); - } - else if (attr_type.type == IETF_ATTR_REMEDIATION_INSTRUCTIONS) - { - ietf_attr_remediation_instr_t *attr_cast; - pen_type_t param; - chunk_t str; - char *instr; - - attr_cast = (ietf_attr_remediation_instr_t*)attr; - param = attr_cast->get_parameters_type(attr_cast); - - if (pen_type_is(param, PEN_IETF, IETF_REMEDIATION_PARAMETERS_STRING)) - { - str = attr_cast->get_string(attr_cast, NULL); - instr = strndup(str.ptr, str.len); - charonservice->add_remediation_instr(charonservice, instr); - free (instr); - } - } -} - -/** - * Handle an ITA attribute - */ -static void handle_ita_attribute(pen_type_t attr_type, pa_tnc_attr_t *attr, - imc_msg_t *out_msg) -{ - if (attr_type.type == ITA_ATTR_GET_SETTINGS) - { - ita_attr_get_settings_t *attr_cast; - - attr_cast = (ita_attr_get_settings_t*)attr; - add_measurement((pen_type_t){ PEN_ITA, ITA_ATTR_SETTINGS }, - out_msg, attr_cast->create_enumerator(attr_cast)); - } -} - -/** - * Handle a TCG attribute - */ -static void handle_tcg_attribute(imc_android_state_t *state, - pen_type_t attr_type, pa_tnc_attr_t *attr, - imc_msg_t *out_msg) -{ - pts_t *pts; - - pts = state->get_pts(state); - switch (attr_type.type) - { - case TCG_PTS_REQ_PROTO_CAPS: - { - tcg_pts_attr_proto_caps_t *attr_cast; - pts_proto_caps_flag_t caps; - - attr_cast = (tcg_pts_attr_proto_caps_t*)attr; - caps = attr_cast->get_flags(attr_cast) & pts->get_proto_caps(pts); - pts->set_proto_caps(pts, caps); - attr = tcg_pts_attr_proto_caps_create(caps, FALSE); - out_msg->add_attribute(out_msg, attr); - break; - } - case TCG_PTS_MEAS_ALGO: - { - tcg_pts_attr_meas_algo_t *attr_cast; - pts_meas_algorithms_t supported, algo; - - if (!pts_meas_algo_probe(&supported)) - { - attr = pts_hash_alg_error_create(PTS_MEAS_ALGO_NONE); - out_msg->add_attribute(out_msg, attr); - break; - } - attr_cast = (tcg_pts_attr_meas_algo_t*)attr; - algo = pts_meas_algo_select(supported, - attr_cast->get_algorithms(attr_cast)); - if (algo == PTS_MEAS_ALGO_NONE) - { - attr = pts_hash_alg_error_create(supported); - out_msg->add_attribute(out_msg, attr); - break; - } - pts->set_meas_algorithm(pts, algo); - attr = tcg_pts_attr_meas_algo_create(algo, TRUE); - out_msg->add_attribute(out_msg, attr); - break; - } - case TCG_PTS_REQ_FILE_MEAS: - { - tcg_pts_attr_req_file_meas_t *attr_cast; - pts_file_meas_t *measurements; - pts_error_code_t pts_error; - u_int32_t delim; - u_int16_t req_id; - bool is_dir; - char *path; - - attr_cast = (tcg_pts_attr_req_file_meas_t*)attr; - path = attr_cast->get_pathname(attr_cast); - if (!pts->is_path_valid(pts, path, &pts_error)) - { /* silently ignore internal errors */ - break; - } - else if (pts_error) - { - attr = ietf_attr_pa_tnc_error_create(pen_type_create(PEN_TCG, - pts_error), attr->get_value(attr)); - out_msg->add_attribute(out_msg, attr); - break; - } - delim = attr_cast->get_delimiter(attr_cast); - if (delim != SOLIDUS_UTF && delim != REVERSE_SOLIDUS_UTF) - { - attr = ietf_attr_pa_tnc_error_create(pen_type_create(PEN_TCG, - TCG_PTS_INVALID_DELIMITER), attr->get_value(attr)); - out_msg->add_attribute(out_msg, attr); - break; - } - req_id = attr_cast->get_request_id(attr_cast); - is_dir = attr_cast->get_directory_flag(attr_cast); - - DBG1(DBG_IMC, "measurement request %d for %s '%s'", req_id, - is_dir ? "directory" : "file", path); - measurements = pts_file_meas_create_from_path(req_id, path, is_dir, - TRUE, pts->get_meas_algorithm(pts)); - if (!measurements) - { - attr = ietf_attr_pa_tnc_error_create(pen_type_create(PEN_TCG, - TCG_PTS_FILE_NOT_FOUND), attr->get_value(attr)); - out_msg->add_attribute(out_msg, attr); - break; - } - attr = tcg_pts_attr_file_meas_create(measurements); - attr->set_noskip_flag(attr, TRUE); - out_msg->add_attribute(out_msg, attr); - break; - } - default: - DBG1(DBG_IMC, "received unsupported TCG attribute '%N'", - tcg_attr_names, attr_type.type); - break; - } -} - -/** - * see section 3.8.3 of TCG TNC IF-IMC Specification 1.3 - */ -static TNC_Result tnc_imc_beginhandshake(TNC_IMCID imc_id, - TNC_ConnectionID connection_id) -{ - imc_state_t *state; - imc_msg_t *out_msg; - TNC_Result result = TNC_RESULT_SUCCESS; - - if (!imc_android) - { - DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name); - return TNC_RESULT_NOT_INITIALIZED; - } - if (!imc_android->get_state(imc_android, connection_id, &state)) - { - return TNC_RESULT_FATAL; - } - if (lib->settings->get_bool(lib->settings, - "android.imc.send_os_info", TRUE)) - { - out_msg = imc_msg_create(imc_android, state, connection_id, imc_id, - TNC_IMVID_ANY, msg_types[0]); - add_measurement((pen_type_t){ PEN_IETF, IETF_ATTR_PRODUCT_INFORMATION }, - out_msg, NULL); - add_measurement((pen_type_t){ PEN_IETF, IETF_ATTR_STRING_VERSION }, - out_msg, NULL); - add_measurement((pen_type_t){ PEN_ITA, ITA_ATTR_DEVICE_ID }, - out_msg, NULL); - /* send PA-TNC message with the excl flag not set */ - result = out_msg->send(out_msg, FALSE); - out_msg->destroy(out_msg); - } - - return result; -} - -static TNC_Result receive_message(imc_android_state_t *state, imc_msg_t *in_msg) -{ - imc_msg_t *out_msg; - enumerator_t *enumerator; - pa_tnc_attr_t *attr; - pen_type_t attr_type; - TNC_Result result; - bool fatal_error = FALSE; - - out_msg = imc_msg_create_as_reply(in_msg); - - /* parse received PA-TNC message and handle local and remote errors */ - result = in_msg->receive(in_msg, out_msg, &fatal_error); - if (result != TNC_RESULT_SUCCESS) - { - out_msg->destroy(out_msg); - return result; - } - - /* analyze PA-TNC attributes */ - enumerator = in_msg->create_attribute_enumerator(in_msg); - while (enumerator->enumerate(enumerator, &attr)) - { - attr_type = attr->get_type(attr); - - switch (attr_type.vendor_id) - { - case PEN_IETF: - handle_ietf_attribute(attr_type, attr, out_msg); - continue; - case PEN_ITA: - handle_ita_attribute(attr_type, attr, out_msg); - continue; - case PEN_TCG: - handle_tcg_attribute(state, attr_type, attr, out_msg); - continue; - default: - continue; - } - } - enumerator->destroy(enumerator); - - if (fatal_error) - { - result = TNC_RESULT_FATAL; - } - else - { - result = out_msg->send(out_msg, TRUE); - } - out_msg->destroy(out_msg); - - return result; -} - -/** - * see section 3.8.4 of TCG TNC IF-IMC Specification 1.3 - - */ -static TNC_Result tnc_imc_receivemessage(TNC_IMCID imc_id, - TNC_ConnectionID connection_id, - TNC_BufferReference msg, - TNC_UInt32 msg_len, - TNC_MessageType msg_type) -{ - imc_state_t *state; - imc_msg_t *in_msg; - TNC_Result result; - - if (!imc_android) - { - DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name); - return TNC_RESULT_NOT_INITIALIZED; - } - if (!imc_android->get_state(imc_android, connection_id, &state)) - { - return TNC_RESULT_FATAL; - } - in_msg = imc_msg_create_from_data(imc_android, state, connection_id, - msg_type, chunk_create(msg, msg_len)); - result = receive_message((imc_android_state_t*)state, in_msg); - in_msg->destroy(in_msg); - - return result; -} - -/** - * see section 3.8.6 of TCG TNC IF-IMV Specification 1.3 - */ -static TNC_Result tnc_imc_receivemessagelong(TNC_IMCID imc_id, - TNC_ConnectionID connection_id, - TNC_UInt32 msg_flags, - TNC_BufferReference msg, - TNC_UInt32 msg_len, - TNC_VendorID msg_vid, - TNC_MessageSubtype msg_subtype, - TNC_UInt32 src_imv_id, - TNC_UInt32 dst_imc_id) -{ - imc_state_t *state; - imc_msg_t *in_msg; - TNC_Result result; - - if (!imc_android) - { - DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name); - return TNC_RESULT_NOT_INITIALIZED; - } - if (!imc_android->get_state(imc_android, connection_id, &state)) - { - return TNC_RESULT_FATAL; - } - in_msg = imc_msg_create_from_long_data(imc_android, state, connection_id, - src_imv_id, dst_imc_id,msg_vid, msg_subtype, - chunk_create(msg, msg_len)); - result = receive_message((imc_android_state_t*)state, in_msg); - in_msg->destroy(in_msg); - - return result; -} - -/** - * see section 3.8.7 of TCG TNC IF-IMC Specification 1.3 - */ -static TNC_Result tnc_imc_batchending(TNC_IMCID imc_id, - TNC_ConnectionID connection_id) -{ - if (!imc_android) - { - DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name); - return TNC_RESULT_NOT_INITIALIZED; - } - return TNC_RESULT_SUCCESS; -} - -/** - * see section 3.8.8 of TCG TNC IF-IMC Specification 1.3 - */ -static TNC_Result tnc_imc_terminate(TNC_IMCID imc_id) -{ - if (!imc_android) - { - DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name); - return TNC_RESULT_NOT_INITIALIZED; - } - imc_android->destroy(imc_android); - imc_android = NULL; - return TNC_RESULT_SUCCESS; -} - -/** - * see section 4.2.8.1 of TCG TNC IF-IMC Specification 1.3 - */ -static TNC_Result tnc_imc_providebindfunction(TNC_IMCID imc_id, - TNC_TNCC_BindFunctionPointer bind_function) -{ - if (!imc_android) - { - DBG1(DBG_IMC, "IMC \"%s\" has not been initialized", imc_name); - return TNC_RESULT_NOT_INITIALIZED; - } - return imc_android->bind_functions(imc_android, bind_function); -} - -/* - * Described in header - */ -bool imc_android_register(plugin_t *plugin, plugin_feature_t *feature, - bool reg, void *data) -{ - JNIEnv *env; - jmethodID method_id; - jobject obj, context = (jobject)data; - jclass cls; - bool success = TRUE; - - androidjni_attach_thread(&env); - if (reg) - { - cls = (*env)->FindClass(env, JNI_PACKAGE_STRING "/imc/AndroidImc"); - if (!cls) - { - goto failed; - } - android_imc_cls = (*env)->NewGlobalRef(env, cls); - method_id = (*env)->GetMethodID(env, cls, "", - "(Landroid/content/Context;)V"); - if (!method_id) - { - goto failed; - } - obj = (*env)->NewObject(env, cls, method_id, context); - if (!obj) - { - goto failed; - } - android_imc = (*env)->NewGlobalRef(env, obj); - androidjni_detach_thread(); - - if (tnc->imcs->load_from_functions(tnc->imcs, "Android", - tnc_imc_initialize, tnc_imc_notifyconnectionchange, - tnc_imc_beginhandshake, tnc_imc_receivemessage, - tnc_imc_receivemessagelong, tnc_imc_batchending, - tnc_imc_terminate, tnc_imc_providebindfunction)) - { - return TRUE; - } -failed: - DBG1(DBG_IMC, "initialization of Android IMC failed"); - androidjni_exception_occurred(env); - success = FALSE; - } - - if (android_imc) - { - (*env)->DeleteGlobalRef(env, android_imc); - android_imc = NULL; - } - if (android_imc_cls) - { - (*env)->DeleteGlobalRef(env, android_imc_cls); - android_imc_cls = NULL; - } - androidjni_detach_thread(); - return success; -} diff --git a/src/frontends/android/jni/libandroidbridge/byod/imc_android.h b/src/frontends/android/jni/libandroidbridge/byod/imc_android.h deleted file mode 100644 index 3bfc6de40..000000000 --- a/src/frontends/android/jni/libandroidbridge/byod/imc_android.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2013 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. - */ - -/** - * @defgroup android_imc android_imc - * @{ @ingroup android_byod - */ - -#ifndef ANDROID_IMC_H_ -#define ANDROID_IMC_H_ - -/** - * Callback for the Android IMC plugin - */ -bool imc_android_register(plugin_t *plugin, plugin_feature_t *feature, - bool reg, void *data); - -#endif /** ANDROID_IMC_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/byod/imc_android_state.c b/src/frontends/android/jni/libandroidbridge/byod/imc_android_state.c deleted file mode 100644 index ea4f92def..000000000 --- a/src/frontends/android/jni/libandroidbridge/byod/imc_android_state.c +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2012 Tobias Brunner - * Copyright (C) 2012 Andreas Steffen - * 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. - */ - -#include "imc_android_state.h" - -#include - -#include - -typedef struct private_imc_android_state_t private_imc_android_state_t; - -/** - * Private data of an imc_state_t object. - */ -struct private_imc_android_state_t { - - /** - * Public interface - */ - imc_android_state_t public; - - /** - * TNCCS connection ID - */ - TNC_ConnectionID connection_id; - - /** - * TNCCS connection state - */ - TNC_ConnectionState state; - - /** - * Assessment/Evaluation Result - */ - TNC_IMV_Evaluation_Result result; - - /** - * Does the TNCCS connection support long message types? - */ - bool has_long; - - /** - * Does the TNCCS connection support exclusive delivery? - */ - bool has_excl; - - /** - * Maximum PA-TNC message size for this TNCCS connection - */ - u_int32_t max_msg_len; - - /** - * PA-TNC attribute segmentation contracts associated with TNCCS connection - */ - seg_contract_manager_t *contracts; - - /** - * TCG Platform Trust Service (PTS) - */ - pts_t *pts; -}; - -METHOD(imc_state_t, get_connection_id, TNC_ConnectionID, - private_imc_android_state_t *this) -{ - return this->connection_id; -} - -METHOD(imc_state_t, has_long, bool, - private_imc_android_state_t *this) -{ - return this->has_long; -} - -METHOD(imc_state_t, has_excl, bool, - private_imc_android_state_t *this) -{ - return this->has_excl; -} - -METHOD(imc_state_t, set_flags, void, - private_imc_android_state_t *this, bool has_long, bool has_excl) -{ - this->has_long = has_long; - this->has_excl = has_excl; -} - -METHOD(imc_state_t, set_max_msg_len, void, - private_imc_android_state_t *this, u_int32_t max_msg_len) -{ - this->max_msg_len = max_msg_len; -} - -METHOD(imc_state_t, get_max_msg_len, u_int32_t, - private_imc_android_state_t *this) -{ - return this->max_msg_len; -} - -METHOD(imc_state_t, get_contracts, seg_contract_manager_t*, - private_imc_android_state_t *this) -{ - return this->contracts; -} - -METHOD(imc_state_t, change_state, void, - private_imc_android_state_t *this, TNC_ConnectionState new_state) -{ - this->state = new_state; -} - -METHOD(imc_state_t, set_result, void, - private_imc_android_state_t *this, TNC_IMCID id, TNC_IMV_Evaluation_Result result) -{ - this->result = result; -} - -METHOD(imc_state_t, get_result, bool, - private_imc_android_state_t *this, TNC_IMCID id, TNC_IMV_Evaluation_Result *result) -{ - if (result) - { - *result = this->result; - } - return this->result != TNC_IMV_EVALUATION_RESULT_DONT_KNOW; -} - -METHOD(imc_state_t, destroy, void, - private_imc_android_state_t *this) -{ - this->contracts->destroy(this->contracts); - this->pts->destroy(this->pts); - free(this); -} - -METHOD(imc_android_state_t, get_pts, pts_t*, - private_imc_android_state_t *this) -{ - return this->pts; -} - -/** - * Described in header. - */ -imc_state_t *imc_android_state_create(TNC_ConnectionID connection_id) -{ - private_imc_android_state_t *this; - - INIT(this, - .public = { - .interface = { - .get_connection_id = _get_connection_id, - .has_long = _has_long, - .has_excl = _has_excl, - .set_flags = _set_flags, - .set_max_msg_len = _set_max_msg_len, - .get_max_msg_len = _get_max_msg_len, - .get_contracts = _get_contracts, - .change_state = _change_state, - .set_result = _set_result, - .get_result = _get_result, - .destroy = _destroy, - }, - .get_pts = _get_pts, - }, - .state = TNC_CONNECTION_STATE_CREATE, - .result = TNC_IMV_EVALUATION_RESULT_DONT_KNOW, - .connection_id = connection_id, - .contracts = seg_contract_manager_create(), - .pts = pts_create(TRUE), - ); - - return &this->public.interface; -} diff --git a/src/frontends/android/jni/libandroidbridge/byod/imc_android_state.h b/src/frontends/android/jni/libandroidbridge/byod/imc_android_state.h deleted file mode 100644 index 68197f331..000000000 --- a/src/frontends/android/jni/libandroidbridge/byod/imc_android_state.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2012 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. - */ - -/** - * @defgroup imc_android_state imc_android_state - * @{ @ingroup android_byod - */ - -#ifndef IMC_ANDROID_STATE_H_ -#define IMC_ANDROID_STATE_H_ - -#include -#include - -typedef struct imc_android_state_t imc_android_state_t; - -/** - * Internal state of an imc_android_t connection instance - */ -struct imc_android_state_t { - - /** - * imc_state_t interface - */ - imc_state_t interface; - - /** - * Get TCG Platform Trust Service (PTS) object - */ - pts_t *(*get_pts)(imc_android_state_t *this); -}; - -/** - * Create an imc_android_state_t instance - * - * @param id connection ID - */ -imc_state_t* imc_android_state_create(TNC_ConnectionID id); - -#endif /** IMC_ANDROID_STATE_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c deleted file mode 100644 index 2655f7361..000000000 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ /dev/null @@ -1,706 +0,0 @@ -/* - * Copyright (C) 2012-2015 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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. - */ - -#include -#include -#include -#include -#include - -#include "charonservice.h" -#include "android_jni.h" -#include "backend/android_attr.h" -#include "backend/android_creds.h" -#include "backend/android_private_key.h" -#include "backend/android_service.h" -#include "kernel/android_ipsec.h" -#include "kernel/android_net.h" - -#ifdef USE_BYOD -#include "byod/imc_android.h" -#endif - -#include -#include -#include -#include -#include - -#define ANDROID_DEBUG_LEVEL 1 -#define ANDROID_RETRASNMIT_TRIES 3 -#define ANDROID_RETRANSMIT_TIMEOUT 2.0 -#define ANDROID_RETRANSMIT_BASE 1.4 - -typedef struct private_charonservice_t private_charonservice_t; - -/** - * private data of charonservice - */ -struct private_charonservice_t { - - /** - * public interface - */ - charonservice_t public; - - /** - * android_attr instance - */ - android_attr_t *attr; - - /** - * android_creds instance - */ - android_creds_t *creds; - - /** - * android_service instance - */ - android_service_t *service; - - /** - * VpnService builder (accessed via JNI) - */ - vpnservice_builder_t *builder; - - /** - * NetworkManager instance (accessed via JNI) - */ - network_manager_t *network_manager; - - /** - * CharonVpnService reference - */ - jobject vpn_service; - - /** - * Sockets that were bypassed and we keep track for - */ - linked_list_t *sockets; -}; - -/** - * Single instance of charonservice_t. - */ -charonservice_t *charonservice; - -/** - * hook in library for debugging messages - */ -extern void (*dbg)(debug_t group, level_t level, char *fmt, ...); - -/** - * Logging hook for library logs, using android specific logging - */ -static void dbg_android(debug_t group, level_t level, char *fmt, ...) -{ - va_list args; - - if (level <= ANDROID_DEBUG_LEVEL) - { - char sgroup[16], buffer[8192]; - char *current = buffer, *next; - - snprintf(sgroup, sizeof(sgroup), "%N", debug_names, group); - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); - while (current) - { /* log each line separately */ - next = strchr(current, '\n'); - if (next) - { - *(next++) = '\0'; - } - __android_log_print(ANDROID_LOG_INFO, "charon", "00[%s] %s\n", - sgroup, current); - current = next; - } - } -} - -METHOD(charonservice_t, update_status, bool, - private_charonservice_t *this, android_vpn_state_t code) -{ - JNIEnv *env; - jmethodID method_id; - bool success = FALSE; - - androidjni_attach_thread(&env); - - method_id = (*env)->GetMethodID(env, android_charonvpnservice_class, - "updateStatus", "(I)V"); - if (!method_id) - { - goto failed; - } - (*env)->CallVoidMethod(env, this->vpn_service, method_id, (jint)code); - success = !androidjni_exception_occurred(env); - -failed: - androidjni_exception_occurred(env); - androidjni_detach_thread(); - return success; -} - -METHOD(charonservice_t, update_imc_state, bool, - private_charonservice_t *this, android_imc_state_t state) -{ - JNIEnv *env; - jmethodID method_id; - bool success = FALSE; - - androidjni_attach_thread(&env); - - method_id = (*env)->GetMethodID(env, android_charonvpnservice_class, - "updateImcState", "(I)V"); - if (!method_id) - { - goto failed; - } - (*env)->CallVoidMethod(env, this->vpn_service, method_id, (jint)state); - success = !androidjni_exception_occurred(env); - -failed: - androidjni_exception_occurred(env); - androidjni_detach_thread(); - return success; -} - -METHOD(charonservice_t, add_remediation_instr, bool, - private_charonservice_t *this, char *instr) -{ - JNIEnv *env; - jmethodID method_id; - jstring jinstr; - bool success = FALSE; - - androidjni_attach_thread(&env); - - method_id = (*env)->GetMethodID(env, android_charonvpnservice_class, - "addRemediationInstruction", - "(Ljava/lang/String;)V"); - if (!method_id) - { - goto failed; - } - jinstr = (*env)->NewStringUTF(env, instr); - if (!jinstr) - { - goto failed; - } - (*env)->CallVoidMethod(env, this->vpn_service, method_id, jinstr); - success = !androidjni_exception_occurred(env); - -failed: - androidjni_exception_occurred(env); - androidjni_detach_thread(); - return success; -} - -/** - * Bypass a single socket - */ -static bool bypass_single_socket(intptr_t fd, private_charonservice_t *this) -{ - JNIEnv *env; - jmethodID method_id; - - androidjni_attach_thread(&env); - - method_id = (*env)->GetMethodID(env, android_charonvpnservice_class, - "protect", "(I)Z"); - if (!method_id) - { - goto failed; - } - if (!(*env)->CallBooleanMethod(env, this->vpn_service, method_id, fd)) - { - DBG2(DBG_KNL, "VpnService.protect() failed"); - goto failed; - } - androidjni_detach_thread(); - return TRUE; - -failed: - androidjni_exception_occurred(env); - androidjni_detach_thread(); - return FALSE; -} - -METHOD(charonservice_t, bypass_socket, bool, - private_charonservice_t *this, int fd, int family) -{ - if (fd >= 0) - { - this->sockets->insert_last(this->sockets, (void*)(intptr_t)fd); - return bypass_single_socket((intptr_t)fd, this); - } - this->sockets->invoke_function(this->sockets, (void*)bypass_single_socket, - this); - return TRUE; -} - -/** - * Converts the given Java array of byte arrays (byte[][]) to a linked list - * of chunk_t objects. - */ -static linked_list_t *convert_array_of_byte_arrays(JNIEnv *env, - jobjectArray jarray) -{ - linked_list_t *list; - jsize i; - - list = linked_list_create(); - for (i = 0; i < (*env)->GetArrayLength(env, jarray); ++i) - { - chunk_t *chunk; - jbyteArray jbytearray; - - chunk = malloc_thing(chunk_t); - list->insert_last(list, chunk); - - jbytearray = (*env)->GetObjectArrayElement(env, jarray, i); - *chunk = chunk_alloc((*env)->GetArrayLength(env, jbytearray)); - (*env)->GetByteArrayRegion(env, jbytearray, 0, chunk->len, chunk->ptr); - (*env)->DeleteLocalRef(env, jbytearray); - } - return list; -} - -METHOD(charonservice_t, get_trusted_certificates, linked_list_t*, - private_charonservice_t *this) -{ - JNIEnv *env; - jmethodID method_id; - jobjectArray jcerts; - linked_list_t *list; - - androidjni_attach_thread(&env); - - method_id = (*env)->GetMethodID(env, - android_charonvpnservice_class, - "getTrustedCertificates", "()[[B"); - if (!method_id) - { - goto failed; - } - jcerts = (*env)->CallObjectMethod(env, this->vpn_service, method_id); - if (!jcerts || androidjni_exception_occurred(env)) - { - goto failed; - } - list = convert_array_of_byte_arrays(env, jcerts); - androidjni_detach_thread(); - return list; - -failed: - androidjni_exception_occurred(env); - androidjni_detach_thread(); - return NULL; -} - -METHOD(charonservice_t, get_user_certificate, linked_list_t*, - private_charonservice_t *this) -{ - JNIEnv *env; - jmethodID method_id; - jobjectArray jencodings; - linked_list_t *list; - - androidjni_attach_thread(&env); - - method_id = (*env)->GetMethodID(env, - android_charonvpnservice_class, - "getUserCertificate", "()[[B"); - if (!method_id) - { - goto failed; - } - jencodings = (*env)->CallObjectMethod(env, this->vpn_service, method_id); - if (!jencodings || androidjni_exception_occurred(env)) - { - goto failed; - } - list = convert_array_of_byte_arrays(env, jencodings); - androidjni_detach_thread(); - return list; - -failed: - androidjni_exception_occurred(env); - androidjni_detach_thread(); - return NULL; -} - -METHOD(charonservice_t, get_user_key, private_key_t*, - private_charonservice_t *this, public_key_t *pubkey) -{ - JNIEnv *env; - jmethodID method_id; - private_key_t *key; - jobject jkey; - - androidjni_attach_thread(&env); - - method_id = (*env)->GetMethodID(env, - android_charonvpnservice_class, - "getUserKey", "()Ljava/security/PrivateKey;"); - if (!method_id) - { - goto failed; - } - jkey = (*env)->CallObjectMethod(env, this->vpn_service, method_id); - if (!jkey || androidjni_exception_occurred(env)) - { - goto failed; - } - key = android_private_key_create(jkey, pubkey); - androidjni_detach_thread(); - return key; - -failed: - DESTROY_IF(pubkey); - androidjni_exception_occurred(env); - androidjni_detach_thread(); - return NULL; -} - -METHOD(charonservice_t, get_vpnservice_builder, vpnservice_builder_t*, - private_charonservice_t *this) -{ - return this->builder; -} - -METHOD(charonservice_t, get_network_manager, network_manager_t*, - private_charonservice_t *this) -{ - return this->network_manager; -} - -/** - * Initiate a new connection - * - * @param settings configuration settings (gets owned) - */ -static void initiate(settings_t *settings) -{ - private_charonservice_t *this = (private_charonservice_t*)charonservice; - - lib->settings->set_str(lib->settings, - "charon.plugins.tnc-imc.preferred_language", - settings->get_str(settings, "global.language", "en")); - /* this is actually the size of the complete IKE/IP packet, so if the MTU - * for the TUN devices has to be reduced to pass traffic the IKE packets - * will be a bit smaller than necessary as there is no IPsec overhead like - * for the tunneled traffic (but compensating that seems like overkill) */ - lib->settings->set_int(lib->settings, - "charon.fragment_size", - settings->get_int(settings, "global.mtu", - ANDROID_DEFAULT_MTU)); - - this->creds->clear(this->creds); - DESTROY_IF(this->service); - this->service = android_service_create(this->creds, settings); -} - -/** - * Initialize/deinitialize Android backend - */ -static bool charonservice_register(plugin_t *plugin, plugin_feature_t *feature, - bool reg, void *data) -{ - private_charonservice_t *this = (private_charonservice_t*)charonservice; - if (reg) - { - lib->credmgr->add_set(lib->credmgr, &this->creds->set); - charon->attributes->add_handler(charon->attributes, - &this->attr->handler); - } - else - { - lib->credmgr->remove_set(lib->credmgr, &this->creds->set); - charon->attributes->remove_handler(charon->attributes, - &this->attr->handler); - if (this->service) - { - this->service->destroy(this->service); - this->service = NULL; - } - } - return TRUE; -} - -/** - * Set strongswan.conf options - */ -static void set_options(char *logfile) -{ - lib->settings->set_int(lib->settings, - "charon.plugins.android_log.loglevel", ANDROID_DEBUG_LEVEL); - /* setup file logger */ - lib->settings->set_str(lib->settings, - "charon.filelog.%s.time_format", "%b %e %T", logfile); - lib->settings->set_bool(lib->settings, - "charon.filelog.%s.append", FALSE, logfile); - lib->settings->set_bool(lib->settings, - "charon.filelog.%s.flush_line", TRUE, logfile); - lib->settings->set_int(lib->settings, - "charon.filelog.%s.default", ANDROID_DEBUG_LEVEL, logfile); - - lib->settings->set_int(lib->settings, - "charon.retransmit_tries", ANDROID_RETRASNMIT_TRIES); - lib->settings->set_double(lib->settings, - "charon.retransmit_timeout", ANDROID_RETRANSMIT_TIMEOUT); - lib->settings->set_double(lib->settings, - "charon.retransmit_base", ANDROID_RETRANSMIT_BASE); - lib->settings->set_bool(lib->settings, - "charon.initiator_only", TRUE); - lib->settings->set_bool(lib->settings, - "charon.close_ike_on_child_failure", TRUE); - /* setting the source address breaks the VpnService.protect() function which - * uses SO_BINDTODEVICE internally. the addresses provided to the kernel as - * auxiliary data have precedence over this option causing a routing loop if - * the gateway is contained in the VPN routes. alternatively, providing an - * explicit device (in addition or instead of the source address) in the - * auxiliary data would also work, but we currently don't have that - * information */ - lib->settings->set_bool(lib->settings, - "charon.plugins.socket-default.set_source", FALSE); - /* the Linux kernel does currently not support UDP encaspulation for IPv6 - * so lets disable IPv6 for now to avoid issues with dual-stack gateways */ - lib->settings->set_bool(lib->settings, - "charon.plugins.socket-default.use_ipv6", FALSE); - -#ifdef USE_BYOD - lib->settings->set_str(lib->settings, - "charon.plugins.eap-tnc.protocol", "tnccs-2.0"); - lib->settings->set_int(lib->settings, - "charon.plugins.eap-ttls.max_message_count", 0); - lib->settings->set_bool(lib->settings, - "android.imc.send_os_info", TRUE); - lib->settings->set_str(lib->settings, - "libtnccs.tnc_config", ""); -#endif -} - -/** - * Initialize the charonservice object - */ -static void charonservice_init(JNIEnv *env, jobject service, jobject builder, - jboolean byod) -{ - private_charonservice_t *this; - static plugin_feature_t features[] = { - PLUGIN_CALLBACK(kernel_ipsec_register, kernel_android_ipsec_create), - PLUGIN_PROVIDE(CUSTOM, "kernel-ipsec"), - PLUGIN_CALLBACK(kernel_net_register, kernel_android_net_create), - PLUGIN_PROVIDE(CUSTOM, "kernel-net"), - PLUGIN_CALLBACK(charonservice_register, NULL), - PLUGIN_PROVIDE(CUSTOM, "android-backend"), - PLUGIN_DEPENDS(CUSTOM, "libcharon"), - }; - - INIT(this, - .public = { - .update_status = _update_status, - .update_imc_state = _update_imc_state, - .add_remediation_instr = _add_remediation_instr, - .bypass_socket = _bypass_socket, - .get_trusted_certificates = _get_trusted_certificates, - .get_user_certificate = _get_user_certificate, - .get_user_key = _get_user_key, - .get_vpnservice_builder = _get_vpnservice_builder, - .get_network_manager = _get_network_manager, - }, - .attr = android_attr_create(), - .creds = android_creds_create(), - .builder = vpnservice_builder_create(builder), - .network_manager = network_manager_create(service), - .sockets = linked_list_create(), - .vpn_service = (*env)->NewGlobalRef(env, service), - ); - charonservice = &this->public; - - lib->plugins->add_static_features(lib->plugins, "androidbridge", features, - countof(features), TRUE, NULL, NULL); - -#ifdef USE_BYOD - if (byod) - { - plugin_feature_t byod_features[] = { - PLUGIN_CALLBACK(imc_android_register, this->vpn_service), - PLUGIN_PROVIDE(CUSTOM, "android-imc"), - PLUGIN_DEPENDS(CUSTOM, "android-backend"), - PLUGIN_DEPENDS(CUSTOM, "imc-manager"), - }; - - lib->plugins->add_static_features(lib->plugins, "android-byod", - byod_features, countof(byod_features), TRUE, NULL, NULL); - } -#endif -} - -/** - * Deinitialize the charonservice object - */ -static void charonservice_deinit(JNIEnv *env) -{ - private_charonservice_t *this = (private_charonservice_t*)charonservice; - - this->network_manager->destroy(this->network_manager); - this->sockets->destroy(this->sockets); - this->builder->destroy(this->builder); - this->creds->destroy(this->creds); - this->attr->destroy(this->attr); - (*env)->DeleteGlobalRef(env, this->vpn_service); - free(this); - charonservice = NULL; -} - -/** - * Handle SIGSEGV/SIGILL signals raised by threads - */ -static void segv_handler(int signal) -{ - dbg_android(DBG_DMN, 1, "thread %u received %d", thread_current_id(), - signal); - exit(1); -} - -/** - * Initialize charon and the libraries via JNI - */ -JNI_METHOD(CharonVpnService, initializeCharon, jboolean, - jobject builder, jstring jlogfile, jboolean byod) -{ - struct sigaction action; - struct utsname utsname; - char *logfile, *plugins; - - /* logging for library during initialization, as we have no bus yet */ - dbg = dbg_android; - - /* initialize library */ - if (!library_init(NULL, "charon")) - { - library_deinit(); - return FALSE; - } - - /* set options before initializing other libraries that might read them */ - logfile = androidjni_convert_jstring(env, jlogfile); - set_options(logfile); - free(logfile); - - if (!libhydra_init()) - { - libhydra_deinit(); - library_deinit(); - return FALSE; - } - - if (!libipsec_init()) - { - libipsec_deinit(); - libhydra_deinit(); - library_deinit(); - return FALSE; - } - - if (!libcharon_init()) - { - libcharon_deinit(); - libipsec_deinit(); - libhydra_deinit(); - library_deinit(); - return FALSE; - } - - charon->load_loggers(charon, NULL, FALSE); - - charonservice_init(env, this, builder, byod); - - if (uname(&utsname) != 0) - { - memset(&utsname, 0, sizeof(utsname)); - } - DBG1(DBG_DMN, "Starting IKE charon daemon (strongSwan "VERSION", %s %s, %s)", - utsname.sysname, utsname.release, utsname.machine); - -#ifdef PLUGINS_BYOD - if (byod) - { - plugins = PLUGINS " " PLUGINS_BYOD; - } - else -#endif - { - plugins = PLUGINS; - } - - if (!charon->initialize(charon, plugins)) - { - libcharon_deinit(); - charonservice_deinit(env); - libipsec_deinit(); - libhydra_deinit(); - library_deinit(); - return FALSE; - } - lib->plugins->status(lib->plugins, LEVEL_CTRL); - - /* add handler for SEGV and ILL etc. */ - action.sa_handler = segv_handler; - action.sa_flags = 0; - sigemptyset(&action.sa_mask); - sigaction(SIGSEGV, &action, NULL); - sigaction(SIGILL, &action, NULL); - sigaction(SIGBUS, &action, NULL); - action.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &action, NULL); - - /* start daemon (i.e. the threads in the thread-pool) */ - charon->start(charon); - return TRUE; -} - -/** - * Deinitialize charon and all libraries - */ -JNI_METHOD(CharonVpnService, deinitializeCharon, void) -{ - /* deinitialize charon before we destroy our own objects */ - libcharon_deinit(); - charonservice_deinit(env); - libipsec_deinit(); - libhydra_deinit(); - library_deinit(); -} - -/** - * Initiate SA - */ -JNI_METHOD(CharonVpnService, initiate, void, - jstring jconfig) -{ - settings_t *settings; - char *config; - - config = androidjni_convert_jstring(env, jconfig); - settings = settings_create_string(config); - initiate(settings); -} diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.h b/src/frontends/android/jni/libandroidbridge/charonservice.h deleted file mode 100644 index 8cb68e099..000000000 --- a/src/frontends/android/jni/libandroidbridge/charonservice.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2012-2013 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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. - */ - -/** - * @defgroup libandroidbridge libandroidbridge - * - * @defgroup android_backend backend - * @ingroup libandroidbridge - * - * @defgroup android_byod byod - * @ingroup libandroidbridge - * - * @defgroup android_kernel kernel - * @ingroup libandroidbridge - * - * @defgroup charonservice charonservice - * @{ @ingroup libandroidbridge - */ - -#ifndef CHARONSERVICE_H_ -#define CHARONSERVICE_H_ - -#include "vpnservice_builder.h" -#include "kernel/network_manager.h" - -#include -#include - -typedef enum android_vpn_state_t android_vpn_state_t; -typedef enum android_imc_state_t android_imc_state_t; -typedef struct charonservice_t charonservice_t; - -/** - * Default value for the MTU of TUN device and the size of IKE fragments - */ -#define ANDROID_DEFAULT_MTU 1400 - -/** - * VPN status codes. As defined in CharonVpnService.java - */ -enum android_vpn_state_t { - CHARONSERVICE_CHILD_STATE_UP = 1, - CHARONSERVICE_CHILD_STATE_DOWN, - CHARONSERVICE_AUTH_ERROR, - CHARONSERVICE_PEER_AUTH_ERROR, - CHARONSERVICE_LOOKUP_ERROR, - CHARONSERVICE_UNREACHABLE_ERROR, - CHARONSERVICE_GENERIC_ERROR, -}; - -/** - * Final IMC state as defined in ImcState.java - */ -enum android_imc_state_t { - ANDROID_IMC_STATE_UNKNOWN = 0, - ANDROID_IMC_STATE_ALLOW = 1, - ANDROID_IMC_STATE_BLOCK = 2, - ANDROID_IMC_STATE_ISOLATE = 3, -}; - -/** - * Public interface of charonservice. - * - * Used to communicate with CharonVpnService via JNI - */ -struct charonservice_t { - - /** - * Update the status in the Java domain (UI) - * - * @param code status code - * @return TRUE on success - */ - bool (*update_status)(charonservice_t *this, android_vpn_state_t code); - - /** - * Update final IMC state in the Java domain (UI) - * - * @param state IMC state - * @return TRUE on success - */ - bool (*update_imc_state)(charonservice_t *this, android_imc_state_t state); - - /** - * Add a remediation instruction via JNI - * - * @param instr remediation instruction - * @return TRUE on success - */ - bool (*add_remediation_instr)(charonservice_t *this, char *instr); - - /** - * Install a bypass policy for the given socket using the protect() Method - * of the Android VpnService interface. - * - * Use -1 as fd to re-bypass previously bypassed sockets. - * - * @param fd socket file descriptor - * @param family socket protocol family - * @return TRUE if operation successful - */ - bool (*bypass_socket)(charonservice_t *this, int fd, int family); - - /** - * Get a list of trusted certificates via JNI - * - * @return list of DER encoded certificates (as chunk_t*), - * NULL on failure - */ - linked_list_t *(*get_trusted_certificates)(charonservice_t *this); - - /** - * Get the configured user certificate chain via JNI - * - * The first item in the returned list is the user certificate followed - * by any remaining elements of the certificate chain. - * - * @return list of DER encoded certificates (as chunk_t*), - * NULL on failure - */ - linked_list_t *(*get_user_certificate)(charonservice_t *this); - - /** - * Get the configured private key via JNI - * - * @param pubkey the public key as extracted from the certificate - * @return PrivateKey object, NULL on failure - */ - private_key_t *(*get_user_key)(charonservice_t *this, public_key_t *pubkey); - - /** - * Get the current vpnservice_builder_t object - * - * @return VpnService.Builder instance - */ - vpnservice_builder_t *(*get_vpnservice_builder)(charonservice_t *this); - - /** - * Get the current network_manager_t object - * - * @return NetworkManager instance - */ - network_manager_t *(*get_network_manager)(charonservice_t *this); -}; - -/** - * The single instance of charonservice_t. - * - * Set between JNI calls to initializeCharon() and deinitializeCharon(). - */ -extern charonservice_t *charonservice; - -#endif /** CHARONSERVICE_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c deleted file mode 100644 index 2eef49f91..000000000 --- a/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (C) 2012 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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. - */ - -#include "android_ipsec.h" -#include "../charonservice.h" - -#include -#include -#include -#include - -typedef struct private_kernel_android_ipsec_t private_kernel_android_ipsec_t; - -struct private_kernel_android_ipsec_t { - - /** - * Public kernel interface - */ - kernel_android_ipsec_t public; - - /** - * Listener for lifetime expire events - */ - ipsec_event_listener_t ipsec_listener; -}; - -/** - * Callback registrered with libipsec. - */ -static void expire(u_int8_t protocol, u_int32_t spi, host_t *dst, bool hard) -{ - hydra->kernel_interface->expire(hydra->kernel_interface, protocol, - spi, dst, hard); -} - -METHOD(kernel_ipsec_t, get_spi, status_t, - private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, - u_int8_t protocol, u_int32_t *spi) -{ - return ipsec->sas->get_spi(ipsec->sas, src, dst, protocol, spi); -} - -METHOD(kernel_ipsec_t, get_cpi, status_t, - private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, - u_int16_t *cpi) -{ - return NOT_SUPPORTED; -} - -METHOD(kernel_ipsec_t, add_sa, status_t, - private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, - u_int32_t spi, u_int8_t protocol, u_int32_t reqid, mark_t mark, - u_int32_t tfc, lifetime_cfg_t *lifetime, u_int16_t enc_alg, chunk_t enc_key, - u_int16_t int_alg, chunk_t int_key, ipsec_mode_t mode, - u_int16_t ipcomp, u_int16_t cpi, u_int32_t replay_window, - bool initiator, bool encap, bool esn, bool inbound, bool update, - linked_list_t *src_ts, linked_list_t *dst_ts) -{ - return ipsec->sas->add_sa(ipsec->sas, src, dst, spi, protocol, reqid, mark, - tfc, lifetime, enc_alg, enc_key, int_alg, int_key, - mode, ipcomp, cpi, initiator, encap, esn, - inbound, update); -} - -METHOD(kernel_ipsec_t, update_sa, status_t, - private_kernel_android_ipsec_t *this, u_int32_t spi, u_int8_t protocol, - u_int16_t cpi, host_t *src, host_t *dst, host_t *new_src, host_t *new_dst, - bool encap, bool new_encap, mark_t mark) -{ - return ipsec->sas->update_sa(ipsec->sas, spi, protocol, cpi, src, dst, - new_src, new_dst, encap, new_encap, mark); -} - -METHOD(kernel_ipsec_t, query_sa, status_t, - private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, - u_int32_t spi, u_int8_t protocol, mark_t mark, - u_int64_t *bytes, u_int64_t *packets, time_t *time) -{ - return ipsec->sas->query_sa(ipsec->sas, src, dst, spi, protocol, mark, - bytes, packets, time); -} - -METHOD(kernel_ipsec_t, del_sa, status_t, - private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, - u_int32_t spi, u_int8_t protocol, u_int16_t cpi, mark_t mark) -{ - return ipsec->sas->del_sa(ipsec->sas, src, dst, spi, protocol, cpi, mark); -} - -METHOD(kernel_ipsec_t, flush_sas, status_t, - private_kernel_android_ipsec_t *this) -{ - return ipsec->sas->flush_sas(ipsec->sas); -} - -METHOD(kernel_ipsec_t, add_policy, status_t, - private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, - traffic_selector_t *src_ts, traffic_selector_t *dst_ts, - policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, mark_t mark, - policy_priority_t priority) -{ - return ipsec->policies->add_policy(ipsec->policies, src, dst, src_ts, - dst_ts, direction, type, sa, mark, - priority); -} - -METHOD(kernel_ipsec_t, query_policy, status_t, - private_kernel_android_ipsec_t *this, traffic_selector_t *src_ts, - traffic_selector_t *dst_ts, policy_dir_t direction, mark_t mark, - time_t *use_time) -{ - return NOT_SUPPORTED; -} - -METHOD(kernel_ipsec_t, del_policy, status_t, - private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, - traffic_selector_t *src_ts, traffic_selector_t *dst_ts, - policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, - mark_t mark, policy_priority_t priority) -{ - return ipsec->policies->del_policy(ipsec->policies, src_ts, dst_ts, - direction, sa->reqid, mark, priority); -} - -METHOD(kernel_ipsec_t, flush_policies, status_t, - private_kernel_android_ipsec_t *this) -{ - ipsec->policies->flush_policies(ipsec->policies); - return SUCCESS; -} - -METHOD(kernel_ipsec_t, bypass_socket, bool, - private_kernel_android_ipsec_t *this, int fd, int family) -{ - return charonservice->bypass_socket(charonservice, fd, family); -} - -METHOD(kernel_ipsec_t, enable_udp_decap, bool, - private_kernel_android_ipsec_t *this, int fd, int family, u_int16_t port) -{ - return NOT_SUPPORTED; -} - -METHOD(kernel_ipsec_t, destroy, void, - private_kernel_android_ipsec_t *this) -{ - ipsec->events->unregister_listener(ipsec->events, &this->ipsec_listener); - free(this); -} - -/* - * Described in header. - */ -kernel_android_ipsec_t *kernel_android_ipsec_create() -{ - private_kernel_android_ipsec_t *this; - - INIT(this, - .public = { - .interface = { - .get_spi = _get_spi, - .get_cpi = _get_cpi, - .add_sa = _add_sa, - .update_sa = _update_sa, - .query_sa = _query_sa, - .del_sa = _del_sa, - .flush_sas = _flush_sas, - .add_policy = _add_policy, - .query_policy = _query_policy, - .del_policy = _del_policy, - .flush_policies = _flush_policies, - .bypass_socket = _bypass_socket, - .enable_udp_decap = _enable_udp_decap, - .destroy = _destroy, - }, - }, - .ipsec_listener = { - .expire = expire, - }, - ); - - ipsec->events->register_listener(ipsec->events, &this->ipsec_listener); - - return &this->public; -} diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.h b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.h deleted file mode 100644 index b68c8b2a9..000000000 --- a/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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. - */ - -/** - * @defgroup kernel_android_ipsec kernel_android_ipsec - * @{ @ingroup android_kernel - */ - -#ifndef KERNEL_ANDROID_IPSEC_H_ -#define KERNEL_ANDROID_IPSEC_H_ - -#include -#include - -typedef struct kernel_android_ipsec_t kernel_android_ipsec_t; - -/** - * Implementation of the ipsec interface using libipsec on Android - */ -struct kernel_android_ipsec_t { - - /** - * Implements kernel_ipsec_t interface - */ - kernel_ipsec_t interface; -}; - -/** - * Create a android ipsec interface instance. - * - * @return kernel_android_ipsec_t instance - */ -kernel_android_ipsec_t *kernel_android_ipsec_create(); - -#endif /** KERNEL_ANDROID_IPSEC_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_net.c b/src/frontends/android/jni/libandroidbridge/kernel/android_net.c deleted file mode 100644 index 2ce1bdfac..000000000 --- a/src/frontends/android/jni/libandroidbridge/kernel/android_net.c +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2012-2015 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. - */ -#include -#include -#include -#include - -#include "android_net.h" - -#include "../android_jni.h" -#include "../charonservice.h" -#include -#include -#include - -/** delay before firing roam events (ms) */ -#define ROAM_DELAY 100 -#define ROAM_DELAY_RECHECK 1000 - -typedef struct private_android_net_t private_android_net_t; - -struct private_android_net_t { - - /** - * Public kernel interface - */ - kernel_net_t public; - - /** - * Reference to NetworkManager object - */ - network_manager_t *network_manager; - - /** - * Earliest time of the next roam event - */ - timeval_t next_roam; - - /** - * Mutex to check and update roam event time, and other private members - */ - mutex_t *mutex; - - /** - * List of virtual IPs - */ - linked_list_t *vips; - - /** - * Socket used to determine source address - */ - int socket_v4; - - /** - * Whether the device is currently connected - */ - bool connected; -}; - -/** - * callback function that raises the delayed roam event - */ -static job_requeue_t roam_event() -{ - /* this will fail if no connection is up */ - charonservice->bypass_socket(charonservice, -1, 0); - hydra->kernel_interface->roam(hydra->kernel_interface, TRUE); - return JOB_REQUEUE_NONE; -} - -/** - * Listen for connectivity change events and queue a roam event - */ -static void connectivity_cb(private_android_net_t *this, - bool disconnected) -{ - timeval_t now; - job_t *job; - - time_monotonic(&now); - this->mutex->lock(this->mutex); - this->connected = !disconnected; - if (!timercmp(&now, &this->next_roam, >)) - { - this->mutex->unlock(this->mutex); - return; - } - timeval_add_ms(&now, ROAM_DELAY); - this->next_roam = now; - this->mutex->unlock(this->mutex); - - job = (job_t*)callback_job_create((callback_job_cb_t)roam_event, NULL, - NULL, NULL); - lib->scheduler->schedule_job_ms(lib->scheduler, job, ROAM_DELAY); -} - -METHOD(kernel_net_t, get_source_addr, host_t*, - private_android_net_t *this, host_t *dest, host_t *src) -{ - union { - struct sockaddr sockaddr; - struct sockaddr_in sin; - struct sockaddr_in6 sin6; - } addr; - socklen_t addrlen; - timeval_t now; - job_t *job; - - addrlen = *dest->get_sockaddr_len(dest); - addr.sockaddr.sa_family = AF_UNSPEC; - if (connect(this->socket_v4, &addr.sockaddr, addrlen) < 0) - { - DBG1(DBG_KNL, "failed to disconnect socket: %s", strerror(errno)); - return NULL; - } - if (android_sdk_version <= ANDROID_JELLY_BEAN_MR2) - { /* this seems to help avoiding the VIP, unless there is no connectivity - * at all */ - charonservice->bypass_socket(charonservice, -1, 0); - } - if (connect(this->socket_v4, dest->get_sockaddr(dest), addrlen) < 0) - { - /* don't report an error if we are not connected (ENETUNREACH) */ - if (errno != ENETUNREACH) - { - DBG1(DBG_KNL, "failed to connect socket: %s", strerror(errno)); - } - else - { - time_monotonic(&now); - this->mutex->lock(this->mutex); - if (this->connected && timercmp(&now, &this->next_roam, >)) - { /* we were not able to find a source address but reportedly are - * connected, trigger a recheck in case an IP address appears - * delayed but the callback is not triggered again */ - timeval_add_ms(&now, ROAM_DELAY_RECHECK); - this->next_roam = now; - this->mutex->unlock(this->mutex); - job = (job_t*)callback_job_create((callback_job_cb_t)roam_event, - NULL, NULL, NULL); - lib->scheduler->schedule_job_ms(lib->scheduler, job, - ROAM_DELAY_RECHECK); - } - else - { - this->mutex->unlock(this->mutex); - } - } - return NULL; - } - if (getsockname(this->socket_v4, &addr.sockaddr, &addrlen) < 0) - { - DBG1(DBG_KNL, "failed to determine src address: %s", strerror(errno)); - return NULL; - } - return host_create_from_sockaddr((sockaddr_t*)&addr); -} - -METHOD(kernel_net_t, get_source_addr_old, host_t*, - private_android_net_t *this, host_t *dest, host_t *src) -{ - host_t *host; - - /* on older Android versions we might get the virtual IP back because - * the protect() implementation there and connect() don't properly work - * together, on newer releases (using fwmarks) that's not a problem */ - host = get_source_addr(this, dest, src); - if (host) - { - this->mutex->lock(this->mutex); - if (this->vips->find_first(this->vips, (void*)host->ip_equals, - NULL, host) == SUCCESS) - { - host->destroy(host); - host = NULL; - } - this->mutex->unlock(this->mutex); - } - return host; -} - -METHOD(kernel_net_t, get_nexthop, host_t*, - private_android_net_t *this, host_t *dest, int prefix, host_t *src) -{ - return NULL; -} - -METHOD(kernel_net_t, get_interface, bool, - private_android_net_t *this, host_t *host, char **name) -{ - if (name) - { /* the actual name does not matter in our case */ - *name = strdup("strongswan"); - } - return TRUE; -} - -METHOD(kernel_net_t, create_address_enumerator, enumerator_t*, - private_android_net_t *this, kernel_address_type_t which) -{ - /* return virtual IPs if requested, nothing otherwise */ - if (which & ADDR_TYPE_VIRTUAL) - { - this->mutex->lock(this->mutex); - return enumerator_create_cleaner( - this->vips->create_enumerator(this->vips), - (void*)this->mutex->unlock, this->mutex); - } - return enumerator_create_empty(); -} - -METHOD(kernel_net_t, add_ip, status_t, - private_android_net_t *this, host_t *virtual_ip, int prefix, char *iface) -{ - this->mutex->lock(this->mutex); - this->vips->insert_last(this->vips, virtual_ip->clone(virtual_ip)); - this->mutex->unlock(this->mutex); - return SUCCESS; -} - -METHOD(kernel_net_t, del_ip, status_t, - private_android_net_t *this, host_t *virtual_ip, int prefix, bool wait) -{ - host_t *vip; - - this->mutex->lock(this->mutex); - if (this->vips->find_first(this->vips, (void*)virtual_ip->ip_equals, - (void**)&vip, virtual_ip) == SUCCESS) - { - this->vips->remove(this->vips, vip, NULL); - vip->destroy(vip); - } - this->mutex->unlock(this->mutex); - return SUCCESS; -} - -METHOD(kernel_net_t, add_route, status_t, - private_android_net_t *this, chunk_t dst_net, u_int8_t prefixlen, - host_t *gateway, host_t *src_ip, char *if_name) -{ - return NOT_SUPPORTED; -} - -METHOD(kernel_net_t, del_route, status_t, - private_android_net_t *this, chunk_t dst_net, u_int8_t prefixlen, - host_t *gateway, host_t *src_ip, char *if_name) -{ - return NOT_SUPPORTED; -} - -METHOD(kernel_net_t, destroy, void, - private_android_net_t *this) -{ - this->network_manager->remove_connectivity_cb(this->network_manager, - (void*)connectivity_cb); - this->mutex->destroy(this->mutex); - this->vips->destroy(this->vips); - close(this->socket_v4); - free(this); -} - -kernel_net_t *kernel_android_net_create() -{ - private_android_net_t *this; - - INIT(this, - .public = { - .get_source_addr = _get_source_addr, - .get_nexthop = _get_nexthop, - .get_interface = _get_interface, - .create_address_enumerator = _create_address_enumerator, - .add_ip = _add_ip, - .del_ip = _del_ip, - .add_route = _add_route, - .del_route = _del_route, - .destroy = _destroy, - }, - .mutex = mutex_create(MUTEX_TYPE_DEFAULT), - .vips = linked_list_create(), - .network_manager = charonservice->get_network_manager(charonservice), - ); - timerclear(&this->next_roam); - - if (android_sdk_version <= ANDROID_JELLY_BEAN_MR2) - { - this->public.get_source_addr = _get_source_addr_old; - } - - this->socket_v4 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (this->socket_v4 < 0) - { - DBG1(DBG_KNL, "failed to create socket to lookup src addresses: %s", - strerror(errno)); - } - charonservice->bypass_socket(charonservice, this->socket_v4, AF_INET); - - this->mutex->lock(this->mutex); - this->network_manager->add_connectivity_cb( - this->network_manager, (void*)connectivity_cb, this); - this->connected = this->network_manager->is_connected(this->network_manager); - this->mutex->unlock(this->mutex); - return &this->public; -} diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_net.h b/src/frontends/android/jni/libandroidbridge/kernel/android_net.h deleted file mode 100644 index 761fa21bc..000000000 --- a/src/frontends/android/jni/libandroidbridge/kernel/android_net.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2012-2015 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. - */ - -/** - * @defgroup android_net android_net - * @{ @ingroup android_kernel - */ - -#ifndef ANDROID_NET_H_ -#define ANDROID_NET_H_ - -#include -#include - -/** - * Create an Android-specific kernel_net_t instance. - * - * @return kernel_net_t instance - */ -kernel_net_t *kernel_android_net_create(); - - -#endif /** ANDROID_NET_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/kernel/network_manager.c b/src/frontends/android/jni/libandroidbridge/kernel/network_manager.c deleted file mode 100644 index 372b25c55..000000000 --- a/src/frontends/android/jni/libandroidbridge/kernel/network_manager.c +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2012-2015 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. - */ - -#include "network_manager.h" - -#include "../android_jni.h" -#include "../charonservice.h" -#include -#include - -typedef struct private_network_manager_t private_network_manager_t; - -struct private_network_manager_t { - - /** - * Public interface - */ - network_manager_t public; - - /** - * Reference to NetworkManager object - */ - jobject obj; - - /** - * Java class for NetworkManager - */ - jclass cls; - - /** - * Registered callback - */ - struct { - connectivity_cb_t cb; - void *data; - } connectivity_cb; - - /** - * Mutex to access callback - */ - mutex_t *mutex; -}; - -JNI_METHOD(NetworkManager, networkChanged, void, - bool disconnected) -{ - private_network_manager_t *nm; - - nm = (private_network_manager_t*)charonservice->get_network_manager( - charonservice); - nm->mutex->lock(nm->mutex); - if (nm->connectivity_cb.cb) - { - nm->connectivity_cb.cb(nm->connectivity_cb.data, disconnected); - } - nm->mutex->unlock(nm->mutex); -} - -METHOD(network_manager_t, add_connectivity_cb, void, - private_network_manager_t *this, connectivity_cb_t cb, void *data) -{ - this->mutex->lock(this->mutex); - if (!this->connectivity_cb.cb) - { - JNIEnv *env; - jmethodID method_id; - - androidjni_attach_thread(&env); - method_id = (*env)->GetMethodID(env, this->cls, "Register", "()V"); - if (!method_id) - { - androidjni_exception_occurred(env); - } - else - { - (*env)->CallVoidMethod(env, this->obj, method_id); - if (!androidjni_exception_occurred(env)) - { - this->connectivity_cb.cb = cb; - this->connectivity_cb.data = data; - } - } - androidjni_detach_thread(); - } - this->mutex->unlock(this->mutex); -} - -/** - * Unregister the NetworkManager via JNI. - * - * this->mutex has to be locked - */ -static void unregister_network_manager(private_network_manager_t *this) -{ - JNIEnv *env; - jmethodID method_id; - - androidjni_attach_thread(&env); - method_id = (*env)->GetMethodID(env, this->cls, "Unregister", "()V"); - if (!method_id) - { - androidjni_exception_occurred(env); - } - else - { - (*env)->CallVoidMethod(env, this->obj, method_id); - androidjni_exception_occurred(env); - } - androidjni_detach_thread(); -} - -METHOD(network_manager_t, remove_connectivity_cb, void, - private_network_manager_t *this, connectivity_cb_t cb) -{ - this->mutex->lock(this->mutex); - if (this->connectivity_cb.cb == cb) - { - this->connectivity_cb.cb = NULL; - unregister_network_manager(this); - } - this->mutex->unlock(this->mutex); -} - -METHOD(network_manager_t, is_connected, bool, - private_network_manager_t *this) -{ - JNIEnv *env; - jmethodID method_id; - bool connected = FALSE; - - androidjni_attach_thread(&env); - method_id = (*env)->GetMethodID(env, this->cls, "isConnected", "()Z"); - if (!method_id) - { - androidjni_exception_occurred(env); - } - else - { - connected = (*env)->CallBooleanMethod(env, this->obj, method_id); - connected = !androidjni_exception_occurred(env) && connected; - } - androidjni_detach_thread(); - return connected; -} - -METHOD(network_manager_t, destroy, void, - private_network_manager_t *this) -{ - JNIEnv *env; - - this->mutex->lock(this->mutex); - if (this->connectivity_cb.cb) - { - this->connectivity_cb.cb = NULL; - unregister_network_manager(this); - } - this->mutex->unlock(this->mutex); - - androidjni_attach_thread(&env); - if (this->obj) - { - (*env)->DeleteGlobalRef(env, this->obj); - } - if (this->cls) - { - (*env)->DeleteGlobalRef(env, this->cls); - } - androidjni_detach_thread(); - this->mutex->destroy(this->mutex); - free(this); -} - -/* - * Described in header. - */ -network_manager_t *network_manager_create(jobject context) -{ - private_network_manager_t *this; - JNIEnv *env; - jmethodID method_id; - jobject obj; - jclass cls; - - INIT(this, - .public = { - .add_connectivity_cb = _add_connectivity_cb, - .remove_connectivity_cb = _remove_connectivity_cb, - .is_connected = _is_connected, - .destroy = _destroy, - }, - .mutex = mutex_create(MUTEX_TYPE_DEFAULT), - ); - - androidjni_attach_thread(&env); - cls = (*env)->FindClass(env, JNI_PACKAGE_STRING "/NetworkManager"); - if (!cls) - { - goto failed; - } - this->cls = (*env)->NewGlobalRef(env, cls); - method_id = (*env)->GetMethodID(env, cls, "", - "(Landroid/content/Context;)V"); - if (!method_id) - { - goto failed; - } - obj = (*env)->NewObject(env, cls, method_id, context); - if (!obj) - { - goto failed; - } - this->obj = (*env)->NewGlobalRef(env, obj); - androidjni_detach_thread(); - return &this->public; - -failed: - DBG1(DBG_KNL, "failed to build NetworkManager object"); - androidjni_exception_occurred(env); - androidjni_detach_thread(); - destroy(this); - return NULL; -}; diff --git a/src/frontends/android/jni/libandroidbridge/kernel/network_manager.h b/src/frontends/android/jni/libandroidbridge/kernel/network_manager.h deleted file mode 100644 index 9a6a715b7..000000000 --- a/src/frontends/android/jni/libandroidbridge/kernel/network_manager.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2012-2015 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. - */ - -/** - * @defgroup network_manager network_manager - * @{ @ingroup android_kernel - */ - -#ifndef NETWORK_MANAGER_H_ -#define NETWORK_MANAGER_H_ - -#include - -#include -#include - -typedef struct network_manager_t network_manager_t; - -/** - * Callback called if connectivity changes somehow. - * - * Implementation should be quick as the call is made by the Java apps main - * thread. - * - * @param data data supplied during registration - * @param disconnected TRUE if currently disconnected - */ -typedef void (*connectivity_cb_t)(void *data, bool disconnected); - -/** - * NetworkManager, used to listen for network changes. - * - * Communicates with NetworkManager via JNI - */ -struct network_manager_t { - - /** - * Register a callback that is called if connectivity changes - * - * @note Only the first registered callback is currently used - * - * @param cb callback to register - * @param data data provided to callback - */ - void (*add_connectivity_cb)(network_manager_t *this, connectivity_cb_t cb, - void *data); - - /** - * Unregister a previously registered callback for connectivity changes - * - * @param cb previously registered callback - */ - void (*remove_connectivity_cb)(network_manager_t *this, - connectivity_cb_t cb); - - /** - * Check whether we currently have connectivity - * - * @return TRUE if currently connected - */ - bool (*is_connected)(network_manager_t *this); - - /** - * Destroy a network_manager_t instance - */ - void (*destroy)(network_manager_t *this); -}; - -/** - * Create a network_manager_t instance - * - * @param context Context object - * @return network_manager_t instance - */ -network_manager_t *network_manager_create(jobject context); - -#endif /** NETWORK_MANAGER_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c deleted file mode 100644 index c7a6eb6da..000000000 --- a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright (C) 2012-2014 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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. - */ - -#include "vpnservice_builder.h" -#include "android_jni.h" - -#include -#include - -typedef struct private_vpnservice_builder_t private_vpnservice_builder_t; - -/** - * private data of vpnservice_builder - */ -struct private_vpnservice_builder_t { - - /** - * public interface - */ - vpnservice_builder_t public; - - /** - * Java object - */ - jobject builder; -}; - -METHOD(vpnservice_builder_t, add_address, bool, - private_vpnservice_builder_t *this, host_t *addr) -{ - JNIEnv *env; - jmethodID method_id; - jstring str; - char buf[INET6_ADDRSTRLEN]; - int prefix; - - androidjni_attach_thread(&env); - - DBG2(DBG_LIB, "builder: adding interface address %H", addr); - - prefix = addr->get_family(addr) == AF_INET ? 32 : 128; - if (snprintf(buf, sizeof(buf), "%H", addr) >= sizeof(buf)) - { - goto failed; - } - - method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, - "addAddress", "(Ljava/lang/String;I)Z"); - if (!method_id) - { - goto failed; - } - str = (*env)->NewStringUTF(env, buf); - if (!str) - { - goto failed; - } - if (!(*env)->CallBooleanMethod(env, this->builder, method_id, str, prefix)) - { - goto failed; - } - androidjni_detach_thread(); - return TRUE; - -failed: - DBG1(DBG_LIB, "builder: failed to add address"); - androidjni_exception_occurred(env); - androidjni_detach_thread(); - return FALSE; -} - -METHOD(vpnservice_builder_t, set_mtu, bool, - private_vpnservice_builder_t *this, int mtu) -{ - JNIEnv *env; - jmethodID method_id; - - androidjni_attach_thread(&env); - - DBG2(DBG_LIB, "builder: setting MTU to %d", mtu); - - method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, - "setMtu", "(I)Z"); - if (!method_id) - { - goto failed; - } - if (!(*env)->CallBooleanMethod(env, this->builder, method_id, mtu)) - { - goto failed; - } - androidjni_detach_thread(); - return TRUE; - -failed: - DBG1(DBG_LIB, "builder: failed to set MTU"); - androidjni_exception_occurred(env); - androidjni_detach_thread(); - return FALSE; -} - -METHOD(vpnservice_builder_t, add_route, bool, - private_vpnservice_builder_t *this, host_t *net, int prefix) -{ - JNIEnv *env; - jmethodID method_id; - jstring str; - char buf[INET6_ADDRSTRLEN]; - - androidjni_attach_thread(&env); - - DBG2(DBG_LIB, "builder: adding route %+H/%d", net, prefix); - - if (snprintf(buf, sizeof(buf), "%+H", net) >= sizeof(buf)) - { - goto failed; - } - - method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, - "addRoute", "(Ljava/lang/String;I)Z"); - if (!method_id) - { - goto failed; - } - str = (*env)->NewStringUTF(env, buf); - if (!str) - { - goto failed; - } - if (!(*env)->CallBooleanMethod(env, this->builder, method_id, str, prefix)) - { - goto failed; - } - androidjni_detach_thread(); - return TRUE; - -failed: - DBG1(DBG_LIB, "builder: failed to add route"); - androidjni_exception_occurred(env); - androidjni_detach_thread(); - return FALSE; -} - -METHOD(vpnservice_builder_t, add_dns, bool, - private_vpnservice_builder_t *this, host_t *dns) -{ - JNIEnv *env; - jmethodID method_id; - jstring str; - char buf[INET6_ADDRSTRLEN]; - - androidjni_attach_thread(&env); - - DBG2(DBG_LIB, "builder: adding DNS server %H", dns); - - if (snprintf(buf, sizeof(buf), "%H", dns) >= sizeof(buf)) - { - goto failed; - } - - method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, - "addDnsServer", "(Ljava/lang/String;)Z"); - if (!method_id) - { - goto failed; - } - str = (*env)->NewStringUTF(env, buf); - if (!str) - { - goto failed; - } - if (!(*env)->CallBooleanMethod(env, this->builder, method_id, str)) - { - goto failed; - } - androidjni_detach_thread(); - return TRUE; - -failed: - DBG1(DBG_LIB, "builder: failed to add DNS server"); - androidjni_exception_occurred(env); - androidjni_detach_thread(); - return FALSE; -} - -/** - * Establish or reestablish the TUN device - */ -static int establish_internal(private_vpnservice_builder_t *this, char *method) -{ - JNIEnv *env; - jmethodID method_id; - int fd; - - androidjni_attach_thread(&env); - - DBG2(DBG_LIB, "builder: building TUN device"); - - method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, - method, "()I"); - if (!method_id) - { - goto failed; - } - fd = (*env)->CallIntMethod(env, this->builder, method_id); - if (fd == -1) - { - goto failed; - } - androidjni_detach_thread(); - return fd; - -failed: - DBG1(DBG_LIB, "builder: failed to build TUN device"); - androidjni_exception_occurred(env); - androidjni_detach_thread(); - return -1; -} - -METHOD(vpnservice_builder_t, establish, int, - private_vpnservice_builder_t *this) -{ - return establish_internal(this, "establish"); -} - -METHOD(vpnservice_builder_t, establish_no_dns, int, - private_vpnservice_builder_t *this) -{ - return establish_internal(this, "establishNoDns"); -} - -METHOD(vpnservice_builder_t, destroy, void, - private_vpnservice_builder_t *this) -{ - JNIEnv *env; - - androidjni_attach_thread(&env); - (*env)->DeleteGlobalRef(env, this->builder); - androidjni_detach_thread(); - free(this); -} - -vpnservice_builder_t *vpnservice_builder_create(jobject builder) -{ - JNIEnv *env; - private_vpnservice_builder_t *this; - - INIT(this, - .public = { - .add_address = _add_address, - .add_route = _add_route, - .add_dns = _add_dns, - .set_mtu = _set_mtu, - .establish = _establish, - .establish_no_dns = _establish_no_dns, - .destroy = _destroy, - }, - ); - - androidjni_attach_thread(&env); - this->builder = (*env)->NewGlobalRef(env, builder); - androidjni_detach_thread(); - - return &this->public; -} diff --git a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h deleted file mode 100644 index 08c436da6..000000000 --- a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2012-2014 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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. - */ - -/** - * @defgroup vpnservice_builder vpnservice_builder - * @{ @ingroup libandroidbridge - */ - -#ifndef VPNSERVICE_BUILDER_H_ -#define VPNSERVICE_BUILDER_H_ - -#include - -#include -#include - -typedef struct vpnservice_builder_t vpnservice_builder_t; - -/** - * VpnService.Builder, used to build a TUN device. - * - * Communicates with CharonVpnService.BuilderAdapter via JNI - */ -struct vpnservice_builder_t { - - /** - * Add an interface address - * - * @param addr the desired interface address - * @return TRUE on success - */ - bool (*add_address)(vpnservice_builder_t *this, host_t *addr); - - /** - * Add a route - * - * @param net the network address - * @param prefix_length the prefix length - * @return TRUE on success - */ - bool (*add_route)(vpnservice_builder_t *this, host_t *net, int prefix); - - /** - * Add a DNS server - * - * @param dns the address of the DNS server - * @return TRUE on success - */ - bool (*add_dns)(vpnservice_builder_t *this, host_t *dns); - - /** - * Set the MTU for the TUN device - * - * @param mtu the MTU to set - * @return TRUE on success - */ - bool (*set_mtu)(vpnservice_builder_t *this, int mtu); - - /** - * Build the TUN device - * - * @return the TUN file descriptor, -1 if failed - */ - int (*establish)(vpnservice_builder_t *this); - - /** - * Build the TUN device without DNS related data - * - * @return the TUN file descriptor, -1 if failed - */ - int (*establish_no_dns)(vpnservice_builder_t *this); - - /** - * Destroy a vpnservice_builder - */ - void (*destroy)(vpnservice_builder_t *this); - -}; - -/** - * Create a vpnservice_builder instance - * - * @param builder CharonVpnService.BuilderAdapter object - * @return vpnservice_builder_t instance - */ -vpnservice_builder_t *vpnservice_builder_create(jobject builder); - -#endif /** VPNSERVICE_BUILDER_H_ @}*/ diff --git a/src/frontends/android/proguard.cfg b/src/frontends/android/proguard.cfg deleted file mode 100644 index b1cdf17b5..000000000 --- a/src/frontends/android/proguard.cfg +++ /dev/null @@ -1,40 +0,0 @@ --optimizationpasses 5 --dontusemixedcaseclassnames --dontskipnonpubliclibraryclasses --dontpreverify --verbose --optimizations !code/simplification/arithmetic,!field/*,!class/merging/* - --keep public class * extends android.app.Activity --keep public class * extends android.app.Application --keep public class * extends android.app.Service --keep public class * extends android.content.BroadcastReceiver --keep public class * extends android.content.ContentProvider --keep public class * extends android.app.backup.BackupAgentHelper --keep public class * extends android.preference.Preference --keep public class com.android.vending.licensing.ILicensingService - --keepclasseswithmembernames class * { - native ; -} - --keepclasseswithmembers class * { - public (android.content.Context, android.util.AttributeSet); -} - --keepclasseswithmembers class * { - public (android.content.Context, android.util.AttributeSet, int); -} - --keepclassmembers class * extends android.app.Activity { - public void *(android.view.View); -} - --keepclassmembers enum * { - public static **[] values(); - public static ** valueOf(java.lang.String); -} - --keep class * implements android.os.Parcelable { - public static final android.os.Parcelable$Creator *; -} diff --git a/src/frontends/android/project.properties b/src/frontends/android/project.properties deleted file mode 100644 index bbe203c84..000000000 --- a/src/frontends/android/project.properties +++ /dev/null @@ -1,11 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "ant.properties", and override values to adapt the script to your -# project structure. - -# Project target. -target=android-21 diff --git a/src/frontends/android/res/drawable-hdpi/ic_launcher.png b/src/frontends/android/res/drawable-hdpi/ic_launcher.png deleted file mode 100644 index 7cd1df4ee..000000000 Binary files a/src/frontends/android/res/drawable-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/frontends/android/res/drawable-mdpi/ic_launcher.png b/src/frontends/android/res/drawable-mdpi/ic_launcher.png deleted file mode 100644 index 200ee9677..000000000 Binary files a/src/frontends/android/res/drawable-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/frontends/android/res/drawable-xhdpi/ic_launcher.png b/src/frontends/android/res/drawable-xhdpi/ic_launcher.png deleted file mode 100644 index 2eb6db1b6..000000000 Binary files a/src/frontends/android/res/drawable-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/frontends/android/res/drawable/remediation_instruction_background_large.xml b/src/frontends/android/res/drawable/remediation_instruction_background_large.xml deleted file mode 100644 index 470fecb12..000000000 --- a/src/frontends/android/res/drawable/remediation_instruction_background_large.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/frontends/android/res/drawable/state_background.xml b/src/frontends/android/res/drawable/state_background.xml deleted file mode 100644 index ee36325cc..000000000 --- a/src/frontends/android/res/drawable/state_background.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/frontends/android/res/layout-large/remediation_instructions.xml b/src/frontends/android/res/layout-large/remediation_instructions.xml deleted file mode 100644 index 5a28dd654..000000000 --- a/src/frontends/android/res/layout-large/remediation_instructions.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/frontends/android/res/layout/imc_state_fragment.xml b/src/frontends/android/res/layout/imc_state_fragment.xml deleted file mode 100644 index 171c88d2d..000000000 --- a/src/frontends/android/res/layout/imc_state_fragment.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/frontends/android/res/layout/log_activity.xml b/src/frontends/android/res/layout/log_activity.xml deleted file mode 100644 index 80fee09fb..000000000 --- a/src/frontends/android/res/layout/log_activity.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - diff --git a/src/frontends/android/res/layout/log_fragment.xml b/src/frontends/android/res/layout/log_fragment.xml deleted file mode 100644 index c2e187a66..000000000 --- a/src/frontends/android/res/layout/log_fragment.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - diff --git a/src/frontends/android/res/layout/login_dialog.xml b/src/frontends/android/res/layout/login_dialog.xml deleted file mode 100644 index 0262af0a3..000000000 --- a/src/frontends/android/res/layout/login_dialog.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/frontends/android/res/layout/main.xml b/src/frontends/android/res/layout/main.xml deleted file mode 100644 index ab03e72bc..000000000 --- a/src/frontends/android/res/layout/main.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - diff --git a/src/frontends/android/res/layout/profile_detail_view.xml b/src/frontends/android/res/layout/profile_detail_view.xml deleted file mode 100644 index 57d5606ff..000000000 --- a/src/frontends/android/res/layout/profile_detail_view.xml +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/frontends/android/res/layout/profile_list_fragment.xml b/src/frontends/android/res/layout/profile_list_fragment.xml deleted file mode 100644 index 50d628bfa..000000000 --- a/src/frontends/android/res/layout/profile_list_fragment.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - diff --git a/src/frontends/android/res/layout/profile_list_item.xml b/src/frontends/android/res/layout/profile_list_item.xml deleted file mode 100644 index 93df7b649..000000000 --- a/src/frontends/android/res/layout/profile_list_item.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/frontends/android/res/layout/remediation_instruction.xml b/src/frontends/android/res/layout/remediation_instruction.xml deleted file mode 100644 index 09c0d43a3..000000000 --- a/src/frontends/android/res/layout/remediation_instruction.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - diff --git a/src/frontends/android/res/layout/remediation_instruction_item.xml b/src/frontends/android/res/layout/remediation_instruction_item.xml deleted file mode 100644 index c25e6c123..000000000 --- a/src/frontends/android/res/layout/remediation_instruction_item.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - diff --git a/src/frontends/android/res/layout/remediation_instructions.xml b/src/frontends/android/res/layout/remediation_instructions.xml deleted file mode 100644 index 84143b575..000000000 --- a/src/frontends/android/res/layout/remediation_instructions.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/frontends/android/res/layout/trusted_certificates_activity.xml b/src/frontends/android/res/layout/trusted_certificates_activity.xml deleted file mode 100644 index 966ecf25e..000000000 --- a/src/frontends/android/res/layout/trusted_certificates_activity.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/src/frontends/android/res/layout/trusted_certificates_item.xml b/src/frontends/android/res/layout/trusted_certificates_item.xml deleted file mode 100644 index 609d06a7a..000000000 --- a/src/frontends/android/res/layout/trusted_certificates_item.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - diff --git a/src/frontends/android/res/layout/two_line_button.xml b/src/frontends/android/res/layout/two_line_button.xml deleted file mode 100644 index 89d095295..000000000 --- a/src/frontends/android/res/layout/two_line_button.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - diff --git a/src/frontends/android/res/layout/vpn_profile_select.xml b/src/frontends/android/res/layout/vpn_profile_select.xml deleted file mode 100644 index 7f49aa4e7..000000000 --- a/src/frontends/android/res/layout/vpn_profile_select.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/frontends/android/res/layout/vpn_state_fragment.xml b/src/frontends/android/res/layout/vpn_state_fragment.xml deleted file mode 100644 index e347c4c4b..000000000 --- a/src/frontends/android/res/layout/vpn_state_fragment.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/src/frontends/android/res/menu/certificates.xml b/src/frontends/android/res/menu/certificates.xml deleted file mode 100644 index 6066cab60..000000000 --- a/src/frontends/android/res/menu/certificates.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - diff --git a/src/frontends/android/res/menu/log.xml b/src/frontends/android/res/menu/log.xml deleted file mode 100644 index 1af5bd397..000000000 --- a/src/frontends/android/res/menu/log.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - diff --git a/src/frontends/android/res/menu/main.xml b/src/frontends/android/res/menu/main.xml deleted file mode 100644 index 3dde5227e..000000000 --- a/src/frontends/android/res/menu/main.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/frontends/android/res/menu/profile_edit.xml b/src/frontends/android/res/menu/profile_edit.xml deleted file mode 100644 index e69020ed0..000000000 --- a/src/frontends/android/res/menu/profile_edit.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - diff --git a/src/frontends/android/res/menu/profile_list.xml b/src/frontends/android/res/menu/profile_list.xml deleted file mode 100644 index 57c9a86a4..000000000 --- a/src/frontends/android/res/menu/profile_list.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/src/frontends/android/res/menu/profile_list_context.xml b/src/frontends/android/res/menu/profile_list_context.xml deleted file mode 100644 index e674ae856..000000000 --- a/src/frontends/android/res/menu/profile_list_context.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/frontends/android/res/values-de/arrays.xml b/src/frontends/android/res/values-de/arrays.xml deleted file mode 100644 index d05140165..000000000 --- a/src/frontends/android/res/values-de/arrays.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - IKEv2 EAP (Benutzername/Passwort) - IKEv2 Zertifikat - IKEv2 Zertifikat + EAP (Benutzername/Passwort) - IKEv2 EAP-TLS (Zertifikat) - IKEv2 EAP-TNC (Benutzername/Passwort) - - \ No newline at end of file diff --git a/src/frontends/android/res/values-de/strings.xml b/src/frontends/android/res/values-de/strings.xml deleted file mode 100644 index 6cd5ba50a..000000000 --- a/src/frontends/android/res/values-de/strings.xml +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - strongSwan VPN Client - strongSwan - Log anzeigen - Suchen - VPN nicht unterstützt - Ihr Gerät unterstützt keine VPN Anwendungen.\nBitte kontaktieren Sie den Hersteller. - VPN Verbindungen sind nicht möglich im abgeriegelten Modus. - Laden… - Profil nicht gefunden - strongSwan-Verknüpfung - - - Log - Logdatei senden - Logdatei ist leer - strongSwan %1$s Logdatei - - - Keine VPN Profile vorhanden. - Profil hinzufügen - Bearbeiten - Löschen - Profile auswählen - Ausgewählte Profile gelöscht - Kein Profil ausgewählt - Ein Profil ausgewählt - %1$d Profile ausgewählt - - - Speichern - Abbrechen - Profilname: - (Gateway-Adresse verwenden) - Gateway: - Typ: - Benutzername: - Passwort: - (anfordern wenn benötigt) - Benutzer-Zertifikat: - Benutzer-Zertifikat auswählen - Wählen Sie ein bestimmtes Benutzer-Zertifikat - CA-Zertifikat: - Automatisch wählen - CA-Zertifikat auswählen - Wählen Sie ein bestimmtes CA-Zertifikat - Erweiterte Einstellungen anzeigen - MTU: - Server Port: - (Standardwert verwenden) - Split-Tunneling: - Blockiere IPv4 Verkehr der nicht für das VPN bestimmt ist - Blockiere IPv6 Verkehr der nicht für das VPN bestimmt ist - - Bitte geben Sie hier die Gateway-Adresse ein - Bitte geben Sie hier Ihren Benutzernamen ein - Kein CA-Zertifikat ausgewählt - Bitte wählen Sie eines aus oder aktivieren Sie Automatisch wählen - Bitte geben Sie eine Nummer von %1$d - %2$d ein - EAP-TNC kann Ihre Privatsphäre beeinträchtigen - Gerätedaten werden an den Gateway-Betreiber gesendet - <p>Trusted Network Connect (TNC) erlaubt Gateway-Betreibern den Gesundheitszustand von Endgeräten zu prüfen.</p><p>Dazu kann der Betreiber Daten verlangen, wie etwa eine eindeutige Identifikationsnummer, eine Liste der installierten Pakete, Systemeinstellungen oder kryptografische Prüfsummen von Dateien.</p><b>Solche Daten werden nur übermittelt nachdem die Identität des Gateways geprüft wurde.</b> - - - CA-Zertifikate - Keine Zertifikate - CA-Zertifikate neu laden - System - Benutzer - Importiert - Zertifikat löschen? - Das Zertifikat wird permanent entfernt! - Zertifikat importieren - Zertifikat erfolgreich importiert - Zertifikat-Import fehlgeschlagen - - - Status: - Profil: - Trennen - Verbinden… - Verbunden - Trennen… - Kein aktives Profil - Fehler - - - Assessment: - Eingeschränkt - Fehlgeschlagen - Korrekturanweisungen anzeigen - - - Korrekturanweisungen - - - Passwort eingeben um zu verbinden - Verbinden - Fehler beim Aufsetzen des VPN: - Gateway-Adresse konnte nicht aufgelöst werden. - Gateway ist nicht erreichbar. - Authentifizierung des Gateway ist fehlgeschlagen. - Benutzerauthentifizierung ist fehlgeschlagen. - Sicherheitsassessment ist fehlgeschlagen. - Unbekannter Fehler während des Verbindens. - Verbinden: %1$s - Verbinde mit \""%1$s\". - VPN verbunden - Dieses VPN Profil ist momentan verbunden! - Neu verbinden - Verbinde %1$s? - Dies ersetzt die aktuelle VPN Verbindung! - Verbinden - - diff --git a/src/frontends/android/res/values-pl/arrays.xml b/src/frontends/android/res/values-pl/arrays.xml deleted file mode 100644 index 30e43f1fb..000000000 --- a/src/frontends/android/res/values-pl/arrays.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - IKEv2 EAP (użytkownik/hasło) - IKEv2 certyfikat - IKEv2 certyfikat + EAP (użytkownik/hasło) - IKEv2 EAP-TLS (certyfikat) - IKEv2 EAP-TNC (użytkownik/hasło) - - \ No newline at end of file diff --git a/src/frontends/android/res/values-pl/strings.xml b/src/frontends/android/res/values-pl/strings.xml deleted file mode 100644 index fb2aba003..000000000 --- a/src/frontends/android/res/values-pl/strings.xml +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - strongSwan klient VPN - strongSwan - Pokaż log - Szukaj - Nie obsługiwany VPN - Urządzenie nie obsługuje aplikacji VPN.\nProszę skontaktować się z producentem. - Polączenia nie sa możliwe w trybie zamkniętym - Wczytywanie… - Nie znaleziono profilu - Skrót strongSwan - - - Log - Prześlij log - Log jest pusty - strongSwan %1$s Log - - - Brak profilu VPN - Dodaj profil VPN - Edytuj - Usuń - Wybierz profile - Wybrane profile usunięte - Nie wybrano profilu - Wybrano jeden profil - Wzbrano %1$d profile" - - - Zapisz - Anuluj - Nazwa profilu: - (użyj adresu bramki) - Bramka: - Typ: - Użytkownik: - Hasło: - (w razie potrzeby zapromptuj) - Certyfikat użytkownika: - Wybierz certyfikat użytkownika - >Wybierz określony certyfikat użytkownika - Certyfikat CA: - Wybierz automatycznie - Wybierz certyfikat CA - Wybierz określony certyfikat CA - Show advanced settings - MTU: - Server port: - (use default) - Split tunneling: - Block IPv4 traffic not destined for the VPN - Block IPv6 traffic not destined for the VPN - - Wprowadź adres bramki - Wprowadź swoją nazwę użytkownika - Nie wybrano żadnego certyfikatu CA - Wybierz lub uaktywnij jeden Wybierz automatycznie - Please enter a number in the range from %1$d - %2$d - EAP-TNC may affect your privacy - Device data is sent to the gateway operator - <p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b> - - - Certyfikaty CA - Brak certyfikatów - Przeładuj certyfikaty CA - System - Użytkownik - Imported - Delete certificate? - The certificate will be permanently removed! - Import certificate - Certificate successfully imported - Failed to import certificate - - - Status: - Profil: - Rozłącz - Łączenie… - Połączony - Przerywam połączenie… - Brak aktywnego VPN - Błąd - - - Assessment: - Restricted - Failed - View remediation instructions - - - Remediation instructions - - - Wprowadż hasło - Połącz - Nie udało się utworzyć tunelu VPN: - Nie znaleziono adresu bramki - Bramka jest nieosiągalna - Błąd przy weryfikacji bramki - Błąd przy autoryzacji użytkownika - Security assessment failed. - Nieznany błąd w czasie połączenia - Łączenie: %1$s - Tworzenie tunelu VPN z \""%1$s\". - Połączenie z VPN - Ten profil VPN jest obecnie połaczony! - Połączyć ponownie - Połącz %1$s? - To zastąpi aktywne połączenie VPN! - Połącz - - diff --git a/src/frontends/android/res/values-ru/arrays.xml b/src/frontends/android/res/values-ru/arrays.xml deleted file mode 100644 index 5fbd43168..000000000 --- a/src/frontends/android/res/values-ru/arrays.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - IKEv2 EAP (Логин/Пароль) - IKEv2 Сертификат - IKEv2 Сертификат + EAP (Логин/Пароль) - IKEv2 EAP-TLS (Сертификат) - IKEv2 EAP-TNC (Логин/Пароль) - - diff --git a/src/frontends/android/res/values-ru/strings.xml b/src/frontends/android/res/values-ru/strings.xml deleted file mode 100644 index eabfc084b..000000000 --- a/src/frontends/android/res/values-ru/strings.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - Клиент strongSwan VPN - strongSwan - Журнал - Поиск - VPN не поддерживается - Ваше устройство не поддерживат VPN приложение.\nПожалуйста свяжитесь с производителем. - VPN соединения не поддерживаются в режиме lockdown. - Загрузка… - Профиль не найден - Ссылка на strongSwan - - - Журнал - Отправить журнал - Журнал пуст - strongSwan %1$s журнал - - - VPN профили не обнаружены. - Добавить VPN профиль - Редактировать - Удалить - Выбрать профили - Выбранные профили удалены - Профили не выбраны - Выбран профиль - %1$d прифиля(ей) выбрано" - - - Сохранить - Отмена - Название профиля: - (адрес шлюза) - Шлюз: - Тип: - Логин: - Пароль: - (спросить если нужно) - Сертификат пользователя: - Выбрать сертификат пользователя - Выбрать сертификат пользователя - Сертификат CA: - Выбрать автоматически - Выбрать сертификат CA - Выбрать CA сертификат - Show advanced settings - MTU: - Server port: - (use default) - Split tunneling: - Block IPv4 traffic not destined for the VPN - Block IPv6 traffic not destined for the VPN - - Пожалуйста введите адрес шлюза - Пожалуйста введите имя пользователя - Не выбран сертификат CA - Пожалуйста выберите один Выбрать автоматически - Please enter a number in the range from %1$d - %2$d - EAP-TNC may affect your privacy - Device data is sent to the gateway operator - <p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b> - - - Сертификаты CA - Нет доступных сертификатов - Обновить сертификат CA - Система - Пользователь - Imported - Delete certificate? - The certificate will be permanently removed! - Import certificate - Certificate successfully imported - Failed to import certificate - - - Статус: - Профиль: - Отключить - Соединение… - Соединен - Отключение… - Нет активных VPN - Ошибка - - - Assessment: - Restricted - Failed - View remediation instructions - - - Remediation instructions - - - Введите пароль для соединения - Соединить - Ошибка подключения к VPN: - Не найден адрес шлюза. - Шлюз недоступен. - Ошибка авторизаци при подключении к шлюзу. - Ошибка авторизации пользователя. - Security assessment failed. - Неизвестная ошибка. - Подключение: %1$s - Подключение к VPN с \""%1$s\". - Соединение с VPN установлено - Подключение к этому профилю VPN уже существует! - Переподключить - Подключить %1$s? - Это заменит ваше текущее VPN соединение! - Соединить - - - diff --git a/src/frontends/android/res/values-ua/arrays.xml b/src/frontends/android/res/values-ua/arrays.xml deleted file mode 100644 index 1acc0d769..000000000 --- a/src/frontends/android/res/values-ua/arrays.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - IKEv2 EAP (Логін/Пароль) - IKEv2 Сертифікати - IKEv2 Сертифікати + EAP (Логін/Пароль) - IKEv2 EAP-TLS (Сертифікати) - IKEv2 EAP-TNC (Логін/Пароль) - - diff --git a/src/frontends/android/res/values-ua/strings.xml b/src/frontends/android/res/values-ua/strings.xml deleted file mode 100644 index d7c238370..000000000 --- a/src/frontends/android/res/values-ua/strings.xml +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - strongSwan VPN клієнт - strongSwan - Перегляд журналу - Пошук - VPN не підтримуеться - Ваш пристрій не підтримує VPN.\nЗв\'яжіться з виробником. - VPN з\'єднання не пітримується у режимі lockdown. - Завантаження… - Профіль не знайдено - strongSwan посилання - - - Журнал - Відправити файл журналу - Журнал порожній - strongSwan %1$s файл журналу - - - Немає VPN профілів - Додати VPN профіль - Редагувати - Видалити - Обрати профіль - Обрані профілі видалено - Профіль не обрано - Один профіль обрано - %1$d профілів обрано" - - - Зберегти - Відміна - Назва профілю: - (використовувати адресу шлюза) - Шлюз: - Тип: - Логін: - Пароль: - (запитати якщо потрібно) - Сертифікат користувача: - Виберіть сертифікат користувача - Вибрати спеціальний сертифікат користувача - Сертифікат CA: - Вибрати автоматично - Вибрати сертифікат CA - Вибрати спеціальний сертифікат CA - Show advanced settings - MTU: - Server port: - (use default) - Split tunneling: - Block IPv4 traffic not destined for the VPN - Block IPv6 traffic not destined for the VPN - - Введіть адресу шлюза тут - Введіть ім\'я користувача тут - Не вибрано сертифікат CA - Будь ласка виберіть один Вибрати автоматично - Please enter a number in the range from %1$d - %2$d - EAP-TNC may affect your privacy - Device data is sent to the gateway operator - <p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b> - - - Сертифікати CA - Немає сертифікатів - Перезавантажити CA сертифікати - Система - Користувач - Imported - Delete certificate? - The certificate will be permanently removed! - Import certificate - Certificate successfully imported - Failed to import certificate - - - Статус: - Профіль: - Роз\'єднати - Підключення… - Підключений - Роз\'єднання… - Немає активних VPN - Помилка - - - Assessment: - Restricted - Failed - View remediation instructions - - - Remediation instructions - - - Введіть пароль для з\'єднання - Підключити - Помилка підлючення VPN: - Помилка пошуку адреси шлюза. - Немає зв\'язку зі шлюзом. - Помилка перевірки данних аутентифікації шлюза. - Помилка аутентифікації користувача. - Security assessment failed. - Невідома помилка під час підключення. - Підключення: %1$s - Підключення VPN з \""%1$s\". - VPN підключено - Цей VPN профіль зараз підключений! - Перепідключитися - Підключити %1$s? - Ця дія замінить ваше поточне VPN з\'єднання! - Підключити - - - diff --git a/src/frontends/android/res/values/arrays.xml b/src/frontends/android/res/values/arrays.xml deleted file mode 100644 index b324b594f..000000000 --- a/src/frontends/android/res/values/arrays.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - IKEv2 EAP (Username/Password) - IKEv2 Certificate - IKEv2 Certificate + EAP (Username/Password) - IKEv2 EAP-TLS (Certificate) - IKEv2 EAP-TNC (Username/Password) - - \ No newline at end of file diff --git a/src/frontends/android/res/values/attrs.xml b/src/frontends/android/res/values/attrs.xml deleted file mode 100644 index 6c3480b0e..000000000 --- a/src/frontends/android/res/values/attrs.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - diff --git a/src/frontends/android/res/values/colors.xml b/src/frontends/android/res/values/colors.xml deleted file mode 100644 index 4af28b4d4..000000000 --- a/src/frontends/android/res/values/colors.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - #D9192C - - #FF9909 - - #99CC00 - - #333333 - - #5a5a5a - - diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml deleted file mode 100644 index 5c8ebab57..000000000 --- a/src/frontends/android/res/values/strings.xml +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - strongSwan VPN Client - strongSwan - View log - Search - VPN not supported - Your device does not support VPN applications.\nPlease contact the manufacturer. - VPN connections are not supported in lockdown mode. - Loading… - Profile not found - strongSwan shortcut - - - Log - Send log file - Log file is empty - strongSwan %1$s Log File - - - No VPN profiles. - Add VPN profile - Edit - Delete - Select profiles - Selected profiles deleted - No profile selected - One profile selected - %1$d profiles selected" - - - Save - Cancel - Profile Name: - (use gateway address) - Gateway: - Type: - Username: - Password: - (prompt when needed) - User certificate: - Select user certificate - Select a specific user certificate - CA certificate: - Select automatically - Select CA certificate - Select a specific CA certificate - Show advanced settings - MTU: - Server port: - (use default) - Split tunneling: - Block IPv4 traffic not destined for the VPN - Block IPv6 traffic not destined for the VPN - - Please enter the gateway address here - Please enter your username here - No CA certificate selected - Please select one or activate Select automatically - Please enter a number in the range from %1$d - %2$d - EAP-TNC may affect your privacy - Device data is sent to the gateway operator - <p>Trusted Network Connect (TNC) allows gateway operators to assess the health of a client device.</p><p>For that purpose the gateway operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the gateway\'s identity.</b> - - - CA certificates - No certificates - Reload CA certificates - System - User - Imported - Delete certificate? - The certificate will be permanently removed! - Import certificate - Certificate successfully imported - Failed to import certificate - - - Status: - Profile: - Disconnect - Connecting… - Connected - Disconnecting… - No active VPN - Error - - - Assessment: - Restricted - Failed - View remediation instructions - - - Remediation instructions - - - Enter password to connect - Connect - Failed to establish VPN: - Gateway address lookup failed. - Gateway is unreachable. - Verifying gateway authentication failed. - User authentication failed. - Security assessment failed. - Unspecified failure while connecting. - Connecting: %1$s - Establishing VPN with \""%1$s\". - VPN connected - This VPN profile is currently connected! - Reconnect - Connect %1$s? - This will replace your active VPN connection! - Connect - - diff --git a/src/frontends/android/res/values/styles.xml b/src/frontends/android/res/values/styles.xml deleted file mode 100644 index 739ba7000..000000000 --- a/src/frontends/android/res/values/styles.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/src/frontends/android/settings.gradle b/src/frontends/android/settings.gradle new file mode 100644 index 000000000..e7b4def49 --- /dev/null +++ b/src/frontends/android/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java b/src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java deleted file mode 100644 index 370a8d5e4..000000000 --- a/src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2012 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.data; - -import java.io.File; -import java.io.FileNotFoundException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.concurrent.ConcurrentHashMap; - -import org.strongswan.android.logic.CharonVpnService; - -import android.content.ContentProvider; -import android.content.ContentValues; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.os.SystemClock; -import android.provider.OpenableColumns; - -public class LogContentProvider extends ContentProvider -{ - private static final String AUTHORITY = "org.strongswan.android.content.log"; - /* an Uri is valid for 30 minutes */ - private static final long URI_VALIDITY = 30 * 60 * 1000; - private static ConcurrentHashMap mUris = new ConcurrentHashMap(); - private File mLogFile; - - public LogContentProvider() - { - } - - @Override - public boolean onCreate() - { - mLogFile = new File(getContext().getFilesDir(), CharonVpnService.LOG_FILE); - return true; - } - - /** - * The log file can only be accessed by Uris created with this method - * @return null if failed to create the Uri - */ - public static Uri createContentUri() - { - SecureRandom random; - try - { - random = SecureRandom.getInstance("SHA1PRNG"); - } - catch (NoSuchAlgorithmException e) - { - return null; - } - Uri uri = Uri.parse("content://" + AUTHORITY + "/" + random.nextLong()); - mUris.put(uri, SystemClock.uptimeMillis()); - return uri; - } - - @Override - public String getType(Uri uri) - { - /* MIME type for our log file */ - return "text/plain"; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, - String[] selectionArgs, String sortOrder) - { - /* this is called by apps to find out the name and size of the file. - * since we only provide a single file this is simple to implement */ - if (projection == null || projection.length < 1) - { - return null; - } - Long timestamp = mUris.get(uri); - if (timestamp == null) - { /* don't check the validity as this information is not really private */ - return null; - } - MatrixCursor cursor = new MatrixCursor(projection, 1); - if (OpenableColumns.DISPLAY_NAME.equals(cursor.getColumnName(0))) - { - cursor.newRow().add(CharonVpnService.LOG_FILE); - } - else if (OpenableColumns.SIZE.equals(cursor.getColumnName(0))) - { - cursor.newRow().add(mLogFile.length()); - } - else - { - return null; - } - return cursor; - } - - @Override - public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException - { - Long timestamp = mUris.get(uri); - if (timestamp != null) - { - long elapsed = SystemClock.uptimeMillis() - timestamp; - if (elapsed > 0 && elapsed < URI_VALIDITY) - { /* we fail if clock wrapped, should happen rarely though */ - return ParcelFileDescriptor.open(mLogFile, ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_READ_ONLY); - } - mUris.remove(uri); - } - return super.openFile(uri, mode); - } - - @Override - public Uri insert(Uri uri, ContentValues values) - { - /* not supported */ - return null; - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) - { - /* not supported */ - return 0; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, - String[] selectionArgs) - { - /* not supported */ - return 0; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java deleted file mode 100644 index 5c64ad0e5..000000000 --- a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2012-2015 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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.data; - - -public class VpnProfile implements Cloneable -{ - /* While storing this as EnumSet would be nicer this simplifies storing it in a database */ - public static final int SPLIT_TUNNELING_BLOCK_IPV4 = 1; - public static final int SPLIT_TUNNELING_BLOCK_IPV6 = 2; - - private String mName, mGateway, mUsername, mPassword, mCertificate, mUserCertificate; - private Integer mMTU, mPort, mSplitTunneling; - private VpnType mVpnType; - private long mId = -1; - - public long getId() - { - return mId; - } - - public void setId(long id) - { - this.mId = id; - } - - public String getName() - { - return mName; - } - - public void setName(String name) - { - this.mName = name; - } - - public String getGateway() - { - return mGateway; - } - - public void setGateway(String gateway) - { - this.mGateway = gateway; - } - - public VpnType getVpnType() - { - return mVpnType; - } - - public void setVpnType(VpnType type) - { - this.mVpnType = type; - } - - public String getUsername() - { - return mUsername; - } - - public void setUsername(String username) - { - this.mUsername = username; - } - - public String getPassword() - { - return mPassword; - } - - public void setPassword(String password) - { - this.mPassword = password; - } - - public String getCertificateAlias() - { - return mCertificate; - } - - public void setCertificateAlias(String alias) - { - this.mCertificate = alias; - } - - public String getUserCertificateAlias() - { - return mUserCertificate; - } - - public void setUserCertificateAlias(String alias) - { - this.mUserCertificate = alias; - } - - public Integer getMTU() - { - return mMTU; - } - - public void setMTU(Integer mtu) - { - this.mMTU = mtu; - } - - public Integer getPort() - { - return mPort; - } - - public void setPort(Integer port) - { - this.mPort = port; - } - - public Integer getSplitTunneling() - { - return mSplitTunneling; - } - - public void setSplitTunneling(Integer splitTunneling) - { - this.mSplitTunneling = splitTunneling; - } - - @Override - public String toString() - { - return mName; - } - - @Override - public boolean equals(Object o) - { - if (o != null && o instanceof VpnProfile) - { - return this.mId == ((VpnProfile)o).getId(); - } - return false; - } - - @Override - public VpnProfile clone() - { - try - { - return (VpnProfile)super.clone(); - } - catch (CloneNotSupportedException e) - { - throw new AssertionError(); - } - } -} diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java deleted file mode 100644 index 45e9b8650..000000000 --- a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright (C) 2012-2015 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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.data; - -import java.util.ArrayList; -import java.util.List; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteQueryBuilder; -import android.util.Log; - -public class VpnProfileDataSource -{ - private static final String TAG = VpnProfileDataSource.class.getSimpleName(); - public static final String KEY_ID = "_id"; - public static final String KEY_NAME = "name"; - public static final String KEY_GATEWAY = "gateway"; - public static final String KEY_VPN_TYPE = "vpn_type"; - public static final String KEY_USERNAME = "username"; - public static final String KEY_PASSWORD = "password"; - public static final String KEY_CERTIFICATE = "certificate"; - public static final String KEY_USER_CERTIFICATE = "user_certificate"; - public static final String KEY_MTU = "mtu"; - public static final String KEY_PORT = "port"; - public static final String KEY_SPLIT_TUNNELING = "split_tunneling"; - - private DatabaseHelper mDbHelper; - private SQLiteDatabase mDatabase; - private final Context mContext; - - private static final String DATABASE_NAME = "strongswan.db"; - private static final String TABLE_VPNPROFILE = "vpnprofile"; - - private static final int DATABASE_VERSION = 7; - - public static final String DATABASE_CREATE = - "CREATE TABLE " + TABLE_VPNPROFILE + " (" + - KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + - KEY_NAME + " TEXT NOT NULL," + - KEY_GATEWAY + " TEXT NOT NULL," + - KEY_VPN_TYPE + " TEXT NOT NULL," + - KEY_USERNAME + " TEXT," + - KEY_PASSWORD + " TEXT," + - KEY_CERTIFICATE + " TEXT," + - KEY_USER_CERTIFICATE + " TEXT," + - KEY_MTU + " INTEGER," + - KEY_PORT + " INTEGER," + - KEY_SPLIT_TUNNELING + " INTEGER" + - ");"; - private static final String[] ALL_COLUMNS = new String[] { - KEY_ID, - KEY_NAME, - KEY_GATEWAY, - KEY_VPN_TYPE, - KEY_USERNAME, - KEY_PASSWORD, - KEY_CERTIFICATE, - KEY_USER_CERTIFICATE, - KEY_MTU, - KEY_PORT, - KEY_SPLIT_TUNNELING, - }; - - private static class DatabaseHelper extends SQLiteOpenHelper - { - public DatabaseHelper(Context context) - { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase database) - { - database.execSQL(DATABASE_CREATE); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) - { - Log.w(TAG, "Upgrading database from version " + oldVersion + - " to " + newVersion); - if (oldVersion < 2) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_USER_CERTIFICATE + - " TEXT;"); - } - if (oldVersion < 3) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_VPN_TYPE + - " TEXT DEFAULT '';"); - } - if (oldVersion < 4) - { /* remove NOT NULL constraint from username column */ - updateColumns(db); - } - if (oldVersion < 5) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_MTU + - " INTEGER;"); - } - if (oldVersion < 6) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_PORT + - " INTEGER;"); - } - if (oldVersion < 7) - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " ADD " + KEY_SPLIT_TUNNELING + - " INTEGER;"); - } - } - - private void updateColumns(SQLiteDatabase db) - { - db.beginTransaction(); - try - { - db.execSQL("ALTER TABLE " + TABLE_VPNPROFILE + " RENAME TO tmp_" + TABLE_VPNPROFILE + ";"); - db.execSQL(DATABASE_CREATE); - StringBuilder insert = new StringBuilder("INSERT INTO " + TABLE_VPNPROFILE + " SELECT "); - SQLiteQueryBuilder.appendColumns(insert, ALL_COLUMNS); - db.execSQL(insert.append(" FROM tmp_" + TABLE_VPNPROFILE + ";").toString()); - db.execSQL("DROP TABLE tmp_" + TABLE_VPNPROFILE + ";"); - db.setTransactionSuccessful(); - } - finally - { - db.endTransaction(); - } - } - } - - /** - * Construct a new VPN profile data source. The context is used to - * open/create the database. - * @param context context used to access the database - */ - public VpnProfileDataSource(Context context) - { - this.mContext = context; - } - - /** - * Open the VPN profile data source. The database is automatically created - * if it does not yet exist. If that fails an exception is thrown. - * @return itself (allows to chain initialization calls) - * @throws SQLException if the database could not be opened or created - */ - public VpnProfileDataSource open() throws SQLException - { - if (mDbHelper == null) - { - mDbHelper = new DatabaseHelper(mContext); - mDatabase = mDbHelper.getWritableDatabase(); - } - return this; - } - - /** - * Close the data source. - */ - public void close() - { - if (mDbHelper != null) - { - mDbHelper.close(); - mDbHelper = null; - } - } - - /** - * Insert the given VPN profile into the database. On success the Id of - * the object is updated and the object returned. - * - * @param profile the profile to add - * @return the added VPN profile or null, if failed - */ - public VpnProfile insertProfile(VpnProfile profile) - { - ContentValues values = ContentValuesFromVpnProfile(profile); - long insertId = mDatabase.insert(TABLE_VPNPROFILE, null, values); - if (insertId == -1) - { - return null; - } - profile.setId(insertId); - return profile; - } - - /** - * Updates the given VPN profile in the database. - * @param profile the profile to update - * @return true if update succeeded, false otherwise - */ - public boolean updateVpnProfile(VpnProfile profile) - { - long id = profile.getId(); - ContentValues values = ContentValuesFromVpnProfile(profile); - return mDatabase.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + id, null) > 0; - } - - /** - * Delete the given VPN profile from the database. - * @param profile the profile to delete - * @return true if deleted, false otherwise - */ - public boolean deleteVpnProfile(VpnProfile profile) - { - long id = profile.getId(); - return mDatabase.delete(TABLE_VPNPROFILE, KEY_ID + " = " + id, null) > 0; - } - - /** - * Get a single VPN profile from the database. - * @param id the ID of the VPN profile - * @return the profile or null, if not found - */ - public VpnProfile getVpnProfile(long id) - { - VpnProfile profile = null; - Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, - KEY_ID + "=" + id, null, null, null, null); - if (cursor.moveToFirst()) - { - profile = VpnProfileFromCursor(cursor); - } - cursor.close(); - return profile; - } - - /** - * Get a list of all VPN profiles stored in the database. - * @return list of VPN profiles - */ - public List getAllVpnProfiles() - { - List vpnProfiles = new ArrayList(); - - Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, null, null, null, null, null); - cursor.moveToFirst(); - while (!cursor.isAfterLast()) - { - VpnProfile vpnProfile = VpnProfileFromCursor(cursor); - vpnProfiles.add(vpnProfile); - cursor.moveToNext(); - } - cursor.close(); - return vpnProfiles; - } - - private VpnProfile VpnProfileFromCursor(Cursor cursor) - { - VpnProfile profile = new VpnProfile(); - profile.setId(cursor.getLong(cursor.getColumnIndex(KEY_ID))); - profile.setName(cursor.getString(cursor.getColumnIndex(KEY_NAME))); - profile.setGateway(cursor.getString(cursor.getColumnIndex(KEY_GATEWAY))); - profile.setVpnType(VpnType.fromIdentifier(cursor.getString(cursor.getColumnIndex(KEY_VPN_TYPE)))); - profile.setUsername(cursor.getString(cursor.getColumnIndex(KEY_USERNAME))); - profile.setPassword(cursor.getString(cursor.getColumnIndex(KEY_PASSWORD))); - profile.setCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_CERTIFICATE))); - profile.setUserCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_USER_CERTIFICATE))); - profile.setMTU(getInt(cursor, cursor.getColumnIndex(KEY_MTU))); - profile.setPort(getInt(cursor, cursor.getColumnIndex(KEY_PORT))); - profile.setSplitTunneling(getInt(cursor, cursor.getColumnIndex(KEY_SPLIT_TUNNELING))); - return profile; - } - - private ContentValues ContentValuesFromVpnProfile(VpnProfile profile) - { - ContentValues values = new ContentValues(); - values.put(KEY_NAME, profile.getName()); - values.put(KEY_GATEWAY, profile.getGateway()); - values.put(KEY_VPN_TYPE, profile.getVpnType().getIdentifier()); - values.put(KEY_USERNAME, profile.getUsername()); - values.put(KEY_PASSWORD, profile.getPassword()); - values.put(KEY_CERTIFICATE, profile.getCertificateAlias()); - values.put(KEY_USER_CERTIFICATE, profile.getUserCertificateAlias()); - values.put(KEY_MTU, profile.getMTU()); - values.put(KEY_PORT, profile.getPort()); - values.put(KEY_SPLIT_TUNNELING, profile.getSplitTunneling()); - return values; - } - - private Integer getInt(Cursor cursor, int columnIndex) - { - return cursor.isNull(columnIndex) ? null : cursor.getInt(columnIndex); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnType.java b/src/frontends/android/src/org/strongswan/android/data/VpnType.java deleted file mode 100644 index bb7fd09f3..000000000 --- a/src/frontends/android/src/org/strongswan/android/data/VpnType.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2012-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.data; - -import java.util.EnumSet; - -public enum VpnType -{ - /* the order here must match the items in R.array.vpn_types */ - IKEV2_EAP("ikev2-eap", EnumSet.of(VpnTypeFeature.USER_PASS)), - IKEV2_CERT("ikev2-cert", EnumSet.of(VpnTypeFeature.CERTIFICATE)), - IKEV2_CERT_EAP("ikev2-cert-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.CERTIFICATE)), - IKEV2_EAP_TLS("ikev2-eap-tls", EnumSet.of(VpnTypeFeature.CERTIFICATE)), - IKEV2_BYOD_EAP("ikev2-byod-eap", EnumSet.of(VpnTypeFeature.USER_PASS, VpnTypeFeature.BYOD)); - - /** - * Features of a VPN type. - */ - public enum VpnTypeFeature - { - /** client certificate is required */ - CERTIFICATE, - /** username and password are required */ - USER_PASS, - /** enable BYOD features */ - BYOD; - } - - private String mIdentifier; - private EnumSet mFeatures; - - /** - * Enum which provides additional information about the supported VPN types. - * - * @param id identifier used to store and transmit this specific type - * @param features of the given VPN type - * @param certificate true if a client certificate is required - */ - VpnType(String id, EnumSet features) - { - mIdentifier = id; - mFeatures = features; - } - - /** - * The identifier used to store this value in the database - * @return identifier - */ - public String getIdentifier() - { - return mIdentifier; - } - - /** - * Checks whether a feature is supported/required by this type of VPN. - * - * @return true if the feature is supported/required - */ - public boolean has(VpnTypeFeature feature) - { - return mFeatures.contains(feature); - } - - /** - * Get the enum entry with the given identifier. - * - * @param identifier get the enum entry with this identifier - * @return the enum entry, or the default if not found - */ - public static VpnType fromIdentifier(String identifier) - { - for (VpnType type : VpnType.values()) - { - if (identifier.equals(type.mIdentifier)) - { - return type; - } - } - return VpnType.IKEV2_EAP; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java deleted file mode 100644 index e5241d5a7..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ /dev/null @@ -1,857 +0,0 @@ -/* - * Copyright (C) 2012-2015 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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.logic; - -import java.io.File; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.security.PrivateKey; -import java.security.cert.CertificateEncodingException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -import org.strongswan.android.data.VpnProfile; -import org.strongswan.android.data.VpnProfileDataSource; -import org.strongswan.android.data.VpnType.VpnTypeFeature; -import org.strongswan.android.logic.VpnStateService.ErrorState; -import org.strongswan.android.logic.VpnStateService.State; -import org.strongswan.android.logic.imc.ImcState; -import org.strongswan.android.logic.imc.RemediationInstruction; -import org.strongswan.android.ui.MainActivity; -import org.strongswan.android.utils.SettingsWriter; - -import android.annotation.TargetApi; -import android.app.PendingIntent; -import android.app.Service; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.net.VpnService; -import android.os.Build; -import android.os.Bundle; -import android.os.IBinder; -import android.os.ParcelFileDescriptor; -import android.security.KeyChain; -import android.security.KeyChainException; -import android.system.OsConstants; -import android.util.Log; - -public class CharonVpnService extends VpnService implements Runnable -{ - private static final String TAG = CharonVpnService.class.getSimpleName(); - public static final String LOG_FILE = "charon.log"; - - private String mLogFile; - private VpnProfileDataSource mDataSource; - private Thread mConnectionHandler; - private VpnProfile mCurrentProfile; - private volatile String mCurrentCertificateAlias; - private volatile String mCurrentUserCertificateAlias; - private VpnProfile mNextProfile; - private volatile boolean mProfileUpdated; - private volatile boolean mTerminate; - private volatile boolean mIsDisconnecting; - private VpnStateService mService; - private final Object mServiceLock = new Object(); - private final ServiceConnection mServiceConnection = new ServiceConnection() { - @Override - public void onServiceDisconnected(ComponentName name) - { /* since the service is local this is theoretically only called when the process is terminated */ - synchronized (mServiceLock) - { - mService = null; - } - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) - { - synchronized (mServiceLock) - { - mService = ((VpnStateService.LocalBinder)service).getService(); - } - /* we are now ready to start the handler thread */ - mConnectionHandler.start(); - } - }; - - /** - * as defined in charonservice.h - */ - static final int STATE_CHILD_SA_UP = 1; - static final int STATE_CHILD_SA_DOWN = 2; - static final int STATE_AUTH_ERROR = 3; - static final int STATE_PEER_AUTH_ERROR = 4; - static final int STATE_LOOKUP_ERROR = 5; - static final int STATE_UNREACHABLE_ERROR = 6; - static final int STATE_GENERIC_ERROR = 7; - - @Override - public int onStartCommand(Intent intent, int flags, int startId) - { - if (intent != null) - { - Bundle bundle = intent.getExtras(); - VpnProfile profile = null; - if (bundle != null) - { - profile = mDataSource.getVpnProfile(bundle.getLong(VpnProfileDataSource.KEY_ID)); - if (profile != null) - { - String password = bundle.getString(VpnProfileDataSource.KEY_PASSWORD); - profile.setPassword(password); - } - } - setNextProfile(profile); - } - return START_NOT_STICKY; - } - - @Override - public void onCreate() - { - mLogFile = getFilesDir().getAbsolutePath() + File.separator + LOG_FILE; - - mDataSource = new VpnProfileDataSource(this); - mDataSource.open(); - /* use a separate thread as main thread for charon */ - mConnectionHandler = new Thread(this); - /* the thread is started when the service is bound */ - bindService(new Intent(this, VpnStateService.class), - mServiceConnection, Service.BIND_AUTO_CREATE); - } - - @Override - public void onRevoke() - { /* the system revoked the rights grated with the initial prepare() call. - * called when the user clicks disconnect in the system's VPN dialog */ - setNextProfile(null); - } - - @Override - public void onDestroy() - { - mTerminate = true; - setNextProfile(null); - try - { - mConnectionHandler.join(); - } - catch (InterruptedException e) - { - e.printStackTrace(); - } - if (mService != null) - { - unbindService(mServiceConnection); - } - mDataSource.close(); - } - - /** - * Set the profile that is to be initiated next. Notify the handler thread. - * - * @param profile the profile to initiate - */ - private void setNextProfile(VpnProfile profile) - { - synchronized (this) - { - this.mNextProfile = profile; - mProfileUpdated = true; - notifyAll(); - } - } - - @Override - public void run() - { - while (true) - { - synchronized (this) - { - try - { - while (!mProfileUpdated) - { - wait(); - } - - mProfileUpdated = false; - stopCurrentConnection(); - if (mNextProfile == null) - { - setState(State.DISABLED); - if (mTerminate) - { - break; - } - } - else - { - mCurrentProfile = mNextProfile; - mNextProfile = null; - - /* store this in a separate (volatile) variable to avoid - * a possible deadlock during deinitialization */ - mCurrentCertificateAlias = mCurrentProfile.getCertificateAlias(); - mCurrentUserCertificateAlias = mCurrentProfile.getUserCertificateAlias(); - - startConnection(mCurrentProfile); - mIsDisconnecting = false; - - BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName(), mCurrentProfile.getSplitTunneling()); - if (initializeCharon(builder, mLogFile, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD))) - { - Log.i(TAG, "charon started"); - SettingsWriter writer = new SettingsWriter(); - writer.setValue("global.language", Locale.getDefault().getLanguage()); - writer.setValue("global.mtu", mCurrentProfile.getMTU()); - writer.setValue("connection.type", mCurrentProfile.getVpnType().getIdentifier()); - writer.setValue("connection.server", mCurrentProfile.getGateway()); - writer.setValue("connection.port", mCurrentProfile.getPort()); - writer.setValue("connection.username", mCurrentProfile.getUsername()); - writer.setValue("connection.password", mCurrentProfile.getPassword()); - initiate(writer.serialize()); - } - else - { - Log.e(TAG, "failed to start charon"); - setError(ErrorState.GENERIC_ERROR); - setState(State.DISABLED); - mCurrentProfile = null; - } - } - } - catch (InterruptedException ex) - { - stopCurrentConnection(); - setState(State.DISABLED); - } - } - } - } - - /** - * Stop any existing connection by deinitializing charon. - */ - private void stopCurrentConnection() - { - synchronized (this) - { - if (mCurrentProfile != null) - { - setState(State.DISCONNECTING); - mIsDisconnecting = true; - deinitializeCharon(); - Log.i(TAG, "charon stopped"); - mCurrentProfile = null; - } - } - } - - /** - * Notify the state service about a new connection attempt. - * Called by the handler thread. - * - * @param profile currently active VPN profile - */ - private void startConnection(VpnProfile profile) - { - synchronized (mServiceLock) - { - if (mService != null) - { - mService.startConnection(profile); - } - } - } - - /** - * Update the current VPN state on the state service. Called by the handler - * thread and any of charon's threads. - * - * @param state current state - */ - private void setState(State state) - { - synchronized (mServiceLock) - { - if (mService != null) - { - mService.setState(state); - } - } - } - - /** - * Set an error on the state service. Called by the handler thread and any - * of charon's threads. - * - * @param error error state - */ - private void setError(ErrorState error) - { - synchronized (mServiceLock) - { - if (mService != null) - { - mService.setError(error); - } - } - } - - /** - * Set the IMC state on the state service. Called by the handler thread and - * any of charon's threads. - * - * @param state IMC state - */ - private void setImcState(ImcState state) - { - synchronized (mServiceLock) - { - if (mService != null) - { - mService.setImcState(state); - } - } - } - - /** - * Set an error on the state service. Called by the handler thread and any - * of charon's threads. - * - * @param error error state - */ - private void setErrorDisconnect(ErrorState error) - { - synchronized (mServiceLock) - { - if (mService != null) - { - if (!mIsDisconnecting) - { - mService.setError(error); - } - } - } - } - - /** - * Updates the state of the current connection. - * Called via JNI by different threads (but not concurrently). - * - * @param status new state - */ - public void updateStatus(int status) - { - switch (status) - { - case STATE_CHILD_SA_DOWN: - if (!mIsDisconnecting) - { - setState(State.CONNECTING); - } - break; - case STATE_CHILD_SA_UP: - setState(State.CONNECTED); - break; - case STATE_AUTH_ERROR: - setErrorDisconnect(ErrorState.AUTH_FAILED); - break; - case STATE_PEER_AUTH_ERROR: - setErrorDisconnect(ErrorState.PEER_AUTH_FAILED); - break; - case STATE_LOOKUP_ERROR: - setErrorDisconnect(ErrorState.LOOKUP_FAILED); - break; - case STATE_UNREACHABLE_ERROR: - setErrorDisconnect(ErrorState.UNREACHABLE); - break; - case STATE_GENERIC_ERROR: - setErrorDisconnect(ErrorState.GENERIC_ERROR); - break; - default: - Log.e(TAG, "Unknown status code received"); - break; - } - } - - /** - * Updates the IMC state of the current connection. - * Called via JNI by different threads (but not concurrently). - * - * @param value new state - */ - public void updateImcState(int value) - { - ImcState state = ImcState.fromValue(value); - if (state != null) - { - setImcState(state); - } - } - - /** - * Add a remediation instruction to the VPN state service. - * Called via JNI by different threads (but not concurrently). - * - * @param xml XML text - */ - public void addRemediationInstruction(String xml) - { - for (RemediationInstruction instruction : RemediationInstruction.fromXml(xml)) - { - synchronized (mServiceLock) - { - if (mService != null) - { - mService.addRemediationInstruction(instruction); - } - } - } - } - - /** - * Function called via JNI to generate a list of DER encoded CA certificates - * as byte array. - * - * @return a list of DER encoded CA certificates - */ - private byte[][] getTrustedCertificates() - { - ArrayList certs = new ArrayList(); - TrustedCertificateManager certman = TrustedCertificateManager.getInstance(); - try - { - String alias = this.mCurrentCertificateAlias; - if (alias != null) - { - 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) - { - e.printStackTrace(); - return null; - } - return certs.toArray(new byte[certs.size()][]); - } - - /** - * Function called via JNI to get a list containing the DER encoded certificates - * of the user selected certificate chain (beginning with the user certificate). - * - * Since this method is called from a thread of charon's thread pool we are safe - * to call methods on KeyChain directly. - * - * @return list containing the certificates (first element is the user certificate) - * @throws InterruptedException - * @throws KeyChainException - * @throws CertificateEncodingException - */ - private byte[][] getUserCertificate() throws KeyChainException, InterruptedException, CertificateEncodingException - { - ArrayList encodings = new ArrayList(); - X509Certificate[] chain = KeyChain.getCertificateChain(getApplicationContext(), mCurrentUserCertificateAlias); - if (chain == null || chain.length == 0) - { - return null; - } - for (X509Certificate cert : chain) - { - encodings.add(cert.getEncoded()); - } - return encodings.toArray(new byte[encodings.size()][]); - } - - /** - * Function called via JNI to get the private key the user selected. - * - * Since this method is called from a thread of charon's thread pool we are safe - * to call methods on KeyChain directly. - * - * @return the private key - * @throws InterruptedException - * @throws KeyChainException - * @throws CertificateEncodingException - */ - private PrivateKey getUserKey() throws KeyChainException, InterruptedException - { - return KeyChain.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias); - } - - /** - * Initialization of charon, provided by libandroidbridge.so - * - * @param builder BuilderAdapter for this connection - * @param logfile absolute path to the logfile - * @param boyd enable BYOD features - * @return TRUE if initialization was successful - */ - public native boolean initializeCharon(BuilderAdapter builder, String logfile, boolean byod); - - /** - * Deinitialize charon, provided by libandroidbridge.so - */ - public native void deinitializeCharon(); - - /** - * Initiate VPN, provided by libandroidbridge.so - */ - public native void initiate(String config); - - /** - * Adapter for VpnService.Builder which is used to access it safely via JNI. - * There is a corresponding C object to access it from native code. - */ - public class BuilderAdapter - { - private final String mName; - private final Integer mSplitTunneling; - private VpnService.Builder mBuilder; - private BuilderCache mCache; - private BuilderCache mEstablishedCache; - - public BuilderAdapter(String name, Integer splitTunneling) - { - mName = name; - mSplitTunneling = splitTunneling; - mBuilder = createBuilder(name); - mCache = new BuilderCache(mSplitTunneling); - } - - private VpnService.Builder createBuilder(String name) - { - VpnService.Builder builder = new CharonVpnService.Builder(); - builder.setSession(mName); - - /* even though the option displayed in the system dialog says "Configure" - * we just use our main Activity */ - Context context = getApplicationContext(); - Intent intent = new Intent(context, MainActivity.class); - PendingIntent pending = PendingIntent.getActivity(context, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - builder.setConfigureIntent(pending); - return builder; - } - - public synchronized boolean addAddress(String address, int prefixLength) - { - try - { - mCache.addAddress(address, prefixLength); - } - catch (IllegalArgumentException ex) - { - return false; - } - return true; - } - - public synchronized boolean addDnsServer(String address) - { - try - { - mBuilder.addDnsServer(address); - mCache.recordAddressFamily(address); - } - catch (IllegalArgumentException ex) - { - return false; - } - return true; - } - - public synchronized boolean addRoute(String address, int prefixLength) - { - try - { - mCache.addRoute(address, prefixLength); - } - catch (IllegalArgumentException ex) - { - return false; - } - return true; - } - - public synchronized boolean addSearchDomain(String domain) - { - try - { - mBuilder.addSearchDomain(domain); - } - catch (IllegalArgumentException ex) - { - return false; - } - return true; - } - - public synchronized boolean setMtu(int mtu) - { - try - { - mCache.setMtu(mtu); - } - catch (IllegalArgumentException ex) - { - return false; - } - return true; - } - - public synchronized int establish() - { - ParcelFileDescriptor fd; - try - { - mCache.applyData(mBuilder); - fd = mBuilder.establish(); - } - catch (Exception ex) - { - ex.printStackTrace(); - return -1; - } - if (fd == null) - { - return -1; - } - /* now that the TUN device is created we don't need the current - * builder anymore, but we might need another when reestablishing */ - mBuilder = createBuilder(mName); - mEstablishedCache = mCache; - mCache = new BuilderCache(mSplitTunneling); - return fd.detachFd(); - } - - public synchronized int establishNoDns() - { - ParcelFileDescriptor fd; - - if (mEstablishedCache == null) - { - return -1; - } - try - { - Builder builder = createBuilder(mName); - mEstablishedCache.applyData(builder); - fd = builder.establish(); - } - catch (Exception ex) - { - ex.printStackTrace(); - return -1; - } - if (fd == null) - { - return -1; - } - return fd.detachFd(); - } - } - - /** - * Cache non DNS related information so we can recreate the builder without - * that information when reestablishing IKE_SAs - */ - public class BuilderCache - { - private final List mAddresses = new ArrayList(); - private final List mRoutesIPv4 = new ArrayList(); - private final List mRoutesIPv6 = new ArrayList(); - private final int mSplitTunneling; - private int mMtu; - private boolean mIPv4Seen, mIPv6Seen; - - public BuilderCache(Integer splitTunneling) - { - mSplitTunneling = splitTunneling != null ? splitTunneling : 0; - } - - public void addAddress(String address, int prefixLength) - { - mAddresses.add(new PrefixedAddress(address, prefixLength)); - recordAddressFamily(address); - } - - public void addRoute(String address, int prefixLength) - { - try - { - if (isIPv6(address)) - { - mRoutesIPv6.add(new PrefixedAddress(address, prefixLength)); - } - else - { - mRoutesIPv4.add(new PrefixedAddress(address, prefixLength)); - } - } - catch (UnknownHostException ex) - { - ex.printStackTrace(); - } - } - - public void setMtu(int mtu) - { - mMtu = mtu; - } - - public void recordAddressFamily(String address) - { - try - { - if (isIPv6(address)) - { - mIPv6Seen = true; - } - else - { - mIPv4Seen = true; - } - } - catch (UnknownHostException ex) - { - ex.printStackTrace(); - } - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public void applyData(VpnService.Builder builder) - { - for (PrefixedAddress address : mAddresses) - { - builder.addAddress(address.mAddress, address.mPrefix); - } - /* add routes depending on whether split tunneling is allowed or not, - * that is, whether we have to handle and block non-VPN traffic */ - if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) == 0) - { - if (mIPv4Seen) - { /* split tunneling is used depending on the routes */ - for (PrefixedAddress route : mRoutesIPv4) - { - builder.addRoute(route.mAddress, route.mPrefix); - } - } - else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - { /* allow traffic that would otherwise be blocked to bypass the VPN */ - builder.allowFamily(OsConstants.AF_INET); - } - } - else if (mIPv4Seen) - { /* only needed if we've seen any addresses. otherwise, traffic - * is blocked by default (we also install no routes in that case) */ - builder.addRoute("0.0.0.0", 0); - } - /* same thing for IPv6 */ - if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) == 0) - { - if (mIPv6Seen) - { - for (PrefixedAddress route : mRoutesIPv6) - { - builder.addRoute(route.mAddress, route.mPrefix); - } - } - else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - { - builder.allowFamily(OsConstants.AF_INET6); - } - } - else if (mIPv6Seen) - { - builder.addRoute("::", 0); - } - builder.setMtu(mMtu); - } - - private boolean isIPv6(String address) throws UnknownHostException - { - InetAddress addr = InetAddress.getByName(address); - if (addr instanceof Inet4Address) - { - return false; - } - else if (addr instanceof Inet6Address) - { - return true; - } - return false; - } - - private class PrefixedAddress - { - public String mAddress; - public int mPrefix; - - public PrefixedAddress(String address, int prefix) - { - this.mAddress = address; - this.mPrefix = prefix; - } - } - } - - /* - * The libraries are extracted to /data/data/org.strongswan.android/... - * during installation. On newer releases most are loaded in JNI_OnLoad. - */ - static - { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) - { - System.loadLibrary("strongswan"); - - if (MainActivity.USE_BYOD) - { - System.loadLibrary("tncif"); - System.loadLibrary("tnccs"); - System.loadLibrary("imcv"); - } - - System.loadLibrary("hydra"); - System.loadLibrary("charon"); - System.loadLibrary("ipsec"); - } - System.loadLibrary("androidbridge"); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/NetworkManager.java b/src/frontends/android/src/org/strongswan/android/logic/NetworkManager.java deleted file mode 100644 index ebe1d0080..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/NetworkManager.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2012-2015 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.logic; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; - -public class NetworkManager extends BroadcastReceiver -{ - private final Context mContext; - private boolean mRegistered; - - public NetworkManager(Context context) - { - mContext = context; - } - - public void Register() - { - mContext.registerReceiver(this, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - } - - public void Unregister() - { - mContext.unregisterReceiver(this); - } - - public boolean isConnected() - { - ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo info = null; - if (cm != null) - { - info = cm.getActiveNetworkInfo(); - } - return info != null && info.isConnected(); - } - - @Override - public void onReceive(Context context, Intent intent) - { - networkChanged(!isConnected()); - } - - /** - * Notify the native parts about a network change - * - * @param disconnected true if no connection is available at the moment - */ - public native void networkChanged(boolean disconnected); -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/StrongSwanApplication.java b/src/frontends/android/src/org/strongswan/android/logic/StrongSwanApplication.java deleted file mode 100644 index d642b67b3..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/StrongSwanApplication.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.logic; - -import java.security.Security; - -import org.strongswan.android.security.LocalCertificateKeyStoreProvider; - -import android.app.Application; -import android.content.Context; - -public class StrongSwanApplication extends Application -{ - private static Context mContext; - - static { - Security.addProvider(new LocalCertificateKeyStoreProvider()); - } - - @Override - public void onCreate() - { - super.onCreate(); - StrongSwanApplication.mContext = getApplicationContext(); - } - - /** - * Returns the current application context - * @return context - */ - public static Context getContext() - { - return StrongSwanApplication.mContext; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java b/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java deleted file mode 100644 index 82a7cbe4e..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (C) 2012-2014 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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.logic; - -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; - -import android.util.Log; - -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(); - - public enum TrustedCertificateSource - { - SYSTEM("system:"), - USER("user:"), - LOCAL("local:"); - - private final String mPrefix; - - private TrustedCertificateSource(String prefix) - { - mPrefix = prefix; - } - - private String getPrefix() - { - return mPrefix; - } - } - - /** - * Private constructor to prevent instantiation from other classes. - */ - private TrustedCertificateManager() - { - for (String name : new String[] { "LocalCertificateStore", "AndroidCAStore" }) - { - KeyStore store; - try - { - store = KeyStore.getInstance(name); - store.load(null,null); - mKeyStores.add(store); - } - catch (Exception e) - { - Log.e(TAG, "Unable to load KeyStore: " + name); - e.printStackTrace(); - } - } - } - - /** - * This is not instantiated until the first call to getInstance() - */ - private static class Singleton { - public static final TrustedCertificateManager mInstance = new TrustedCertificateManager(); - } - - /** - * Get the single instance of the CA certificate manager. - * @return CA certificate manager - */ - public static TrustedCertificateManager getInstance() - { - return Singleton.mInstance; - } - - /** - * 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 reset() - { - Log.d(TAG, "Force reload of cached CA certificates on next load"); - this.mReload = true; - return this; - } - - /** - * Ensures that the certificates are loaded but does not force a reload. - * As this takes a while if the certificates are not loaded yet it should - * be called asynchronously. - * @return reference to itself - */ - public TrustedCertificateManager load() - { - Log.d(TAG, "Ensure cached CA certificates are loaded"); - this.mLock.writeLock().lock(); - if (!this.mLoaded || this.mReload) - { - this.mReload = false; - loadCertificates(); - } - this.mLock.writeLock().unlock(); - return this; - } - - /** - * Opens the CA certificate KeyStore and loads the cached certificates. - * The lock must be locked when calling this method. - */ - private void loadCertificates() - { - Log.d(TAG, "Load cached CA certificates"); - Hashtable certs = new Hashtable(); - for (KeyStore store : this.mKeyStores) - { - 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 - */ - private void fetchCertificates(Hashtable certs, KeyStore store) - { - try - { - Enumeration aliases = store.aliases(); - while (aliases.hasMoreElements()) - { - String alias = aliases.nextElement(); - Certificate cert; - cert = store.getCertificate(alias); - if (cert != null && cert instanceof X509Certificate) - { - certs.put(alias, (X509Certificate)cert); - } - } - } - catch (KeyStoreException ex) - { - ex.printStackTrace(); - } - } - - /** - * Retrieve the CA certificate with the given alias. - * @param alias alias of the certificate to get - * @return the certificate, null if not found - */ - public X509Certificate getCACertificateFromAlias(String alias) - { - X509Certificate certificate = null; - - if (this.mLock.readLock().tryLock()) - { - certificate = this.mCACerts.get(alias); - this.mLock.readLock().unlock(); - } - else - { /* if we cannot get the lock load it directly from the KeyStore, - * should be fast for a single certificate */ - for (KeyStore store : this.mKeyStores) - { - try - { - Certificate cert = store.getCertificate(alias); - if (cert != null && cert instanceof X509Certificate) - { - certificate = (X509Certificate)cert; - break; - } - } - catch (KeyStoreException e) - { - e.printStackTrace(); - } - } - } - return certificate; - } - - /** - * Get all CA certificates (from all keystores). - * @return Hashtable mapping aliases to certificates - */ - @SuppressWarnings("unchecked") - public Hashtable getAllCACertificates() - { - Hashtable certs; - this.mLock.readLock().lock(); - certs = (Hashtable)this.mCACerts.clone(); - this.mLock.readLock().unlock(); - return certs; - } - - /** - * Get all certificates from the given source. - * @param source type to filter certificates - * @return Hashtable mapping aliases to certificates - */ - public Hashtable getCACertificates(TrustedCertificateSource source) - { - Hashtable certs = new Hashtable(); - this.mLock.readLock().lock(); - for (String alias : this.mCACerts.keySet()) - { - if (alias.startsWith(source.getPrefix())) - { - certs.put(alias, this.mCACerts.get(alias)); - } - } - this.mLock.readLock().unlock(); - return certs; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/VpnStateService.java b/src/frontends/android/src/org/strongswan/android/logic/VpnStateService.java deleted file mode 100644 index 7b40e942f..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/VpnStateService.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (C) 2012-2013 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.logic; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.Callable; - -import org.strongswan.android.data.VpnProfile; -import org.strongswan.android.logic.imc.ImcState; -import org.strongswan.android.logic.imc.RemediationInstruction; - -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; - -public class VpnStateService extends Service -{ - private final List mListeners = new ArrayList(); - private final IBinder mBinder = new LocalBinder(); - private long mConnectionID = 0; - private Handler mHandler; - private VpnProfile mProfile; - private State mState = State.DISABLED; - private ErrorState mError = ErrorState.NO_ERROR; - private ImcState mImcState = ImcState.UNKNOWN; - private final LinkedList mRemediationInstructions = new LinkedList(); - - public enum State - { - DISABLED, - CONNECTING, - CONNECTED, - DISCONNECTING, - } - - public enum ErrorState - { - NO_ERROR, - AUTH_FAILED, - PEER_AUTH_FAILED, - LOOKUP_FAILED, - UNREACHABLE, - GENERIC_ERROR, - } - - /** - * Listener interface for bound clients that are interested in changes to - * this Service. - */ - public interface VpnStateListener - { - public void stateChanged(); - } - - /** - * Simple Binder that allows to directly access this Service class itself - * after binding to it. - */ - public class LocalBinder extends Binder - { - public VpnStateService getService() - { - return VpnStateService.this; - } - } - - @Override - public void onCreate() - { - /* this handler allows us to notify listeners from the UI thread and - * not from the threads that actually report any state changes */ - mHandler = new Handler(); - } - - @Override - public IBinder onBind(Intent intent) - { - return mBinder; - } - - @Override - public void onDestroy() - { - } - - /** - * Register a listener with this Service. We assume this is called from - * the main thread so no synchronization is happening. - * - * @param listener listener to register - */ - public void registerListener(VpnStateListener listener) - { - mListeners.add(listener); - } - - /** - * Unregister a listener from this Service. - * - * @param listener listener to unregister - */ - public void unregisterListener(VpnStateListener listener) - { - mListeners.remove(listener); - } - - /** - * Get the current VPN profile. - * - * @return profile - */ - public VpnProfile getProfile() - { /* only updated from the main thread so no synchronization needed */ - return mProfile; - } - - /** - * Get the current connection ID. May be used to track which state - * changes have already been handled. - * - * Is increased when startConnection() is called. - * - * @return connection ID - */ - public long getConnectionID() - { /* only updated from the main thread so no synchronization needed */ - return mConnectionID; - } - - /** - * Get the current state. - * - * @return state - */ - public State getState() - { /* only updated from the main thread so no synchronization needed */ - return mState; - } - - /** - * Get the current error, if any. - * - * @return error - */ - public ErrorState getErrorState() - { /* only updated from the main thread so no synchronization needed */ - return mError; - } - - /** - * Get the current IMC state, if any. - * - * @return imc state - */ - public ImcState getImcState() - { /* only updated from the main thread so no synchronization needed */ - return mImcState; - } - - /** - * Get the remediation instructions, if any. - * - * @return read-only list of instructions - */ - public List getRemediationInstructions() - { /* only updated from the main thread so no synchronization needed */ - return Collections.unmodifiableList(mRemediationInstructions); - } - - /** - * Disconnect any existing connection and shutdown the daemon, the - * VpnService is not stopped but it is reset so new connections can be - * started. - */ - public void disconnect() - { - /* as soon as the TUN device is created by calling establish() on the - * VpnService.Builder object the system binds to the service and keeps - * bound until the file descriptor of the TUN device is closed. thus - * calling stopService() here would not stop (destroy) the service yet, - * instead we call startService() with an empty Intent which shuts down - * the daemon (and closes the TUN device, if any) */ - Context context = getApplicationContext(); - Intent intent = new Intent(context, CharonVpnService.class); - context.startService(intent); - } - - /** - * Update state and notify all listeners about the change. By using a Handler - * this is done from the main UI thread and not the initial reporter thread. - * Also, in doing the actual state change from the main thread, listeners - * see all changes and none are skipped. - * - * @param change the state update to perform before notifying listeners, returns true if state changed - */ - private void notifyListeners(final Callable change) - { - mHandler.post(new Runnable() { - @Override - public void run() - { - try - { - if (change.call()) - { /* otherwise there is no need to notify the listeners */ - for (VpnStateListener listener : mListeners) - { - listener.stateChanged(); - } - } - } - catch (Exception e) - { - e.printStackTrace(); - } - } - }); - } - - /** - * Called when a connection is started. Sets the currently active VPN - * profile, resets IMC and Error state variables, sets the State to - * CONNECTING, increases the connection ID, and notifies all listeners. - * - * May be called from threads other than the main thread. - * - * @param profile current profile - */ - public void startConnection(final VpnProfile profile) - { - notifyListeners(new Callable() { - @Override - public Boolean call() throws Exception - { - VpnStateService.this.mConnectionID++; - VpnStateService.this.mProfile = profile; - VpnStateService.this.mState = State.CONNECTING; - VpnStateService.this.mError = ErrorState.NO_ERROR; - VpnStateService.this.mImcState = ImcState.UNKNOWN; - VpnStateService.this.mRemediationInstructions.clear(); - return true; - } - }); - } - - /** - * Update the state and notify all listeners, if changed. - * - * May be called from threads other than the main thread. - * - * @param state new state - */ - public void setState(final State state) - { - notifyListeners(new Callable() { - @Override - public Boolean call() throws Exception - { - if (VpnStateService.this.mState != state) - { - VpnStateService.this.mState = state; - return true; - } - return false; - } - }); - } - - /** - * Set the current error state and notify all listeners, if changed. - * - * May be called from threads other than the main thread. - * - * @param error error state - */ - public void setError(final ErrorState error) - { - notifyListeners(new Callable() { - @Override - public Boolean call() throws Exception - { - if (VpnStateService.this.mError != error) - { - VpnStateService.this.mError = error; - return true; - } - return false; - } - }); - } - - /** - * Set the current IMC state and notify all listeners, if changed. - * - * Setting the state to UNKNOWN clears all remediation instructions. - * - * May be called from threads other than the main thread. - * - * @param error error state - */ - public void setImcState(final ImcState state) - { - notifyListeners(new Callable() { - @Override - public Boolean call() throws Exception - { - if (state == ImcState.UNKNOWN) - { - VpnStateService.this.mRemediationInstructions.clear(); - } - if (VpnStateService.this.mImcState != state) - { - VpnStateService.this.mImcState = state; - return true; - } - return false; - } - }); - } - - /** - * Add the given remediation instruction to the internal list. Listeners - * are not notified. - * - * Instructions are cleared if the IMC state is set to UNKNOWN. - * - * May be called from threads other than the main thread. - * - * @param instruction remediation instruction - */ - public void addRemediationInstruction(final RemediationInstruction instruction) - { - mHandler.post(new Runnable() { - @Override - public void run() - { - VpnStateService.this.mRemediationInstructions.add(instruction); - } - }); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/AndroidImc.java b/src/frontends/android/src/org/strongswan/android/logic/imc/AndroidImc.java deleted file mode 100644 index 351fab801..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/AndroidImc.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2013 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.logic.imc; - -import org.strongswan.android.logic.imc.attributes.Attribute; -import org.strongswan.android.logic.imc.attributes.AttributeType; -import org.strongswan.android.logic.imc.collectors.Collector; -import org.strongswan.android.logic.imc.collectors.DeviceIdCollector; -import org.strongswan.android.logic.imc.collectors.InstalledPackagesCollector; -import org.strongswan.android.logic.imc.collectors.PortFilterCollector; -import org.strongswan.android.logic.imc.collectors.ProductInformationCollector; -import org.strongswan.android.logic.imc.collectors.SettingsCollector; -import org.strongswan.android.logic.imc.collectors.StringVersionCollector; - -import android.content.Context; - -public class AndroidImc -{ - private final Context mContext; - - public AndroidImc(Context context) - { - mContext = context; - } - - /** - * Get a measurement (the binary encoding of the requested attribute) for - * the given vendor specific attribute type. - * - * @param vendor vendor ID - * @param type vendor specific attribute type - * @return encoded attribute, or null if not available or failed - */ - public byte[] getMeasurement(int vendor, int type) - { - return getMeasurement(vendor, type, null); - } - - /** - * Get a measurement (the binary encoding of the requested attribute) for - * the given vendor specific attribute type. - * - * @param vendor vendor ID - * @param type vendor specific attribute type - * @param args optional arguments for a measurement - * @return encoded attribute, or null if not available or failed - */ - public byte[] getMeasurement(int vendor, int type, String[] args) - { - AttributeType attributeType = AttributeType.fromValues(vendor, type); - Collector collector = null; - - switch (attributeType) - { - case IETF_PRODUCT_INFORMATION: - collector = new ProductInformationCollector(); - break; - case IETF_STRING_VERSION: - collector = new StringVersionCollector(); - break; - case IETF_PORT_FILTER: - collector = new PortFilterCollector(); - break; - case IETF_INSTALLED_PACKAGES: - collector = new InstalledPackagesCollector(mContext); - break; - case ITA_SETTINGS: - collector = new SettingsCollector(mContext, args); - break; - case ITA_DEVICE_ID: - collector = new DeviceIdCollector(mContext); - break; - default: - break; - } - if (collector != null) - { - Attribute attribute = collector.getMeasurement(); - if (attribute != null) - { - return attribute.getEncoding(); - } - } - return null; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/ImcState.java b/src/frontends/android/src/org/strongswan/android/logic/imc/ImcState.java deleted file mode 100644 index 4fc3834f9..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/ImcState.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2013 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.logic.imc; - -public enum ImcState -{ - UNKNOWN(0), - ALLOW(1), - BLOCK(2), - ISOLATE(3); - - private final int mValue; - - private ImcState(int value) - { - mValue = value; - } - - /** - * Get the numeric value of the IMC state. - * @return numeric value - */ - public int getValue() - { - return mValue; - } - - /** - * Get the enum entry from a numeric value, if defined - * - * @param value numeric value - * @return the enum entry or null - */ - public static ImcState fromValue(int value) - { - for (ImcState state : ImcState.values()) - { - if (state.mValue == value) - { - return state; - } - } - return null; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/RemediationInstruction.java b/src/frontends/android/src/org/strongswan/android/logic/imc/RemediationInstruction.java deleted file mode 100644 index 5435ad88c..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/RemediationInstruction.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (C) 2013 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.logic.imc; - -import java.io.IOException; -import java.io.StringReader; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Xml; - -public class RemediationInstruction implements Parcelable -{ - private String mTitle; - private String mDescription; - private String mHeader; - private final List mItems = new LinkedList(); - - @Override - public int describeContents() - { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) - { - dest.writeString(mTitle); - dest.writeString(mDescription); - dest.writeString(mHeader); - dest.writeStringList(mItems); - } - - public static final Parcelable.Creator CREATOR = new Creator() { - - @Override - public RemediationInstruction[] newArray(int size) - { - return new RemediationInstruction[size]; - } - - @Override - public RemediationInstruction createFromParcel(Parcel source) - { - return new RemediationInstruction(source); - } - }; - - private RemediationInstruction() - { - } - - private RemediationInstruction(Parcel source) - { - mTitle = source.readString(); - mDescription = source.readString(); - mHeader = source.readString(); - source.readStringList(mItems); - } - - public String getTitle() - { - return mTitle; - } - - private void setTitle(String title) - { - mTitle = title; - } - - public String getDescription() - { - return mDescription; - } - - private void setDescription(String description) - { - mDescription = description; - } - - public String getHeader() - { - return mHeader; - } - - private void setHeader(String header) - { - mHeader = header; - } - - public List getItems() - { - return Collections.unmodifiableList(mItems); - } - - private void addItem(String item) - { - mItems.add(item); - } - - /** - * Create a list of RemediationInstruction objects from the given XML data. - * - * @param xml XML data - * @return list of RemediationInstruction objects - */ - public static List fromXml(String xml) - { - List instructions = new LinkedList(); - XmlPullParser parser = Xml.newPullParser(); - try - { - parser.setInput(new StringReader(xml)); - parser.nextTag(); - readInstructions(parser, instructions); - } - catch (XmlPullParserException e) - { - e.printStackTrace(); - } - catch (IOException e) - { - e.printStackTrace(); - } - return instructions; - } - - /** - * Read a <remediationinstructions> element and store the extracted - * RemediationInstruction objects in the given list. - * - * @param parser - * @param instructions - * @throws XmlPullParserException - * @throws IOException - */ - private static void readInstructions(XmlPullParser parser, List instructions) throws XmlPullParserException, IOException - { - parser.require(XmlPullParser.START_TAG, null, "remediationinstructions"); - while (parser.next() != XmlPullParser.END_TAG) - { - if (parser.getEventType() != XmlPullParser.START_TAG) - { - continue; - } - if (parser.getName().equals("instruction")) - { - RemediationInstruction instruction = new RemediationInstruction(); - readInstruction(parser, instruction); - instructions.add(instruction); - } - else - { - skipTag(parser); - } - } - } - - /** - * Read an <instruction> element and store the information in the - * given RemediationInstruction object. - * - * @param parser - * @param instruction - * @throws XmlPullParserException - * @throws IOException - */ - private static void readInstruction(XmlPullParser parser, RemediationInstruction instruction) throws XmlPullParserException, IOException - { - parser.require(XmlPullParser.START_TAG, null, "instruction"); - while (parser.next() != XmlPullParser.END_TAG) - { - if (parser.getEventType() != XmlPullParser.START_TAG) - { - continue; - } - String name = parser.getName(); - if (name.equals("title")) - { - instruction.setTitle(parser.nextText()); - } - else if (name.equals("description")) - { - instruction.setDescription(parser.nextText()); - } - else if (name.equals("itemsheader")) - { - instruction.setHeader(parser.nextText()); - } - else if (name.equals("items")) - { - readItems(parser, instruction); - } - else - { - skipTag(parser); - } - } - } - - /** - * Read all items of an <items> node and add them to the given - * RemediationInstruction object. - * - * @param parser - * @param instruction - * @throws XmlPullParserException - * @throws IOException - */ - private static void readItems(XmlPullParser parser, RemediationInstruction instruction) throws XmlPullParserException, IOException - { - while (parser.next() != XmlPullParser.END_TAG) - { - if (parser.getEventType() != XmlPullParser.START_TAG) - { - continue; - } - if (parser.getName().equals("item")) - { - instruction.addItem(parser.nextText()); - } - else - { - skipTag(parser); - } - } - } - - /** - * Skip the current tag and all child elements. - * - * @param parser - * @throws XmlPullParserException - * @throws IOException - */ - private static void skipTag(XmlPullParser parser) throws XmlPullParserException, IOException - { - int depth = 1; - - parser.require(XmlPullParser.START_TAG, null, null); - while (depth != 0) - { - switch (parser.next()) - { - case XmlPullParser.END_TAG: - depth--; - break; - case XmlPullParser.START_TAG: - depth++; - break; - } - } - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/Attribute.java b/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/Attribute.java deleted file mode 100644 index ca759000f..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/Attribute.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2013 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.logic.imc.attributes; - -/** - * Interface to be implemented by attribute classes - */ -public interface Attribute -{ - /** - * Returns the binary encoding of the attribute - * @return binary encoding - */ - public byte[] getEncoding(); -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/AttributeType.java b/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/AttributeType.java deleted file mode 100644 index 11f1c61da..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/AttributeType.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2013 Tobias Brunner - * Copyright (C) 2012 Christoph Buehler - * Copyright (C) 2012 Patrick Loetscher - * 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.logic.imc.attributes; - -public enum AttributeType -{ - /* IETF standard PA-TNC attribute types defined by RFC 5792 */ - IETF_TESTING(PrivateEnterpriseNumber.IETF, 0), - IETF_ATTRIBUTE_REQUEST(PrivateEnterpriseNumber.IETF, 1), - IETF_PRODUCT_INFORMATION(PrivateEnterpriseNumber.IETF, 2), - IETF_NUMERIC_VERSION(PrivateEnterpriseNumber.IETF, 3), - IETF_STRING_VERSION(PrivateEnterpriseNumber.IETF, 4), - IETF_OPERATIONAL_STATUS(PrivateEnterpriseNumber.IETF, 5), - IETF_PORT_FILTER(PrivateEnterpriseNumber.IETF, 6), - IETF_INSTALLED_PACKAGES(PrivateEnterpriseNumber.IETF, 7), - IETF_PA_TNC_ERROR(PrivateEnterpriseNumber.IETF, 8), - IETF_ASSESSMENT_RESULT(PrivateEnterpriseNumber.IETF, 9), - IETF_REMEDIATION_INSTRUCTIONS(PrivateEnterpriseNumber.IETF, 10), - IETF_FORWARDING_ENABLED(PrivateEnterpriseNumber.IETF, 11), - IETF_FACTORY_DEFAULT_PWD_ENABLED(PrivateEnterpriseNumber.IETF, 12), - IETF_RESERVED(PrivateEnterpriseNumber.IETF, 0xffffffff), - /* ITA attributes */ - ITA_SETTINGS(PrivateEnterpriseNumber.ITA, 4), - ITA_DEVICE_ID(PrivateEnterpriseNumber.ITA, 8); - - private PrivateEnterpriseNumber mVendor; - private int mType; - - /** - * Enum type for vendor specific attributes (defined in their namespace) - * - * @param vendor private enterprise number of vendor - * @param type vendor specific attribute type - */ - private AttributeType(PrivateEnterpriseNumber vendor, int type) - { - mVendor = vendor; - mType = type; - } - - /** - * Get private enterprise number of vendor - * - * @return PEN - */ - public PrivateEnterpriseNumber getVendor() - { - return mVendor; - } - - /** - * Get vendor specific type - * - * @return type - */ - public int getType() - { - return mType; - } - - /** - * Get the enum entry from the given numeric values, if defined - * - * @param vendor vendor id - * @param type vendor specific type - * @return enum entry or null - */ - public static AttributeType fromValues(int vendor, int type) - { - PrivateEnterpriseNumber pen = PrivateEnterpriseNumber.fromValue(vendor); - - if (pen == null) - { - return null; - } - for (AttributeType attr : AttributeType.values()) - { - if (attr.mVendor == pen && attr.mType == type) - { - return attr; - } - } - return null; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/DeviceIdAttribute.java b/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/DeviceIdAttribute.java deleted file mode 100644 index ecab7db24..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/DeviceIdAttribute.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2013 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.logic.imc.attributes; - -/** - * ITA Device ID attribute - * - * 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Device ID (Variable Length) | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ -public class DeviceIdAttribute implements Attribute -{ - private String mDeviceId; - - /** - * Set the device ID - * @param version version number - */ - public void setDeviceId(String deviceId) - { - this.mDeviceId = deviceId; - } - - @Override - public byte[] getEncoding() - { - return mDeviceId.getBytes(); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/InstalledPackagesAttribute.java b/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/InstalledPackagesAttribute.java deleted file mode 100644 index dd1ad7292..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/InstalledPackagesAttribute.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2013 Tobias Brunner - * Copyright (C) 2012 Christoph Buehler - * Copyright (C) 2012 Patrick Loetscher - * 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.logic.imc.attributes; - -import java.util.LinkedList; - -import org.strongswan.android.utils.BufferedByteWriter; - -import android.util.Pair; - -/** - * PA-TNC Installed Packages attribute (see section 4.2.7 of RFC 5792) - * - * 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Reserved | Package Count | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Pkg Name Len | Package Name (Variable Length) | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Version Len | Package Version Number (Variable Length) | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ -public class InstalledPackagesAttribute implements Attribute -{ - private final short RESERVED = 0; - private final LinkedList> mPackages = new LinkedList>(); - - /** - * Add an installed package to this attribute. - * @param name name of the package - * @param version version number of the package - */ - public void addPackage(String name, String version) - { - mPackages.add(new Pair(name, version)); - } - - @Override - public byte[] getEncoding() - { - BufferedByteWriter writer = new BufferedByteWriter(); - writer.put16(RESERVED); - writer.put16((short)mPackages.size()); - for (Pair pair : mPackages) - { - writer.putLen8(pair.first.getBytes()); - writer.putLen8(pair.second.getBytes()); - } - return writer.toByteArray(); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/PortFilterAttribute.java b/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/PortFilterAttribute.java deleted file mode 100644 index 191690b94..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/PortFilterAttribute.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2013 Tobias Brunner - * Copyright (C) 2012 Christoph Buehler - * Copyright (C) 2012 Patrick Loetscher - * 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.logic.imc.attributes; - -import java.util.LinkedList; - -import org.strongswan.android.logic.imc.collectors.Protocol; -import org.strongswan.android.utils.BufferedByteWriter; - -import android.util.Pair; - -/** - * PA-TNC Port Filter attribute (see section 4.2.6 of RFC 5792) - * - * 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Reserved |B| Protocol | Port Number | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Reserved |B| Protocol | Port Number | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ -public class PortFilterAttribute implements Attribute -{ - private final LinkedList> mPorts = new LinkedList>(); - - /** - * Add an open port with the given protocol and port number - * @param protocol transport protocol - * @param port port number - */ - public void addPort(Protocol protocol, short port) - { - mPorts.add(new Pair(protocol, port)); - } - - @Override - public byte[] getEncoding() - { - BufferedByteWriter writer = new BufferedByteWriter(); - for (Pair port : mPorts) - { - /* we report open ports, so the BLOCKED flag is not set */ - writer.put((byte)0); - writer.put(port.first.getValue()); - writer.put16(port.second); - } - return writer.toByteArray(); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/PrivateEnterpriseNumber.java b/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/PrivateEnterpriseNumber.java deleted file mode 100644 index 9db702ec0..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/PrivateEnterpriseNumber.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2013 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.logic.imc.attributes; - -public enum PrivateEnterpriseNumber -{ - IETF(0x000000), - GOOGLE(0x002B79), - ITA(0x00902a), - UNASSIGNED(0xfffffe), - RESERVED(0xffffff); - - private int mValue; - - /** - * Enum for private enterprise numbers (PEN) as allocated by IANA - * - * @param value numeric value - */ - private PrivateEnterpriseNumber(int value) - { - mValue = value; - } - - /** - * Get the numeric value of a PEN - * - * @return numeric value - */ - public int getValue() - { - return mValue; - } - - /** - * Get the enum entry from a numeric value, if defined - * - * @param value numeric value - * @return the enum entry or null - */ - public static PrivateEnterpriseNumber fromValue(int value) - { - for (PrivateEnterpriseNumber pen : PrivateEnterpriseNumber.values()) - { - if (pen.mValue == value) - { - return pen; - } - } - return null; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/ProductInformationAttribute.java b/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/ProductInformationAttribute.java deleted file mode 100644 index cace18d55..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/ProductInformationAttribute.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2013 Tobias Brunner - * Copyright (C) 2012 Christoph Buehler - * Copyright (C) 2012 Patrick Loetscher - * 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.logic.imc.attributes; - -import org.strongswan.android.utils.BufferedByteWriter; - -/** - * PA-TNC Product Information attribute (see section 4.2.2 of RFC 5792) - * - * 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Product Vendor ID | Product ID | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Product ID | Product Name (Variable Length) | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ -public class ProductInformationAttribute implements Attribute -{ - private final String PRODUCT_NAME = "Android"; - private final short PRODUCT_ID = 0; - - @Override - public byte[] getEncoding() - { - BufferedByteWriter writer = new BufferedByteWriter(); - writer.put24(PrivateEnterpriseNumber.GOOGLE.getValue()); - writer.put16(PRODUCT_ID); - writer.put(PRODUCT_NAME.getBytes()); - return writer.toByteArray(); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/SettingsAttribute.java b/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/SettingsAttribute.java deleted file mode 100644 index 37d820168..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/SettingsAttribute.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2013 Tobias Brunner - * Copyright (C) 2012 Christoph Buehler - * Copyright (C) 2012 Patrick Loetscher - * 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.logic.imc.attributes; - -import java.util.LinkedList; - -import org.strongswan.android.utils.BufferedByteWriter; - -import android.util.Pair; - -/** - * ITA Settings attribute - * - * 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Settings Count | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Name Length | Name (Variable Length) ~ - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * ~ Name (Variable Length) ~ - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Value Length | Value (Variable Length) ~ - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * ~ Value (Variable Length) ~ - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Name Length | Name (Variable Length) ~ - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * ~ Name (Variable Length) ~ - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Value Length | Value (Variable Length) ~ - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * ~ Value (Variable Length) ~ - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * ........................... - */ -public class SettingsAttribute implements Attribute -{ - private final LinkedList> mSettings = new LinkedList>(); - - /** - * Add a setting to this attribute. - * @param name name of the setting - * @param value value of the setting - */ - public void addSetting(String name, String value) - { - mSettings.add(new Pair(name, value)); - } - - @Override - public byte[] getEncoding() - { - BufferedByteWriter writer = new BufferedByteWriter(); - writer.put32(mSettings.size()); - for (Pair pair : mSettings) - { - writer.putLen16(pair.first.getBytes()); - writer.putLen16(pair.second.getBytes()); - } - return writer.toByteArray(); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/StringVersionAttribute.java b/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/StringVersionAttribute.java deleted file mode 100644 index 4b6f2bc37..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/attributes/StringVersionAttribute.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2013 Tobias Brunner - * Copyright (C) 2012 Christoph Buehler - * Copyright (C) 2012 Patrick Loetscher - * 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.logic.imc.attributes; - -import org.strongswan.android.utils.BufferedByteWriter; - -/** - * PA-TNC String Version attribute (see section 4.2.4 of RFC 5792) - * - * 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Version Len | Product Version Number (Variable Length) | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Build Num Len | Internal Build Number (Variable Length) | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | Config. Len | Configuration Version Number (Variable Length)| - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ -public class StringVersionAttribute implements Attribute -{ - private String mVersionNumber; - private String mBuildNumber; - - /** - * Set the product version number - * @param version version number - */ - public void setProductVersionNumber(String version) - { - this.mVersionNumber = version; - } - - /** - * Set the internal build number - * @param build build number - */ - public void setInternalBuildNumber(String build) - { - this.mBuildNumber = build; - } - - @Override - public byte[] getEncoding() - { - BufferedByteWriter writer = new BufferedByteWriter(); - writer.putLen8(mVersionNumber.getBytes()); - writer.putLen8(mBuildNumber.getBytes()); - /* we don't provide a configuration number */ - writer.put((byte)0); - return writer.toByteArray(); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/Collector.java b/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/Collector.java deleted file mode 100644 index a686f13a1..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/Collector.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2013 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.logic.imc.collectors; - -import org.strongswan.android.logic.imc.attributes.Attribute; - -/** - * Interface for measurement collectors - */ -public interface Collector -{ - /** - * This method shall return the result of a measurement, if available - * @return attribute or null - */ - public abstract Attribute getMeasurement(); -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/DeviceIdCollector.java b/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/DeviceIdCollector.java deleted file mode 100644 index ebe9e10b0..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/DeviceIdCollector.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2013 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.logic.imc.collectors; - -import org.strongswan.android.logic.imc.attributes.Attribute; -import org.strongswan.android.logic.imc.attributes.DeviceIdAttribute; - -import android.content.ContentResolver; -import android.content.Context; - -public class DeviceIdCollector implements Collector -{ - private final ContentResolver mContentResolver; - - public DeviceIdCollector(Context context) - { - mContentResolver = context.getContentResolver(); - } - - @Override - public Attribute getMeasurement() - { - String id = android.provider.Settings.Secure.getString(mContentResolver, "android_id"); - if (id != null) - { - DeviceIdAttribute attribute = new DeviceIdAttribute(); - attribute.setDeviceId(id); - return attribute; - } - return null; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/InstalledPackagesCollector.java b/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/InstalledPackagesCollector.java deleted file mode 100644 index caa5170ec..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/InstalledPackagesCollector.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2013 Tobias Brunner - * Copyright (C) 2012 Christoph Buehler - * Copyright (C) 2012 Patrick Loetscher - * 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.logic.imc.collectors; - -import java.util.List; - -import org.strongswan.android.logic.imc.attributes.Attribute; -import org.strongswan.android.logic.imc.attributes.InstalledPackagesAttribute; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; - -public class InstalledPackagesCollector implements Collector -{ - private final PackageManager mPackageManager; - - public InstalledPackagesCollector(Context context) - { - mPackageManager = context.getPackageManager(); - } - - @Override - public Attribute getMeasurement() - { - InstalledPackagesAttribute attribute = new InstalledPackagesAttribute(); - List packages = mPackageManager.getInstalledPackages(0); - for (PackageInfo info : packages) - { - if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 || - info.packageName == null || info.versionName == null) - { /* ignore packages installed in the system image */ - continue; - } - attribute.addPackage(info.packageName, info.versionName); - } - return attribute; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/PortFilterCollector.java b/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/PortFilterCollector.java deleted file mode 100644 index ed86686d8..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/PortFilterCollector.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2013 Tobias Brunner - * Copyright (C) 2012 Christoph Buehler - * Copyright (C) 2012 Patrick Loetscher - * 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.logic.imc.collectors; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.strongswan.android.logic.imc.attributes.Attribute; -import org.strongswan.android.logic.imc.attributes.PortFilterAttribute; - -public class PortFilterCollector implements Collector -{ - private static Pattern LISTEN = Pattern.compile("\\bLISTEN\\b"); - private static Pattern PROTOCOL = Pattern.compile("\\b(tcp|udp)6?\\b"); - private static Pattern PORT = Pattern.compile("[:]{1,3}(\\d{1,5})\\b(?!\\.)"); - - @Override - public Attribute getMeasurement() - { - PortFilterAttribute attribute = null; - try - { - Process netstat = Runtime.getRuntime().exec("netstat -n"); - try - { - BufferedReader reader = new BufferedReader(new InputStreamReader(netstat.getInputStream())); - String line; - attribute = new PortFilterAttribute(); - while ((line = reader.readLine()) != null) - { - if (!LISTEN.matcher(line).find()) - { - continue; - } - Matcher protocolMatcher = PROTOCOL.matcher(line); - Matcher portMatcher = PORT.matcher(line); - if (protocolMatcher.find() && portMatcher.find()) - { - Protocol protocol = Protocol.fromName(protocolMatcher.group()); - if (protocol == null) - { - continue; - } - int port = Integer.parseInt(portMatcher.group(1)); - attribute.addPort(protocol, (short)port); - } - } - } - finally - { - netstat.destroy(); - } - } - catch (IOException e) - { - e.printStackTrace(); - } - return attribute; - } - -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/ProductInformationCollector.java b/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/ProductInformationCollector.java deleted file mode 100644 index c377e9041..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/ProductInformationCollector.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2013 Tobias Brunner - * Copyright (C) 2012 Christoph Buehler - * Copyright (C) 2012 Patrick Loetscher - * 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.logic.imc.collectors; - -import org.strongswan.android.logic.imc.attributes.Attribute; -import org.strongswan.android.logic.imc.attributes.ProductInformationAttribute; - -public class ProductInformationCollector implements Collector -{ - @Override - public Attribute getMeasurement() - { /* this is currently hardcoded in the attribute */ - return new ProductInformationAttribute(); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/Protocol.java b/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/Protocol.java deleted file mode 100644 index 7320652a1..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/Protocol.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2013 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.logic.imc.collectors; - -public enum Protocol -{ - TCP((byte)6, "tcp", "tcp6"), - UDP((byte)17, "udp", "udp6"); - - private final byte mValue; - private String[] mNames; - - private Protocol(byte value, String... names) - { - mValue = value; - mNames = names; - } - - /** - * Get the numeric value of the protocol. - * @return numeric value - */ - public byte getValue() - { - return mValue; - } - - /** - * Get the protocol from the given protocol name, if found. - * @param name protocol name (e.g. "udp" or "tcp") - * @return enum entry or null - */ - public static Protocol fromName(String name) - { - for (Protocol protocol : Protocol.values()) - { - for (String keyword : protocol.mNames) - { - if (keyword.equalsIgnoreCase(name)) - { - return protocol; - } - } - } - return null; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/SettingsCollector.java b/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/SettingsCollector.java deleted file mode 100644 index 658c2daea..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/SettingsCollector.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2013 Tobias Brunner - * Copyright (C) 2012 Christoph Buehler - * Copyright (C) 2012 Patrick Loetscher - * 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.logic.imc.collectors; - -import java.util.Locale; - -import org.strongswan.android.logic.imc.attributes.Attribute; -import org.strongswan.android.logic.imc.attributes.SettingsAttribute; - -import android.content.ContentResolver; -import android.content.Context; - -public class SettingsCollector implements Collector -{ - private final ContentResolver mContentResolver; - private final String[] mSettings; - - public SettingsCollector(Context context, String[] args) - { - mContentResolver = context.getContentResolver(); - mSettings = args; - } - - @Override - public Attribute getMeasurement() - { - if (mSettings == null || mSettings.length == 0) - { - return null; - } - SettingsAttribute attribute = new SettingsAttribute(); - for (String name : mSettings) - { - String value = android.provider.Settings.Secure.getString(mContentResolver, name.toLowerCase(Locale.US)); - if (value == null) - { - value = android.provider.Settings.System.getString(mContentResolver, name.toLowerCase(Locale.US)); - } - if (value != null) - { - attribute.addSetting(name, value); - } - } - return attribute; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/StringVersionCollector.java b/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/StringVersionCollector.java deleted file mode 100644 index 6e0df94a5..000000000 --- a/src/frontends/android/src/org/strongswan/android/logic/imc/collectors/StringVersionCollector.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2013 Tobias Brunner - * Copyright (C) 2012 Christoph Buehler - * Copyright (C) 2012 Patrick Loetscher - * 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.logic.imc.collectors; - -import org.strongswan.android.logic.imc.attributes.Attribute; -import org.strongswan.android.logic.imc.attributes.StringVersionAttribute; - -public class StringVersionCollector implements Collector -{ - @Override - public Attribute getMeasurement() - { - StringVersionAttribute attribute = new StringVersionAttribute(); - attribute.setProductVersionNumber(android.os.Build.VERSION.RELEASE); - attribute.setInternalBuildNumber(android.os.Build.DISPLAY); - return attribute; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreProvider.java b/src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreProvider.java deleted file mode 100644 index c49b1044f..000000000 --- a/src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreProvider.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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()); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreSpi.java b/src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreSpi.java deleted file mode 100644 index 64a48a9bb..000000000 --- a/src/frontends/android/src/org/strongswan/android/security/LocalCertificateKeyStoreSpi.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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(); - } - } -} diff --git a/src/frontends/android/src/org/strongswan/android/security/LocalCertificateStore.java b/src/frontends/android/src/org/strongswan/android/security/LocalCertificateStore.java deleted file mode 100644 index cec5c603d..000000000 --- a/src/frontends/android/src/org/strongswan/android/security/LocalCertificateStore.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 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; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/security/TrustedCertificateEntry.java b/src/frontends/android/src/org/strongswan/android/security/TrustedCertificateEntry.java deleted file mode 100644 index 143741faf..000000000 --- a/src/frontends/android/src/org/strongswan/android/security/TrustedCertificateEntry.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2012 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.cert.X509Certificate; - -import android.net.http.SslCertificate; - -public class TrustedCertificateEntry implements Comparable -{ - private final X509Certificate mCert; - private final String mAlias; - private String mSubjectPrimary; - private String mSubjectSecondary = ""; - private String mString; - - /** - * Create an entry for certificate lists. - * - * @param alias alias of the certificate (as used in the KeyStore) - * @param cert certificate associated with that alias - */ - public TrustedCertificateEntry(String alias, X509Certificate cert) - { - mCert = cert; - mAlias = alias; - - SslCertificate ssl = new SslCertificate(mCert); - String o = ssl.getIssuedTo().getOName(); - String ou = ssl.getIssuedTo().getUName(); - String cn = ssl.getIssuedTo().getCName(); - if (!o.isEmpty()) - { - mSubjectPrimary = o; - if (!cn.isEmpty()) - { - mSubjectSecondary = cn; - } - else if (!ou.isEmpty()) - { - mSubjectSecondary = ou; - } - } - else if (!cn.isEmpty()) - { - mSubjectPrimary = cn; - } - else - { - mSubjectPrimary = ssl.getIssuedTo().getDName(); - } - } - - /** - * The main subject of this certificate (O, CN or the complete DN, whatever - * is found first). - * - * @return the main subject - */ - public String getSubjectPrimary() - { - return mSubjectPrimary; - } - - /** - * Get the secondary subject of this certificate (either CN or OU if primary - * subject is O, empty otherwise) - * - * @return the secondary subject - */ - public String getSubjectSecondary() - { - return mSubjectSecondary; - } - - /** - * The alias associated with this certificate. - * - * @return KeyStore alias of this certificate - */ - public String getAlias() - { - return mAlias; - } - - /** - * The certificate. - * - * @return certificate - */ - public X509Certificate getCertificate() - { - return mCert; - } - - @Override - public String toString() - { /* combination of both subject lines, used for filtering lists */ - if (mString == null) - { - mString = mSubjectPrimary; - if (!mSubjectSecondary.isEmpty()) - { - mString += ", " + mSubjectSecondary; - } - } - return mString; - } - - @Override - public int compareTo(TrustedCertificateEntry another) - { - int diff = mSubjectPrimary.compareToIgnoreCase(another.mSubjectPrimary); - if (diff == 0) - { - diff = mSubjectSecondary.compareToIgnoreCase(another.mSubjectSecondary); - } - return diff; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java b/src/frontends/android/src/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java deleted file mode 100644 index c381900c6..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/CertificateDeleteConfirmationDialog.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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/ImcStateFragment.java b/src/frontends/android/src/org/strongswan/android/ui/ImcStateFragment.java deleted file mode 100644 index 5b1799744..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/ImcStateFragment.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2013 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.util.ArrayList; - -import org.strongswan.android.R; -import org.strongswan.android.logic.VpnStateService; -import org.strongswan.android.logic.VpnStateService.VpnStateListener; -import org.strongswan.android.logic.imc.ImcState; -import org.strongswan.android.logic.imc.RemediationInstruction; - -import android.app.Fragment; -import android.app.FragmentManager; -import android.app.FragmentTransaction; -import android.app.Service; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Bundle; -import android.os.IBinder; -import android.view.GestureDetector; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnTouchListener; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -public class ImcStateFragment extends Fragment implements VpnStateListener -{ - private TextView mStateView; - private TextView mAction; - private LinearLayout mButton; - private VpnStateService mService; - private final ServiceConnection mServiceConnection = new ServiceConnection() { - @Override - public void onServiceDisconnected(ComponentName name) - { - mService = null; - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) - { - mService = ((VpnStateService.LocalBinder)service).getService(); - mService.registerListener(ImcStateFragment.this); - updateView(); - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - /* bind to the service only seems to work from the ApplicationContext */ - Context context = getActivity().getApplicationContext(); - context.bindService(new Intent(context, VpnStateService.class), - mServiceConnection, Service.BIND_AUTO_CREATE); - /* hide it initially */ - getFragmentManager().beginTransaction().hide(this).commit(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) - { - View view = inflater.inflate(R.layout.imc_state_fragment, container, false); - - mButton = (LinearLayout)view.findViewById(R.id.imc_state_button); - mButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) - { - Intent intent; - if (mService != null && !mService.getRemediationInstructions().isEmpty()) - { - intent = new Intent(getActivity(), RemediationInstructionsActivity.class); - intent.putParcelableArrayListExtra(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS, - new ArrayList(mService.getRemediationInstructions())); - } - else - { - intent = new Intent(getActivity(), LogActivity.class); - } - startActivity(intent); - } - }); - final GestureDetector gestures = new GestureDetector(getActivity(), new GestureDetector.SimpleOnGestureListener() { - /* a better value would be getScaledTouchExplorationTapSlop() but that is hidden */ - private final int mMinDistance = ViewConfiguration.get(getActivity()).getScaledTouchSlop() * 4; - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) - { - if (Math.abs(e1.getX() - e2.getX()) >= mMinDistance) - { /* only if the user swiped a minimum horizontal distance */ - if (mService != null) - { - mService.setImcState(ImcState.UNKNOWN); - } - return true; - } - return false; - } - }); - mButton.setOnTouchListener(new OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) - { - return gestures.onTouchEvent(event); - } - }); - - mStateView = (TextView)view.findViewById(R.id.imc_state); - mAction = (TextView)view.findViewById(R.id.action); - - return view; - } - - @Override - public void onStart() - { - super.onStart(); - if (mService != null) - { - mService.registerListener(this); - updateView(); - } - } - - @Override - public void onStop() - { - super.onStop(); - if (mService != null) - { - mService.unregisterListener(this); - } - } - - @Override - public void onDestroy() - { - super.onDestroy(); - if (mService != null) - { - getActivity().getApplicationContext().unbindService(mServiceConnection); - } - } - - @Override - public void stateChanged() - { - updateView(); - } - - public void updateView() - { - FragmentManager fm = getFragmentManager(); - if (fm == null) - { - return; - } - FragmentTransaction ft = fm.beginTransaction(); - - switch (mService.getImcState()) - { - case UNKNOWN: - case ALLOW: - ft.hide(this); - break; - case ISOLATE: - mStateView.setText(R.string.imc_state_isolate); - mStateView.setTextColor(getResources().getColor(R.color.warning_text)); - ft.show(this); - break; - case BLOCK: - mStateView.setText(R.string.imc_state_block); - mStateView.setTextColor(getResources().getColor(R.color.error_text)); - ft.show(this); - break; - } - ft.commit(); - - mAction.setText(mService.getRemediationInstructions().isEmpty() ? R.string.show_log - : R.string.show_remediation_instructions); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/LogActivity.java b/src/frontends/android/src/org/strongswan/android/ui/LogActivity.java deleted file mode 100644 index a5efecc09..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/LogActivity.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2012 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.File; - -import org.strongswan.android.R; -import org.strongswan.android.data.LogContentProvider; -import org.strongswan.android.logic.CharonVpnService; - -import android.app.Activity; -import android.content.Intent; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.Toast; - -public class LogActivity extends Activity -{ - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.log_activity); - - getActionBar().setDisplayHomeAsUpEnabled(true); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) - { - getMenuInflater().inflate(R.menu.log, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case android.R.id.home: - finish(); - return true; - case R.id.menu_send_log: - File logfile = new File(getFilesDir(), CharonVpnService.LOG_FILE); - if (!logfile.exists() || logfile.length() == 0) - { - Toast.makeText(this, getString(R.string.empty_log), Toast.LENGTH_SHORT).show(); - return true; - } - - String version = ""; - try - { - version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; - } - catch (NameNotFoundException e) - { - e.printStackTrace(); - } - - Intent intent = new Intent(Intent.ACTION_SEND); - intent.putExtra(Intent.EXTRA_EMAIL, new String[] { MainActivity.CONTACT_EMAIL }); - intent.putExtra(Intent.EXTRA_SUBJECT, String.format(getString(R.string.log_mail_subject), version)); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_STREAM, LogContentProvider.createContentUri()); - startActivity(Intent.createChooser(intent, getString(R.string.send_log))); - return true; - } - return super.onOptionsItemSelected(item); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/LogFragment.java b/src/frontends/android/src/org/strongswan/android/ui/LogFragment.java deleted file mode 100644 index 8740e0c46..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/LogFragment.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2012 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.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.StringReader; - -import org.strongswan.android.R; -import org.strongswan.android.logic.CharonVpnService; - -import android.app.Fragment; -import android.os.Bundle; -import android.os.FileObserver; -import android.os.Handler; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -public class LogFragment extends Fragment implements Runnable -{ - private String mLogFilePath; - private Handler mLogHandler; - private TextView mLogView; - private LogScrollView mScrollView; - private BufferedReader mReader; - private Thread mThread; - private volatile boolean mRunning; - private FileObserver mDirectoryObserver; - - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - mLogFilePath = getActivity().getFilesDir() + File.separator + CharonVpnService.LOG_FILE; - /* use a handler to update the log view */ - mLogHandler = new Handler(); - - mDirectoryObserver = new LogDirectoryObserver(getActivity().getFilesDir().getAbsolutePath()); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) - { - View view = inflater.inflate(R.layout.log_fragment, null); - mLogView = (TextView)view.findViewById(R.id.log_view); - mScrollView = (LogScrollView)view.findViewById(R.id.scroll_view); - return view; - } - - @Override - public void onStart() - { - super.onStart(); - startLogReader(); - mDirectoryObserver.startWatching(); - } - - @Override - public void onStop() - { - super.onStop(); - mDirectoryObserver.stopWatching(); - stopLogReader(); - } - - /** - * Start reading from the log file - */ - private void startLogReader() - { - try - { - mReader = new BufferedReader(new FileReader(mLogFilePath)); - } - catch (FileNotFoundException e) - { - mReader = new BufferedReader(new StringReader("")); - } - - mLogView.setText(""); - mRunning = true; - mThread = new Thread(this); - mThread.start(); - } - - /** - * Stop reading from the log file - */ - private void stopLogReader() - { - try - { - mRunning = false; - mThread.interrupt(); - mThread.join(); - } - catch (InterruptedException e) - { - } - } - - /** - * Write the given log line to the TextView. We strip the prefix off to save - * some space (it is not that helpful for regular users anyway). - * - * @param line log line to log - */ - public void logLine(final String line) - { - mLogHandler.post(new Runnable() { - @Override - public void run() - { - /* strip off prefix (month=3, day=2, time=8, thread=2, spaces=3) */ - mLogView.append((line.length() > 18 ? line.substring(18) : line) + '\n'); - /* calling autoScroll() directly does not work, probably because content - * is not yet updated, so we post this to be done later */ - mScrollView.post(new Runnable() { - @Override - public void run() - { - mScrollView.autoScroll(); - } - }); - } - }); - } - - @Override - public void run() - { - while (mRunning) - { - try - { /* this works as long as the file is not truncated */ - String line = mReader.readLine(); - if (line == null) - { /* wait until there is more to log */ - Thread.sleep(1000); - } - else - { - logLine(line); - } - } - catch (Exception e) - { - break; - } - } - } - - /** - * FileObserver that checks for changes regarding the log file. Since charon - * truncates it (for which there is no explicit event) we check for any modification - * to the file, keep track of the file size and reopen it if it got smaller. - */ - private class LogDirectoryObserver extends FileObserver - { - private final File mFile; - private long mSize; - - public LogDirectoryObserver(String path) - { - super(path, FileObserver.CREATE | FileObserver.MODIFY | FileObserver.DELETE); - mFile = new File(mLogFilePath); - mSize = mFile.length(); - } - - @Override - public void onEvent(int event, String path) - { - if (path == null || !path.equals(CharonVpnService.LOG_FILE)) - { - return; - } - switch (event) - { /* even though we only subscribed for these we check them, - * as strange events are sometimes received */ - case FileObserver.CREATE: - case FileObserver.DELETE: - restartLogReader(); - break; - case FileObserver.MODIFY: - /* if the size got smaller reopen the log file, as it was probably truncated */ - long size = mFile.length(); - if (size < mSize) - { - restartLogReader(); - } - mSize = size; - break; - } - } - - private void restartLogReader() - { - /* we are called from a separate thread, so we use the handler */ - mLogHandler.post(new Runnable() { - @Override - public void run() - { - stopLogReader(); - startLogReader(); - } - }); - } - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/LogScrollView.java b/src/frontends/android/src/org/strongswan/android/ui/LogScrollView.java deleted file mode 100644 index 7eee820ce..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/LogScrollView.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2012 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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 android.content.Context; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.widget.ScrollView; - -public class LogScrollView extends ScrollView -{ - private boolean mAutoScroll = true; - - public LogScrollView(Context context) - { - super(context); - } - - public LogScrollView(Context context, AttributeSet attrs) - { - super(context, attrs); - } - - public LogScrollView(Context context, AttributeSet attrs, int defStyle) - { - super(context, attrs, defStyle); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) - { - /* disable auto-scrolling when the user starts scrolling around */ - if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) - { - mAutoScroll = false; - } - return super.onTouchEvent(ev); - } - - /** - * Call this to move newly added content into view by scrolling to the bottom. - * Nothing happens if auto-scrolling is disabled. - */ - public void autoScroll() - { - if (mAutoScroll) - { - fullScroll(View.FOCUS_DOWN); - } - } - - @Override - protected void onScrollChanged(int l, int t, int oldl, int oldt) - { - super.onScrollChanged(l, t, oldl, oldt); - /* if the user scrolls to the bottom we enable auto-scrolling again */ - if (t == getChildAt(getChildCount() - 1).getHeight() - getHeight()) - { - mAutoScroll = true; - } - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java deleted file mode 100644 index e1b3e0783..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java +++ /dev/null @@ -1,440 +0,0 @@ -/* - * Copyright (C) 2012 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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 org.strongswan.android.data.VpnProfile; -import org.strongswan.android.data.VpnProfileDataSource; -import org.strongswan.android.data.VpnType.VpnTypeFeature; -import org.strongswan.android.logic.CharonVpnService; -import org.strongswan.android.logic.TrustedCertificateManager; -import org.strongswan.android.logic.VpnStateService; -import org.strongswan.android.logic.VpnStateService.State; -import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener; - -import android.app.ActionBar; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.app.Dialog; -import android.app.DialogFragment; -import android.app.Fragment; -import android.app.FragmentManager; -import android.app.FragmentTransaction; -import android.app.Service; -import android.content.ActivityNotFoundException; -import android.content.ComponentName; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.ServiceConnection; -import android.net.VpnService; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.IBinder; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.Window; -import android.widget.EditText; -import android.widget.Toast; - -public class MainActivity extends Activity implements OnVpnProfileSelectedListener -{ - public static final String CONTACT_EMAIL = "android@strongswan.org"; - public static final String START_PROFILE = "org.strongswan.android.action.START_PROFILE"; - public static final String EXTRA_VPN_PROFILE_ID = "org.strongswan.android.VPN_PROFILE_ID"; - /** Use "bring your own device" (BYOD) features */ - public static final boolean USE_BYOD = true; - private static final int PREPARE_VPN_SERVICE = 0; - private static final String PROFILE_NAME = "org.strongswan.android.MainActivity.PROFILE_NAME"; - private static final String PROFILE_REQUIRES_PASSWORD = "org.strongswan.android.MainActivity.REQUIRES_PASSWORD"; - private static final String PROFILE_RECONNECT = "org.strongswan.android.MainActivity.RECONNECT"; - private static final String DIALOG_TAG = "Dialog"; - - private Bundle mProfileInfo; - private VpnStateService mService; - private final ServiceConnection mServiceConnection = new ServiceConnection() { - @Override - public void onServiceDisconnected(ComponentName name) - { - mService = null; - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) - { - mService = ((VpnStateService.LocalBinder)service).getService(); - - if (START_PROFILE.equals(getIntent().getAction())) - { - startVpnProfile(getIntent()); - } - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - setContentView(R.layout.main); - - this.bindService(new Intent(this, VpnStateService.class), - mServiceConnection, Service.BIND_AUTO_CREATE); - - ActionBar bar = getActionBar(); - bar.setDisplayShowTitleEnabled(false); - - /* load CA certificates in a background task */ - new LoadCertificatesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - @Override - protected void onDestroy() - { - super.onDestroy(); - if (mService != null) - { - this.unbindService(mServiceConnection); - } - } - - /** - * Due to launchMode=singleTop this is called if the Activity already exists - */ - @Override - protected void onNewIntent(Intent intent) - { - super.onNewIntent(intent); - - if (START_PROFILE.equals(intent.getAction())) - { - startVpnProfile(intent); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) - { - getMenuInflater().inflate(R.menu.main, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case R.id.menu_manage_certs: - Intent certIntent = new Intent(this, TrustedCertificatesActivity.class); - startActivity(certIntent); - return true; - case R.id.menu_show_log: - Intent logIntent = new Intent(this, LogActivity.class); - startActivity(logIntent); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - /** - * Prepare the VpnService. If this succeeds the current VPN profile is - * started. - * @param profileInfo a bundle containing the information about the profile to be started - */ - protected void prepareVpnService(Bundle profileInfo) - { - Intent intent; - try - { - intent = VpnService.prepare(this); - } - catch (IllegalStateException ex) - { - /* this happens if the always-on VPN feature (Android 4.2+) is activated */ - VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported_during_lockdown); - return; - } - /* store profile info until the user grants us permission */ - mProfileInfo = profileInfo; - if (intent != null) - { - try - { - startActivityForResult(intent, PREPARE_VPN_SERVICE); - } - catch (ActivityNotFoundException ex) - { - /* it seems some devices, even though they come with Android 4, - * don't have the VPN components built into the system image. - * com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog - * will not be found then */ - VpnNotSupportedError.showWithMessage(this, R.string.vpn_not_supported); - } - } - else - { /* user already granted permission to use VpnService */ - onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - switch (requestCode) - { - case PREPARE_VPN_SERVICE: - if (resultCode == RESULT_OK && mProfileInfo != null) - { - Intent intent = new Intent(this, CharonVpnService.class); - intent.putExtras(mProfileInfo); - this.startService(intent); - } - break; - default: - super.onActivityResult(requestCode, resultCode, data); - } - } - - @Override - public void onVpnProfileSelected(VpnProfile profile) - { - Bundle profileInfo = new Bundle(); - profileInfo.putLong(VpnProfileDataSource.KEY_ID, profile.getId()); - profileInfo.putString(VpnProfileDataSource.KEY_USERNAME, profile.getUsername()); - profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, profile.getPassword()); - profileInfo.putBoolean(PROFILE_REQUIRES_PASSWORD, profile.getVpnType().has(VpnTypeFeature.USER_PASS)); - profileInfo.putString(PROFILE_NAME, profile.getName()); - - removeFragmentByTag(DIALOG_TAG); - - if (mService != null && mService.getState() == State.CONNECTED) - { - profileInfo.putBoolean(PROFILE_RECONNECT, mService.getProfile().getId() == profile.getId()); - - ConfirmationDialog dialog = new ConfirmationDialog(); - dialog.setArguments(profileInfo); - dialog.show(this.getFragmentManager(), DIALOG_TAG); - return; - } - startVpnProfile(profileInfo); - } - - /** - * Start the given VPN profile asking the user for a password if required. - * @param profileInfo data about the profile - */ - private void startVpnProfile(Bundle profileInfo) - { - if (profileInfo.getBoolean(PROFILE_REQUIRES_PASSWORD) && - profileInfo.getString(VpnProfileDataSource.KEY_PASSWORD) == null) - { - LoginDialog login = new LoginDialog(); - login.setArguments(profileInfo); - login.show(getFragmentManager(), DIALOG_TAG); - return; - } - prepareVpnService(profileInfo); - } - - /** - * Start the VPN profile referred to by the given intent. Displays an error - * if the profile doesn't exist. - * @param intent Intent that caused us to start this - */ - private void startVpnProfile(Intent intent) - { - long profileId = intent.getLongExtra(EXTRA_VPN_PROFILE_ID, 0); - if (profileId <= 0) - { /* invalid invocation */ - return; - } - VpnProfileDataSource dataSource = new VpnProfileDataSource(this); - dataSource.open(); - VpnProfile profile = dataSource.getVpnProfile(profileId); - dataSource.close(); - - if (profile != null) - { - onVpnProfileSelected(profile); - } - else - { - Toast.makeText(this, R.string.profile_not_found, Toast.LENGTH_LONG).show(); - } - } - - /** - * Class that loads the cached CA certificates. - */ - private class LoadCertificatesTask extends AsyncTask - { - @Override - protected void onPreExecute() - { - setProgressBarIndeterminateVisibility(true); - } - @Override - protected TrustedCertificateManager doInBackground(Void... params) - { - return TrustedCertificateManager.getInstance().load(); - } - @Override - protected void onPostExecute(TrustedCertificateManager result) - { - setProgressBarIndeterminateVisibility(false); - } - } - - /** - * Dismiss dialog if shown - */ - public void removeFragmentByTag(String tag) - { - FragmentManager fm = getFragmentManager(); - Fragment login = fm.findFragmentByTag(tag); - if (login != null) - { - FragmentTransaction ft = fm.beginTransaction(); - ft.remove(login); - ft.commit(); - } - } - - /** - * Class that displays a confirmation dialog if a VPN profile is already connected - * and then initiates the selected VPN profile if the user confirms the dialog. - */ - public static class ConfirmationDialog extends DialogFragment - { - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) - { - final Bundle profileInfo = getArguments(); - int icon = android.R.drawable.ic_dialog_alert; - int title = R.string.connect_profile_question; - int message = R.string.replaces_active_connection; - int button = R.string.connect; - - if (profileInfo.getBoolean(PROFILE_RECONNECT)) - { - icon = android.R.drawable.ic_dialog_info; - title = R.string.vpn_connected; - message = R.string.vpn_profile_connected; - button = R.string.reconnect; - } - - return new AlertDialog.Builder(getActivity()) - .setIcon(icon) - .setTitle(String.format(getString(title), profileInfo.getString(PROFILE_NAME))) - .setMessage(message) - .setPositiveButton(button, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int whichButton) - { - MainActivity activity = (MainActivity)getActivity(); - activity.startVpnProfile(profileInfo); - } - }) - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) - { - dismiss(); - } - }).create(); - } - } - - /** - * Class that displays a login dialog and initiates the selected VPN - * profile if the user confirms the dialog. - */ - public static class LoginDialog extends DialogFragment - { - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) - { - final Bundle profileInfo = getArguments(); - LayoutInflater inflater = getActivity().getLayoutInflater(); - View view = inflater.inflate(R.layout.login_dialog, null); - EditText username = (EditText)view.findViewById(R.id.username); - username.setText(profileInfo.getString(VpnProfileDataSource.KEY_USERNAME)); - final EditText password = (EditText)view.findViewById(R.id.password); - - Builder adb = new AlertDialog.Builder(getActivity()); - adb.setView(view); - adb.setTitle(getString(R.string.login_title)); - adb.setPositiveButton(R.string.login_confirm, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int whichButton) - { - MainActivity activity = (MainActivity)getActivity(); - profileInfo.putString(VpnProfileDataSource.KEY_PASSWORD, password.getText().toString().trim()); - activity.prepareVpnService(profileInfo); - } - }); - adb.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) - { - dismiss(); - } - }); - return adb.create(); - } - } - - /** - * Class representing an error message which is displayed if VpnService is - * not supported on the current device. - */ - public static class VpnNotSupportedError extends DialogFragment - { - static final String ERROR_MESSAGE_ID = "org.strongswan.android.VpnNotSupportedError.MessageId"; - - public static void showWithMessage(Activity activity, int messageId) - { - Bundle bundle = new Bundle(); - bundle.putInt(ERROR_MESSAGE_ID, messageId); - VpnNotSupportedError dialog = new VpnNotSupportedError(); - dialog.setArguments(bundle); - dialog.show(activity.getFragmentManager(), DIALOG_TAG); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) - { - final Bundle arguments = getArguments(); - final int messageId = arguments.getInt(ERROR_MESSAGE_ID); - return new AlertDialog.Builder(getActivity()) - .setTitle(R.string.vpn_not_supported_title) - .setMessage(messageId) - .setCancelable(false) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) - { - dialog.dismiss(); - } - }).create(); - } - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/RemediationInstructionFragment.java b/src/frontends/android/src/org/strongswan/android/ui/RemediationInstructionFragment.java deleted file mode 100644 index 04c288bcf..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/RemediationInstructionFragment.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2013 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 org.strongswan.android.logic.imc.RemediationInstruction; - -import android.app.ListFragment; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.TextView; - -public class RemediationInstructionFragment extends ListFragment -{ - public static final String ARG_REMEDIATION_INSTRUCTION = "instruction"; - private RemediationInstruction mInstruction = null; - private TextView mTitle; - private TextView mDescription; - private TextView mHeader; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) - { - return inflater.inflate(R.layout.remediation_instruction, container, false); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) - { - super.onActivityCreated(savedInstanceState); - - if (savedInstanceState != null) - { - mInstruction = savedInstanceState.getParcelable(ARG_REMEDIATION_INSTRUCTION); - } - /* show dividers only between list items */ - getListView().setHeaderDividersEnabled(false); - getListView().setFooterDividersEnabled(false); - /* don't show loader while adapter is not set */ - setListShown(true); - mTitle = (TextView)getView().findViewById(R.id.title); - mDescription = (TextView)getView().findViewById(R.id.description); - mHeader = (TextView)getView().findViewById(R.id.list_header); - } - - @Override - public void onSaveInstanceState(Bundle outState) - { - super.onSaveInstanceState(outState); - outState.putParcelable(ARG_REMEDIATION_INSTRUCTION, mInstruction); - } - - @Override - public void onStart() - { - super.onStart(); - - Bundle args = getArguments(); - if (args != null) - { - mInstruction = args.getParcelable(ARG_REMEDIATION_INSTRUCTION); - } - updateView(mInstruction); - } - - public void updateView(RemediationInstruction instruction) - { - mInstruction = instruction; - if (mInstruction != null) - { - mTitle.setText(mInstruction.getTitle()); - mDescription.setText(mInstruction.getDescription()); - if (mInstruction.getHeader() != null) - { - mHeader.setText(mInstruction.getHeader()); - setListAdapter(new ArrayAdapter(getActivity(), - android.R.layout.simple_list_item_1, mInstruction.getItems())); - } - else - { - mHeader.setText(""); - setListAdapter(null); - } - } - else - { - mTitle.setText(""); - mDescription.setText(""); - mHeader.setText(""); - setListAdapter(null); - } - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/RemediationInstructionsActivity.java b/src/frontends/android/src/org/strongswan/android/ui/RemediationInstructionsActivity.java deleted file mode 100644 index 66d0de261..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/RemediationInstructionsActivity.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2013 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.util.ArrayList; - -import org.strongswan.android.R; -import org.strongswan.android.logic.imc.RemediationInstruction; -import org.strongswan.android.ui.RemediationInstructionsFragment.OnRemediationInstructionSelectedListener; - -import android.app.Activity; -import android.os.Bundle; -import android.view.MenuItem; - -public class RemediationInstructionsActivity extends Activity implements OnRemediationInstructionSelectedListener -{ - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.remediation_instructions); - getActionBar().setDisplayHomeAsUpEnabled(true); - - if (savedInstanceState != null) - { /* only update if we're not restoring */ - return; - } - RemediationInstructionsFragment frag = (RemediationInstructionsFragment)getFragmentManager().findFragmentById(R.id.remediation_instructions_fragment); - if (frag != null) - { /* two-pane layout, update fragment */ - Bundle extras = getIntent().getExtras(); - ArrayList list = extras.getParcelableArrayList(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS); - frag.updateView(list); - } - else - { /* one-pane layout, create fragment */ - frag = new RemediationInstructionsFragment(); - frag.setArguments(getIntent().getExtras()); - getFragmentManager().beginTransaction().add(R.id.fragment_container, frag).commit(); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case android.R.id.home: - /* one-pane layout, pop possible fragment from stack, finish otherwise */ - if (!getFragmentManager().popBackStackImmediate()) - { - finish(); - } - getActionBar().setTitle(getTitle()); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - @Override - public void onRemediationInstructionSelected(RemediationInstruction instruction) - { - RemediationInstructionFragment frag = (RemediationInstructionFragment)getFragmentManager().findFragmentById(R.id.remediation_instruction_fragment); - - if (frag != null) - { /* two-pane layout, update directly */ - frag.updateView(instruction); - } - else - { /* one-pane layout, replace fragment */ - frag = new RemediationInstructionFragment(); - Bundle args = new Bundle(); - args.putParcelable(RemediationInstructionFragment.ARG_REMEDIATION_INSTRUCTION, instruction); - frag.setArguments(args); - - getFragmentManager().beginTransaction().replace(R.id.fragment_container, frag).addToBackStack(null).commit(); - getActionBar().setTitle(instruction.getTitle()); - } - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/RemediationInstructionsFragment.java b/src/frontends/android/src/org/strongswan/android/ui/RemediationInstructionsFragment.java deleted file mode 100644 index 86467dc35..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/RemediationInstructionsFragment.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2013 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.util.ArrayList; - -import org.strongswan.android.R; -import org.strongswan.android.logic.imc.RemediationInstruction; -import org.strongswan.android.ui.adapter.RemediationInstructionAdapter; - -import android.app.Activity; -import android.app.ListFragment; -import android.os.Bundle; -import android.view.View; -import android.widget.ListView; - -public class RemediationInstructionsFragment extends ListFragment -{ - public static final String EXTRA_REMEDIATION_INSTRUCTIONS = "instructions"; - private static final String KEY_POSITION = "position"; - private ArrayList mInstructions = null; - private OnRemediationInstructionSelectedListener mListener; - private RemediationInstructionAdapter mAdapter; - private int mCurrentPosition = -1; - - /** - * The activity containing this fragment should implement this interface - */ - public interface OnRemediationInstructionSelectedListener - { - public void onRemediationInstructionSelected(RemediationInstruction instruction); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) - { - super.onActivityCreated(savedInstanceState); - - if (savedInstanceState != null) - { - mInstructions = savedInstanceState.getParcelableArrayList(EXTRA_REMEDIATION_INSTRUCTIONS); - mCurrentPosition = savedInstanceState.getInt(KEY_POSITION); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) - { - super.onSaveInstanceState(outState); - outState.putParcelableArrayList(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS, mInstructions); - outState.putInt(KEY_POSITION, mCurrentPosition); - } - - @Override - public void onAttach(Activity activity) - { - super.onAttach(activity); - - if (activity instanceof OnRemediationInstructionSelectedListener) - { - mListener = (OnRemediationInstructionSelectedListener)activity; - } - } - - @Override - public void onStart() - { - super.onStart(); - - boolean two_pane = getFragmentManager().findFragmentById(R.id.remediation_instruction_fragment) != null; - if (two_pane) - { /* two-pane layout, make list items selectable */ - getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); - } - - Bundle args = getArguments(); - if (mInstructions == null && args != null) - { - mInstructions = args.getParcelableArrayList(EXTRA_REMEDIATION_INSTRUCTIONS); - } - updateView(mInstructions); - - if (two_pane && mCurrentPosition == -1 && mInstructions.size() > 0) - { /* two-pane layout, select first instruction */ - mCurrentPosition = 0; - mListener.onRemediationInstructionSelected(mInstructions.get(0)); - } - getListView().setItemChecked(mCurrentPosition, true); - } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) - { - mCurrentPosition = position; - mListener.onRemediationInstructionSelected(mInstructions.get(position)); - getListView().setItemChecked(position, true); - } - - public void updateView(ArrayList instructions) - { - if (mAdapter == null) - { - mAdapter = new RemediationInstructionAdapter(getActivity()); - setListAdapter(mAdapter); - } - mInstructions = instructions; - mAdapter.setData(mInstructions); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateImportActivity.java b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateImportActivity.java deleted file mode 100644 index 61bd2c9a2..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateImportActivity.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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.FileNotFoundException; -import java.io.InputStream; -import java.security.KeyStore; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; - -import org.strongswan.android.R; -import org.strongswan.android.data.VpnProfileDataSource; -import org.strongswan.android.logic.TrustedCertificateManager; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.app.FragmentTransaction; -import android.content.DialogInterface; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.widget.Toast; - -public class TrustedCertificateImportActivity extends Activity -{ - private static final int OPEN_DOCUMENT = 0; - private static final String DIALOG_TAG = "Dialog"; - - /* same as those listed in the manifest */ - private static final String[] ACCEPTED_MIME_TYPES = { - "application/x-x509-ca-cert", - "application/x-x509-server-cert", - "application/x-pem-file", - "application/pkix-cert" - }; - - @TargetApi(Build.VERSION_CODES.KITKAT) - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - if (savedInstanceState != null) - { /* do nothing when we are restoring */ - return; - } - - Intent intent = getIntent(); - String action = intent.getAction(); - if (Intent.ACTION_VIEW.equals(action)) - { - importCertificate(intent.getData()); - } - else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) - { - Intent openIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - openIntent.setType("*/*"); - openIntent.putExtra(Intent.EXTRA_MIME_TYPES, ACCEPTED_MIME_TYPES); - startActivityForResult(openIntent, OPEN_DOCUMENT); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - switch (requestCode) - { - case OPEN_DOCUMENT: - if (resultCode == Activity.RESULT_OK && data != null) - { - importCertificate(data.getData()); - return; - } - finish(); - return; - } - super.onActivityResult(requestCode, resultCode, data); - } - - /** - * Import the file pointed to by the given URI as a certificate. - * @param uri - */ - private void importCertificate(Uri uri) - { - X509Certificate certificate = parseCertificate(uri); - if (certificate == null) - { - Toast.makeText(this, R.string.cert_import_failed, Toast.LENGTH_LONG).show(); - finish(); - return; - } - /* Ask the user whether to import the certificate. This is particularly - * necessary because the import activity can be triggered by any app on - * the system. Also, if our app is the only one that is registered to - * open certificate files by MIME type the user would have no idea really - * where the file was imported just by reading the Toast we display. */ - ConfirmImportDialog dialog = new ConfirmImportDialog(); - Bundle args = new Bundle(); - args.putSerializable(VpnProfileDataSource.KEY_CERTIFICATE, certificate); - dialog.setArguments(args); - FragmentTransaction ft = getFragmentManager().beginTransaction(); - ft.add(dialog, DIALOG_TAG); - ft.commit(); - } - - /** - * Load the file from the given URI and try to parse it as X.509 certificate. - * @param uri - * @return certificate or null - */ - private X509Certificate parseCertificate(Uri uri) - { - X509Certificate certificate = null; - try - { - CertificateFactory factory = CertificateFactory.getInstance("X.509"); - InputStream in = getContentResolver().openInputStream(uri); - certificate = (X509Certificate)factory.generateCertificate(in); - /* we don't check whether it's actually a CA certificate or not */ - } - catch (CertificateException e) - { - e.printStackTrace(); - } - catch (FileNotFoundException e) - { - e.printStackTrace(); - } - return certificate; - } - - - /** - * Try to store the given certificate in the KeyStore. - * @param certificate - * @return whether it was successfully stored - */ - private boolean storeCertificate(X509Certificate certificate) - { - try - { - KeyStore store = KeyStore.getInstance("LocalCertificateStore"); - store.load(null, null); - store.setCertificateEntry(null, certificate); - TrustedCertificateManager.getInstance().reset(); - return true; - } - catch (Exception e) - { - e.printStackTrace(); - return false; - } - } - - /** - * Class that displays a confirmation dialog when a certificate should get - * imported. If the user confirms the import we try to store it. - */ - public static class ConfirmImportDialog extends DialogFragment - { - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) - { - final X509Certificate certificate; - - certificate = (X509Certificate)getArguments().getSerializable(VpnProfileDataSource.KEY_CERTIFICATE); - - return new AlertDialog.Builder(getActivity()) - .setIcon(R.drawable.ic_launcher) - .setTitle(R.string.import_certificate) - .setMessage(certificate.getSubjectDN().toString()) - .setPositiveButton(R.string.import_certificate, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int whichButton) - { - TrustedCertificateImportActivity activity = (TrustedCertificateImportActivity)getActivity(); - if (activity.storeCertificate(certificate)) - { - Toast.makeText(getActivity(), R.string.cert_imported_successfully, Toast.LENGTH_LONG).show(); - getActivity().setResult(Activity.RESULT_OK); - } - else - { - Toast.makeText(getActivity(), R.string.cert_import_failed, Toast.LENGTH_LONG).show(); - } - getActivity().finish(); - } - }) - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) - { - getActivity().finish(); - } - }).create(); - } - - @Override - public void onCancel(DialogInterface dialog) - { - getActivity().finish(); - } - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java deleted file mode 100644 index 8bd39c435..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificateListFragment.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2012-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.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Hashtable; -import java.util.List; -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; - -import android.app.Activity; -import android.app.ListFragment; -import android.app.LoaderManager.LoaderCallbacks; -import android.content.AsyncTaskLoader; -import android.content.Context; -import android.content.Loader; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.ListView; -import android.widget.SearchView; -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 TrustedCertificateSource mSource = TrustedCertificateSource.SYSTEM; - - /** - * The activity containing this fragment should implement this interface - */ - public interface OnTrustedCertificateSelectedListener { - public void onTrustedCertificateSelected(TrustedCertificateEntry selected); - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) - { - super.onActivityCreated(savedInstanceState); - setHasOptionsMenu(true); - - setEmptyText(getString(R.string.no_certificates)); - - mAdapter = new TrustedCertificateAdapter(getActivity()); - setListAdapter(mAdapter); - - setListShown(false); - - Bundle arguments = getArguments(); - if (arguments != null) - { - mSource = (TrustedCertificateSource)arguments.getSerializable(EXTRA_CERTIFICATE_SOURCE); - } - - getLoaderManager().initLoader(0, null, this); - } - - @Override - public void onDestroy() - { - super.onDestroy(); - } - - @Override - public void onAttach(Activity activity) - { - super.onAttach(activity); - - if (activity instanceof OnTrustedCertificateSelectedListener) - { - mListener = (OnTrustedCertificateSelectedListener)activity; - } - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) - { - MenuItem item = menu.add(R.string.search); - item.setIcon(android.R.drawable.ic_menu_search); - item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - - SearchView sv = new SearchView(getActivity()); - sv.setOnQueryTextListener(this); - item.setActionView(sv); - } - - @Override - public boolean onQueryTextSubmit(String query) - { /* already handled when the text changes */ - return true; - } - - @Override - public boolean onQueryTextChange(String newText) - { - String search = TextUtils.isEmpty(newText) ? null : newText; - mAdapter.getFilter().filter(search); - 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 */ - return new CertificateListLoader(getActivity(), mSource); - } - - @Override - public void onLoadFinished(Loader> loader, List data) - { - mAdapter.setData(data); - - if (isResumed()) - { - setListShown(true); - } - else - { - setListShownNoAnimation(true); - } - } - - @Override - public void onLoaderReset(Loader> loader) - { - mAdapter.setData(null); - } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) - { - if (mListener != null) - { - mListener.onTrustedCertificateSelected(mAdapter.getItem(position)); - } - } - - public static class CertificateListLoader extends AsyncTaskLoader> - { - private List mData; - private final TrustedCertificateSource mSource; - - public CertificateListLoader(Context context, TrustedCertificateSource source) - { - super(context); - mSource = source; - } - - @Override - public List loadInBackground() - { - TrustedCertificateManager certman = TrustedCertificateManager.getInstance().load(); - Hashtable certificates = certman.getCACertificates(mSource); - List selected; - - selected = new ArrayList(); - for (Entry entry : certificates.entrySet()) - { - selected.add(new TrustedCertificateEntry(entry.getKey(), entry.getValue())); - } - Collections.sort(selected); - return selected; - } - - @Override - protected void onStartLoading() - { - if (mData != null) - { /* if we have data ready, deliver it directly */ - deliverResult(mData); - return; - } - forceLoad(); - } - - @Override - public void deliverResult(List data) - { - if (isReset()) - { - return; - } - mData = data; - if (isStarted()) - { /* if it is started we deliver the data directly, - * otherwise this is handled in onStartLoading */ - super.deliverResult(data); - } - } - - @Override - protected void onReset() - { - mData = null; - } - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java b/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java deleted file mode 100644 index 663950c16..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/TrustedCertificatesActivity.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (C) 2012-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.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; -import android.app.Activity; -import android.app.Fragment; -import android.app.FragmentTransaction; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; - -public class TrustedCertificatesActivity extends Activity implements TrustedCertificateListFragment.OnTrustedCertificateSelectedListener, OnCertificateDeleteListener -{ - public static final String SELECT_CERTIFICATE = "org.strongswan.android.action.SELECT_CERTIFICATE"; - private static final String DIALOG_TAG = "Dialog"; - private static final int IMPORT_CERTIFICATE = 0; - private boolean mSelect; - - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - 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) - .setTag(listener) - .setTabListener(listener)); - listener = new TrustedCertificatesTabListener(this, "user", TrustedCertificateSource.USER); - actionBar.addTab(actionBar - .newTab() - .setText(R.string.user_tab) - .setTag(listener) - .setTabListener(listener)); - listener = new TrustedCertificatesTabListener(this, "local", TrustedCertificateSource.LOCAL); - actionBar.addTab(actionBar - .newTab() - .setText(R.string.local_tab) - .setTag(listener) - .setTabListener(listener)); - - if (savedInstanceState != null) - { - actionBar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); - } - mSelect = SELECT_CERTIFICATE.equals(getIntent().getAction()); - } - - @Override - protected void onSaveInstanceState(Bundle outState) - { - super.onSaveInstanceState(outState); - outState.putInt("tab", getActionBar().getSelectedNavigationIndex()); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) - { - getMenuInflater().inflate(R.menu.certificates, menu); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) - { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) - { - menu.removeItem(R.id.menu_import_certificate); - } - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case android.R.id.home: - finish(); - return true; - case R.id.menu_reload_certs: - reloadCertificates(); - return true; - case R.id.menu_import_certificate: - Intent intent = new Intent(this, TrustedCertificateImportActivity.class); - startActivityForResult(intent, IMPORT_CERTIFICATE); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - switch (requestCode) - { - case IMPORT_CERTIFICATE: - if (resultCode == Activity.RESULT_OK) - { - reloadCertificates(); - } - return; - } - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - public void onTrustedCertificateSelected(TrustedCertificateEntry selected) - { - if (mSelect) - { - /* the user selected a certificate, return to calling activity */ - Intent intent = new Intent(); - intent.putExtra(VpnProfileDataSource.KEY_CERTIFICATE, selected.getAlias()); - setResult(Activity.RESULT_OK, intent); - finish(); - } - else - { - TrustedCertificatesTabListener listener; - listener = (TrustedCertificatesTabListener)getActionBar().getSelectedTab().getTag(); - if (listener.mTag == "local") - { - Bundle args = new Bundle(); - args.putString(CertificateDeleteConfirmationDialog.ALIAS, selected.getAlias()); - CertificateDeleteConfirmationDialog dialog = new CertificateDeleteConfirmationDialog(); - dialog.setArguments(args); - dialog.show(this.getFragmentManager(), DIALOG_TAG); - } - } - } - - @Override - public void onDelete(String alias) - { - try - { - KeyStore store = KeyStore.getInstance("LocalCertificateStore"); - store.load(null, null); - store.deleteEntry(alias); - reloadCertificates(); - } - catch (Exception e) - { - e.printStackTrace(); - } - } - - private void reloadCertificates() - { - TrustedCertificateManager.getInstance().reset(); - for (int i = 0; i < getActionBar().getTabCount(); i++) - { - Tab tab = getActionBar().getTabAt(i); - TrustedCertificatesTabListener listener = (TrustedCertificatesTabListener)tab.getTag(); - listener.reset(); - } - } - - public static class TrustedCertificatesTabListener implements ActionBar.TabListener - { - private final String mTag; - private final TrustedCertificateSource mSource; - private Fragment mFragment; - - public TrustedCertificatesTabListener(Activity activity, String tag, TrustedCertificateSource source) - { - mTag = tag; - 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 */ - mFragment = activity.getFragmentManager().findFragmentByTag(mTag); - if (mFragment != null && !mFragment.isDetached()) - { - FragmentTransaction ft = activity.getFragmentManager().beginTransaction(); - ft.detach(mFragment); - ft.commit(); - } - } - - @Override - public void onTabSelected(Tab tab, FragmentTransaction ft) - { - if (mFragment == null) - { - mFragment = new TrustedCertificateListFragment(); - Bundle args = new Bundle(); - args.putSerializable(TrustedCertificateListFragment.EXTRA_CERTIFICATE_SOURCE, mSource); - mFragment.setArguments(args); - ft.add(android.R.id.content, mFragment, mTag); - } - else - { - ft.attach(mFragment); - } - } - - @Override - public void onTabUnselected(Tab tab, FragmentTransaction ft) - { - if (mFragment != null) - { - ft.detach(mFragment); - } - } - - @Override - public void onTabReselected(Tab tab, FragmentTransaction ft) - { - /* nothing to be done */ - } - - public void reset() - { - if (mFragment != null) - { - ((TrustedCertificateListFragment)mFragment).reset(); - } - } - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java deleted file mode 100644 index a8b3daa06..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ /dev/null @@ -1,653 +0,0 @@ -/* - * Copyright (C) 2012-2014 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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.security.cert.X509Certificate; - -import org.strongswan.android.R; -import org.strongswan.android.data.VpnProfile; -import org.strongswan.android.data.VpnProfileDataSource; -import org.strongswan.android.data.VpnType; -import org.strongswan.android.data.VpnType.VpnTypeFeature; -import org.strongswan.android.logic.TrustedCertificateManager; -import org.strongswan.android.security.TrustedCertificateEntry; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.security.KeyChain; -import android.security.KeyChainAliasCallback; -import android.security.KeyChainException; -import android.text.Html; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; -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.TextView; - -public class VpnProfileDetailActivity extends Activity -{ - private static final int SELECT_TRUSTED_CERTIFICATE = 0; - private static final int MTU_MIN = 1280; - private static final int MTU_MAX = 1500; - - private VpnProfileDataSource mDataSource; - private Long mId; - private TrustedCertificateEntry mCertEntry; - private String mUserCertLoading; - private TrustedCertificateEntry mUserCertEntry; - private VpnType mVpnType = VpnType.IKEV2_EAP; - private VpnProfile mProfile; - private EditText mName; - private EditText mGateway; - private Spinner mSelectVpnType; - private ViewGroup mUsernamePassword; - private EditText mUsername; - private EditText mPassword; - private ViewGroup mUserCertificate; - private RelativeLayout mSelectUserCert; - private CheckBox mCheckAuto; - private RelativeLayout mSelectCert; - private RelativeLayout mTncNotice; - private CheckBox mShowAdvanced; - private ViewGroup mAdvancedSettings; - private EditText mMTU; - private EditText mPort; - private CheckBox mBlockIPv4; - private CheckBox mBlockIPv6; - - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - /* the title is set when we load the profile, if any */ - getActionBar().setDisplayHomeAsUpEnabled(true); - - mDataSource = new VpnProfileDataSource(this); - mDataSource.open(); - - setContentView(R.layout.profile_detail_view); - - mName = (EditText)findViewById(R.id.name); - mGateway = (EditText)findViewById(R.id.gateway); - mSelectVpnType = (Spinner)findViewById(R.id.vpn_type); - 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 = (RelativeLayout)findViewById(R.id.select_user_certificate); - - mCheckAuto = (CheckBox)findViewById(R.id.ca_auto); - mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate); - - mShowAdvanced = (CheckBox)findViewById(R.id.show_advanced); - mAdvancedSettings = (ViewGroup)findViewById(R.id.advanced_settings); - - mMTU = (EditText)findViewById(R.id.mtu); - mPort = (EditText)findViewById(R.id.port); - mBlockIPv4 = (CheckBox)findViewById(R.id.split_tunneling_v4); - mBlockIPv6 = (CheckBox)findViewById(R.id.split_tunneling_v6); - - mSelectVpnType.setOnItemSelectedListener(new OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) - { - mVpnType = VpnType.values()[position]; - updateCredentialView(); - } - - @Override - public void onNothingSelected(AdapterView parent) - { /* should not happen */ - mVpnType = VpnType.IKEV2_EAP; - updateCredentialView(); - } - }); - - ((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) - { - new TncNoticeDialog().show(VpnProfileDetailActivity.this.getFragmentManager(), "TncNotice"); - } - }); - - mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener()); - - mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) - { - updateCertificateSelector(); - } - }); - - mSelectCert.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) - { - Intent intent = new Intent(VpnProfileDetailActivity.this, TrustedCertificatesActivity.class); - intent.setAction(TrustedCertificatesActivity.SELECT_CERTIFICATE); - startActivityForResult(intent, SELECT_TRUSTED_CERTIFICATE); - } - }); - - mShowAdvanced.setOnCheckedChangeListener(new OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) - { - updateAdvancedSettings(); - } - }); - - mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID); - if (mId == null) - { - Bundle extras = getIntent().getExtras(); - mId = extras == null ? null : extras.getLong(VpnProfileDataSource.KEY_ID); - } - - loadProfileData(savedInstanceState); - - updateCredentialView(); - updateCertificateSelector(); - updateAdvancedSettings(); - } - - @Override - protected void onDestroy() - { - super.onDestroy(); - mDataSource.close(); - } - - @Override - protected void onSaveInstanceState(Bundle outState) - { - super.onSaveInstanceState(outState); - if (mId != null) - { - outState.putLong(VpnProfileDataSource.KEY_ID, mId); - } - if (mUserCertEntry != null) - { - outState.putString(VpnProfileDataSource.KEY_USER_CERTIFICATE, mUserCertEntry.getAlias()); - } - if (mCertEntry != null) - { - outState.putString(VpnProfileDataSource.KEY_CERTIFICATE, mCertEntry.getAlias()); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) - { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.profile_edit, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case android.R.id.home: - case R.id.menu_cancel: - finish(); - return true; - case R.id.menu_accept: - saveProfile(); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - switch (requestCode) - { - case SELECT_TRUSTED_CERTIFICATE: - if (resultCode == RESULT_OK) - { - String alias = data.getStringExtra(VpnProfileDataSource.KEY_CERTIFICATE); - X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias); - mCertEntry = certificate == null ? null : new TrustedCertificateEntry(alias, certificate); - updateCertificateSelector(); - } - break; - default: - super.onActivityResult(requestCode, resultCode, data); - } - } - - /** - * Update the UI to enter credentials depending on the type of VPN currently selected - */ - private void updateCredentialView() - { - mUsernamePassword.setVisibility(mVpnType.has(VpnTypeFeature.USER_PASS) ? View.VISIBLE : View.GONE); - mUserCertificate.setVisibility(mVpnType.has(VpnTypeFeature.CERTIFICATE) ? View.VISIBLE : View.GONE); - mTncNotice.setVisibility(mVpnType.has(VpnTypeFeature.BYOD) ? View.VISIBLE : View.GONE); - - if (mVpnType.has(VpnTypeFeature.CERTIFICATE)) - { - if (mUserCertLoading != null) - { - ((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 */ - ((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 - { - ((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); - } - } - } - - /** - * Show an alert in case the previously selected certificate is not found anymore - * or the user did not select a certificate in the spinner. - */ - private void showCertificateAlert() - { - AlertDialog.Builder adb = new AlertDialog.Builder(VpnProfileDetailActivity.this); - adb.setTitle(R.string.alert_text_nocertfound_title); - adb.setMessage(R.string.alert_text_nocertfound); - adb.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) - { - dialog.cancel(); - } - }); - adb.show(); - } - - /** - * Update the CA certificate selection UI depending on whether the - * certificate should be automatically selected or not. - */ - private void updateCertificateSelector() - { - if (!mCheckAuto.isChecked()) - { - mSelectCert.setEnabled(true); - mSelectCert.setVisibility(View.VISIBLE); - - if (mCertEntry != null) - { - ((TextView)mSelectCert.findViewById(android.R.id.text1)).setText(mCertEntry.getSubjectPrimary()); - ((TextView)mSelectCert.findViewById(android.R.id.text2)).setText(mCertEntry.getSubjectSecondary()); - } - else - { - ((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 - { - mSelectCert.setEnabled(false); - mSelectCert.setVisibility(View.GONE); - } - } - - /** - * Update the advanced settings UI depending on whether any advanced - * settings have already been made. - */ - private void updateAdvancedSettings() - { - boolean show = mShowAdvanced.isChecked(); - if (!show && mProfile != null) - { - Integer st = mProfile.getSplitTunneling(); - show = mProfile.getMTU() != null || mProfile.getPort() != null || (st != null && st != 0); - } - mShowAdvanced.setVisibility(!show ? View.VISIBLE : View.GONE); - mAdvancedSettings.setVisibility(show ? View.VISIBLE : View.GONE); - } - - /** - * Save or update the profile depending on whether we actually have a - * profile object or not (this was created in updateProfileData) - */ - private void saveProfile() - { - if (verifyInput()) - { - if (mProfile != null) - { - updateProfileData(); - mDataSource.updateVpnProfile(mProfile); - } - else - { - mProfile = new VpnProfile(); - updateProfileData(); - mDataSource.insertProfile(mProfile); - } - setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_ID, mProfile.getId())); - finish(); - } - } - - /** - * Verify the user input and display error messages. - * @return true if the input is valid - */ - private boolean verifyInput() - { - boolean valid = true; - if (mGateway.getText().toString().trim().isEmpty()) - { - mGateway.setError(getString(R.string.alert_text_no_input_gateway)); - valid = false; - } - if (mVpnType.has(VpnTypeFeature.USER_PASS)) - { - if (mUsername.getText().toString().trim().isEmpty()) - { - mUsername.setError(getString(R.string.alert_text_no_input_username)); - valid = false; - } - } - if (mVpnType.has(VpnTypeFeature.CERTIFICATE) && mUserCertEntry == null) - { /* let's show an error icon */ - ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError(""); - valid = false; - } - if (!mCheckAuto.isChecked() && mCertEntry == null) - { - showCertificateAlert(); - valid = false; - } - Integer mtu = getInteger(mMTU); - if (mtu != null && (mtu < MTU_MIN || mtu > MTU_MAX)) - { - mMTU.setError(String.format(getString(R.string.alert_text_out_of_range), MTU_MIN, MTU_MAX)); - valid = false; - } - Integer port = getInteger(mPort); - if (port != null && (port < 1 || port > 65535)) - { - mPort.setError(String.format(getString(R.string.alert_text_out_of_range), 1, 65535)); - valid = false; - } - return valid; - } - - /** - * Update the profile object with the data entered by the user - */ - private void updateProfileData() - { - /* the name is optional, we default to the gateway if none is given */ - String name = mName.getText().toString().trim(); - String gateway = mGateway.getText().toString().trim(); - mProfile.setName(name.isEmpty() ? gateway : name); - mProfile.setGateway(gateway); - mProfile.setVpnType(mVpnType); - if (mVpnType.has(VpnTypeFeature.USER_PASS)) - { - mProfile.setUsername(mUsername.getText().toString().trim()); - String password = mPassword.getText().toString().trim(); - password = password.isEmpty() ? null : password; - mProfile.setPassword(password); - } - if (mVpnType.has(VpnTypeFeature.CERTIFICATE)) - { - mProfile.setUserCertificateAlias(mUserCertEntry.getAlias()); - } - String certAlias = mCheckAuto.isChecked() ? null : mCertEntry.getAlias(); - mProfile.setCertificateAlias(certAlias); - mProfile.setMTU(getInteger(mMTU)); - mProfile.setPort(getInteger(mPort)); - int st = 0; - st |= mBlockIPv4.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4 : 0; - st |= mBlockIPv6.isChecked() ? VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6 : 0; - mProfile.setSplitTunneling(st == 0 ? null : st); - } - - /** - * Load an existing profile if we got an ID - * - * @param savedInstanceState previously saved state - */ - private void loadProfileData(Bundle savedInstanceState) - { - String useralias = null, alias = null; - - getActionBar().setTitle(R.string.add_profile); - if (mId != null && mId != 0) - { - mProfile = mDataSource.getVpnProfile(mId); - if (mProfile != null) - { - mName.setText(mProfile.getName()); - mGateway.setText(mProfile.getGateway()); - mVpnType = mProfile.getVpnType(); - mUsername.setText(mProfile.getUsername()); - mPassword.setText(mProfile.getPassword()); - mMTU.setText(mProfile.getMTU() != null ? mProfile.getMTU().toString() : null); - mPort.setText(mProfile.getPort() != null ? mProfile.getPort().toString() : null); - mBlockIPv4.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) != 0 : false); - mBlockIPv6.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) != 0 : false); - useralias = mProfile.getUserCertificateAlias(); - alias = mProfile.getCertificateAlias(); - getActionBar().setTitle(mProfile.getName()); - } - else - { - Log.e(VpnProfileDetailActivity.class.getSimpleName(), - "VPN profile with id " + mId + " not found"); - finish(); - } - } - - mSelectVpnType.setSelection(mVpnType.ordinal()); - - /* check if the user selected a user certificate previously */ - useralias = savedInstanceState == null ? useralias: savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE); - if (useralias != null) - { - UserCertificateLoader loader = new UserCertificateLoader(this, useralias); - mUserCertLoading = useralias; - loader.execute(); - } - - /* check if the user selected a CA certificate previously */ - alias = savedInstanceState == null ? alias : savedInstanceState.getString(VpnProfileDataSource.KEY_CERTIFICATE); - mCheckAuto.setChecked(alias == null); - if (alias != null) - { - X509Certificate certificate = TrustedCertificateManager.getInstance().getCACertificateFromAlias(alias); - if (certificate != null) - { - mCertEntry = new TrustedCertificateEntry(alias, certificate); - } - else - { /* previously selected certificate is not here anymore */ - showCertificateAlert(); - mCertEntry = null; - } - } - } - - /** - * Get the integer value in the given text box or null if empty - * - * @param view text box (numeric entry assumed) - */ - private Integer getInteger(EditText view) - { - String value = view.getText().toString().trim(); - return value.isEmpty() ? null : Integer.valueOf(value); - } - - private class SelectUserCertOnClickListener implements OnClickListener, KeyChainAliasCallback - { - @Override - public void onClick(View v) - { - String useralias = mUserCertEntry != null ? mUserCertEntry.getAlias() : null; - KeyChain.choosePrivateKeyAlias(VpnProfileDetailActivity.this, this, new String[] { "RSA" }, null, null, -1, useralias); - } - - @Override - public void alias(final String alias) - { - if (alias != null) - { /* otherwise the dialog was canceled, the request denied */ - try - { - final X509Certificate[] chain = KeyChain.getCertificateChain(VpnProfileDetailActivity.this, alias); - /* alias() is not called from our main thread */ - runOnUiThread(new Runnable() { - @Override - public void run() - { - if (chain != null && chain.length > 0) - { - mUserCertEntry = new TrustedCertificateEntry(alias, chain[0]); - } - updateCredentialView(); - } - }); - } - catch (KeyChainException e) - { - e.printStackTrace(); - } - catch (InterruptedException e) - { - e.printStackTrace(); - } - } - } - } - - /** - * Load the selected user certificate asynchronously. This cannot be done - * from the main thread as getCertificateChain() calls back to our main - * thread to bind to the KeyChain service resulting in a deadlock. - */ - private class UserCertificateLoader extends AsyncTask - { - private final Context mContext; - private final String mAlias; - - public UserCertificateLoader(Context context, String alias) - { - mContext = context; - mAlias = alias; - } - - @Override - protected X509Certificate doInBackground(Void... params) - { - X509Certificate[] chain = null; - try - { - chain = KeyChain.getCertificateChain(mContext, mAlias); - } - catch (KeyChainException e) - { - e.printStackTrace(); - } - catch (InterruptedException e) - { - e.printStackTrace(); - } - if (chain != null && chain.length > 0) - { - return chain[0]; - } - return null; - } - - @Override - protected void onPostExecute(X509Certificate result) - { - if (result != null) - { - mUserCertEntry = new TrustedCertificateEntry(mAlias, result); - } - else - { /* previously selected certificate is not here anymore */ - ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError(""); - mUserCertEntry = null; - } - mUserCertLoading = null; - updateCredentialView(); - } - } - - /** - * Dialog with notification message if EAP-TNC is used. - */ - public static class TncNoticeDialog extends DialogFragment - { - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) - { - return new AlertDialog.Builder(getActivity()) - .setTitle(R.string.tnc_notice_title) - .setMessage(Html.fromHtml(getString(R.string.tnc_notice_details))) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) - { - dialog.dismiss(); - } - }).create(); - } - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java deleted file mode 100644 index fb684b53d..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (C) 2012 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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.util.ArrayList; -import java.util.HashSet; -import java.util.List; - -import org.strongswan.android.R; -import org.strongswan.android.data.VpnProfile; -import org.strongswan.android.data.VpnProfileDataSource; -import org.strongswan.android.ui.adapter.VpnProfileAdapter; - -import android.app.Activity; -import android.app.Fragment; -import android.content.Context; -import android.content.Intent; -import android.content.res.TypedArray; -import android.os.Bundle; -import android.util.AttributeSet; -import android.view.ActionMode; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView.MultiChoiceModeListener; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ListView; -import android.widget.Toast; - -public class VpnProfileListFragment extends Fragment -{ - private static final int ADD_REQUEST = 1; - private static final int EDIT_REQUEST = 2; - - private List mVpnProfiles; - private VpnProfileDataSource mDataSource; - private VpnProfileAdapter mListAdapter; - private ListView mListView; - private OnVpnProfileSelectedListener mListener; - private boolean mReadOnly; - - /** - * The activity containing this fragment should implement this interface - */ - public interface OnVpnProfileSelectedListener { - public void onVpnProfileSelected(VpnProfile profile); - } - - @Override - public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) - { - super.onInflate(activity, attrs, savedInstanceState); - TypedArray a = activity.obtainStyledAttributes(attrs, R.styleable.Fragment); - mReadOnly = a.getBoolean(R.styleable.Fragment_read_only, false); - a.recycle(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) - { - View view = inflater.inflate(R.layout.profile_list_fragment, null); - - mListView = (ListView)view.findViewById(R.id.profile_list); - mListView.setAdapter(mListAdapter); - mListView.setEmptyView(view.findViewById(R.id.profile_list_empty)); - mListView.setOnItemClickListener(mVpnProfileClicked); - - if (!mReadOnly) - { - mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); - mListView.setMultiChoiceModeListener(mVpnProfileSelected); - } - return view; - } - - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - Bundle args = getArguments(); - if (args != null) - { - mReadOnly = args.getBoolean("read_only", mReadOnly); - } - - if (!mReadOnly) - { - setHasOptionsMenu(true); - } - - Context context = getActivity().getApplicationContext(); - - mDataSource = new VpnProfileDataSource(this.getActivity()); - mDataSource.open(); - - /* cached list of profiles used as backend for the ListView */ - mVpnProfiles = mDataSource.getAllVpnProfiles(); - - mListAdapter = new VpnProfileAdapter(context, R.layout.profile_list_item, mVpnProfiles); - } - - @Override - public void onDestroy() - { - super.onDestroy(); - mDataSource.close(); - } - - @Override - public void onAttach(Activity activity) - { - super.onAttach(activity); - - if (activity instanceof OnVpnProfileSelectedListener) - { - mListener = (OnVpnProfileSelectedListener)activity; - } - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) - { - inflater.inflate(R.menu.profile_list, menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case R.id.add_profile: - Intent connectionIntent = new Intent(getActivity(), - VpnProfileDetailActivity.class); - startActivityForResult(connectionIntent, ADD_REQUEST); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) - { - switch (requestCode) - { - case ADD_REQUEST: - case EDIT_REQUEST: - if (resultCode != Activity.RESULT_OK) - { - return; - } - long id = data.getLongExtra(VpnProfileDataSource.KEY_ID, 0); - VpnProfile profile = mDataSource.getVpnProfile(id); - if (profile != null) - { /* in case this was an edit, we remove it first */ - mVpnProfiles.remove(profile); - mVpnProfiles.add(profile); - mListAdapter.notifyDataSetChanged(); - } - return; - } - super.onActivityResult(requestCode, resultCode, data); - } - - private final OnItemClickListener mVpnProfileClicked = new OnItemClickListener() { - @Override - public void onItemClick(AdapterView a, View v, int position, long id) - { - if (mListener != null) - { - mListener.onVpnProfileSelected((VpnProfile)a.getItemAtPosition(position)); - } - } - }; - - private final MultiChoiceModeListener mVpnProfileSelected = new MultiChoiceModeListener() { - private HashSet mSelected; - private MenuItem mEditProfile; - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) - { - return false; - } - - @Override - public void onDestroyActionMode(ActionMode mode) - { - } - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) - { - MenuInflater inflater = mode.getMenuInflater(); - inflater.inflate(R.menu.profile_list_context, menu); - mEditProfile = menu.findItem(R.id.edit_profile); - mSelected = new HashSet(); - mode.setTitle(R.string.select_profiles); - return true; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) - { - switch (item.getItemId()) - { - case R.id.edit_profile: - { - int position = mSelected.iterator().next(); - VpnProfile profile = (VpnProfile)mListView.getItemAtPosition(position); - Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class); - connectionIntent.putExtra(VpnProfileDataSource.KEY_ID, profile.getId()); - startActivityForResult(connectionIntent, EDIT_REQUEST); - break; - } - case R.id.delete_profile: - { - ArrayList profiles = new ArrayList(); - for (int position : mSelected) - { - profiles.add((VpnProfile)mListView.getItemAtPosition(position)); - } - for (VpnProfile profile : profiles) - { - mDataSource.deleteVpnProfile(profile); - mVpnProfiles.remove(profile); - } - mListAdapter.notifyDataSetChanged(); - Toast.makeText(VpnProfileListFragment.this.getActivity(), - R.string.profiles_deleted, Toast.LENGTH_SHORT).show(); - break; - } - default: - return false; - } - mode.finish(); - return true; - } - - @Override - public void onItemCheckedStateChanged(ActionMode mode, int position, - long id, boolean checked) - { - if (checked) - { - mSelected.add(position); - } - else - { - mSelected.remove(position); - } - final int checkedCount = mSelected.size(); - mEditProfile.setEnabled(checkedCount == 1); - switch (checkedCount) - { - case 0: - mode.setSubtitle(R.string.no_profile_selected); - break; - case 1: - mode.setSubtitle(R.string.one_profile_selected); - break; - default: - mode.setSubtitle(String.format(getString(R.string.x_profiles_selected), checkedCount)); - break; - } - } - }; -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileSelectActivity.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileSelectActivity.java deleted file mode 100644 index b4d34f5a5..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileSelectActivity.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2012 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 org.strongswan.android.data.VpnProfile; -import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; - -public class VpnProfileSelectActivity extends Activity implements OnVpnProfileSelectedListener -{ - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.vpn_profile_select); - - /* we should probably return a result also if the user clicks the back - * button before selecting a profile */ - setResult(RESULT_CANCELED); - } - - @Override - public void onVpnProfileSelected(VpnProfile profile) - { - Intent shortcut = new Intent(MainActivity.START_PROFILE); - shortcut.putExtra(MainActivity.EXTRA_VPN_PROFILE_ID, profile.getId()); - - Intent intent = new Intent(); - intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcut); - intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, profile.getName()); - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(this, R.drawable.ic_launcher)); - setResult(RESULT_OK, intent); - finish(); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java deleted file mode 100644 index 160ba951b..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java +++ /dev/null @@ -1,414 +0,0 @@ -/* - * Copyright (C) 2012-2013 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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.util.ArrayList; -import java.util.List; - -import org.strongswan.android.R; -import org.strongswan.android.data.VpnProfile; -import org.strongswan.android.logic.VpnStateService; -import org.strongswan.android.logic.VpnStateService.ErrorState; -import org.strongswan.android.logic.VpnStateService.State; -import org.strongswan.android.logic.VpnStateService.VpnStateListener; -import org.strongswan.android.logic.imc.ImcState; -import org.strongswan.android.logic.imc.RemediationInstruction; - -import android.app.AlertDialog; -import android.app.Fragment; -import android.app.ProgressDialog; -import android.app.Service; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Bundle; -import android.os.IBinder; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.TextView; - -public class VpnStateFragment extends Fragment implements VpnStateListener -{ - private static final String KEY_ERROR_CONNECTION_ID = "error_connection_id"; - private static final String KEY_DISMISSED_CONNECTION_ID = "dismissed_connection_id"; - - private TextView mProfileNameView; - private TextView mProfileView; - private TextView mStateView; - private int stateBaseColor; - private Button mActionButton; - private ProgressDialog mConnectDialog; - private ProgressDialog mDisconnectDialog; - private ProgressDialog mProgressDialog; - private AlertDialog mErrorDialog; - private long mErrorConnectionID; - private long mDismissedConnectionID; - private VpnStateService mService; - private final ServiceConnection mServiceConnection = new ServiceConnection() { - @Override - public void onServiceDisconnected(ComponentName name) - { - mService = null; - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) - { - mService = ((VpnStateService.LocalBinder)service).getService(); - mService.registerListener(VpnStateFragment.this); - updateView(); - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - /* bind to the service only seems to work from the ApplicationContext */ - Context context = getActivity().getApplicationContext(); - context.bindService(new Intent(context, VpnStateService.class), - mServiceConnection, Service.BIND_AUTO_CREATE); - - mErrorConnectionID = 0; - mDismissedConnectionID = 0; - if (savedInstanceState != null && savedInstanceState.containsKey(KEY_ERROR_CONNECTION_ID)) - { - mErrorConnectionID = (Long)savedInstanceState.getSerializable(KEY_ERROR_CONNECTION_ID); - mDismissedConnectionID = (Long)savedInstanceState.getSerializable(KEY_DISMISSED_CONNECTION_ID); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) - { - super.onSaveInstanceState(outState); - - outState.putSerializable(KEY_ERROR_CONNECTION_ID, mErrorConnectionID); - outState.putSerializable(KEY_DISMISSED_CONNECTION_ID, mDismissedConnectionID); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) - { - View view = inflater.inflate(R.layout.vpn_state_fragment, null); - - mActionButton = (Button)view.findViewById(R.id.action); - mActionButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) - { - if (mService != null) - { - mService.disconnect(); - } - } - }); - enableActionButton(false); - - mStateView = (TextView)view.findViewById(R.id.vpn_state); - stateBaseColor = mStateView.getCurrentTextColor(); - mProfileView = (TextView)view.findViewById(R.id.vpn_profile_label); - mProfileNameView = (TextView)view.findViewById(R.id.vpn_profile_name); - - return view; - } - - @Override - public void onStart() - { - super.onStart(); - if (mService != null) - { - mService.registerListener(this); - updateView(); - } - } - - @Override - public void onStop() - { - super.onStop(); - if (mService != null) - { - mService.unregisterListener(this); - } - hideErrorDialog(); - hideProgressDialog(); - } - - @Override - public void onDestroy() - { - super.onDestroy(); - if (mService != null) - { - getActivity().getApplicationContext().unbindService(mServiceConnection); - } - } - - @Override - public void stateChanged() - { - updateView(); - } - - public void updateView() - { - long connectionID = mService.getConnectionID(); - VpnProfile profile = mService.getProfile(); - State state = mService.getState(); - ErrorState error = mService.getErrorState(); - ImcState imcState = mService.getImcState(); - String name = "", gateway = ""; - - if (profile != null) - { - name = profile.getName(); - gateway = profile.getGateway(); - } - - if (reportError(connectionID, name, error, imcState)) - { - return; - } - - enableActionButton(false); - mProfileNameView.setText(name); - - switch (state) - { - case DISABLED: - showProfile(false); - hideProgressDialog(); - mStateView.setText(R.string.state_disabled); - mStateView.setTextColor(stateBaseColor); - break; - case CONNECTING: - showProfile(true); - showConnectDialog(name, gateway); - mStateView.setText(R.string.state_connecting); - mStateView.setTextColor(stateBaseColor); - break; - case CONNECTED: - showProfile(true); - hideProgressDialog(); - enableActionButton(true); - mStateView.setText(R.string.state_connected); - mStateView.setTextColor(getResources().getColor(R.color.success_text)); - break; - case DISCONNECTING: - showProfile(true); - showDisconnectDialog(name); - mStateView.setText(R.string.state_disconnecting); - mStateView.setTextColor(stateBaseColor); - break; - } - } - - private boolean reportError(long connectionID, String name, ErrorState error, ImcState imcState) - { - if (connectionID > mDismissedConnectionID) - { /* report error if it hasn't been dismissed yet */ - mErrorConnectionID = connectionID; - } - else - { /* ignore all other errors */ - error = ErrorState.NO_ERROR; - } - if (error == ErrorState.NO_ERROR) - { - hideErrorDialog(); - return false; - } - else if (mErrorDialog != null) - { /* we already show the dialog */ - return true; - } - hideProgressDialog(); - mProfileNameView.setText(name); - showProfile(true); - enableActionButton(false); - mStateView.setText(R.string.state_error); - mStateView.setTextColor(getResources().getColor(R.color.error_text)); - switch (error) - { - case AUTH_FAILED: - if (imcState == ImcState.BLOCK) - { - showErrorDialog(R.string.error_assessment_failed); - } - else - { - showErrorDialog(R.string.error_auth_failed); - } - break; - case PEER_AUTH_FAILED: - showErrorDialog(R.string.error_peer_auth_failed); - break; - case LOOKUP_FAILED: - showErrorDialog(R.string.error_lookup_failed); - break; - case UNREACHABLE: - showErrorDialog(R.string.error_unreachable); - break; - default: - showErrorDialog(R.string.error_generic); - break; - } - return true; - } - - private void showProfile(boolean show) - { - mProfileView.setVisibility(show ? View.VISIBLE : View.GONE); - mProfileNameView.setVisibility(show ? View.VISIBLE : View.GONE); - } - - private void enableActionButton(boolean enable) - { - mActionButton.setEnabled(enable); - mActionButton.setVisibility(enable ? View.VISIBLE : View.GONE); - } - - private void hideProgressDialog() - { - if (mProgressDialog != null) - { - mProgressDialog.dismiss(); - mProgressDialog = null; - mDisconnectDialog = mConnectDialog = null; - } - } - - private void hideErrorDialog() - { - if (mErrorDialog != null) - { - mErrorDialog.dismiss(); - mErrorDialog = null; - } - } - - private void clearError() - { - if (mService != null) - { - mService.disconnect(); - } - mDismissedConnectionID = mErrorConnectionID; - updateView(); - } - - private void showConnectDialog(String profile, String gateway) - { - if (mConnectDialog != null) - { /* already showing the dialog */ - return; - } - hideProgressDialog(); - mConnectDialog = new ProgressDialog(getActivity()); - mConnectDialog.setTitle(String.format(getString(R.string.connecting_title), profile)); - mConnectDialog.setMessage(String.format(getString(R.string.connecting_message), gateway)); - mConnectDialog.setIndeterminate(true); - mConnectDialog.setCancelable(false); - mConnectDialog.setButton(DialogInterface.BUTTON_NEGATIVE, - getString(android.R.string.cancel), - new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - if (mService != null) - { - mService.disconnect(); - } - } - }); - mConnectDialog.show(); - mProgressDialog = mConnectDialog; - } - - private void showDisconnectDialog(String profile) - { - if (mDisconnectDialog != null) - { /* already showing the dialog */ - return; - } - hideProgressDialog(); - mDisconnectDialog = new ProgressDialog(getActivity()); - mDisconnectDialog.setMessage(getString(R.string.state_disconnecting)); - mDisconnectDialog.setIndeterminate(true); - mDisconnectDialog.setCancelable(false); - mDisconnectDialog.show(); - mProgressDialog = mDisconnectDialog; - } - - private void showErrorDialog(int textid) - { - final List instructions = mService.getRemediationInstructions(); - final boolean show_instructions = mService.getImcState() == ImcState.BLOCK && !instructions.isEmpty(); - int text = show_instructions ? R.string.show_remediation_instructions : R.string.show_log; - - mErrorDialog = new AlertDialog.Builder(getActivity()) - .setMessage(getString(R.string.error_introduction) + " " + getString(textid)) - .setCancelable(false) - .setNeutralButton(text, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) - { - clearError(); - dialog.dismiss(); - Intent intent; - if (show_instructions) - { - intent = new Intent(getActivity(), RemediationInstructionsActivity.class); - intent.putParcelableArrayListExtra(RemediationInstructionsFragment.EXTRA_REMEDIATION_INSTRUCTIONS, - new ArrayList(instructions)); - } - else - { - intent = new Intent(getActivity(), LogActivity.class); - } - startActivity(intent); - } - }) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) - { - clearError(); - dialog.dismiss(); - } - }).create(); - mErrorDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) - { - mErrorDialog = null; - } - }); - mErrorDialog.show(); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/adapter/RemediationInstructionAdapter.java b/src/frontends/android/src/org/strongswan/android/ui/adapter/RemediationInstructionAdapter.java deleted file mode 100644 index e9ab52287..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/adapter/RemediationInstructionAdapter.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2013 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.adapter; - -import java.util.List; - -import org.strongswan.android.R; -import org.strongswan.android.logic.imc.RemediationInstruction; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.TextView; - -public class RemediationInstructionAdapter extends ArrayAdapter -{ - public RemediationInstructionAdapter(Context context) - { - super(context, 0); - } - - /** - * Set new data for this adapter. - * - * @param data the new data (null to clear) - */ - public void setData(List data) - { - clear(); - if (data != null) - { - addAll(data); - } - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) - { - View view; - if (convertView != null) - { - view = convertView; - } - else - { - LayoutInflater inflater = LayoutInflater.from(getContext()); - view = inflater.inflate(R.layout.remediation_instruction_item, parent, false); - } - RemediationInstruction item = getItem(position); - TextView text = (TextView)view.findViewById(android.R.id.text1); - text.setText(item.getTitle()); - text = (TextView)view.findViewById(android.R.id.text2); - text.setText(item.getDescription()); - return view; - } -} 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 deleted file mode 100644 index 3795bb199..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2012 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.adapter; - -import java.util.List; - -import org.strongswan.android.R; -import org.strongswan.android.security.TrustedCertificateEntry; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.TextView; - -public class TrustedCertificateAdapter extends ArrayAdapter -{ - public TrustedCertificateAdapter(Context context) - { - super(context, R.layout.trusted_certificates_item); - } - - /** - * Set new data for this adapter. - * - * @param data the new data (null to clear) - */ - public void setData(List data) - { - clear(); - if (data != null) - { - addAll(data); - } - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) - { - View view; - if (convertView != null) - { - view = convertView; - } - else - { - LayoutInflater inflater = LayoutInflater.from(getContext()); - view = inflater.inflate(R.layout.trusted_certificates_item, parent, false); - } - TrustedCertificateEntry item = getItem(position); - TextView text = (TextView)view.findViewById(R.id.subject_primary); - text.setText(item.getSubjectPrimary()); - text = (TextView)view.findViewById(R.id.subject_secondary); - text.setText(item.getSubjectSecondary()); - return view; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java b/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java deleted file mode 100644 index f3bb271bc..000000000 --- a/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2012 Tobias Brunner - * Copyright (C) 2012 Giuliano Grassi - * Copyright (C) 2012 Ralf Sager - * 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.adapter; - -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import org.strongswan.android.R; -import org.strongswan.android.data.VpnProfile; -import org.strongswan.android.data.VpnType.VpnTypeFeature; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.TextView; - -public class VpnProfileAdapter extends ArrayAdapter -{ - private final int resource; - private final List items; - - public VpnProfileAdapter(Context context, int resource, - List items) - { - super(context, resource, items); - this.resource = resource; - this.items = items; - sortItems(); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) - { - View vpnProfileView; - if (convertView != null) - { - vpnProfileView = convertView; - } - else - { - LayoutInflater inflater = LayoutInflater.from(getContext()); - vpnProfileView = inflater.inflate(resource, null); - } - VpnProfile profile = getItem(position); - TextView tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_name); - tv.setText(profile.getName()); - tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_gateway); - tv.setText(getContext().getString(R.string.profile_gateway_label) + " " + profile.getGateway()); - tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_username); - if (profile.getVpnType().has(VpnTypeFeature.USER_PASS)) - { /* if the view is reused we make sure it is visible */ - tv.setVisibility(View.VISIBLE); - tv.setText(getContext().getString(R.string.profile_username_label) + " " + profile.getUsername()); - } - else - { - tv.setVisibility(View.GONE); - } - tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_certificate); - if (profile.getVpnType().has(VpnTypeFeature.CERTIFICATE)) - { - tv.setText(getContext().getString(R.string.profile_user_certificate_label) + " " + profile.getUserCertificateAlias()); - tv.setVisibility(View.VISIBLE); - } - else - { - tv.setVisibility(View.GONE); - } - return vpnProfileView; - } - - @Override - public void notifyDataSetChanged() - { - sortItems(); - super.notifyDataSetChanged(); - } - - private void sortItems() - { - Collections.sort(this.items, new Comparator() { - @Override - public int compare(VpnProfile lhs, VpnProfile rhs) - { - return lhs.getName().compareToIgnoreCase(rhs.getName()); - } - }); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/utils/BufferedByteWriter.java b/src/frontends/android/src/org/strongswan/android/utils/BufferedByteWriter.java deleted file mode 100644 index efc728377..000000000 --- a/src/frontends/android/src/org/strongswan/android/utils/BufferedByteWriter.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2013 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; - -import java.nio.ByteBuffer; - -/** - * Very similar to ByteBuffer (although with a stripped interface) but it - * automatically resizes the underlying buffer. - */ -public class BufferedByteWriter -{ - /** - * The underlying byte buffer - */ - private byte[] mBuffer; - - /** - * ByteBuffer used as wrapper around the buffer to easily convert values - */ - private ByteBuffer mWriter; - - /** - * Create a writer with a default initial capacity - */ - public BufferedByteWriter() - { - this(0); - } - - /** - * Create a writer with the given initial capacity (helps avoid expensive - * resizing if known). - * @param capacity initial capacity - */ - public BufferedByteWriter(int capacity) - { - capacity = capacity > 4 ? capacity : 32; - mBuffer = new byte[capacity]; - mWriter = ByteBuffer.wrap(mBuffer); - } - - /** - * Ensure that there is enough space available to write the requested - * number of bytes. If necessary the internal buffer is resized. - * @param required required number of bytes - */ - private void ensureCapacity(int required) - { - if (mWriter.remaining() >= required) - { - return; - } - byte[] buffer = new byte[(mBuffer.length + required) * 2]; - System.arraycopy(mBuffer, 0, buffer, 0, mWriter.position()); - mBuffer = buffer; - ByteBuffer writer = ByteBuffer.wrap(buffer); - writer.position(mWriter.position()); - mWriter = writer; - } - - /** - * Write the given byte array to the buffer - * @param value - * @return the writer - */ - public BufferedByteWriter put(byte[] value) - { - ensureCapacity(value.length); - mWriter.put(value); - return this; - } - - /** - * Write the given byte to the buffer - * @param value - * @return the writer - */ - public BufferedByteWriter put(byte value) - { - ensureCapacity(1); - mWriter.put(value); - return this; - } - - /** - * Write the 8-bit length of the given data followed by the data itself - * @param value - * @return the writer - */ - public BufferedByteWriter putLen8(byte[] value) - { - ensureCapacity(1 + value.length); - mWriter.put((byte)value.length); - mWriter.put(value); - return this; - } - - /** - * Write the 16-bit length of the given data followed by the data itself - * @param value - * @return the writer - */ - public BufferedByteWriter putLen16(byte[] value) - { - ensureCapacity(2 + value.length); - mWriter.putShort((short)value.length); - mWriter.put(value); - return this; - } - - /** - * Write the given short value (16-bit) in big-endian order to the buffer - * @param value - * @return the writer - */ - public BufferedByteWriter put16(short value) - { - ensureCapacity(2); - mWriter.putShort(value); - return this; - } - - /** - * Write 24-bit of the given value in big-endian order to the buffer - * @param value - * @return the writer - */ - public BufferedByteWriter put24(int value) - { - ensureCapacity(3); - mWriter.put((byte)(value >> 16)); - mWriter.putShort((short)value); - return this; - } - - /** - * Write the given int value (32-bit) in big-endian order to the buffer - * @param value - * @return the writer - */ - public BufferedByteWriter put32(int value) - { - ensureCapacity(4); - mWriter.putInt(value); - return this; - } - - /** - * Write the given long value (64-bit) in big-endian order to the buffer - * @param value - * @return the writer - */ - public BufferedByteWriter put64(long value) - { - ensureCapacity(8); - mWriter.putLong(value); - return this; - } - - /** - * Convert the internal buffer to a new byte array. - * @return byte array - */ - public byte[] toByteArray() - { - int length = mWriter.position(); - byte[] bytes = new byte[length]; - System.arraycopy(mBuffer, 0, bytes, 0, length); - return bytes; - } -} diff --git a/src/frontends/android/src/org/strongswan/android/utils/SettingsWriter.java b/src/frontends/android/src/org/strongswan/android/utils/SettingsWriter.java deleted file mode 100644 index 01c0ab8a8..000000000 --- a/src/frontends/android/src/org/strongswan/android/utils/SettingsWriter.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2015 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; - -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.Map.Entry; -import java.util.regex.Pattern; - - -/** - * Simple generator for data/files that may be parsed by libstrongswan's - * settings_t class. - */ -public class SettingsWriter -{ - /** - * Top-level section - */ - private final SettingsSection mTop = new SettingsSection(); - - /** - * Set a string value - * @param key - * @param value - * @return the writer - */ - public SettingsWriter setValue(String key, String value) - { - Pattern pattern = Pattern.compile("[^#{}=\"\\n\\t ]+"); - if (key == null || !pattern.matcher(key).matches()) - { - return this; - } - String[] keys = key.split("\\."); - SettingsSection section = mTop; - section = findOrCreateSection(Arrays.copyOfRange(keys, 0, keys.length-1)); - section.Settings.put(keys[keys.length-1], value); - return this; - } - - /** - * Set an integer value - * @param key - * @param value - * @return the writer - */ - public SettingsWriter setValue(String key, Integer value) - { - return setValue(key, value == null ? null : value.toString()); - } - - /** - * Set a boolean value - * @param key - * @param value - * @return the writer - */ - public SettingsWriter setValue(String key, Boolean value) - { - return setValue(key, value == null ? null : value ? "1" : "0"); - } - - /** - * Serializes the settings to a string in the format understood by - * libstrongswan's settings_t parser. - * @return serialized settings - */ - public String serialize() - { - StringBuilder builder = new StringBuilder(); - serializeSection(mTop, builder); - return builder.toString(); - } - - /** - * Serialize the settings in a section and recursively serialize sub-sections - * @param section - * @param builder - */ - private void serializeSection(SettingsSection section, StringBuilder builder) - { - for (Entry setting : section.Settings.entrySet()) - { - builder.append(setting.getKey()).append('='); - if (setting.getValue() != null) - { - builder.append("\"").append(escapeValue(setting.getValue())).append("\""); - } - builder.append('\n'); - } - - for (Entry subsection : section.Sections.entrySet()) - { - builder.append(subsection.getKey()).append(" {\n"); - serializeSection(subsection.getValue(), builder); - builder.append("}\n"); - } - } - - /** - * Escape value so it may be wrapped in " - * @param value - * @return - */ - private String escapeValue(String value) - { - return value.replace("\"", "\\\""); - } - - /** - * Find or create the nested sections with the given names - * @param sections list of section names - * @return final section - */ - private SettingsSection findOrCreateSection(String[] sections) - { - SettingsSection section = mTop; - for (String name : sections) - { - SettingsSection subsection = section.Sections.get(name); - if (subsection == null) - { - subsection = new SettingsSection(); - section.Sections.put(name, subsection); - } - section = subsection; - } - return section; - } - - /** - * A section containing sub-sections and settings. - */ - private class SettingsSection - { - /** - * Assigned key/value pairs - */ - LinkedHashMap Settings = new LinkedHashMap(); - - /** - * Assigned sub-sections - */ - LinkedHashMap Sections = new LinkedHashMap(); - } -} diff --git a/src/frontends/android/src/org/strongswan/android/utils/Utils.java b/src/frontends/android/src/org/strongswan/android/utils/Utils.java deleted file mode 100644 index b5c447f31..000000000 --- a/src/frontends/android/src/org/strongswan/android/utils/Utils.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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); - } -} -- cgit v1.2.3