From e95ec50b468958db084cdbb03b275fb2d0e6aa4d Mon Sep 17 00:00:00 2001 From: Dominik Date: Sat, 14 Apr 2012 16:18:06 +0200 Subject: changing package name back --- org_apg/AndroidManifest.xml | 50 +- org_apg/res/layout/edit_key_key_item.xml | 4 +- org_apg/res/layout/edit_key_section.xml | 4 +- org_apg/res/layout/edit_key_user_id_item.xml | 4 +- org_apg/res/layout/key_server_editor.xml | 4 +- org_apg/res/layout/main.xml | 4 +- org_apg/res/values/static_strings.xml | 2 +- org_apg/res/values/strings.xml | 4 +- org_apg/res/xml/apg_preferences.xml | 10 +- org_apg/src/org/apg/Apg.java | 2280 -------------------- org_apg/src/org/apg/ApgService.java | 657 ------ org_apg/src/org/apg/AskForSecretKeyPassPhrase.java | 139 -- org_apg/src/org/apg/CachedPassPhrase.java | 62 - org_apg/src/org/apg/Constants.java | 54 - org_apg/src/org/apg/DataDestination.java | 95 - org_apg/src/org/apg/DataSource.java | 118 - org_apg/src/org/apg/FileDialog.java | 129 -- org_apg/src/org/apg/HkpKeyServer.java | 256 --- org_apg/src/org/apg/IApgService.aidl | 125 -- org_apg/src/org/apg/Id.java | 193 -- org_apg/src/org/apg/InputData.java | 39 - org_apg/src/org/apg/KeyServer.java | 60 - org_apg/src/org/apg/PausableThread.java | 49 - org_apg/src/org/apg/PositionAwareInputStream.java | 81 - org_apg/src/org/apg/Preferences.java | 184 -- org_apg/src/org/apg/Primes.java | 185 -- org_apg/src/org/apg/ProgressDialogUpdater.java | 25 - org_apg/src/org/apg/Service.java | 92 - .../org/apg/provider/ApgServiceBlobDatabase.java | 68 - .../org/apg/provider/ApgServiceBlobProvider.java | 152 -- org_apg/src/org/apg/provider/DataProvider.java | 381 ---- org_apg/src/org/apg/provider/Database.java | 616 ------ org_apg/src/org/apg/provider/KeyRings.java | 33 - org_apg/src/org/apg/provider/Keys.java | 51 - org_apg/src/org/apg/provider/UserIds.java | 31 - org_apg/src/org/apg/ui/AboutActivity.java | 65 - org_apg/src/org/apg/ui/BaseActivity.java | 408 ---- org_apg/src/org/apg/ui/DecryptActivity.java | 864 -------- org_apg/src/org/apg/ui/EditKeyActivity.java | 414 ---- org_apg/src/org/apg/ui/EncryptActivity.java | 1042 --------- org_apg/src/org/apg/ui/GeneralActivity.java | 191 -- org_apg/src/org/apg/ui/HelpActivity.java | 79 - .../src/org/apg/ui/ImportFromQRCodeActivity.java | 152 -- org_apg/src/org/apg/ui/KeyListActivity.java | 769 ------- .../src/org/apg/ui/KeyServerExportActivity.java | 132 -- .../org/apg/ui/KeyServerPreferenceActivity.java | 128 -- org_apg/src/org/apg/ui/KeyServerQueryActivity.java | 329 --- org_apg/src/org/apg/ui/MailListActivity.java | 221 -- org_apg/src/org/apg/ui/MainActivity.java | 213 -- org_apg/src/org/apg/ui/PreferencesActivity.java | 237 -- org_apg/src/org/apg/ui/PublicKeyListActivity.java | 195 -- org_apg/src/org/apg/ui/SecretKeyListActivity.java | 196 -- .../org/apg/ui/SelectPublicKeyListActivity.java | 189 -- .../org/apg/ui/SelectSecretKeyListActivity.java | 121 -- org_apg/src/org/apg/ui/SignKeyActivity.java | 327 --- org_apg/src/org/apg/ui/widget/DashboardLayout.java | 186 -- org_apg/src/org/apg/ui/widget/Editor.java | 25 - .../org/apg/ui/widget/IntegerListPreference.java | 95 - org_apg/src/org/apg/ui/widget/KeyEditor.java | 235 -- org_apg/src/org/apg/ui/widget/KeyServerEditor.java | 78 - org_apg/src/org/apg/ui/widget/SectionView.java | 331 --- .../apg/ui/widget/SelectPublicKeyListAdapter.java | 216 -- .../apg/ui/widget/SelectSecretKeyListAdapter.java | 181 -- org_apg/src/org/apg/ui/widget/UserIdEditor.java | 195 -- org_apg/src/org/apg/util/ApgCon.java | 836 ------- org_apg/src/org/apg/util/ApgConInterface.java | 21 - org_apg/src/org/apg/util/Choice.java | 45 - org_apg/src/org/apg/util/Compatibility.java | 95 - org_apg/src/org/apg/util/Constants.java | 6 - org_apg/src/org/apg/util/IterableIterator.java | 31 - org_apg/src/org/apg/util/Utils.java | 75 - org_apg/src/org/thialfihar/android/apg/Apg.java | 2280 ++++++++++++++++++++ .../src/org/thialfihar/android/apg/ApgService.java | 658 ++++++ .../android/apg/AskForSecretKeyPassPhrase.java | 139 ++ .../thialfihar/android/apg/CachedPassPhrase.java | 62 + .../src/org/thialfihar/android/apg/Constants.java | 54 + .../thialfihar/android/apg/DataDestination.java | 95 + .../src/org/thialfihar/android/apg/DataSource.java | 118 + .../src/org/thialfihar/android/apg/FileDialog.java | 129 ++ .../org/thialfihar/android/apg/HkpKeyServer.java | 256 +++ .../org/thialfihar/android/apg/IApgService.aidl | 125 ++ org_apg/src/org/thialfihar/android/apg/Id.java | 193 ++ .../src/org/thialfihar/android/apg/InputData.java | 39 + .../src/org/thialfihar/android/apg/KeyServer.java | 60 + .../org/thialfihar/android/apg/PausableThread.java | 49 + .../android/apg/PositionAwareInputStream.java | 81 + .../org/thialfihar/android/apg/Preferences.java | 184 ++ org_apg/src/org/thialfihar/android/apg/Primes.java | 185 ++ .../android/apg/ProgressDialogUpdater.java | 25 + .../src/org/thialfihar/android/apg/Service.java | 92 + .../apg/provider/ApgServiceBlobDatabase.java | 68 + .../apg/provider/ApgServiceBlobProvider.java | 152 ++ .../android/apg/provider/DataProvider.java | 381 ++++ .../thialfihar/android/apg/provider/Database.java | 616 ++++++ .../thialfihar/android/apg/provider/KeyRings.java | 33 + .../org/thialfihar/android/apg/provider/Keys.java | 51 + .../thialfihar/android/apg/provider/UserIds.java | 31 + .../thialfihar/android/apg/service/ApgService.java | 12 + .../thialfihar/android/apg/ui/AboutActivity.java | 65 + .../thialfihar/android/apg/ui/BaseActivity.java | 408 ++++ .../thialfihar/android/apg/ui/DecryptActivity.java | 864 ++++++++ .../thialfihar/android/apg/ui/EditKeyActivity.java | 412 ++++ .../thialfihar/android/apg/ui/EncryptActivity.java | 1042 +++++++++ .../thialfihar/android/apg/ui/GeneralActivity.java | 191 ++ .../thialfihar/android/apg/ui/HelpActivity.java | 79 + .../android/apg/ui/ImportFromQRCodeActivity.java | 152 ++ .../thialfihar/android/apg/ui/KeyListActivity.java | 769 +++++++ .../android/apg/ui/KeyServerExportActivity.java | 132 ++ .../apg/ui/KeyServerPreferenceActivity.java | 128 ++ .../android/apg/ui/KeyServerQueryActivity.java | 329 +++ .../android/apg/ui/MailListActivity.java | 221 ++ .../thialfihar/android/apg/ui/MainActivity.java | 213 ++ .../android/apg/ui/PreferencesActivity.java | 237 ++ .../android/apg/ui/PublicKeyListActivity.java | 195 ++ .../android/apg/ui/SecretKeyListActivity.java | 196 ++ .../apg/ui/SelectPublicKeyListActivity.java | 189 ++ .../apg/ui/SelectSecretKeyListActivity.java | 121 ++ .../thialfihar/android/apg/ui/SignKeyActivity.java | 327 +++ .../android/apg/ui/widget/DashboardLayout.java | 186 ++ .../thialfihar/android/apg/ui/widget/Editor.java | 25 + .../apg/ui/widget/IntegerListPreference.java | 95 + .../android/apg/ui/widget/KeyEditor.java | 235 ++ .../android/apg/ui/widget/KeyServerEditor.java | 78 + .../android/apg/ui/widget/SectionView.java | 331 +++ .../apg/ui/widget/SelectPublicKeyListAdapter.java | 216 ++ .../apg/ui/widget/SelectSecretKeyListAdapter.java | 181 ++ .../android/apg/ui/widget/UserIdEditor.java | 195 ++ .../org/thialfihar/android/apg/util/ApgCon.java | 836 +++++++ .../android/apg/util/ApgConInterface.java | 21 + .../org/thialfihar/android/apg/util/Choice.java | 45 + .../thialfihar/android/apg/util/Compatibility.java | 95 + .../org/thialfihar/android/apg/util/Constants.java | 6 + .../android/apg/util/IterableIterator.java | 31 + .../src/org/thialfihar/android/apg/util/Utils.java | 75 + 134 files changed, 15132 insertions(+), 15121 deletions(-) delete mode 100644 org_apg/src/org/apg/Apg.java delete mode 100644 org_apg/src/org/apg/ApgService.java delete mode 100644 org_apg/src/org/apg/AskForSecretKeyPassPhrase.java delete mode 100644 org_apg/src/org/apg/CachedPassPhrase.java delete mode 100644 org_apg/src/org/apg/Constants.java delete mode 100644 org_apg/src/org/apg/DataDestination.java delete mode 100644 org_apg/src/org/apg/DataSource.java delete mode 100644 org_apg/src/org/apg/FileDialog.java delete mode 100644 org_apg/src/org/apg/HkpKeyServer.java delete mode 100644 org_apg/src/org/apg/IApgService.aidl delete mode 100644 org_apg/src/org/apg/Id.java delete mode 100644 org_apg/src/org/apg/InputData.java delete mode 100644 org_apg/src/org/apg/KeyServer.java delete mode 100644 org_apg/src/org/apg/PausableThread.java delete mode 100644 org_apg/src/org/apg/PositionAwareInputStream.java delete mode 100644 org_apg/src/org/apg/Preferences.java delete mode 100644 org_apg/src/org/apg/Primes.java delete mode 100644 org_apg/src/org/apg/ProgressDialogUpdater.java delete mode 100644 org_apg/src/org/apg/Service.java delete mode 100644 org_apg/src/org/apg/provider/ApgServiceBlobDatabase.java delete mode 100644 org_apg/src/org/apg/provider/ApgServiceBlobProvider.java delete mode 100644 org_apg/src/org/apg/provider/DataProvider.java delete mode 100644 org_apg/src/org/apg/provider/Database.java delete mode 100644 org_apg/src/org/apg/provider/KeyRings.java delete mode 100644 org_apg/src/org/apg/provider/Keys.java delete mode 100644 org_apg/src/org/apg/provider/UserIds.java delete mode 100644 org_apg/src/org/apg/ui/AboutActivity.java delete mode 100644 org_apg/src/org/apg/ui/BaseActivity.java delete mode 100644 org_apg/src/org/apg/ui/DecryptActivity.java delete mode 100644 org_apg/src/org/apg/ui/EditKeyActivity.java delete mode 100644 org_apg/src/org/apg/ui/EncryptActivity.java delete mode 100644 org_apg/src/org/apg/ui/GeneralActivity.java delete mode 100644 org_apg/src/org/apg/ui/HelpActivity.java delete mode 100644 org_apg/src/org/apg/ui/ImportFromQRCodeActivity.java delete mode 100644 org_apg/src/org/apg/ui/KeyListActivity.java delete mode 100644 org_apg/src/org/apg/ui/KeyServerExportActivity.java delete mode 100644 org_apg/src/org/apg/ui/KeyServerPreferenceActivity.java delete mode 100644 org_apg/src/org/apg/ui/KeyServerQueryActivity.java delete mode 100644 org_apg/src/org/apg/ui/MailListActivity.java delete mode 100644 org_apg/src/org/apg/ui/MainActivity.java delete mode 100644 org_apg/src/org/apg/ui/PreferencesActivity.java delete mode 100644 org_apg/src/org/apg/ui/PublicKeyListActivity.java delete mode 100644 org_apg/src/org/apg/ui/SecretKeyListActivity.java delete mode 100644 org_apg/src/org/apg/ui/SelectPublicKeyListActivity.java delete mode 100644 org_apg/src/org/apg/ui/SelectSecretKeyListActivity.java delete mode 100644 org_apg/src/org/apg/ui/SignKeyActivity.java delete mode 100644 org_apg/src/org/apg/ui/widget/DashboardLayout.java delete mode 100644 org_apg/src/org/apg/ui/widget/Editor.java delete mode 100644 org_apg/src/org/apg/ui/widget/IntegerListPreference.java delete mode 100644 org_apg/src/org/apg/ui/widget/KeyEditor.java delete mode 100644 org_apg/src/org/apg/ui/widget/KeyServerEditor.java delete mode 100644 org_apg/src/org/apg/ui/widget/SectionView.java delete mode 100644 org_apg/src/org/apg/ui/widget/SelectPublicKeyListAdapter.java delete mode 100644 org_apg/src/org/apg/ui/widget/SelectSecretKeyListAdapter.java delete mode 100644 org_apg/src/org/apg/ui/widget/UserIdEditor.java delete mode 100644 org_apg/src/org/apg/util/ApgCon.java delete mode 100644 org_apg/src/org/apg/util/ApgConInterface.java delete mode 100644 org_apg/src/org/apg/util/Choice.java delete mode 100644 org_apg/src/org/apg/util/Compatibility.java delete mode 100644 org_apg/src/org/apg/util/Constants.java delete mode 100644 org_apg/src/org/apg/util/IterableIterator.java delete mode 100644 org_apg/src/org/apg/util/Utils.java create mode 100644 org_apg/src/org/thialfihar/android/apg/Apg.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ApgService.java create mode 100644 org_apg/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java create mode 100644 org_apg/src/org/thialfihar/android/apg/CachedPassPhrase.java create mode 100644 org_apg/src/org/thialfihar/android/apg/Constants.java create mode 100644 org_apg/src/org/thialfihar/android/apg/DataDestination.java create mode 100644 org_apg/src/org/thialfihar/android/apg/DataSource.java create mode 100644 org_apg/src/org/thialfihar/android/apg/FileDialog.java create mode 100644 org_apg/src/org/thialfihar/android/apg/HkpKeyServer.java create mode 100644 org_apg/src/org/thialfihar/android/apg/IApgService.aidl create mode 100644 org_apg/src/org/thialfihar/android/apg/Id.java create mode 100644 org_apg/src/org/thialfihar/android/apg/InputData.java create mode 100644 org_apg/src/org/thialfihar/android/apg/KeyServer.java create mode 100644 org_apg/src/org/thialfihar/android/apg/PausableThread.java create mode 100644 org_apg/src/org/thialfihar/android/apg/PositionAwareInputStream.java create mode 100644 org_apg/src/org/thialfihar/android/apg/Preferences.java create mode 100644 org_apg/src/org/thialfihar/android/apg/Primes.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ProgressDialogUpdater.java create mode 100644 org_apg/src/org/thialfihar/android/apg/Service.java create mode 100644 org_apg/src/org/thialfihar/android/apg/provider/ApgServiceBlobDatabase.java create mode 100644 org_apg/src/org/thialfihar/android/apg/provider/ApgServiceBlobProvider.java create mode 100644 org_apg/src/org/thialfihar/android/apg/provider/DataProvider.java create mode 100644 org_apg/src/org/thialfihar/android/apg/provider/Database.java create mode 100644 org_apg/src/org/thialfihar/android/apg/provider/KeyRings.java create mode 100644 org_apg/src/org/thialfihar/android/apg/provider/Keys.java create mode 100644 org_apg/src/org/thialfihar/android/apg/provider/UserIds.java create mode 100644 org_apg/src/org/thialfihar/android/apg/service/ApgService.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/AboutActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/BaseActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/DecryptActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/EditKeyActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/EncryptActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/GeneralActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/HelpActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/ImportFromQRCodeActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/KeyListActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/KeyServerExportActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/KeyServerPreferenceActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/KeyServerQueryActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/MailListActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/MainActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/PreferencesActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/PublicKeyListActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/SecretKeyListActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/SelectPublicKeyListActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/SelectSecretKeyListActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/SignKeyActivity.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/widget/DashboardLayout.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/widget/Editor.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/widget/IntegerListPreference.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/widget/KeyServerEditor.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/widget/SectionView.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/widget/SelectPublicKeyListAdapter.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/widget/SelectSecretKeyListAdapter.java create mode 100644 org_apg/src/org/thialfihar/android/apg/ui/widget/UserIdEditor.java create mode 100644 org_apg/src/org/thialfihar/android/apg/util/ApgCon.java create mode 100644 org_apg/src/org/thialfihar/android/apg/util/ApgConInterface.java create mode 100644 org_apg/src/org/thialfihar/android/apg/util/Choice.java create mode 100644 org_apg/src/org/thialfihar/android/apg/util/Compatibility.java create mode 100644 org_apg/src/org/thialfihar/android/apg/util/Constants.java create mode 100644 org_apg/src/org/thialfihar/android/apg/util/IterableIterator.java create mode 100644 org_apg/src/org/thialfihar/android/apg/util/Utils.java (limited to 'org_apg') diff --git a/org_apg/AndroidManifest.xml b/org_apg/AndroidManifest.xml index 4c1eaea63..ca39827fa 100644 --- a/org_apg/AndroidManifest.xml +++ b/org_apg/AndroidManifest.xml @@ -2,12 +2,12 @@ + android:versionName="2.0" > - + @@ -78,8 +78,8 @@ android:uiOptions="splitActionBarWhenNarrow" android:windowSoftInputMode="stateHidden" > - - + + @@ -91,7 +91,7 @@ android:launchMode="singleTop" android:uiOptions="splitActionBarWhenNarrow" > - + @@ -109,7 +109,7 @@ android:label="@string/title_selectSignature" android:launchMode="singleTop" > - + @@ -128,10 +128,10 @@ android:uiOptions="splitActionBarWhenNarrow" android:windowSoftInputMode="stateHidden" > - - - - + + + + @@ -145,9 +145,9 @@ android:uiOptions="splitActionBarWhenNarrow" android:windowSoftInputMode="stateHidden" > - - - + + + @@ -220,18 +220,18 @@ android:label="@string/title_about" android:theme="@style/Theme.Sherlock.Light.Dialog" /> - + + android:authorities="org.thialfihar.android.apg.provider" + android:readPermission="org.thialfihar.android.apg.permission.READ_KEY_DETAILS" /> + android:name="org.thialfihar.android.apg.provider.ApgServiceBlobProvider" + android:authorities="org.thialfihar.android.apg.provider.apgserviceblobprovider" + android:permission="org.thialfihar.android.apg.permission.STORE_BLOBS" /> \ No newline at end of file diff --git a/org_apg/res/layout/edit_key_key_item.xml b/org_apg/res/layout/edit_key_key_item.xml index cf816c797..5556ec22e 100644 --- a/org_apg/res/layout/edit_key_key_item.xml +++ b/org_apg/res/layout/edit_key_key_item.xml @@ -15,7 +15,7 @@ limitations under the License. --> - @@ -131,4 +131,4 @@ android:layout_height="1dip" android:background="?android:attr/listDivider" /> - \ No newline at end of file + \ No newline at end of file diff --git a/org_apg/res/layout/edit_key_section.xml b/org_apg/res/layout/edit_key_section.xml index 333d0b753..e8f788f6d 100644 --- a/org_apg/res/layout/edit_key_section.xml +++ b/org_apg/res/layout/edit_key_section.xml @@ -15,7 +15,7 @@ limitations under the License. --> - @@ -54,4 +54,4 @@ android:orientation="vertical" android:paddingBottom="6dip" /> - \ No newline at end of file + \ No newline at end of file diff --git a/org_apg/res/layout/edit_key_user_id_item.xml b/org_apg/res/layout/edit_key_user_id_item.xml index 1961cf109..26a7304e0 100644 --- a/org_apg/res/layout/edit_key_user_id_item.xml +++ b/org_apg/res/layout/edit_key_user_id_item.xml @@ -15,7 +15,7 @@ limitations under the License. --> - @@ -104,4 +104,4 @@ android:layout_height="1dip" android:background="?android:attr/listDivider" /> - \ No newline at end of file + \ No newline at end of file diff --git a/org_apg/res/layout/key_server_editor.xml b/org_apg/res/layout/key_server_editor.xml index a02540c2c..e4c25b316 100644 --- a/org_apg/res/layout/key_server_editor.xml +++ b/org_apg/res/layout/key_server_editor.xml @@ -14,7 +14,7 @@ limitations under the License. --> - - + diff --git a/org_apg/res/layout/main.xml b/org_apg/res/layout/main.xml index 6f0a66a71..e4c509c70 100644 --- a/org_apg/res/layout/main.xml +++ b/org_apg/res/layout/main.xml @@ -20,7 +20,7 @@ android:layout_height="fill_parent" android:orientation="vertical" > - @@ -66,7 +66,7 @@ android:drawableTop="@drawable/dashboard_help" android:onClick="helpOnClick" android:text="@string/dashboard_help" /> - + diff --git a/org_apg/res/values/static_strings.xml b/org_apg/res/values/static_strings.xml index c8a6a5026..c69cf8adf 100644 --- a/org_apg/res/values/static_strings.xml +++ b/org_apg/res/values/static_strings.xml @@ -1,7 +1,7 @@ - APG+ + APG https://github.com/dschuermann/apg \ No newline at end of file diff --git a/org_apg/res/values/strings.xml b/org_apg/res/values/strings.xml index 5499c2bc8..606e351b1 100644 --- a/org_apg/res/values/strings.xml +++ b/org_apg/res/values/strings.xml @@ -327,10 +327,10 @@ - Android Privacy Guard (APG) is a OpenPGP implementation for Android.\n\nAPG+ is a fork based on the original APG to introduce more features and a new user interface. + Android Privacy Guard (APG) is a OpenPGP implementation for Android. License: Apache License 2.0 Version: - Developer: Thialfihar (Main developer), Senecaso (QRCode, sign key, upload key), Markus Doits (AIDL), Oliver Runge, Dominik Schürmann (APG+ fork) + Developer: Thialfihar (Main developer), Senecaso (QRCode, sign key, upload key), Markus Doits (AIDL), Oliver Runge, Dominik Schürmann (New user interface) Manage Keys diff --git a/org_apg/res/xml/apg_preferences.xml b/org_apg/res/xml/apg_preferences.xml index f32c2d73f..778f51375 100644 --- a/org_apg/res/xml/apg_preferences.xml +++ b/org_apg/res/xml/apg_preferences.xml @@ -18,7 +18,7 @@ - - - - - diff --git a/org_apg/src/org/apg/Apg.java b/org_apg/src/org/apg/Apg.java deleted file mode 100644 index ec4c202e7..000000000 --- a/org_apg/src/org/apg/Apg.java +++ /dev/null @@ -1,2280 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -import org.apg.KeyServer.AddKeyException; -import org.apg.provider.DataProvider; -import org.apg.provider.Database; -import org.apg.provider.KeyRings; -import org.apg.provider.Keys; -import org.apg.provider.UserIds; -import org.apg.ui.BaseActivity; -import org.apg.ui.widget.KeyEditor; -import org.apg.ui.widget.SectionView; -import org.apg.ui.widget.UserIdEditor; -import org.apg.util.IterableIterator; -import org.apg.util.Utils; -import org.spongycastle.bcpg.ArmoredInputStream; -import org.spongycastle.bcpg.ArmoredOutputStream; -import org.spongycastle.bcpg.BCPGOutputStream; -import org.spongycastle.bcpg.CompressionAlgorithmTags; -import org.spongycastle.bcpg.HashAlgorithmTags; -import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; -import org.spongycastle.bcpg.sig.KeyFlags; -import org.spongycastle.jce.provider.BouncyCastleProvider; -import org.spongycastle.jce.spec.ElGamalParameterSpec; -import org.spongycastle.openpgp.PGPCompressedData; -import org.spongycastle.openpgp.PGPCompressedDataGenerator; -import org.spongycastle.openpgp.PGPEncryptedData; -import org.spongycastle.openpgp.PGPEncryptedDataGenerator; -import org.spongycastle.openpgp.PGPEncryptedDataList; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPKeyPair; -import org.spongycastle.openpgp.PGPKeyRing; -import org.spongycastle.openpgp.PGPKeyRingGenerator; -import org.spongycastle.openpgp.PGPLiteralData; -import org.spongycastle.openpgp.PGPLiteralDataGenerator; -import org.spongycastle.openpgp.PGPObjectFactory; -import org.spongycastle.openpgp.PGPOnePassSignature; -import org.spongycastle.openpgp.PGPOnePassSignatureList; -import org.spongycastle.openpgp.PGPPBEEncryptedData; -import org.spongycastle.openpgp.PGPPrivateKey; -import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSecretKey; -import org.spongycastle.openpgp.PGPSecretKeyRing; -import org.spongycastle.openpgp.PGPSignature; -import org.spongycastle.openpgp.PGPSignatureGenerator; -import org.spongycastle.openpgp.PGPSignatureList; -import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; -import org.spongycastle.openpgp.PGPSignatureSubpacketVector; -import org.spongycastle.openpgp.PGPUtil; -import org.spongycastle.openpgp.PGPV3SignatureGenerator; -import org.apg.R; - -import android.app.Activity; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.net.Uri; -import android.os.Bundle; -import android.os.Environment; -import android.os.Message; -import android.view.ViewGroup; - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.EOFException; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.RandomAccessFile; -import java.math.BigInteger; -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SecureRandom; -import java.security.Security; -import java.security.SignatureException; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Vector; -import java.util.regex.Pattern; - -public class Apg { - private static final String PACKAGE_NAME = "org.apg"; - private static final String INTENT_PREFIX = "org.apg.intent."; - - public static class Intent { - public static final String DECRYPT = INTENT_PREFIX + "DECRYPT"; - public static final String ENCRYPT = INTENT_PREFIX + "ENCRYPT"; - public static final String DECRYPT_FILE = INTENT_PREFIX + "DECRYPT_FILE"; - public static final String ENCRYPT_FILE = INTENT_PREFIX + "ENCRYPT_FILE"; - public static final String DECRYPT_AND_RETURN = INTENT_PREFIX + "DECRYPT_AND_RETURN"; - public static final String ENCRYPT_AND_RETURN = INTENT_PREFIX + "ENCRYPT_AND_RETURN"; - public static final String SELECT_PUBLIC_KEYS = INTENT_PREFIX + "SELECT_PUBLIC_KEYS"; - public static final String SELECT_SECRET_KEY = INTENT_PREFIX + "SELECT_SECRET_KEY"; - public static final String IMPORT = INTENT_PREFIX + "IMPORT"; - public static final String LOOK_UP_KEY_ID = INTENT_PREFIX + "LOOK_UP_KEY_ID"; - public static final String LOOK_UP_KEY_ID_AND_RETURN = INTENT_PREFIX - + "LOOK_UP_KEY_ID_AND_RETURN"; - public static final String GENERATE_SIGNATURE = INTENT_PREFIX + "GENERATE_SIGNATURE"; - public static final String EXPORT_KEY_TO_SERVER = INTENT_PREFIX + "EXPORT_KEY_TO_SERVER"; - public static final String IMPORT_FROM_QR_CODE = INTENT_PREFIX + "IMPORT_FROM_QR_CODE"; - public static final String CREATE_KEY = INTENT_PREFIX + "CREATE_KEY"; - public static final String EDIT_KEY = INTENT_PREFIX + "EDIT_KEY"; - } - - public static final String EXTRA_TEXT = "text"; - public static final String EXTRA_DATA = "data"; - public static final String EXTRA_ERROR = "error"; - public static final String EXTRA_DECRYPTED_MESSAGE = "decryptedMessage"; - public static final String EXTRA_DECRYPTED_DATA = "decryptedData"; - public static final String EXTRA_ENCRYPTED_MESSAGE = "encryptedMessage"; - public static final String EXTRA_ENCRYPTED_DATA = "encryptedData"; - public static final String EXTRA_RESULT_URI = "resultUri"; - public static final String EXTRA_SIGNATURE = "signature"; - public static final String EXTRA_SIGNATURE_KEY_ID = "signatureKeyId"; - public static final String EXTRA_SIGNATURE_USER_ID = "signatureUserId"; - public static final String EXTRA_SIGNATURE_SUCCESS = "signatureSuccess"; - public static final String EXTRA_SIGNATURE_UNKNOWN = "signatureUnknown"; - public static final String EXTRA_SIGNATURE_DATA = "signatureData"; - public static final String EXTRA_SIGNATURE_TEXT = "signatureText"; - public static final String EXTRA_USER_ID = "userId"; - public static final String EXTRA_USER_IDS = "userIds"; - public static final String EXTRA_KEY_ID = "keyId"; - public static final String EXTRA_REPLY_TO = "replyTo"; - public static final String EXTRA_SEND_TO = "sendTo"; - public static final String EXTRA_SUBJECT = "subject"; - public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryptionKeyIds"; - public static final String EXTRA_SELECTION = "selection"; - public static final String EXTRA_ASCII_ARMOUR = "asciiArmour"; - public static final String EXTRA_BINARY = "binary"; - public static final String EXTRA_KEY_SERVERS = "keyServers"; - public static final String EXTRA_EXPECTED_FINGERPRINT = "expectedFingerprint"; - public static final String EXTRA_NO_PASSPHRASE = "noPassphrase"; - public static final String EXTRA_GENERATE_DEFAULT_KEYS = "generateDefaultKeys"; - - public static final String AUTHORITY = DataProvider.AUTHORITY; - - public static final Uri CONTENT_URI_SECRET_KEY_RINGS = Uri.parse("content://" + AUTHORITY - + "/key_rings/secret/"); - public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_KEY_ID = Uri.parse("content://" - + AUTHORITY + "/key_rings/secret/key_id/"); - public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_EMAILS = Uri.parse("content://" - + AUTHORITY + "/key_rings/secret/emails/"); - - public static final Uri CONTENT_URI_PUBLIC_KEY_RINGS = Uri.parse("content://" + AUTHORITY - + "/key_rings/public/"); - public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_KEY_ID = Uri.parse("content://" - + AUTHORITY + "/key_rings/public/key_id/"); - public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_EMAILS = Uri.parse("content://" - + AUTHORITY + "/key_rings/public/emails/"); - - private static String VERSION = null; - - private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[] { - SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, - SymmetricKeyAlgorithmTags.AES_128, SymmetricKeyAlgorithmTags.CAST5, - SymmetricKeyAlgorithmTags.TRIPLE_DES }; - private static final int[] PREFERRED_HASH_ALGORITHMS = new int[] { HashAlgorithmTags.SHA1, - HashAlgorithmTags.SHA256, HashAlgorithmTags.RIPEMD160 }; - private static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[] { - CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2, - CompressionAlgorithmTags.ZIP }; - - public static Pattern PGP_MESSAGE = Pattern.compile( - ".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL); - - public static Pattern PGP_SIGNED_MESSAGE = Pattern - .compile( - ".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*", - Pattern.DOTALL); - - public static Pattern PGP_PUBLIC_KEY = Pattern.compile( - ".*?(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----).*", - Pattern.DOTALL); - - private static HashMap mPassPhraseCache = new HashMap(); - private static String mEditPassPhrase = null; - - private static Database mDatabase = null; - - public static class GeneralException extends Exception { - static final long serialVersionUID = 0xf812773342L; - - public GeneralException(String message) { - super(message); - } - } - - public static class NoAsymmetricEncryptionException extends Exception { - static final long serialVersionUID = 0xf812773343L; - - public NoAsymmetricEncryptionException() { - super(); - } - } - - public static void initialize(Context context) { - if (mDatabase == null) { - mDatabase = new Database(context); - } - } - - public static Database getDatabase() { - return mDatabase; - } - - public static void setEditPassPhrase(String passPhrase) { - mEditPassPhrase = passPhrase; - } - - public static String getEditPassPhrase() { - return mEditPassPhrase; - } - - public static void setCachedPassPhrase(long keyId, String passPhrase) { - mPassPhraseCache.put(keyId, new CachedPassPhrase(new Date().getTime(), passPhrase)); - } - - public static String getCachedPassPhrase(long keyId) { - long realId = keyId; - if (realId != Id.key.symmetric) { - PGPSecretKeyRing keyRing = getSecretKeyRing(keyId); - if (keyRing == null) { - return null; - } - PGPSecretKey masterKey = getMasterKey(keyRing); - if (masterKey == null) { - return null; - } - realId = masterKey.getKeyID(); - } - CachedPassPhrase cpp = mPassPhraseCache.get(realId); - if (cpp == null) { - return null; - } - // set it again to reset the cache life cycle - setCachedPassPhrase(realId, cpp.passPhrase); - return cpp.passPhrase; - } - - public static int cleanUpCache(int ttl, int initialDelay) { - int delay = initialDelay; - long realTtl = ttl * 1000; - long now = new Date().getTime(); - Vector oldKeys = new Vector(); - for (Map.Entry pair : mPassPhraseCache.entrySet()) { - long lived = now - pair.getValue().timestamp; - if (lived >= realTtl) { - oldKeys.add(pair.getKey()); - } else { - // see, whether the remaining time for this cache entry improves our - // check delay - long nextCheck = realTtl - lived + 1000; - if (nextCheck < delay) { - delay = (int) nextCheck; - } - } - } - - for (long keyId : oldKeys) { - mPassPhraseCache.remove(keyId); - } - - return delay; - } - - public static PGPSecretKey createKey(Context context, int algorithmChoice, int keySize, - String passPhrase, PGPSecretKey masterKey) throws NoSuchAlgorithmException, - PGPException, NoSuchProviderException, GeneralException, - InvalidAlgorithmParameterException { - - if (keySize < 512) { - throw new GeneralException(context.getString(R.string.error_keySizeMinimum512bit)); - } - - Security.addProvider(new BouncyCastleProvider()); - - if (passPhrase == null) { - passPhrase = ""; - } - - int algorithm = 0; - KeyPairGenerator keyGen = null; - - switch (algorithmChoice) { - case Id.choice.algorithm.dsa: { - keyGen = KeyPairGenerator.getInstance("DSA", new BouncyCastleProvider()); - keyGen.initialize(keySize, new SecureRandom()); - algorithm = PGPPublicKey.DSA; - break; - } - - case Id.choice.algorithm.elgamal: { - if (masterKey == null) { - throw new GeneralException( - context.getString(R.string.error_masterKeyMustNotBeElGamal)); - } - keyGen = KeyPairGenerator.getInstance("ELGAMAL", new BouncyCastleProvider()); - BigInteger p = Primes.getBestPrime(keySize); - BigInteger g = new BigInteger("2"); - - ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g); - - keyGen.initialize(elParams); - algorithm = PGPPublicKey.ELGAMAL_ENCRYPT; - break; - } - - case Id.choice.algorithm.rsa: { - keyGen = KeyPairGenerator.getInstance("RSA", new BouncyCastleProvider()); - keyGen.initialize(keySize, new SecureRandom()); - - algorithm = PGPPublicKey.RSA_GENERAL; - break; - } - - default: { - throw new GeneralException(context.getString(R.string.error_unknownAlgorithmChoice)); - } - } - - PGPKeyPair keyPair = new PGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date()); - - PGPSecretKey secretKey = null; - if (masterKey == null) { - // enough for now, as we assemble the key again later anyway - secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, keyPair, "", - PGPEncryptedData.CAST5, passPhrase.toCharArray(), null, null, - new SecureRandom(), new BouncyCastleProvider().getName()); - - } else { - PGPPublicKey tmpKey = masterKey.getPublicKey(); - PGPPublicKey masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(), - tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime()); - PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(passPhrase.toCharArray(), - new BouncyCastleProvider()); - - PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); - PGPKeyRingGenerator ringGen = new PGPKeyRingGenerator( - PGPSignature.POSITIVE_CERTIFICATION, masterKeyPair, "", PGPEncryptedData.CAST5, - passPhrase.toCharArray(), null, null, new SecureRandom(), - new BouncyCastleProvider().getName()); - ringGen.addSubKey(keyPair); - PGPSecretKeyRing secKeyRing = ringGen.generateSecretKeyRing(); - Iterator it = secKeyRing.getSecretKeys(); - // first one is the master key - it.next(); - secretKey = it.next(); - } - - return secretKey; - } - - public static void buildSecretKey(Activity context, SectionView userIdsView, - SectionView keysView, String oldPassPhrase, String newPassPhrase, - ProgressDialogUpdater progress) throws Apg.GeneralException, NoSuchProviderException, - PGPException, NoSuchAlgorithmException, SignatureException, IOException, - Database.GeneralException { - - if (progress != null) - progress.setProgress(R.string.progress_buildingKey, 0, 100); - - Security.addProvider(new BouncyCastleProvider()); - - if (oldPassPhrase == null || oldPassPhrase.equals("")) { - oldPassPhrase = ""; - } - - if (newPassPhrase == null || newPassPhrase.equals("")) { - newPassPhrase = ""; - } - - Vector userIds = new Vector(); - Vector keys = new Vector(); - - ViewGroup userIdEditors = userIdsView.getEditors(); - ViewGroup keyEditors = keysView.getEditors(); - - boolean gotMainUserId = false; - for (int i = 0; i < userIdEditors.getChildCount(); ++i) { - UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i); - String userId = null; - try { - userId = editor.getValue(); - } catch (UserIdEditor.NoNameException e) { - throw new Apg.GeneralException(context.getString(R.string.error_userIdNeedsAName)); - } catch (UserIdEditor.NoEmailException e) { - throw new Apg.GeneralException( - context.getString(R.string.error_userIdNeedsAnEmailAddress)); - } catch (UserIdEditor.InvalidEmailException e) { - throw new Apg.GeneralException("" + e); - } - - if (userId.equals("")) { - continue; - } - - if (editor.isMainUserId()) { - userIds.insertElementAt(userId, 0); - gotMainUserId = true; - } else { - userIds.add(userId); - } - } - - if (userIds.size() == 0) { - throw new Apg.GeneralException(context.getString(R.string.error_keyNeedsAUserId)); - } - - if (!gotMainUserId) { - throw new Apg.GeneralException( - context.getString(R.string.error_mainUserIdMustNotBeEmpty)); - } - - if (keyEditors.getChildCount() == 0) { - throw new Apg.GeneralException(context.getString(R.string.error_keyNeedsMasterKey)); - } - - for (int i = 0; i < keyEditors.getChildCount(); ++i) { - KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i); - keys.add(editor.getValue()); - } - - if (progress != null) - progress.setProgress(R.string.progress_preparingMasterKey, 10, 100); - KeyEditor keyEditor = (KeyEditor) keyEditors.getChildAt(0); - int usageId = keyEditor.getUsage(); - boolean canSign = (usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt); - boolean canEncrypt = (usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt); - - String mainUserId = userIds.get(0); - - PGPSecretKey masterKey = keys.get(0); - PGPPublicKey tmpKey = masterKey.getPublicKey(); - PGPPublicKey masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(), - tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime()); - PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(oldPassPhrase.toCharArray(), - new BouncyCastleProvider()); - - if (progress != null) - progress.setProgress(R.string.progress_certifyingMasterKey, 20, 100); - for (int i = 0; i < userIds.size(); ++i) { - String userId = userIds.get(i); - - PGPSignatureGenerator sGen = new PGPSignatureGenerator(masterPublicKey.getAlgorithm(), - HashAlgorithmTags.SHA1, new BouncyCastleProvider()); - - sGen.initSign(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); - - PGPSignature certification = sGen.generateCertification(userId, masterPublicKey); - - masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification); - } - - // TODO: cross-certify the master key with every sub key - - PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); - - PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); - PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); - - int keyFlags = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA; - if (canEncrypt) { - keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; - } - hashedPacketsGen.setKeyFlags(true, keyFlags); - - hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS); - hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS); - hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS); - - // TODO: this doesn't work quite right yet - if (keyEditor.getExpiryDate() != null) { - GregorianCalendar creationDate = new GregorianCalendar(); - creationDate.setTime(getCreationDate(masterKey)); - GregorianCalendar expiryDate = keyEditor.getExpiryDate(); - long numDays = Utils.getNumDaysBetween(creationDate, expiryDate); - if (numDays <= 0) { - throw new GeneralException( - context.getString(R.string.error_expiryMustComeAfterCreation)); - } - hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400); - } - - if (progress != null) { - progress.setProgress(R.string.progress_buildingMasterKeyRing, 30, 100); - } - - PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, - masterKeyPair, mainUserId, PGPEncryptedData.CAST5, newPassPhrase.toCharArray(), - hashedPacketsGen.generate(), unhashedPacketsGen.generate(), new SecureRandom(), - new BouncyCastleProvider().getName()); - - if (progress != null) - progress.setProgress(R.string.progress_addingSubKeys, 40, 100); - for (int i = 1; i < keys.size(); ++i) { - if (progress != null) - progress.setProgress(40 + 50 * (i - 1) / (keys.size() - 1), 100); - PGPSecretKey subKey = keys.get(i); - keyEditor = (KeyEditor) keyEditors.getChildAt(i); - PGPPublicKey subPublicKey = subKey.getPublicKey(); - PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(oldPassPhrase.toCharArray(), - new BouncyCastleProvider()); - PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey.getAlgorithm(), - subPublicKey.getKey(new BouncyCastleProvider()), subPrivateKey.getKey(), - subPublicKey.getCreationTime()); - - hashedPacketsGen = new PGPSignatureSubpacketGenerator(); - unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); - - keyFlags = 0; - usageId = keyEditor.getUsage(); - canSign = (usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt); - canEncrypt = (usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt); - if (canSign) { - keyFlags |= KeyFlags.SIGN_DATA; - } - if (canEncrypt) { - keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; - } - hashedPacketsGen.setKeyFlags(true, keyFlags); - - // TODO: this doesn't work quite right yet - if (keyEditor.getExpiryDate() != null) { - GregorianCalendar creationDate = new GregorianCalendar(); - creationDate.setTime(getCreationDate(masterKey)); - GregorianCalendar expiryDate = keyEditor.getExpiryDate(); - long numDays = Utils.getNumDaysBetween(creationDate, expiryDate); - if (numDays <= 0) { - throw new GeneralException( - context.getString(R.string.error_expiryMustComeAfterCreation)); - } - hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400); - } - - keyGen.addSubKey(subKeyPair, hashedPacketsGen.generate(), unhashedPacketsGen.generate()); - } - - PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing(); - PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing(); - - if (progress != null) - progress.setProgress(R.string.progress_savingKeyRing, 90, 100); - mDatabase.saveKeyRing(secretKeyRing); - mDatabase.saveKeyRing(publicKeyRing); - - if (progress != null) - progress.setProgress(R.string.progress_done, 100, 100); - } - - public static PGPKeyRing decodeKeyRing(InputStream is) throws IOException { - InputStream in = PGPUtil.getDecoderStream(is); - PGPObjectFactory objectFactory = new PGPObjectFactory(in); - Object obj = objectFactory.nextObject(); - - if (obj instanceof PGPKeyRing) { - return (PGPKeyRing) obj; - } - - return null; - } - - public static int storeKeyRingInCache(PGPKeyRing keyring) { - int status = Integer.MIN_VALUE; // out of bounds value (Id.retrun_value.*) - try { - if (keyring instanceof PGPSecretKeyRing) { - PGPSecretKeyRing secretKeyRing = (PGPSecretKeyRing) keyring; - boolean save = true; - try { - PGPPrivateKey testKey = secretKeyRing.getSecretKey().extractPrivateKey( - new char[] {}, new BouncyCastleProvider()); - if (testKey == null) { - // this is bad, something is very wrong... likely a --export-secret-subkeys - // export - save = false; - status = Id.return_value.bad; - } - } catch (PGPException e) { - // all good if this fails, we likely didn't use the right password - } - - if (save) { - status = mDatabase.saveKeyRing(secretKeyRing); - } - } else if (keyring instanceof PGPPublicKeyRing) { - PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring; - status = mDatabase.saveKeyRing(publicKeyRing); - } - } catch (IOException e) { - status = Id.return_value.error; - } catch (Database.GeneralException e) { - status = Id.return_value.error; - } - - return status; - } - - public static boolean uploadKeyRingToServer(HkpKeyServer server, PGPPublicKeyRing keyring) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ArmoredOutputStream aos = new ArmoredOutputStream(bos); - try { - aos.write(keyring.getEncoded()); - aos.close(); - - String armouredKey = bos.toString("UTF-8"); - server.add(armouredKey); - - return true; - } catch (IOException e) { - return false; - } catch (AddKeyException e) { - // TODO: tell the user? - return false; - } finally { - try { - bos.close(); - } catch (IOException e) { - } - } - } - - public static Bundle importKeyRings(Activity context, int type, InputData data, - ProgressDialogUpdater progress) throws GeneralException, FileNotFoundException, - PGPException, IOException { - Bundle returnData = new Bundle(); - - if (type == Id.type.secret_key) { - if (progress != null) - progress.setProgress(R.string.progress_importingSecretKeys, 0, 100); - } else { - if (progress != null) - progress.setProgress(R.string.progress_importingPublicKeys, 0, 100); - } - - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - throw new GeneralException(context.getString(R.string.error_externalStorageNotReady)); - } - - PositionAwareInputStream progressIn = new PositionAwareInputStream(data.getInputStream()); - // need to have access to the bufferedInput, so we can reuse it for the possible - // PGPObject chunks after the first one, e.g. files with several consecutive ASCII - // armour blocks - BufferedInputStream bufferedInput = new BufferedInputStream(progressIn); - int newKeys = 0; - int oldKeys = 0; - int badKeys = 0; - try { - PGPKeyRing keyring = decodeKeyRing(bufferedInput); - while (keyring != null) { - int status = Integer.MIN_VALUE; // out of bounds value - - // if this key is what we expect it to be, save it - if ((type == Id.type.secret_key && keyring instanceof PGPSecretKeyRing) - || (type == Id.type.public_key && keyring instanceof PGPPublicKeyRing)) { - status = storeKeyRingInCache(keyring); - } - - if (status == Id.return_value.error) { - throw new GeneralException(context.getString(R.string.error_savingKeys)); - } - - // update the counts to display to the user at the end - if (status == Id.return_value.updated) { - ++oldKeys; - } else if (status == Id.return_value.ok) { - ++newKeys; - } else if (status == Id.return_value.bad) { - ++badKeys; - } - - if (progress != null) { - progress.setProgress((int) (100 * progressIn.position() / data.getSize()), 100); - } - // TODO: needed? - // obj = objectFactory.nextObject(); - - keyring = decodeKeyRing(bufferedInput); - } - } catch (EOFException e) { - // nothing to do, we are done - } - - returnData.putInt("added", newKeys); - returnData.putInt("updated", oldKeys); - returnData.putInt("bad", badKeys); - - if (progress != null) - progress.setProgress(R.string.progress_done, 100, 100); - - return returnData; - } - - public static Bundle exportKeyRings(Activity context, Vector keyRingIds, - OutputStream outStream, ProgressDialogUpdater progress) throws GeneralException, - FileNotFoundException, PGPException, IOException { - Bundle returnData = new Bundle(); - - if (keyRingIds.size() == 1) { - if (progress != null) - progress.setProgress(R.string.progress_exportingKey, 0, 100); - } else { - if (progress != null) - progress.setProgress(R.string.progress_exportingKeys, 0, 100); - } - - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - throw new GeneralException(context.getString(R.string.error_externalStorageNotReady)); - } - ArmoredOutputStream out = new ArmoredOutputStream(outStream); - - int numKeys = 0; - for (int i = 0; i < keyRingIds.size(); ++i) { - if (progress != null) - progress.setProgress(i * 100 / keyRingIds.size(), 100); - Object obj = mDatabase.getKeyRing(keyRingIds.get(i)); - PGPPublicKeyRing publicKeyRing; - PGPSecretKeyRing secretKeyRing; - - if (obj instanceof PGPSecretKeyRing) { - secretKeyRing = (PGPSecretKeyRing) obj; - secretKeyRing.encode(out); - } else if (obj instanceof PGPPublicKeyRing) { - publicKeyRing = (PGPPublicKeyRing) obj; - publicKeyRing.encode(out); - } else { - continue; - } - ++numKeys; - } - out.close(); - returnData.putInt("exported", numKeys); - - if (progress != null) - progress.setProgress(R.string.progress_done, 100, 100); - - return returnData; - } - - public static Date getCreationDate(PGPPublicKey key) { - return key.getCreationTime(); - } - - public static Date getCreationDate(PGPSecretKey key) { - return key.getPublicKey().getCreationTime(); - } - - @SuppressWarnings("unchecked") - public static PGPPublicKey getMasterKey(PGPPublicKeyRing keyRing) { - if (keyRing == null) { - return null; - } - for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { - if (key.isMasterKey()) { - return key; - } - } - - return null; - } - - @SuppressWarnings("unchecked") - public static PGPSecretKey getMasterKey(PGPSecretKeyRing keyRing) { - if (keyRing == null) { - return null; - } - for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { - if (key.isMasterKey()) { - return key; - } - } - - return null; - } - - @SuppressWarnings("unchecked") - public static Vector getEncryptKeys(PGPPublicKeyRing keyRing) { - Vector encryptKeys = new Vector(); - - for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { - if (isEncryptionKey(key)) { - encryptKeys.add(key); - } - } - - return encryptKeys; - } - - @SuppressWarnings("unchecked") - public static Vector getSigningKeys(PGPSecretKeyRing keyRing) { - Vector signingKeys = new Vector(); - - for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { - if (isSigningKey(key)) { - signingKeys.add(key); - } - } - - return signingKeys; - } - - public static Vector getUsableEncryptKeys(PGPPublicKeyRing keyRing) { - Vector usableKeys = new Vector(); - Vector encryptKeys = getEncryptKeys(keyRing); - PGPPublicKey masterKey = null; - for (int i = 0; i < encryptKeys.size(); ++i) { - PGPPublicKey key = encryptKeys.get(i); - if (!isExpired(key)) { - if (key.isMasterKey()) { - masterKey = key; - } else { - usableKeys.add(key); - } - } - } - if (masterKey != null) { - usableKeys.add(masterKey); - } - return usableKeys; - } - - public static boolean isExpired(PGPPublicKey key) { - Date creationDate = getCreationDate(key); - Date expiryDate = getExpiryDate(key); - Date now = new Date(); - if (now.compareTo(creationDate) >= 0 - && (expiryDate == null || now.compareTo(expiryDate) <= 0)) { - return false; - } - return true; - } - - public static boolean isExpired(PGPSecretKey key) { - return isExpired(key.getPublicKey()); - } - - public static Vector getUsableSigningKeys(PGPSecretKeyRing keyRing) { - Vector usableKeys = new Vector(); - Vector signingKeys = getSigningKeys(keyRing); - PGPSecretKey masterKey = null; - for (int i = 0; i < signingKeys.size(); ++i) { - PGPSecretKey key = signingKeys.get(i); - if (key.isMasterKey()) { - masterKey = key; - } else { - usableKeys.add(key); - } - } - if (masterKey != null) { - usableKeys.add(masterKey); - } - return usableKeys; - } - - public static Date getExpiryDate(PGPPublicKey key) { - Date creationDate = getCreationDate(key); - if (key.getValidDays() == 0) { - // no expiry - return null; - } - Calendar calendar = GregorianCalendar.getInstance(); - calendar.setTime(creationDate); - calendar.add(Calendar.DATE, key.getValidDays()); - Date expiryDate = calendar.getTime(); - - return expiryDate; - } - - public static Date getExpiryDate(PGPSecretKey key) { - return getExpiryDate(key.getPublicKey()); - } - - public static PGPPublicKey getEncryptPublicKey(long masterKeyId) { - PGPPublicKeyRing keyRing = getPublicKeyRing(masterKeyId); - if (keyRing == null) { - return null; - } - Vector encryptKeys = getUsableEncryptKeys(keyRing); - if (encryptKeys.size() == 0) { - return null; - } - return encryptKeys.get(0); - } - - public static PGPSecretKey getSigningKey(long masterKeyId) { - PGPSecretKeyRing keyRing = getSecretKeyRing(masterKeyId); - if (keyRing == null) { - return null; - } - Vector signingKeys = getUsableSigningKeys(keyRing); - if (signingKeys.size() == 0) { - return null; - } - return signingKeys.get(0); - } - - @SuppressWarnings("unchecked") - public static String getMainUserId(PGPPublicKey key) { - for (String userId : new IterableIterator(key.getUserIDs())) { - return userId; - } - return null; - } - - @SuppressWarnings("unchecked") - public static String getMainUserId(PGPSecretKey key) { - for (String userId : new IterableIterator(key.getUserIDs())) { - return userId; - } - return null; - } - - public static String getMainUserIdSafe(Context context, PGPPublicKey key) { - String userId = getMainUserId(key); - if (userId == null) { - userId = context.getResources().getString(R.string.unknownUserId); - } - return userId; - } - - public static String getMainUserIdSafe(Context context, PGPSecretKey key) { - String userId = getMainUserId(key); - if (userId == null) { - userId = context.getResources().getString(R.string.unknownUserId); - } - return userId; - } - - @SuppressWarnings("unchecked") - public static boolean isEncryptionKey(PGPPublicKey key) { - if (!key.isEncryptionKey()) { - return false; - } - - if (key.getVersion() <= 3) { - // this must be true now - return key.isEncryptionKey(); - } - - // special cases - if (key.getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT) { - return true; - } - - if (key.getAlgorithm() == PGPPublicKey.RSA_ENCRYPT) { - return true; - } - - for (PGPSignature sig : new IterableIterator(key.getSignatures())) { - if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) { - continue; - } - PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); - - if (hashed != null - && (hashed.getKeyFlags() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0) { - return true; - } - - PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); - - if (unhashed != null - && (unhashed.getKeyFlags() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0) { - return true; - } - } - return false; - } - - public static boolean isEncryptionKey(PGPSecretKey key) { - return isEncryptionKey(key.getPublicKey()); - } - - @SuppressWarnings("unchecked") - public static boolean isSigningKey(PGPPublicKey key) { - if (key.getVersion() <= 3) { - return true; - } - - // special case - if (key.getAlgorithm() == PGPPublicKey.RSA_SIGN) { - return true; - } - - for (PGPSignature sig : new IterableIterator(key.getSignatures())) { - if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) { - continue; - } - PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); - - if (hashed != null && (hashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) { - return true; - } - - PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); - - if (unhashed != null && (unhashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) { - return true; - } - } - - return false; - } - - public static boolean isSigningKey(PGPSecretKey key) { - return isSigningKey(key.getPublicKey()); - } - - public static String getAlgorithmInfo(PGPPublicKey key) { - return getAlgorithmInfo(key.getAlgorithm(), key.getBitStrength()); - } - - public static String getAlgorithmInfo(PGPSecretKey key) { - return getAlgorithmInfo(key.getPublicKey()); - } - - public static String getAlgorithmInfo(int algorithm, int keySize) { - String algorithmStr = null; - - switch (algorithm) { - case PGPPublicKey.RSA_ENCRYPT: - case PGPPublicKey.RSA_GENERAL: - case PGPPublicKey.RSA_SIGN: { - algorithmStr = "RSA"; - break; - } - - case PGPPublicKey.DSA: { - algorithmStr = "DSA"; - break; - } - - case PGPPublicKey.ELGAMAL_ENCRYPT: - case PGPPublicKey.ELGAMAL_GENERAL: { - algorithmStr = "ElGamal"; - break; - } - - default: { - algorithmStr = "???"; - break; - } - } - return algorithmStr + ", " + keySize + "bit"; - } - - public static String convertToHex(byte[] fp) { - String fingerPrint = ""; - for (int i = 0; i < fp.length; ++i) { - if (i != 0 && i % 10 == 0) { - fingerPrint += " "; - } else if (i != 0 && i % 2 == 0) { - fingerPrint += " "; - } - String chunk = Integer.toHexString((fp[i] + 256) % 256).toUpperCase(); - while (chunk.length() < 2) { - chunk = "0" + chunk; - } - fingerPrint += chunk; - } - - return fingerPrint; - - } - - public static String getFingerPrint(long keyId) { - PGPPublicKey key = Apg.getPublicKey(keyId); - if (key == null) { - PGPSecretKey secretKey = Apg.getSecretKey(keyId); - if (secretKey == null) { - return ""; - } - key = secretKey.getPublicKey(); - } - - return convertToHex(key.getFingerprint()); - } - - public static String getSmallFingerPrint(long keyId) { - String fingerPrint = Long.toHexString(keyId & 0xffffffffL).toUpperCase(); - while (fingerPrint.length() < 8) { - fingerPrint = "0" + fingerPrint; - } - return fingerPrint; - } - - public static String keyToHex(long keyId) { - return getSmallFingerPrint(keyId >> 32) + getSmallFingerPrint(keyId); - } - - public static long keyFromHex(String data) { - int len = data.length(); - String s2 = data.substring(len - 8); - String s1 = data.substring(0, len - 8); - return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16); - } - - public static void deleteKey(int keyRingId) { - mDatabase.deleteKeyRing(keyRingId); - } - - public static PGPKeyRing getKeyRing(int keyRingId) { - return (PGPKeyRing) mDatabase.getKeyRing(keyRingId); - } - - public static PGPSecretKeyRing getSecretKeyRing(long keyId) { - byte[] data = mDatabase.getKeyRingDataFromKeyId(Id.database.type_secret, keyId); - if (data == null) { - return null; - } - try { - return new PGPSecretKeyRing(data); - } catch (IOException e) { - // no good way to handle this, return null - // TODO: some info? - } catch (PGPException e) { - // no good way to handle this, return null - // TODO: some info? - } - return null; - } - - public static PGPPublicKeyRing getPublicKeyRing(long keyId) { - byte[] data = mDatabase.getKeyRingDataFromKeyId(Id.database.type_public, keyId); - if (data == null) { - return null; - } - try { - return new PGPPublicKeyRing(data); - } catch (IOException e) { - // no good way to handle this, return null - // TODO: some info? - } - return null; - } - - public static PGPSecretKey getSecretKey(long keyId) { - PGPSecretKeyRing keyRing = getSecretKeyRing(keyId); - if (keyRing == null) { - return null; - } - return keyRing.getSecretKey(keyId); - } - - public static PGPPublicKey getPublicKey(long keyId) { - PGPPublicKeyRing keyRing = getPublicKeyRing(keyId); - if (keyRing == null) { - return null; - } - - return keyRing.getPublicKey(keyId); - } - - public static Vector getKeyRingIds(int type) { - SQLiteDatabase db = mDatabase.db(); - Vector keyIds = new Vector(); - Cursor c = db.query(KeyRings.TABLE_NAME, new String[] { KeyRings._ID }, KeyRings.TYPE - + " = ?", new String[] { "" + type }, null, null, null); - if (c != null && c.moveToFirst()) { - do { - keyIds.add(c.getInt(0)); - } while (c.moveToNext()); - } - - if (c != null) { - c.close(); - } - - return keyIds; - } - - public static String getMainUserId(long keyId, int type) { - SQLiteDatabase db = mDatabase.db(); - Cursor c = db.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON (" - + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "." - + Keys.KEY_RING_ID + ") " + " INNER JOIN " + Keys.TABLE_NAME + " AS masterKey ON (" - + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + "masterKey." - + Keys.KEY_RING_ID + " AND " + "masterKey." + Keys.IS_MASTER_KEY + " = '1') " - + " INNER JOIN " + UserIds.TABLE_NAME + " ON (" + UserIds.TABLE_NAME + "." - + UserIds.KEY_ID + " = " + "masterKey." + Keys._ID + " AND " + UserIds.TABLE_NAME - + "." + UserIds.RANK + " = '0')", new String[] { UserIds.USER_ID }, Keys.TABLE_NAME - + "." + Keys.KEY_ID + " = ? AND " + KeyRings.TABLE_NAME + "." + KeyRings.TYPE - + " = ?", new String[] { "" + keyId, "" + type, }, null, null, null); - String userId = ""; - if (c != null && c.moveToFirst()) { - do { - userId = c.getString(0); - } while (c.moveToNext()); - } - - if (c != null) { - c.close(); - } - - return userId; - } - - public static void encrypt(Context context, InputData data, OutputStream outStream, - boolean armored, long encryptionKeyIds[], long signatureKeyId, - String signaturePassPhrase, ProgressDialogUpdater progress, int symmetricAlgorithm, - int hashAlgorithm, int compression, boolean forceV3Signature, String passPhrase) - throws IOException, GeneralException, PGPException, NoSuchProviderException, - NoSuchAlgorithmException, SignatureException { - Security.addProvider(new BouncyCastleProvider()); - - if (encryptionKeyIds == null) { - encryptionKeyIds = new long[0]; - } - - ArmoredOutputStream armorOut = null; - OutputStream out = null; - OutputStream encryptOut = null; - if (armored) { - armorOut = new ArmoredOutputStream(outStream); - armorOut.setHeader("Version", getFullVersion(context)); - out = armorOut; - } else { - out = outStream; - } - PGPSecretKey signingKey = null; - PGPSecretKeyRing signingKeyRing = null; - PGPPrivateKey signaturePrivateKey = null; - - if (encryptionKeyIds.length == 0 && passPhrase == null) { - throw new GeneralException( - context.getString(R.string.error_noEncryptionKeysOrPassPhrase)); - } - - if (signatureKeyId != 0) { - signingKeyRing = getSecretKeyRing(signatureKeyId); - signingKey = getSigningKey(signatureKeyId); - if (signingKey == null) { - throw new GeneralException(context.getString(R.string.error_signatureFailed)); - } - - if (signaturePassPhrase == null) { - throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase)); - } - if (progress != null) - progress.setProgress(R.string.progress_extractingSignatureKey, 0, 100); - signaturePrivateKey = signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(), - new BouncyCastleProvider()); - if (signaturePrivateKey == null) { - throw new GeneralException( - context.getString(R.string.error_couldNotExtractPrivateKey)); - } - } - if (progress != null) - progress.setProgress(R.string.progress_preparingStreams, 5, 100); - - // encrypt and compress input file content - PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(symmetricAlgorithm, true, - new SecureRandom(), new BouncyCastleProvider()); - - if (encryptionKeyIds.length == 0) { - // symmetric encryption - cPk.addMethod(passPhrase.toCharArray()); - } - for (int i = 0; i < encryptionKeyIds.length; ++i) { - PGPPublicKey key = getEncryptPublicKey(encryptionKeyIds[i]); - if (key != null) { - cPk.addMethod(key); - } - } - encryptOut = cPk.open(out, new byte[1 << 16]); - - PGPSignatureGenerator signatureGenerator = null; - PGPV3SignatureGenerator signatureV3Generator = null; - - if (signatureKeyId != 0) { - if (progress != null) - progress.setProgress(R.string.progress_preparingSignature, 10, 100); - if (forceV3Signature) { - signatureV3Generator = new PGPV3SignatureGenerator(signingKey.getPublicKey() - .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider()); - signatureV3Generator.initSign(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey); - } else { - signatureGenerator = new PGPSignatureGenerator(signingKey.getPublicKey() - .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider()); - signatureGenerator.initSign(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey); - - String userId = getMainUserId(getMasterKey(signingKeyRing)); - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - spGen.setSignerUserID(false, userId); - signatureGenerator.setHashedSubpackets(spGen.generate()); - } - } - - PGPCompressedDataGenerator compressGen = null; - BCPGOutputStream bcpgOut = null; - if (compression == Id.choice.compression.none) { - bcpgOut = new BCPGOutputStream(encryptOut); - } else { - compressGen = new PGPCompressedDataGenerator(compression); - bcpgOut = new BCPGOutputStream(compressGen.open(encryptOut)); - } - if (signatureKeyId != 0) { - if (forceV3Signature) { - signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut); - } else { - signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); - } - } - - PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); - // file name not needed, so empty string - OutputStream pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", new Date(), - new byte[1 << 16]); - if (progress != null) - progress.setProgress(R.string.progress_encrypting, 20, 100); - - long done = 0; - int n = 0; - byte[] buffer = new byte[1 << 16]; - InputStream in = data.getInputStream(); - while ((n = in.read(buffer)) > 0) { - pOut.write(buffer, 0, n); - if (signatureKeyId != 0) { - if (forceV3Signature) { - signatureV3Generator.update(buffer, 0, n); - } else { - signatureGenerator.update(buffer, 0, n); - } - } - done += n; - if (data.getSize() != 0) { - if (progress != null) - progress.setProgress((int) (20 + (95 - 20) * done / data.getSize()), 100); - } - } - - literalGen.close(); - - if (signatureKeyId != 0) { - if (progress != null) - progress.setProgress(R.string.progress_generatingSignature, 95, 100); - if (forceV3Signature) { - signatureV3Generator.generate().encode(pOut); - } else { - signatureGenerator.generate().encode(pOut); - } - } - if (compressGen != null) { - compressGen.close(); - } - encryptOut.close(); - if (armored) { - armorOut.close(); - } - - if (progress != null) - progress.setProgress(R.string.progress_done, 100, 100); - } - - public static void signText(Context context, InputData data, OutputStream outStream, - long signatureKeyId, String signaturePassPhrase, int hashAlgorithm, - boolean forceV3Signature, ProgressDialogUpdater progress) throws GeneralException, - PGPException, IOException, NoSuchAlgorithmException, SignatureException { - Security.addProvider(new BouncyCastleProvider()); - - ArmoredOutputStream armorOut = new ArmoredOutputStream(outStream); - armorOut.setHeader("Version", getFullVersion(context)); - - PGPSecretKey signingKey = null; - PGPSecretKeyRing signingKeyRing = null; - PGPPrivateKey signaturePrivateKey = null; - - if (signatureKeyId == 0) { - throw new GeneralException(context.getString(R.string.error_noSignatureKey)); - } - - signingKeyRing = getSecretKeyRing(signatureKeyId); - signingKey = getSigningKey(signatureKeyId); - if (signingKey == null) { - throw new GeneralException(context.getString(R.string.error_signatureFailed)); - } - - if (signaturePassPhrase == null) { - throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase)); - } - signaturePrivateKey = signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(), - new BouncyCastleProvider()); - if (signaturePrivateKey == null) { - throw new GeneralException(context.getString(R.string.error_couldNotExtractPrivateKey)); - } - if (progress != null) - progress.setProgress(R.string.progress_preparingStreams, 0, 100); - - if (progress != null) - progress.setProgress(R.string.progress_preparingSignature, 30, 100); - - PGPSignatureGenerator signatureGenerator = null; - PGPV3SignatureGenerator signatureV3Generator = null; - - if (forceV3Signature) { - signatureV3Generator = new PGPV3SignatureGenerator(signingKey.getPublicKey() - .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider()); - signatureV3Generator - .initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey); - } else { - signatureGenerator = new PGPSignatureGenerator( - signingKey.getPublicKey().getAlgorithm(), hashAlgorithm, - new BouncyCastleProvider()); - signatureGenerator.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey); - - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - String userId = getMainUserId(getMasterKey(signingKeyRing)); - spGen.setSignerUserID(false, userId); - signatureGenerator.setHashedSubpackets(spGen.generate()); - } - - if (progress != null) - progress.setProgress(R.string.progress_signing, 40, 100); - - armorOut.beginClearText(hashAlgorithm); - - InputStream inStream = data.getInputStream(); - final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream)); - - final byte[] newline = "\r\n".getBytes("UTF-8"); - - if (forceV3Signature) { - processLine(reader.readLine(), armorOut, signatureV3Generator); - } else { - processLine(reader.readLine(), armorOut, signatureGenerator); - } - - while (true) { - final String line = reader.readLine(); - - if (line == null) { - armorOut.write(newline); - break; - } - - armorOut.write(newline); - if (forceV3Signature) { - signatureV3Generator.update(newline); - processLine(line, armorOut, signatureV3Generator); - } else { - signatureGenerator.update(newline); - processLine(line, armorOut, signatureGenerator); - } - } - - armorOut.endClearText(); - - BCPGOutputStream bOut = new BCPGOutputStream(armorOut); - if (forceV3Signature) { - signatureV3Generator.generate().encode(bOut); - } else { - signatureGenerator.generate().encode(bOut); - } - armorOut.close(); - - if (progress != null) - progress.setProgress(R.string.progress_done, 100, 100); - } - - public static void generateSignature(Context context, InputData data, OutputStream outStream, - boolean armored, boolean binary, long signatureKeyId, String signaturePassPhrase, - int hashAlgorithm, boolean forceV3Signature, ProgressDialogUpdater progress) - throws GeneralException, PGPException, IOException, NoSuchAlgorithmException, - SignatureException { - Security.addProvider(new BouncyCastleProvider()); - - ArmoredOutputStream armorOut = null; - OutputStream out = null; - if (armored) { - armorOut = new ArmoredOutputStream(outStream); - armorOut.setHeader("Version", getFullVersion(context)); - out = armorOut; - } else { - out = outStream; - } - - PGPSecretKey signingKey = null; - PGPSecretKeyRing signingKeyRing = null; - PGPPrivateKey signaturePrivateKey = null; - - if (signatureKeyId == 0) { - throw new GeneralException(context.getString(R.string.error_noSignatureKey)); - } - - signingKeyRing = getSecretKeyRing(signatureKeyId); - signingKey = getSigningKey(signatureKeyId); - if (signingKey == null) { - throw new GeneralException(context.getString(R.string.error_signatureFailed)); - } - - if (signaturePassPhrase == null) { - throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase)); - } - signaturePrivateKey = signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(), - new BouncyCastleProvider()); - if (signaturePrivateKey == null) { - throw new GeneralException(context.getString(R.string.error_couldNotExtractPrivateKey)); - } - if (progress != null) - progress.setProgress(R.string.progress_preparingStreams, 0, 100); - - if (progress != null) - progress.setProgress(R.string.progress_preparingSignature, 30, 100); - - PGPSignatureGenerator signatureGenerator = null; - PGPV3SignatureGenerator signatureV3Generator = null; - - int type = PGPSignature.CANONICAL_TEXT_DOCUMENT; - if (binary) { - type = PGPSignature.BINARY_DOCUMENT; - } - - if (forceV3Signature) { - signatureV3Generator = new PGPV3SignatureGenerator(signingKey.getPublicKey() - .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider()); - signatureV3Generator.initSign(type, signaturePrivateKey); - } else { - signatureGenerator = new PGPSignatureGenerator( - signingKey.getPublicKey().getAlgorithm(), hashAlgorithm, - new BouncyCastleProvider()); - signatureGenerator.initSign(type, signaturePrivateKey); - - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - String userId = getMainUserId(getMasterKey(signingKeyRing)); - spGen.setSignerUserID(false, userId); - signatureGenerator.setHashedSubpackets(spGen.generate()); - } - - if (progress != null) - progress.setProgress(R.string.progress_signing, 40, 100); - - InputStream inStream = data.getInputStream(); - if (binary) { - byte[] buffer = new byte[1 << 16]; - int n = 0; - while ((n = inStream.read(buffer)) > 0) { - if (forceV3Signature) { - signatureV3Generator.update(buffer, 0, n); - } else { - signatureGenerator.update(buffer, 0, n); - } - } - } else { - final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream)); - final byte[] newline = "\r\n".getBytes("UTF-8"); - - while (true) { - final String line = reader.readLine(); - - if (line == null) { - break; - } - - if (forceV3Signature) { - processLine(line, null, signatureV3Generator); - signatureV3Generator.update(newline); - } else { - processLine(line, null, signatureGenerator); - signatureGenerator.update(newline); - } - } - } - - BCPGOutputStream bOut = new BCPGOutputStream(out); - if (forceV3Signature) { - signatureV3Generator.generate().encode(bOut); - } else { - signatureGenerator.generate().encode(bOut); - } - out.close(); - outStream.close(); - - if (progress != null) - progress.setProgress(R.string.progress_done, 100, 100); - } - - public static long getDecryptionKeyId(Context context, InputData data) throws GeneralException, - NoAsymmetricEncryptionException, IOException { - InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); - PGPObjectFactory pgpF = new PGPObjectFactory(in); - PGPEncryptedDataList enc; - Object o = pgpF.nextObject(); - - // the first object might be a PGP marker packet. - if (o instanceof PGPEncryptedDataList) { - enc = (PGPEncryptedDataList) o; - } else { - enc = (PGPEncryptedDataList) pgpF.nextObject(); - } - - if (enc == null) { - throw new GeneralException(context.getString(R.string.error_invalidData)); - } - - // TODO: currently we always only look at the first known key - // find the secret key - PGPSecretKey secretKey = null; - Iterator it = enc.getEncryptedDataObjects(); - boolean gotAsymmetricEncryption = false; - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPublicKeyEncryptedData) { - gotAsymmetricEncryption = true; - PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) obj; - secretKey = getSecretKey(pbe.getKeyID()); - if (secretKey != null) { - break; - } - } - } - - if (!gotAsymmetricEncryption) { - throw new NoAsymmetricEncryptionException(); - } - - if (secretKey == null) { - return Id.key.none; - } - - return secretKey.getKeyID(); - } - - public static boolean hasSymmetricEncryption(Context context, InputData data) - throws GeneralException, IOException { - InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); - PGPObjectFactory pgpF = new PGPObjectFactory(in); - PGPEncryptedDataList enc; - Object o = pgpF.nextObject(); - - // the first object might be a PGP marker packet. - if (o instanceof PGPEncryptedDataList) { - enc = (PGPEncryptedDataList) o; - } else { - enc = (PGPEncryptedDataList) pgpF.nextObject(); - } - - if (enc == null) { - throw new GeneralException(context.getString(R.string.error_invalidData)); - } - - Iterator it = enc.getEncryptedDataObjects(); - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPBEEncryptedData) { - return true; - } - } - - return false; - } - - public static Bundle decrypt(Context context, InputData data, OutputStream outStream, - String passPhrase, ProgressDialogUpdater progress, boolean assumeSymmetric) - throws IOException, GeneralException, PGPException, SignatureException { - if (passPhrase == null) { - passPhrase = ""; - } - Bundle returnData = new Bundle(); - InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); - PGPObjectFactory pgpF = new PGPObjectFactory(in); - PGPEncryptedDataList enc; - Object o = pgpF.nextObject(); - long signatureKeyId = 0; - - int currentProgress = 0; - if (progress != null) - progress.setProgress(R.string.progress_readingData, currentProgress, 100); - - if (o instanceof PGPEncryptedDataList) { - enc = (PGPEncryptedDataList) o; - } else { - enc = (PGPEncryptedDataList) pgpF.nextObject(); - } - - if (enc == null) { - throw new GeneralException(context.getString(R.string.error_invalidData)); - } - - InputStream clear = null; - PGPEncryptedData encryptedData = null; - - currentProgress += 5; - - // TODO: currently we always only look at the first known key or symmetric encryption, - // there might be more... - if (assumeSymmetric) { - PGPPBEEncryptedData pbe = null; - Iterator it = enc.getEncryptedDataObjects(); - // find secret key - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPBEEncryptedData) { - pbe = (PGPPBEEncryptedData) obj; - break; - } - } - - if (pbe == null) { - throw new GeneralException( - context.getString(R.string.error_noSymmetricEncryptionPacket)); - } - - if (progress != null) - progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100); - clear = pbe.getDataStream(passPhrase.toCharArray(), new BouncyCastleProvider()); - encryptedData = pbe; - currentProgress += 5; - } else { - if (progress != null) - progress.setProgress(R.string.progress_findingKey, currentProgress, 100); - PGPPublicKeyEncryptedData pbe = null; - PGPSecretKey secretKey = null; - Iterator it = enc.getEncryptedDataObjects(); - // find secret key - while (it.hasNext()) { - Object obj = it.next(); - if (obj instanceof PGPPublicKeyEncryptedData) { - PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; - secretKey = getSecretKey(encData.getKeyID()); - if (secretKey != null) { - pbe = encData; - break; - } - } - } - - if (secretKey == null) { - throw new GeneralException(context.getString(R.string.error_noSecretKeyFound)); - } - - currentProgress += 5; - if (progress != null) - progress.setProgress(R.string.progress_extractingKey, currentProgress, 100); - PGPPrivateKey privateKey = null; - try { - privateKey = secretKey.extractPrivateKey(passPhrase.toCharArray(), - new BouncyCastleProvider()); - } catch (PGPException e) { - throw new PGPException(context.getString(R.string.error_wrongPassPhrase)); - } - if (privateKey == null) { - throw new GeneralException( - context.getString(R.string.error_couldNotExtractPrivateKey)); - } - currentProgress += 5; - if (progress != null) - progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100); - clear = pbe.getDataStream(privateKey, new BouncyCastleProvider()); - encryptedData = pbe; - currentProgress += 5; - } - - PGPObjectFactory plainFact = new PGPObjectFactory(clear); - Object dataChunk = plainFact.nextObject(); - PGPOnePassSignature signature = null; - PGPPublicKey signatureKey = null; - int signatureIndex = -1; - - if (dataChunk instanceof PGPCompressedData) { - if (progress != null) - progress.setProgress(R.string.progress_decompressingData, currentProgress, 100); - PGPObjectFactory fact = new PGPObjectFactory( - ((PGPCompressedData) dataChunk).getDataStream()); - dataChunk = fact.nextObject(); - plainFact = fact; - currentProgress += 10; - } - - if (dataChunk instanceof PGPOnePassSignatureList) { - if (progress != null) - progress.setProgress(R.string.progress_processingSignature, currentProgress, 100); - returnData.putBoolean(EXTRA_SIGNATURE, true); - PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; - for (int i = 0; i < sigList.size(); ++i) { - signature = sigList.get(i); - signatureKey = getPublicKey(signature.getKeyID()); - if (signatureKeyId == 0) { - signatureKeyId = signature.getKeyID(); - } - if (signatureKey == null) { - signature = null; - } else { - signatureIndex = i; - signatureKeyId = signature.getKeyID(); - String userId = null; - PGPPublicKeyRing sigKeyRing = getPublicKeyRing(signatureKeyId); - if (sigKeyRing != null) { - userId = getMainUserId(getMasterKey(sigKeyRing)); - } - returnData.putString(EXTRA_SIGNATURE_USER_ID, userId); - break; - } - } - - returnData.putLong(EXTRA_SIGNATURE_KEY_ID, signatureKeyId); - - if (signature != null) { - signature.initVerify(signatureKey, new BouncyCastleProvider()); - } else { - returnData.putBoolean(EXTRA_SIGNATURE_UNKNOWN, true); - } - - dataChunk = plainFact.nextObject(); - currentProgress += 10; - } - - if (dataChunk instanceof PGPSignatureList) { - dataChunk = plainFact.nextObject(); - } - - if (dataChunk instanceof PGPLiteralData) { - if (progress != null) - progress.setProgress(R.string.progress_decrypting, currentProgress, 100); - PGPLiteralData literalData = (PGPLiteralData) dataChunk; - OutputStream out = outStream; - - byte[] buffer = new byte[1 << 16]; - InputStream dataIn = literalData.getInputStream(); - - int startProgress = currentProgress; - int endProgress = 100; - if (signature != null) { - endProgress = 90; - } else if (encryptedData.isIntegrityProtected()) { - endProgress = 95; - } - int n = 0; - int done = 0; - long startPos = data.getStreamPosition(); - while ((n = dataIn.read(buffer)) > 0) { - out.write(buffer, 0, n); - done += n; - if (signature != null) { - try { - signature.update(buffer, 0, n); - } catch (SignatureException e) { - returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, false); - signature = null; - } - } - // unknown size, but try to at least have a moving, slowing down progress bar - currentProgress = startProgress + (endProgress - startProgress) * done - / (done + 100000); - if (data.getSize() - startPos == 0) { - currentProgress = endProgress; - } else { - currentProgress = (int) (startProgress + (endProgress - startProgress) - * (data.getStreamPosition() - startPos) / (data.getSize() - startPos)); - } - if (progress != null) - progress.setProgress(currentProgress, 100); - } - - if (signature != null) { - if (progress != null) - progress.setProgress(R.string.progress_verifyingSignature, 90, 100); - PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject(); - PGPSignature messageSignature = signatureList.get(signatureIndex); - if (signature.verify(messageSignature)) { - returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, true); - } else { - returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, false); - } - } - } - - // TODO: add integrity somewhere - if (encryptedData.isIntegrityProtected()) { - if (progress != null) - progress.setProgress(R.string.progress_verifyingIntegrity, 95, 100); - if (encryptedData.verify()) { - // passed - } else { - // failed - } - } else { - // no integrity check - } - - if (progress != null) - progress.setProgress(R.string.progress_done, 100, 100); - return returnData; - } - - public static Bundle verifyText(BaseActivity context, InputData data, OutputStream outStream, - ProgressDialogUpdater progress) throws IOException, GeneralException, PGPException, - SignatureException { - Bundle returnData = new Bundle(); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ArmoredInputStream aIn = new ArmoredInputStream(data.getInputStream()); - - if (progress != null) - progress.setProgress(R.string.progress_done, 0, 100); - - // mostly taken from ClearSignedFileProcessor - ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); - int lookAhead = readInputLine(lineOut, aIn); - byte[] lineSep = getLineSeparator(); - - byte[] line = lineOut.toByteArray(); - out.write(line, 0, getLengthWithoutSeparator(line)); - out.write(lineSep); - - while (lookAhead != -1 && aIn.isClearText()) { - lookAhead = readInputLine(lineOut, lookAhead, aIn); - line = lineOut.toByteArray(); - out.write(line, 0, getLengthWithoutSeparator(line)); - out.write(lineSep); - } - - out.close(); - - byte[] clearText = out.toByteArray(); - outStream.write(clearText); - - returnData.putBoolean(EXTRA_SIGNATURE, true); - - if (progress != null) - progress.setProgress(R.string.progress_processingSignature, 60, 100); - PGPObjectFactory pgpFact = new PGPObjectFactory(aIn); - - PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject(); - if (sigList == null) { - throw new GeneralException(context.getString(R.string.error_corruptData)); - } - PGPSignature signature = null; - long signatureKeyId = 0; - PGPPublicKey signatureKey = null; - for (int i = 0; i < sigList.size(); ++i) { - signature = sigList.get(i); - signatureKey = getPublicKey(signature.getKeyID()); - if (signatureKeyId == 0) { - signatureKeyId = signature.getKeyID(); - } - if (signatureKey == null) { - Bundle pauseData = new Bundle(); - pauseData.putInt(Constants.extras.STATUS, Id.message.unknown_signature_key); - pauseData.putLong(Constants.extras.KEY_ID, signatureKeyId); - Message msg = new Message(); - msg.setData(pauseData); - context.sendMessage(msg); - // pause here - context.getRunningThread().pause(); - // see whether the key was found in the meantime - signatureKey = getPublicKey(signature.getKeyID()); - } - - if (signatureKey == null) { - signature = null; - } else { - signatureKeyId = signature.getKeyID(); - String userId = null; - PGPPublicKeyRing sigKeyRing = getPublicKeyRing(signatureKeyId); - if (sigKeyRing != null) { - userId = getMainUserId(getMasterKey(sigKeyRing)); - } - returnData.putString(EXTRA_SIGNATURE_USER_ID, userId); - break; - } - } - - returnData.putLong(EXTRA_SIGNATURE_KEY_ID, signatureKeyId); - - if (signature == null) { - returnData.putBoolean(EXTRA_SIGNATURE_UNKNOWN, true); - if (progress != null) - progress.setProgress(R.string.progress_done, 100, 100); - return returnData; - } - - signature.initVerify(signatureKey, new BouncyCastleProvider()); - - InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText)); - - lookAhead = readInputLine(lineOut, sigIn); - - processLine(signature, lineOut.toByteArray()); - - if (lookAhead != -1) { - do { - lookAhead = readInputLine(lineOut, lookAhead, sigIn); - - signature.update((byte) '\r'); - signature.update((byte) '\n'); - - processLine(signature, lineOut.toByteArray()); - } while (lookAhead != -1); - } - - returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, signature.verify()); - - if (progress != null) - progress.setProgress(R.string.progress_done, 100, 100); - return returnData; - } - - public static int getStreamContent(Context context, InputStream inStream) throws IOException { - InputStream in = PGPUtil.getDecoderStream(inStream); - PGPObjectFactory pgpF = new PGPObjectFactory(in); - Object object = pgpF.nextObject(); - while (object != null) { - if (object instanceof PGPPublicKeyRing || object instanceof PGPSecretKeyRing) { - return Id.content.keys; - } else if (object instanceof PGPEncryptedDataList) { - return Id.content.encrypted_data; - } - object = pgpF.nextObject(); - } - - return Id.content.unknown; - } - - private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, - final PGPSignatureGenerator pSignatureGenerator) throws IOException, SignatureException { - - if (pLine == null) { - return; - } - - final char[] chars = pLine.toCharArray(); - int len = chars.length; - - while (len > 0) { - if (!Character.isWhitespace(chars[len - 1])) { - break; - } - len--; - } - - final byte[] data = pLine.substring(0, len).getBytes("UTF-8"); - - if (pArmoredOutput != null) { - pArmoredOutput.write(data); - } - pSignatureGenerator.update(data); - } - - private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, - final PGPV3SignatureGenerator pSignatureGenerator) throws IOException, - SignatureException { - - if (pLine == null) { - return; - } - - final char[] chars = pLine.toCharArray(); - int len = chars.length; - - while (len > 0) { - if (!Character.isWhitespace(chars[len - 1])) { - break; - } - len--; - } - - final byte[] data = pLine.substring(0, len).getBytes("UTF-8"); - - if (pArmoredOutput != null) { - pArmoredOutput.write(data); - } - pSignatureGenerator.update(data); - } - - // taken from ClearSignedFileProcessor in BC - private static void processLine(PGPSignature sig, byte[] line) throws SignatureException, - IOException { - int length = getLengthWithoutWhiteSpace(line); - if (length > 0) { - sig.update(line, 0, length); - } - } - - private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) - throws IOException { - bOut.reset(); - - int lookAhead = -1; - int ch; - - while ((ch = fIn.read()) >= 0) { - bOut.write(ch); - if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); - break; - } - } - - return lookAhead; - } - - private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn) - throws IOException { - bOut.reset(); - - int ch = lookAhead; - - do { - bOut.write(ch); - if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); - break; - } - } while ((ch = fIn.read()) >= 0); - - if (ch < 0) { - lookAhead = -1; - } - - return lookAhead; - } - - private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) - throws IOException { - int lookAhead = fIn.read(); - - if (lastCh == '\r' && lookAhead == '\n') { - bOut.write(lookAhead); - lookAhead = fIn.read(); - } - - return lookAhead; - } - - private static int getLengthWithoutSeparator(byte[] line) { - int end = line.length - 1; - - while (end >= 0 && isLineEnding(line[end])) { - end--; - } - - return end + 1; - } - - private static boolean isLineEnding(byte b) { - return b == '\r' || b == '\n'; - } - - private static int getLengthWithoutWhiteSpace(byte[] line) { - int end = line.length - 1; - - while (end >= 0 && isWhiteSpace(line[end])) { - end--; - } - - return end + 1; - } - - private static boolean isWhiteSpace(byte b) { - return b == '\r' || b == '\n' || b == '\t' || b == ' '; - } - - private static byte[] getLineSeparator() { - String nl = System.getProperty("line.separator"); - byte[] nlBytes = new byte[nl.length()]; - - for (int i = 0; i != nlBytes.length; i++) { - nlBytes[i] = (byte) nl.charAt(i); - } - - return nlBytes; - } - - public static boolean isReleaseVersion(Context context) { - try { - PackageInfo pi = context.getPackageManager().getPackageInfo(PACKAGE_NAME, 0); - if (pi.versionCode % 100 == 99) { - return true; - } else { - return false; - } - } catch (NameNotFoundException e) { - // impossible! - return false; - } - } - - public static String getVersion(Context context) { - if (VERSION != null) { - return VERSION; - } - try { - PackageInfo pi = context.getPackageManager().getPackageInfo(PACKAGE_NAME, 0); - VERSION = pi.versionName; - return VERSION; - } catch (NameNotFoundException e) { - // impossible! - return "0.0.0"; - } - } - - public static String getFullVersion(Context context) { - return "APG v" + getVersion(context); - } - - public static String generateRandomString(int length) { - SecureRandom random = new SecureRandom(); - /* - * try { random = SecureRandom.getInstance("SHA1PRNG", new BouncyCastleProvider()); } catch - * (NoSuchAlgorithmException e) { // TODO: need to handle this case somehow return null; } - */ - byte bytes[] = new byte[length]; - random.nextBytes(bytes); - String result = ""; - for (int i = 0; i < length; ++i) { - int v = (bytes[i] + 256) % 64; - if (v < 10) { - result += (char) ('0' + v); - } else if (v < 36) { - result += (char) ('A' + v - 10); - } else if (v < 62) { - result += (char) ('a' + v - 36); - } else if (v == 62) { - result += '_'; - } else if (v == 63) { - result += '.'; - } - } - return result; - } - - static long getLengthOfStream(InputStream in) throws IOException { - long size = 0; - long n = 0; - byte dummy[] = new byte[0x10000]; - while ((n = in.read(dummy)) > 0) { - size += n; - } - return size; - } - - public static void deleteFileSecurely(Context context, File file, ProgressDialogUpdater progress) - throws FileNotFoundException, IOException { - long length = file.length(); - SecureRandom random = new SecureRandom(); - RandomAccessFile raf = new RandomAccessFile(file, "rws"); - raf.seek(0); - raf.getFilePointer(); - byte[] data = new byte[1 << 16]; - int pos = 0; - String msg = context.getString(R.string.progress_deletingSecurely, file.getName()); - while (pos < length) { - if (progress != null) - progress.setProgress(msg, (int) (100 * pos / length), 100); - random.nextBytes(data); - raf.write(data); - pos += data.length; - } - raf.close(); - file.delete(); - } -} diff --git a/org_apg/src/org/apg/ApgService.java b/org_apg/src/org/apg/ApgService.java deleted file mode 100644 index 2d41f88ae..000000000 --- a/org_apg/src/org/apg/ApgService.java +++ /dev/null @@ -1,657 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; - -import org.apg.provider.KeyRings; -import org.apg.provider.Keys; -import org.apg.provider.UserIds; -import org.apg.IApgService; - -import android.content.ContentResolver; -import android.content.Intent; -import android.database.Cursor; -import android.database.sqlite.SQLiteQueryBuilder; -import android.net.Uri; -import android.os.Bundle; -import android.os.IBinder; -import android.util.Log; - -public class ApgService extends Service { - private final static String TAG = "ApgService"; - public static final boolean LOCAL_LOGV = true; - public static final boolean LOCAL_LOGD = true; - - @Override - public IBinder onBind(Intent intent) { - if (LOCAL_LOGD) - Log.d(TAG, "bound"); - return mBinder; - } - - /** error status */ - private static enum error { - ARGUMENTS_MISSING, APG_FAILURE, NO_MATCHING_SECRET_KEY, PRIVATE_KEY_PASSPHRASE_WRONG, PRIVATE_KEY_PASSPHRASE_MISSING; - - public int shiftedOrdinal() { - return ordinal() + 100; - } - } - - private static enum call { - encrypt_with_passphrase, encrypt_with_public_key, decrypt, get_keys - } - - /** all arguments that can be passed by calling application */ - public static enum arg { - MESSAGE, // message to encrypt or to decrypt - SYMMETRIC_PASSPHRASE, // key for symmetric en/decryption - PUBLIC_KEYS, // public keys for encryption - ENCRYPTION_ALGORYTHM, // encryption algorithm - HASH_ALGORYTHM, // hash algorithm - ARMORED_OUTPUT, // whether to armor output - FORCE_V3_SIGNATURE, // whether to force v3 signature - COMPRESSION, // what compression to use for encrypted output - SIGNATURE_KEY, // key for signing - PRIVATE_KEY_PASSPHRASE, // passphrase for encrypted private key - KEY_TYPE, // type of key (private or public) - BLOB, // blob passed - } - - /** all things that might be returned */ - private static enum ret { - ERRORS, // string array list with errors - WARNINGS, // string array list with warnings - ERROR, // numeric error - RESULT, // en-/decrypted - FINGERPRINTS, // fingerprints of keys - USER_IDS, // user ids - } - - /** required arguments for each AIDL function */ - private static final HashMap> FUNCTIONS_REQUIRED_ARGS = new HashMap>(); - static { - HashSet args = new HashSet(); - args.add(arg.SYMMETRIC_PASSPHRASE); - FUNCTIONS_REQUIRED_ARGS.put(call.encrypt_with_passphrase.name(), args); - - args = new HashSet(); - args.add(arg.PUBLIC_KEYS); - FUNCTIONS_REQUIRED_ARGS.put(call.encrypt_with_public_key.name(), args); - - args = new HashSet(); - FUNCTIONS_REQUIRED_ARGS.put(call.decrypt.name(), args); - - args = new HashSet(); - args.add(arg.KEY_TYPE); - FUNCTIONS_REQUIRED_ARGS.put(call.get_keys.name(), args); - } - - /** optional arguments for each AIDL function */ - private static final HashMap> FUNCTIONS_OPTIONAL_ARGS = new HashMap>(); - static { - HashSet args = new HashSet(); - args.add(arg.ENCRYPTION_ALGORYTHM); - args.add(arg.HASH_ALGORYTHM); - args.add(arg.ARMORED_OUTPUT); - args.add(arg.FORCE_V3_SIGNATURE); - args.add(arg.COMPRESSION); - args.add(arg.PRIVATE_KEY_PASSPHRASE); - args.add(arg.SIGNATURE_KEY); - args.add(arg.BLOB); - args.add(arg.MESSAGE); - FUNCTIONS_OPTIONAL_ARGS.put(call.encrypt_with_passphrase.name(), args); - FUNCTIONS_OPTIONAL_ARGS.put(call.encrypt_with_public_key.name(), args); - - args = new HashSet(); - args.add(arg.SYMMETRIC_PASSPHRASE); - args.add(arg.PUBLIC_KEYS); - args.add(arg.PRIVATE_KEY_PASSPHRASE); - args.add(arg.MESSAGE); - args.add(arg.BLOB); - FUNCTIONS_OPTIONAL_ARGS.put(call.decrypt.name(), args); - } - - /** a map from ApgService parameters to function calls to get the default */ - private static final HashMap FUNCTIONS_DEFAULTS = new HashMap(); - static { - FUNCTIONS_DEFAULTS.put(arg.ENCRYPTION_ALGORYTHM, "getDefaultEncryptionAlgorithm"); - FUNCTIONS_DEFAULTS.put(arg.HASH_ALGORYTHM, "getDefaultHashAlgorithm"); - FUNCTIONS_DEFAULTS.put(arg.ARMORED_OUTPUT, "getDefaultAsciiArmour"); - FUNCTIONS_DEFAULTS.put(arg.FORCE_V3_SIGNATURE, "getForceV3Signatures"); - FUNCTIONS_DEFAULTS.put(arg.COMPRESSION, "getDefaultMessageCompression"); - } - - /** a map of the default function names to their method */ - private static final HashMap FUNCTIONS_DEFAULTS_METHODS = new HashMap(); - static { - try { - FUNCTIONS_DEFAULTS_METHODS.put("getDefaultEncryptionAlgorithm", - Preferences.class.getMethod("getDefaultEncryptionAlgorithm")); - FUNCTIONS_DEFAULTS_METHODS.put("getDefaultHashAlgorithm", - Preferences.class.getMethod("getDefaultHashAlgorithm")); - FUNCTIONS_DEFAULTS_METHODS.put("getDefaultAsciiArmour", - Preferences.class.getMethod("getDefaultAsciiArmour")); - FUNCTIONS_DEFAULTS_METHODS.put("getForceV3Signatures", - Preferences.class.getMethod("getForceV3Signatures")); - FUNCTIONS_DEFAULTS_METHODS.put("getDefaultMessageCompression", - Preferences.class.getMethod("getDefaultMessageCompression")); - } catch (Exception e) { - Log.e(TAG, "Function method exception: " + e.getMessage()); - } - } - - private static void writeToOutputStream(InputStream is, OutputStream os) throws IOException { - byte[] buffer = new byte[8]; - int len = 0; - while ((len = is.read(buffer)) != -1) { - os.write(buffer, 0, len); - } - } - - private static Cursor getKeyEntries(HashMap pParams) { - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + "(" - + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "." - + Keys.KEY_RING_ID + " AND " + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY - + " = '1'" + ") " + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + "(" - + Keys.TABLE_NAME + "." + Keys._ID + " = " + UserIds.TABLE_NAME + "." - + UserIds.KEY_ID + " AND " + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); - - String orderBy = pParams.containsKey("order_by") ? (String) pParams.get("order_by") - : UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC"; - - String typeVal[] = null; - String typeWhere = null; - if (pParams.containsKey("key_type")) { - typeWhere = KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?"; - typeVal = new String[] { "" + pParams.get("key_type") }; - } - return qb.query(Apg.getDatabase().db(), (String[]) pParams.get("columns"), typeWhere, - typeVal, null, null, orderBy); - } - - /** - * maps a fingerprint or user id of a key to a master key in database - * - * @param search_key - * fingerprint or user id to search for - * @return master key if found, or 0 - */ - private static long getMasterKey(String pSearchKey, Bundle pReturn) { - if (pSearchKey == null || pSearchKey.length() != 8) { - return 0; - } - ArrayList keyList = new ArrayList(); - keyList.add(pSearchKey); - long[] keys = getMasterKey(keyList, pReturn); - if (keys.length > 0) { - return keys[0]; - } else { - return 0; - } - } - - /** - * maps fingerprints or user ids of keys to master keys in database - * - * @param search_keys - * a list of keys (fingerprints or user ids) to look for in database - * @return an array of master keys - */ - private static long[] getMasterKey(ArrayList pSearchKeys, Bundle pReturn) { - - HashMap qParams = new HashMap(); - qParams.put("columns", new String[] { KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 0 - UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 1 - }); - qParams.put("key_type", Id.database.type_public); - - Cursor mCursor = getKeyEntries(qParams); - - if (LOCAL_LOGV) - Log.v(TAG, "going through installed user keys"); - ArrayList masterKeys = new ArrayList(); - while (mCursor.moveToNext()) { - long curMkey = mCursor.getLong(0); - String curUser = mCursor.getString(1); - - String curFprint = Apg.getSmallFingerPrint(curMkey); - if (LOCAL_LOGV) - Log.v(TAG, "current user: " + curUser + " (" + curFprint + ")"); - if (pSearchKeys.contains(curFprint) || pSearchKeys.contains(curUser)) { - if (LOCAL_LOGV) - Log.v(TAG, "master key found for: " + curFprint); - masterKeys.add(curMkey); - pSearchKeys.remove(curFprint); - } else { - if (LOCAL_LOGV) - Log.v(TAG, "Installed key " + curFprint - + " is not in the list of public keys to encrypt with"); - } - } - mCursor.close(); - - long[] masterKeyLongs = new long[masterKeys.size()]; - int i = 0; - for (Long key : masterKeys) { - masterKeyLongs[i++] = key; - } - - if (i == 0) { - Log.w(TAG, "Found not one public key"); - pReturn.getStringArrayList(ret.WARNINGS.name()).add( - "Searched for public key(s) but found not one"); - } - - for (String key : pSearchKeys) { - Log.w(TAG, "Searched for key " + key + " but cannot find it in APG"); - pReturn.getStringArrayList(ret.WARNINGS.name()).add( - "Searched for key " + key + " but cannot find it in APG"); - } - - return masterKeyLongs; - } - - /** - * Add default arguments if missing - * - * @param args - * the bundle to add default parameters to if missing - */ - private void addDefaultArguments(String pCall, Bundle pArgs) { - // check whether there are optional elements defined for that call - if (FUNCTIONS_OPTIONAL_ARGS.containsKey(pCall)) { - Preferences preferences = Preferences.getPreferences(getBaseContext(), true); - - Iterator iter = FUNCTIONS_DEFAULTS.keySet().iterator(); - while (iter.hasNext()) { - arg currentArg = iter.next(); - String currentKey = currentArg.name(); - if (!pArgs.containsKey(currentKey) - && FUNCTIONS_OPTIONAL_ARGS.get(pCall).contains(currentArg)) { - String currentFunctionName = FUNCTIONS_DEFAULTS.get(currentArg); - try { - Class returnType = FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName) - .getReturnType(); - if (returnType == String.class) { - pArgs.putString(currentKey, - (String) FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName) - .invoke(preferences)); - } else if (returnType == boolean.class) { - pArgs.putBoolean(currentKey, - (Boolean) FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName) - .invoke(preferences)); - } else if (returnType == int.class) { - pArgs.putInt(currentKey, - (Integer) FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName) - .invoke(preferences)); - } else { - Log.e(TAG, "Unknown return type " + returnType.toString() - + " for default option"); - } - } catch (Exception e) { - Log.e(TAG, "Exception in add_default_arguments " + e.getMessage()); - } - } - } - } - } - - /** - * updates a Bundle with default return values - * - * @param pReturn - * the Bundle to update - */ - private void addDefaultReturns(Bundle pReturn) { - ArrayList errors = new ArrayList(); - ArrayList warnings = new ArrayList(); - - pReturn.putStringArrayList(ret.ERRORS.name(), errors); - pReturn.putStringArrayList(ret.WARNINGS.name(), warnings); - } - - /** - * checks for required arguments and adds them to the error if missing - * - * @param function - * the functions required arguments to check for - * @param pArgs - * the Bundle of arguments to check - * @param pReturn - * the bundle to write errors to - */ - private void checkForRequiredArgs(String pFunction, Bundle pArgs, Bundle pReturn) { - if (FUNCTIONS_REQUIRED_ARGS.containsKey(pFunction)) { - Iterator iter = FUNCTIONS_REQUIRED_ARGS.get(pFunction).iterator(); - while (iter.hasNext()) { - String curArg = iter.next().name(); - if (!pArgs.containsKey(curArg)) { - pReturn.getStringArrayList(ret.ERRORS.name()) - .add("Argument missing: " + curArg); - } - } - } - - if (pFunction.equals(call.encrypt_with_passphrase.name()) - || pFunction.equals(call.encrypt_with_public_key.name()) - || pFunction.equals(call.decrypt.name())) { - // check that either MESSAGE or BLOB are there - if (!pArgs.containsKey(arg.MESSAGE.name()) && !pArgs.containsKey(arg.BLOB.name())) { - pReturn.getStringArrayList(ret.ERRORS.name()).add( - "Arguments missing: Neither MESSAGE nor BLOG found"); - } - - } - } - - /** - * checks for unknown arguments and add them to warning if found - * - * @param function - * the functions name to check against - * @param pArgs - * the Bundle of arguments to check - * @param pReturn - * the bundle to write warnings to - */ - private void checkForUnknownArgs(String pFunction, Bundle pArgs, Bundle pReturn) { - - HashSet allArgs = new HashSet(); - if (FUNCTIONS_REQUIRED_ARGS.containsKey(pFunction)) { - allArgs.addAll(FUNCTIONS_REQUIRED_ARGS.get(pFunction)); - } - if (FUNCTIONS_OPTIONAL_ARGS.containsKey(pFunction)) { - allArgs.addAll(FUNCTIONS_OPTIONAL_ARGS.get(pFunction)); - } - - ArrayList unknownArgs = new ArrayList(); - Iterator iter = pArgs.keySet().iterator(); - while (iter.hasNext()) { - String curKey = iter.next(); - try { - arg curArg = arg.valueOf(curKey); - if (!allArgs.contains(curArg)) { - pReturn.getStringArrayList(ret.WARNINGS.name()).add( - "Unknown argument: " + curKey); - unknownArgs.add(curKey); - } - } catch (Exception e) { - pReturn.getStringArrayList(ret.WARNINGS.name()).add("Unknown argument: " + curKey); - unknownArgs.add(curKey); - } - } - - // remove unknown arguments so our bundle has just what we need - for (String arg : unknownArgs) { - pArgs.remove(arg); - } - } - - private boolean prepareArgs(String pCall, Bundle pArgs, Bundle pReturn) { - Apg.initialize(getBaseContext()); - - /* add default return values for all functions */ - addDefaultReturns(pReturn); - - /* add default arguments if missing */ - addDefaultArguments(pCall, pArgs); - if (LOCAL_LOGV) - Log.v(TAG, "add_default_arguments"); - - /* check for required arguments */ - checkForRequiredArgs(pCall, pArgs, pReturn); - if (LOCAL_LOGV) - Log.v(TAG, "check_required_args"); - - /* check for unknown arguments and add to warning if found */ - checkForUnknownArgs(pCall, pArgs, pReturn); - if (LOCAL_LOGV) - Log.v(TAG, "check_unknown_args"); - - /* return if errors happened */ - if (pReturn.getStringArrayList(ret.ERRORS.name()).size() != 0) { - if (LOCAL_LOGV) - Log.v(TAG, "Errors after preparing, not executing " + pCall); - pReturn.putInt(ret.ERROR.name(), error.ARGUMENTS_MISSING.shiftedOrdinal()); - return false; - } - if (LOCAL_LOGV) - Log.v(TAG, "error return"); - - return true; - } - - private boolean encrypt(Bundle pArgs, Bundle pReturn) { - boolean isBlob = pArgs.containsKey(arg.BLOB.name()); - - long pubMasterKeys[] = {}; - if (pArgs.containsKey(arg.PUBLIC_KEYS.name())) { - ArrayList list = pArgs.getStringArrayList(arg.PUBLIC_KEYS.name()); - ArrayList pubKeys = new ArrayList(); - if (LOCAL_LOGV) - Log.v(TAG, "Long size: " + list.size()); - Iterator iter = list.iterator(); - while (iter.hasNext()) { - pubKeys.add(iter.next()); - } - pubMasterKeys = getMasterKey(pubKeys, pReturn); - } - - InputStream inStream = null; - if (isBlob) { - ContentResolver cr = getContentResolver(); - try { - inStream = cr.openInputStream(Uri.parse(pArgs.getString(arg.BLOB.name()))); - } catch (Exception e) { - Log.e(TAG, "... exception on opening blob", e); - } - } else { - inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes()); - } - InputData in = new InputData(inStream, 0); // XXX Size second param? - - OutputStream out = new ByteArrayOutputStream(); - if (LOCAL_LOGV) - Log.v(TAG, "About to encrypt"); - try { - Apg.encrypt(getBaseContext(), // context - in, // input stream - out, // output stream - pArgs.getBoolean(arg.ARMORED_OUTPUT.name()), // ARMORED_OUTPUT - pubMasterKeys, // encryption keys - getMasterKey(pArgs.getString(arg.SIGNATURE_KEY.name()), pReturn), // signature - // key - pArgs.getString(arg.PRIVATE_KEY_PASSPHRASE.name()), // signature passphrase - null, // progress - pArgs.getInt(arg.ENCRYPTION_ALGORYTHM.name()), // encryption - pArgs.getInt(arg.HASH_ALGORYTHM.name()), // hash - pArgs.getInt(arg.COMPRESSION.name()), // compression - pArgs.getBoolean(arg.FORCE_V3_SIGNATURE.name()), // mPreferences.getForceV3Signatures(), - pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) // passPhrase - ); - } catch (Exception e) { - Log.e(TAG, "Exception in encrypt"); - String msg = e.getMessage(); - if (msg.equals(getBaseContext().getString(R.string.error_noSignaturePassPhrase))) { - pReturn.getStringArrayList(ret.ERRORS.name()).add( - "Cannot encrypt (" + arg.PRIVATE_KEY_PASSPHRASE.name() + " missing): " - + msg); - pReturn.putInt(ret.ERROR.name(), - error.PRIVATE_KEY_PASSPHRASE_MISSING.shiftedOrdinal()); - } else if (msg.equals(getBaseContext().getString( - R.string.error_couldNotExtractPrivateKey))) { - pReturn.getStringArrayList(ret.ERRORS.name()).add( - "Cannot encrypt (" + arg.PRIVATE_KEY_PASSPHRASE.name() - + " probably wrong): " + msg); - pReturn.putInt(ret.ERROR.name(), - error.PRIVATE_KEY_PASSPHRASE_WRONG.shiftedOrdinal()); - } else { - pReturn.getStringArrayList(ret.ERRORS.name()).add( - "Internal failure (" + e.getClass() + ") in APG when encrypting: " - + e.getMessage()); - pReturn.putInt(ret.ERROR.name(), error.APG_FAILURE.shiftedOrdinal()); - } - return false; - } - if (LOCAL_LOGV) - Log.v(TAG, "Encrypted"); - if (isBlob) { - ContentResolver cr = getContentResolver(); - try { - OutputStream outStream = cr.openOutputStream(Uri.parse(pArgs.getString(arg.BLOB - .name()))); - writeToOutputStream(new ByteArrayInputStream(out.toString().getBytes()), outStream); - outStream.close(); - } catch (Exception e) { - Log.e(TAG, "... exception on writing blob", e); - } - } else { - pReturn.putString(ret.RESULT.name(), out.toString()); - } - return true; - } - - private final IApgService.Stub mBinder = new IApgService.Stub() { - - public boolean getKeys(Bundle pArgs, Bundle pReturn) { - - prepareArgs("get_keys", pArgs, pReturn); - - HashMap qParams = new HashMap(); - qParams.put("columns", new String[] { - KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 0 - UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 1 - }); - - qParams.put("key_type", pArgs.getInt(arg.KEY_TYPE.name())); - - Cursor cursor = getKeyEntries(qParams); - ArrayList fPrints = new ArrayList(); - ArrayList ids = new ArrayList(); - while (cursor.moveToNext()) { - if (LOCAL_LOGV) - Log.v(TAG, "adding key " + Apg.getSmallFingerPrint(cursor.getLong(0))); - fPrints.add(Apg.getSmallFingerPrint(cursor.getLong(0))); - ids.add(cursor.getString(1)); - } - cursor.close(); - - pReturn.putStringArrayList(ret.FINGERPRINTS.name(), fPrints); - pReturn.putStringArrayList(ret.USER_IDS.name(), ids); - return true; - } - - public boolean encryptWithPublicKey(Bundle pArgs, Bundle pReturn) { - if (!prepareArgs("encrypt_with_public_key", pArgs, pReturn)) { - return false; - } - - return encrypt(pArgs, pReturn); - } - - public boolean encryptWithPassphrase(Bundle pArgs, Bundle pReturn) { - if (!prepareArgs("encrypt_with_passphrase", pArgs, pReturn)) { - return false; - } - - return encrypt(pArgs, pReturn); - - } - - public boolean decrypt(Bundle pArgs, Bundle pReturn) { - if (!prepareArgs("decrypt", pArgs, pReturn)) { - return false; - } - - boolean isBlob = pArgs.containsKey(arg.BLOB.name()); - - String passphrase = pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) != null ? pArgs - .getString(arg.SYMMETRIC_PASSPHRASE.name()) : pArgs - .getString(arg.PRIVATE_KEY_PASSPHRASE.name()); - - InputStream inStream = null; - if (isBlob) { - ContentResolver cr = getContentResolver(); - try { - inStream = cr.openInputStream(Uri.parse(pArgs.getString(arg.BLOB.name()))); - } catch (Exception e) { - Log.e(TAG, "... exception on opening blob", e); - } - } else { - inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes()); - } - - InputData in = new InputData(inStream, 0); // XXX what size in second parameter? - OutputStream out = new ByteArrayOutputStream(); - if (LOCAL_LOGV) - Log.v(TAG, "About to decrypt"); - try { - Apg.decrypt(getBaseContext(), in, out, passphrase, null, // progress - pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) != null // symmetric - ); - } catch (Exception e) { - Log.e(TAG, "Exception in decrypt"); - String msg = e.getMessage(); - if (msg.equals(getBaseContext().getString(R.string.error_noSecretKeyFound))) { - pReturn.getStringArrayList(ret.ERRORS.name()).add("Cannot decrypt: " + msg); - pReturn.putInt(ret.ERROR.name(), error.NO_MATCHING_SECRET_KEY.shiftedOrdinal()); - } else if (msg.equals(getBaseContext().getString(R.string.error_wrongPassPhrase))) { - pReturn.getStringArrayList(ret.ERRORS.name()).add( - "Cannot decrypt (" + arg.PRIVATE_KEY_PASSPHRASE.name() - + " wrong/missing): " + msg); - pReturn.putInt(ret.ERROR.name(), - error.PRIVATE_KEY_PASSPHRASE_WRONG.shiftedOrdinal()); - } else { - pReturn.getStringArrayList(ret.ERRORS.name()).add( - "Internal failure (" + e.getClass() + ") in APG when decrypting: " - + msg); - pReturn.putInt(ret.ERROR.name(), error.APG_FAILURE.shiftedOrdinal()); - } - return false; - } - if (LOCAL_LOGV) - Log.v(TAG, "... decrypted"); - - if (isBlob) { - ContentResolver cr = getContentResolver(); - try { - OutputStream outStream = cr.openOutputStream(Uri.parse(pArgs.getString(arg.BLOB - .name()))); - writeToOutputStream(new ByteArrayInputStream(out.toString().getBytes()), - outStream); - outStream.close(); - } catch (Exception e) { - Log.e(TAG, "... exception on writing blob", e); - } - } else { - pReturn.putString(ret.RESULT.name(), out.toString()); - } - return true; - } - - }; -} diff --git a/org_apg/src/org/apg/AskForSecretKeyPassPhrase.java b/org_apg/src/org/apg/AskForSecretKeyPassPhrase.java deleted file mode 100644 index ac3b678d8..000000000 --- a/org_apg/src/org/apg/AskForSecretKeyPassPhrase.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -import org.spongycastle.jce.provider.BouncyCastleProvider; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPPrivateKey; -import org.spongycastle.openpgp.PGPSecretKey; -import org.apg.R; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; - -public class AskForSecretKeyPassPhrase { - public static interface PassPhraseCallbackInterface { - void passPhraseCallback(long keyId, String passPhrase); - } - - public static Dialog createDialog(Activity context, long secretKeyId, - PassPhraseCallbackInterface callback) { - AlertDialog.Builder alert = new AlertDialog.Builder(context); - - alert.setTitle(R.string.title_authentication); - - final PGPSecretKey secretKey; - final Activity activity = context; - - if (secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none) { - secretKey = null; - alert.setMessage(context.getString(R.string.passPhraseForSymmetricEncryption)); - } else { - secretKey = Apg.getMasterKey(Apg.getSecretKeyRing(secretKeyId)); - if (secretKey == null) { - alert.setTitle(R.string.title_keyNotFound); - alert.setMessage(context.getString(R.string.keyNotFound, secretKeyId)); - alert.setPositiveButton(android.R.string.ok, new OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - activity.removeDialog(Id.dialog.pass_phrase); - } - }); - alert.setCancelable(false); - return alert.create(); - } - String userId = Apg.getMainUserIdSafe(context, secretKey); - alert.setMessage(context.getString(R.string.passPhraseFor, userId)); - } - - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View view = inflater.inflate(R.layout.passphrase, null); - final EditText input = (EditText) view.findViewById(R.id.passphrase_passphrase); - - final TextView labelNotUsed = (TextView) view - .findViewById(R.id.passphrase_label_passphrase_again); - labelNotUsed.setVisibility(View.GONE); - final EditText inputNotUsed = (EditText) view - .findViewById(R.id.passphrase_passphrase_again); - inputNotUsed.setVisibility(View.GONE); - - alert.setView(view); - - final PassPhraseCallbackInterface cb = callback; - alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - activity.removeDialog(Id.dialog.pass_phrase); - String passPhrase = input.getText().toString(); - long keyId; - if (secretKey != null) { - try { - PGPPrivateKey testKey = secretKey.extractPrivateKey( - passPhrase.toCharArray(), new BouncyCastleProvider()); - if (testKey == null) { - Toast.makeText(activity, R.string.error_couldNotExtractPrivateKey, - Toast.LENGTH_SHORT).show(); - return; - } - } catch (PGPException e) { - Toast.makeText(activity, R.string.wrongPassPhrase, Toast.LENGTH_SHORT) - .show(); - return; - } - keyId = secretKey.getKeyID(); - } else { - keyId = Id.key.symmetric; - } - cb.passPhraseCallback(keyId, passPhrase); - } - }); - - alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - activity.removeDialog(Id.dialog.pass_phrase); - } - }); - - // check if the key has no passphrase - if (secretKey != null) { - try { - Log.d("APG", "check if key has no passphrase..."); - PGPPrivateKey testKey = secretKey.extractPrivateKey("".toCharArray(), - new BouncyCastleProvider()); - if (testKey != null) { - Log.d("APG", "Key has no passphrase!"); - - cb.passPhraseCallback(secretKey.getKeyID(), null); - - return null; - } - } catch (PGPException e) { - - } - } - return alert.create(); - } -} diff --git a/org_apg/src/org/apg/CachedPassPhrase.java b/org_apg/src/org/apg/CachedPassPhrase.java deleted file mode 100644 index 857136b8c..000000000 --- a/org_apg/src/org/apg/CachedPassPhrase.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -public class CachedPassPhrase { - public final long timestamp; - public final String passPhrase; - - public CachedPassPhrase(long timestamp, String passPhrase) { - super(); - this.timestamp = timestamp; - this.passPhrase = passPhrase; - } - - @Override - public int hashCode() { - int hc1 = (int) (this.timestamp & 0xffffffff); - int hc2 = (this.passPhrase == null ? 0 : this.passPhrase.hashCode()); - return (hc1 + hc2) * hc2 + hc1; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof CachedPassPhrase)) { - return false; - } - - CachedPassPhrase o = (CachedPassPhrase) other; - if (timestamp != o.timestamp) { - return false; - } - - if (passPhrase != o.passPhrase) { - if (passPhrase == null || o.passPhrase == null) { - return false; - } - - if (!passPhrase.equals(o.passPhrase)) { - return false; - } - } - - return true; - } - - @Override - public String toString() { - return "(" + timestamp + ", *******)"; - } -} diff --git a/org_apg/src/org/apg/Constants.java b/org_apg/src/org/apg/Constants.java deleted file mode 100644 index 9669d4b0d..000000000 --- a/org_apg/src/org/apg/Constants.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -import android.os.Environment; - -public final class Constants { - - public static final String TAG = "APG"; - - public static final class path { - public static final String APP_DIR = Environment.getExternalStorageDirectory() + "/APG"; - } - - public static final class pref { - public static final String HAS_SEEN_HELP = "seenHelp"; - public static final String HAS_SEEN_CHANGE_LOG = "seenChangeLogDialog"; - public static final String DEFAULT_ENCRYPTION_ALGORITHM = "defaultEncryptionAlgorithm"; - public static final String DEFAULT_HASH_ALGORITHM = "defaultHashAlgorithm"; - public static final String DEFAULT_ASCII_ARMOUR = "defaultAsciiArmour"; - public static final String DEFAULT_MESSAGE_COMPRESSION = "defaultMessageCompression"; - public static final String DEFAULT_FILE_COMPRESSION = "defaultFileCompression"; - public static final String PASS_PHRASE_CACHE_TTL = "passPhraseCacheTtl"; - public static final String LANGUAGE = "language"; - public static final String FORCE_V3_SIGNATURES = "forceV3Signatures"; - public static final String KEY_SERVERS = "keyServers"; - } - - public static final class defaults { - public static final String KEY_SERVERS = "pool.sks-keyservers.net, subkeys.pgp.net, pgp.mit.edu"; - } - - public static final class extras { - public static final String PROGRESS = "progress"; - public static final String PROGRESS_MAX = "max"; - public static final String STATUS = "status"; - public static final String MESSAGE = "message"; - public static final String KEY_ID = "keyId"; - } -} diff --git a/org_apg/src/org/apg/DataDestination.java b/org_apg/src/org/apg/DataDestination.java deleted file mode 100644 index 99e40e3ed..000000000 --- a/org_apg/src/org/apg/DataDestination.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -import org.apg.Apg.GeneralException; -import org.apg.R; - -import android.content.Context; -import android.os.Environment; - -public class DataDestination { - private String mStreamFilename; - private String mFilename; - private int mMode = Id.mode.undefined; - - public DataDestination() { - - } - - public void setMode(int mode) { - mMode = mode; - } - - public void setFilename(String filename) { - mFilename = filename; - } - - public String getStreamFilename() { - return mStreamFilename; - } - - public OutputStream getOutputStream(Context context) throws Apg.GeneralException, - FileNotFoundException, IOException { - OutputStream out = null; - mStreamFilename = null; - - switch (mMode) { - case Id.mode.stream: { - try { - while (true) { - mStreamFilename = Apg.generateRandomString(32); - if (mStreamFilename == null) { - throw new Apg.GeneralException("couldn't generate random file name"); - } - context.openFileInput(mStreamFilename).close(); - } - } catch (FileNotFoundException e) { - // found a name that isn't used yet - } - out = context.openFileOutput(mStreamFilename, Context.MODE_PRIVATE); - break; - } - - case Id.mode.byte_array: { - out = new ByteArrayOutputStream(); - break; - } - - case Id.mode.file: { - if (mFilename.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) { - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - throw new GeneralException( - context.getString(R.string.error_externalStorageNotReady)); - } - } - out = new FileOutputStream(mFilename); - break; - } - - default: { - break; - } - } - - return out; - } -} diff --git a/org_apg/src/org/apg/DataSource.java b/org_apg/src/org/apg/DataSource.java deleted file mode 100644 index 89afd2150..000000000 --- a/org_apg/src/org/apg/DataSource.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; - -import org.apg.Apg.GeneralException; -import org.apg.R; - -import android.content.Context; -import android.net.Uri; -import android.os.Environment; - -public class DataSource { - private Uri mContentUri = null; - private String mText = null; - private byte[] mData = null; - - public DataSource() { - - } - - public void setUri(Uri uri) { - mContentUri = uri; - mText = null; - mData = null; - } - - public void setUri(String uri) { - if (uri.startsWith("/")) { - setUri(Uri.parse("file://" + uri)); - } else { - setUri(Uri.parse(uri)); - } - } - - public void setText(String text) { - mText = text; - mData = null; - mContentUri = null; - } - - public void setData(byte[] data) { - mData = data; - mText = null; - mContentUri = null; - } - - public boolean isText() { - return mText != null; - } - - public boolean isBinary() { - return mData != null || mContentUri != null; - } - - public InputData getInputData(Context context, boolean withSize) throws GeneralException, - FileNotFoundException, IOException { - InputStream in = null; - long size = 0; - - if (mContentUri != null) { - if (mContentUri.getScheme().equals("file")) { - // get the rest after "file://" - String path = Uri.decode(mContentUri.toString().substring(7)); - if (path.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) { - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - throw new GeneralException( - context.getString(R.string.error_externalStorageNotReady)); - } - } - in = new FileInputStream(path); - File file = new File(path); - if (withSize) { - size = file.length(); - } - } else { - in = context.getContentResolver().openInputStream(mContentUri); - if (withSize) { - InputStream tmp = context.getContentResolver().openInputStream(mContentUri); - size = Apg.getLengthOfStream(tmp); - tmp.close(); - } - } - } else if (mText != null || mData != null) { - byte[] bytes = null; - if (mData != null) { - bytes = mData; - } else { - bytes = mText.getBytes(); - } - in = new ByteArrayInputStream(bytes); - if (withSize) { - size = bytes.length; - } - } - - return new InputData(in, size); - } - -} diff --git a/org_apg/src/org/apg/FileDialog.java b/org_apg/src/org/apg/FileDialog.java deleted file mode 100644 index d8956d978..000000000 --- a/org_apg/src/org/apg/FileDialog.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -import org.apg.R; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.net.Uri; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.Toast; - -public class FileDialog { - private static EditText mFilename; - private static ImageButton mBrowse; - private static CheckBox mCheckBox; - private static Activity mActivity; - private static int mRequestCode; - - public static interface OnClickListener { - public void onCancelClick(); - - public void onOkClick(String filename, boolean checkbox); - } - - public static AlertDialog build(Activity activity, String title, String message, - String defaultFile, OnClickListener onClickListener, String fileManagerTitle, - String fileManagerButton, String checkboxText, int requestCode) { - // TODO: fileManagerTitle and fileManagerButton are deprecated, no use for them right now, - // but maybe the Intent now used will someday support them again, so leaving them in - LayoutInflater inflater = (LayoutInflater) activity - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - AlertDialog.Builder alert = new AlertDialog.Builder(activity); - - alert.setTitle(title); - alert.setMessage(message); - - View view = inflater.inflate(R.layout.file_dialog, null); - - mActivity = activity; - mFilename = (EditText) view.findViewById(R.id.input); - mFilename.setText(defaultFile); - mBrowse = (ImageButton) view.findViewById(R.id.btn_browse); - mBrowse.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - openFile(); - } - }); - mRequestCode = requestCode; - mCheckBox = (CheckBox) view.findViewById(R.id.checkbox); - if (checkboxText == null) { - mCheckBox.setEnabled(false); - mCheckBox.setVisibility(View.GONE); - } else { - mCheckBox.setEnabled(true); - mCheckBox.setVisibility(View.VISIBLE); - mCheckBox.setText(checkboxText); - } - - alert.setView(view); - - final OnClickListener clickListener = onClickListener; - - alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - boolean checked = false; - if (mCheckBox.isEnabled()) { - checked = mCheckBox.isChecked(); - } - clickListener.onOkClick(mFilename.getText().toString(), checked); - } - }); - - alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - clickListener.onCancelClick(); - } - }); - return alert.create(); - } - - public static void setFilename(String filename) { - if (mFilename != null) { - mFilename.setText(filename); - } - } - - /** - * Opens the file manager to select a file to open. - */ - private static void openFile() { - String filename = mFilename.getText().toString(); - - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - - intent.setData(Uri.parse("file://" + filename)); - intent.setType("text/plain"); // only .asc or .gpg files - - try { - mActivity.startActivityForResult(intent, mRequestCode); - } catch (ActivityNotFoundException e) { - // No compatible file manager was found. - Toast.makeText(mActivity, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show(); - } - } -} diff --git a/org_apg/src/org/apg/HkpKeyServer.java b/org_apg/src/org/apg/HkpKeyServer.java deleted file mode 100644 index cd6a31ed4..000000000 --- a/org_apg/src/org/apg/HkpKeyServer.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.InetAddress; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLEncoder; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.Vector; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.util.EntityUtils; - -import android.text.Html; - -public class HkpKeyServer extends KeyServer { - private static class HttpError extends Exception { - private static final long serialVersionUID = 1718783705229428893L; - private int mCode; - private String mData; - - public HttpError(int code, String data) { - super("" + code + ": " + data); - mCode = code; - mData = data; - } - - public int getCode() { - return mCode; - } - - public String getData() { - return mData; - } - } - - private String mHost; - private short mPort = 11371; - - // example: - // pub 2048R/9F5C9090 2009-08-17 Jörg Runge - // <joerg@joergrunge.de> - public static Pattern PUB_KEY_LINE = Pattern - .compile( - "pub +([0-9]+)([a-z]+)/.*?0x([0-9a-z]+).*? +([0-9-]+) +(.+)[\n\r]+((?: +.+[\n\r]+)*)", - Pattern.CASE_INSENSITIVE); - public static Pattern USER_ID_LINE = Pattern.compile("^ +(.+)$", Pattern.MULTILINE - | Pattern.CASE_INSENSITIVE); - - public HkpKeyServer(String host) { - mHost = host; - } - - public HkpKeyServer(String host, short port) { - mHost = host; - mPort = port; - } - - static private String readAll(InputStream in, String encoding) throws IOException { - ByteArrayOutputStream raw = new ByteArrayOutputStream(); - - byte buffer[] = new byte[1 << 16]; - int n = 0; - while ((n = in.read(buffer)) != -1) { - raw.write(buffer, 0, n); - } - - if (encoding == null) { - encoding = "utf8"; - } - return raw.toString(encoding); - } - - // TODO: replace this with httpclient - private String query(String request) throws QueryException, HttpError { - InetAddress ips[]; - try { - ips = InetAddress.getAllByName(mHost); - } catch (UnknownHostException e) { - throw new QueryException(e.toString()); - } - for (int i = 0; i < ips.length; ++i) { - try { - String url = "http://" + ips[i].getHostAddress() + ":" + mPort + request; - URL realUrl = new URL(url); - HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); - conn.setConnectTimeout(5000); - conn.setReadTimeout(25000); - conn.connect(); - int response = conn.getResponseCode(); - if (response >= 200 && response < 300) { - return readAll(conn.getInputStream(), conn.getContentEncoding()); - } else { - String data = readAll(conn.getErrorStream(), conn.getContentEncoding()); - throw new HttpError(response, data); - } - } catch (MalformedURLException e) { - // nothing to do, try next IP - } catch (IOException e) { - // nothing to do, try next IP - } - } - - throw new QueryException("querying server(s) for '" + mHost + "' failed"); - } - - // TODO: replace this with httpclient - @Override - public List search(String query) throws QueryException, TooManyResponses, - InsufficientQuery { - Vector results = new Vector(); - - if (query.length() < 3) { - throw new InsufficientQuery(); - } - - String encodedQuery; - try { - encodedQuery = URLEncoder.encode(query, "utf8"); - } catch (UnsupportedEncodingException e) { - return null; - } - String request = "/pks/lookup?op=index&search=" + encodedQuery; - - String data = null; - try { - data = query(request); - } catch (HttpError e) { - if (e.getCode() == 404) { - return results; - } else { - if (e.getData().toLowerCase().contains("no keys found")) { - return results; - } else if (e.getData().toLowerCase().contains("too many")) { - throw new TooManyResponses(); - } else if (e.getData().toLowerCase().contains("insufficient")) { - throw new InsufficientQuery(); - } - } - throw new QueryException("querying server(s) for '" + mHost + "' failed"); - } - - Matcher matcher = PUB_KEY_LINE.matcher(data); - while (matcher.find()) { - KeyInfo info = new KeyInfo(); - info.size = Integer.parseInt(matcher.group(1)); - info.algorithm = matcher.group(2); - info.keyId = Apg.keyFromHex(matcher.group(3)); - info.fingerPrint = Apg.getSmallFingerPrint(info.keyId); - String chunks[] = matcher.group(4).split("-"); - info.date = new GregorianCalendar(Integer.parseInt(chunks[0]), - Integer.parseInt(chunks[1]), Integer.parseInt(chunks[2])).getTime(); - info.userIds = new Vector(); - if (matcher.group(5).startsWith("*** KEY")) { - info.revoked = matcher.group(5); - } else { - String tmp = matcher.group(5).replaceAll("<.*?>", ""); - tmp = Html.fromHtml(tmp).toString(); - info.userIds.add(tmp); - } - if (matcher.group(6).length() > 0) { - Matcher matcher2 = USER_ID_LINE.matcher(matcher.group(6)); - while (matcher2.find()) { - String tmp = matcher2.group(1).replaceAll("<.*?>", ""); - tmp = Html.fromHtml(tmp).toString(); - info.userIds.add(tmp); - } - } - results.add(info); - } - - return results; - } - - @Override - public String get(long keyId) throws QueryException { - HttpClient client = new DefaultHttpClient(); - try { - HttpGet get = new HttpGet("http://" + mHost + ":" + mPort - + "/pks/lookup?op=get&search=0x" + Apg.keyToHex(keyId)); - - HttpResponse response = client.execute(get); - if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { - throw new QueryException("not found"); - } - - HttpEntity entity = response.getEntity(); - InputStream is = entity.getContent(); - String data = readAll(is, EntityUtils.getContentCharSet(entity)); - Matcher matcher = Apg.PGP_PUBLIC_KEY.matcher(data); - if (matcher.find()) { - return matcher.group(1); - } - } catch (IOException e) { - // nothing to do, better luck on the next keyserver - } finally { - client.getConnectionManager().shutdown(); - } - - return null; - } - - @Override - void add(String armouredText) throws AddKeyException { - HttpClient client = new DefaultHttpClient(); - try { - HttpPost post = new HttpPost("http://" + mHost + ":" + mPort + "/pks/add"); - - List nameValuePairs = new ArrayList(2); - nameValuePairs.add(new BasicNameValuePair("keytext", armouredText)); - post.setEntity(new UrlEncodedFormEntity(nameValuePairs)); - - HttpResponse response = client.execute(post); - if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { - throw new AddKeyException(); - } - } catch (IOException e) { - // nothing to do, better luck on the next keyserver - } finally { - client.getConnectionManager().shutdown(); - } - } -} diff --git a/org_apg/src/org/apg/IApgService.aidl b/org_apg/src/org/apg/IApgService.aidl deleted file mode 100644 index 8101bd2a4..000000000 --- a/org_apg/src/org/apg/IApgService.aidl +++ /dev/null @@ -1,125 +0,0 @@ -package org.apg; - -interface IApgService { - - /* All functions fill the returnVals Bundle with the following keys: - * - * ArrayList "WARNINGS" = Warnings, if any - * ArrayList "ERRORS" = Human readable error descriptions, if any - * int "ERROR" = Numeric representation of error, if any - * starting with 100: - * 100: Required argument missing - * 101: Generic failure of APG - * 102: No matching private key found - * 103: Private key's passphrase wrong - * 104: Private key's passphrase missing - */ - - /* ******************************************************** - * Encryption - * ********************************************************/ - - /* All encryption function's arguments - * - * Bundle params' keys: - * (optional/required) - * TYPE "STRING KEY" = EXPLANATION / VALUES - * - * (required) - * String "MESSAGE" = Message to encrypt - * OR - * String "BLOB" = ContentUri to a file handle - * with binary data to encrypt - * (Attention: file will be overwritten - * with encrypted content!) - * - * (optional) - * int "ENCRYPTION_ALGORYTHM" = Encryption Algorithm - * 7: AES-128, 8: AES-192, 9: AES-256, - * 4: Blowfish, 10: Twofish, 3: CAST5, - * 6: DES, 2: Triple DES, 1: IDEA - * (optional) - * int "HASH_ALGORYTHM" = Hash Algorithm - * 1: MD5, 3: RIPEMD-160, 2: SHA-1, - * 11: SHA-224, 8: SHA-256, 9: SHA-384, - * 10: SHA-512 - * (optional) - * Boolean "ARMORED_OUTPUT" = Armor output - * - * (optional) - * Boolean "FORCE_V3_SIGNATURE" = Force V3 Signatures - * - * (optional) - * int "COMPRESSION" = Compression to use - * 0x21070001: none, 1: Zip, 2: Zlib, - * 3: BZip2 - * (optional) - * String "SIGNATURE_KEY" = Key to sign with - * - * (optional) - * String "PRIVATE_KEY_PASSPHRASE" = Passphrase for signing key - * - * Bundle returnVals (in addition to the ERRORS/WARNINGS above): - * If "MESSAGE" was set: - * String "RESULT" = Encrypted message - */ - - /* Additional argument for function below: - * (required) - * String "SYMMETRIC_PASSPHRASE" = Symmetric passphrase to use - */ - boolean encryptWithPassphrase(in Bundle params, out Bundle returnVals); - - /* Additional argument: - * (required) - * ArrayList "PUBLIC_KEYS" = Public keys (8char fingerprint "123ABC12" OR - * complete id "Alice Meyer ") - */ - boolean encryptWithPublicKey(in Bundle params, out Bundle returnVals); - - /* ******************************************************** - * Decryption - * ********************************************************/ - - /* Bundle params: - * (required) - * String "MESSAGE" = Message to dencrypt - * OR - * String "BLOB" = ContentUri to a file handle - * with binary data to dencrypt - * (Attention: file will be overwritten - * with dencrypted content!) - * - * (optional) - * String "SYMMETRIC_PASSPHRASE" = Symmetric passphrase for decryption - * - * (optional) - * String "PRIVATE_KEY_PASSPHRASE" = Private keys's passphrase on asymmetric encryption - * - * Bundle return_vals: - * If "MESSAGE" was set: - * String "RESULT" = Decrypted message - */ - boolean decrypt(in Bundle params, out Bundle returnVals); - - /* ******************************************************** - * Get key information - * ********************************************************/ - - /* Get info about all available keys - * - * Bundle params: - * (required) - * int "KEY_TYPE" = info about what type of keys to return - * 0: public keys - * 1: private keys - * - * Returns: - * StringArrayList "FINGERPRINTS" = Short fingerprints of keys - * - * StringArrayList "USER_IDS" = User ids of corresponding fingerprints - * (order is the same as in FINGERPRINTS) - */ - boolean getKeys(in Bundle params, out Bundle returnVals); - -} \ No newline at end of file diff --git a/org_apg/src/org/apg/Id.java b/org_apg/src/org/apg/Id.java deleted file mode 100644 index 9a2d7f90d..000000000 --- a/org_apg/src/org/apg/Id.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -import org.spongycastle.bcpg.CompressionAlgorithmTags; - -public final class Id { - - public static final String TAG = "APG"; - - public static final class menu { - public static final int export = 0x21070001; - public static final int delete = 0x21070002; - public static final int edit = 0x21070003; - public static final int update = 0x21070004; - public static final int exportToServer = 0x21070005; - public static final int share = 0x21070006; - public static final int signKey = 0x21070007; - - public static final class option { - public static final int new_pass_phrase = 0x21070001; - public static final int create = 0x21070002; - public static final int about = 0x21070003; - public static final int manage_public_keys = 0x21070004; - public static final int manage_secret_keys = 0x21070005; - public static final int import_keys = 0x21070006; - public static final int export_keys = 0x21070007; - public static final int preferences = 0x21070008; - public static final int search = 0x21070009; - public static final int help = 0x21070010; - public static final int key_server = 0x21070011; - public static final int scanQRCode = 0x21070012; - public static final int encrypt = 0x21070013; - public static final int encrypt_to_clipboard = 0x21070014; - public static final int decrypt = 0x21070015; - public static final int reply = 0x21070016; - public static final int cancel = 0x21070017; - public static final int save = 0x21070018; - public static final int okay = 0x21070019; - - } - } - - public static final class message { - public static final int progress_update = 0x21070001; - public static final int done = 0x21070002; - public static final int import_keys = 0x21070003; - public static final int export_keys = 0x21070004; - public static final int import_done = 0x21070005; - public static final int export_done = 0x21070006; - public static final int create_key = 0x21070007; - public static final int edit_key = 0x21070008; - public static final int delete_done = 0x21070009; - public static final int query_done = 0x21070010; - public static final int unknown_signature_key = 0x21070011; - } - - public static final class request { - public static final int public_keys = 0x21070001; - public static final int secret_keys = 0x21070002; - public static final int filename = 0x21070003; - public static final int output_filename = 0x21070004; - public static final int key_server_preference = 0x21070005; - public static final int look_up_key_id = 0x21070006; - public static final int export_to_server = 0x21070007; - public static final int import_from_qr_code = 0x21070008; - public static final int sign_key = 0x21070009; - } - - public static final class dialog { - public static final int pass_phrase = 0x21070001; - public static final int encrypting = 0x21070002; - public static final int decrypting = 0x21070003; - public static final int new_pass_phrase = 0x21070004; - public static final int pass_phrases_do_not_match = 0x21070005; - public static final int no_pass_phrase = 0x21070006; - public static final int saving = 0x21070007; - public static final int delete_key = 0x21070008; - public static final int import_keys = 0x21070009; - public static final int importing = 0x2107000a; - public static final int export_key = 0x2107000b; - public static final int export_keys = 0x2107000c; - public static final int exporting = 0x2107000d; - public static final int new_account = 0x2107000e; - // public static final int about = 0x2107000f; - public static final int change_log = 0x21070010; - public static final int output_filename = 0x21070011; - public static final int delete_file = 0x21070012; - public static final int deleting = 0x21070013; - public static final int help = 0x21070014; - public static final int querying = 0x21070015; - public static final int lookup_unknown_key = 0x21070016; - public static final int signing = 0x21070017; - } - - public static final class task { - public static final int import_keys = 0x21070001; - public static final int export_keys = 0x21070002; - } - - public static final class database { - public static final int type_public = 0; - public static final int type_secret = 1; - } - - public static final class type { - public static final int public_key = 0x21070001; - public static final int secret_key = 0x21070002; - public static final int user_id = 0x21070003; - public static final int key = 0x21070004; - } - - public static final class choice { - public static final class algorithm { - public static final int dsa = 0x21070001; - public static final int elgamal = 0x21070002; - public static final int rsa = 0x21070003; - } - - public static final class compression { - public static final int none = 0x21070001; - public static final int zlib = CompressionAlgorithmTags.ZLIB; - public static final int bzip2 = CompressionAlgorithmTags.BZIP2; - public static final int zip = CompressionAlgorithmTags.ZIP; - } - - public static final class usage { - public static final int sign_only = 0x21070001; - public static final int encrypt_only = 0x21070002; - public static final int sign_and_encrypt = 0x21070003; - } - - public static final class action { - public static final int encrypt = 0x21070001; - public static final int decrypt = 0x21070002; - public static final int import_public = 0x21070003; - public static final int import_secret = 0x21070004; - } - } - - public static final class return_value { - public static final int ok = 0; - public static final int error = -1; - public static final int no_master_key = -2; - public static final int updated = 1; - public static final int bad = -3; - } - - public static final class target { - public static final int clipboard = 0x21070001; - public static final int email = 0x21070002; - public static final int file = 0x21070003; - public static final int message = 0x21070004; - } - - public static final class mode { - public static final int undefined = 0x21070001; - public static final int byte_array = 0x21070002; - public static final int file = 0x21070003; - public static final int stream = 0x21070004; - } - - public static final class key { - public static final int none = 0; - public static final int symmetric = -1; - } - - public static final class content { - public static final int unknown = 0; - public static final int encrypted_data = 1; - public static final int keys = 2; - } - - public static final class keyserver { - public static final int search = 0x21070001; - public static final int get = 0x21070002; - public static final int add = 0x21070003; - } -} diff --git a/org_apg/src/org/apg/InputData.java b/org_apg/src/org/apg/InputData.java deleted file mode 100644 index e82b4a555..000000000 --- a/org_apg/src/org/apg/InputData.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -import java.io.InputStream; - -public class InputData { - private PositionAwareInputStream mInputStream; - private long mSize; - - public InputData(InputStream inputStream, long size) { - mInputStream = new PositionAwareInputStream(inputStream); - mSize = size; - } - - public InputStream getInputStream() { - return mInputStream; - } - - public long getSize() { - return mSize; - } - - public long getStreamPosition() { - return mInputStream.position(); - } -} diff --git a/org_apg/src/org/apg/KeyServer.java b/org_apg/src/org/apg/KeyServer.java deleted file mode 100644 index c928b3f82..000000000 --- a/org_apg/src/org/apg/KeyServer.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -import java.io.Serializable; -import java.util.Date; -import java.util.List; -import java.util.Vector; - -public abstract class KeyServer { - static public class QueryException extends Exception { - private static final long serialVersionUID = 2703768928624654512L; - - public QueryException(String message) { - super(message); - } - } - - static public class TooManyResponses extends Exception { - private static final long serialVersionUID = 2703768928624654513L; - } - - static public class InsufficientQuery extends Exception { - private static final long serialVersionUID = 2703768928624654514L; - } - - static public class AddKeyException extends Exception { - private static final long serialVersionUID = -507574859137295530L; - } - - static public class KeyInfo implements Serializable { - private static final long serialVersionUID = -7797972113284992662L; - public Vector userIds; - public String revoked; - public Date date; - public String fingerPrint; - public long keyId; - public int size; - public String algorithm; - } - - abstract List search(String query) throws QueryException, TooManyResponses, - InsufficientQuery; - - abstract String get(long keyId) throws QueryException; - - abstract void add(String armouredText) throws AddKeyException; -} diff --git a/org_apg/src/org/apg/PausableThread.java b/org_apg/src/org/apg/PausableThread.java deleted file mode 100644 index 2fc3737d7..000000000 --- a/org_apg/src/org/apg/PausableThread.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -public class PausableThread extends Thread { - private boolean mPaused = false; - - public PausableThread(Runnable runnable) { - super(runnable); - } - - public void pause() { - synchronized (this) { - mPaused = true; - while (mPaused) { - try { - wait(); - } catch (InterruptedException e) { - // ignore - } - } - } - } - - public void unpause() { - synchronized (this) { - mPaused = false; - notify(); - } - } - - public boolean isPaused() { - synchronized (this) { - return mPaused; - } - } -} diff --git a/org_apg/src/org/apg/PositionAwareInputStream.java b/org_apg/src/org/apg/PositionAwareInputStream.java deleted file mode 100644 index 41ec7618d..000000000 --- a/org_apg/src/org/apg/PositionAwareInputStream.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -import java.io.IOException; -import java.io.InputStream; - -public class PositionAwareInputStream extends InputStream { - private InputStream mStream; - private long mPosition; - - public PositionAwareInputStream(InputStream in) { - mStream = in; - mPosition = 0; - } - - @Override - public int read() throws IOException { - int ch = mStream.read(); - ++mPosition; - return ch; - } - - @Override - public int available() throws IOException { - return mStream.available(); - } - - @Override - public void close() throws IOException { - mStream.close(); - } - - @Override - public boolean markSupported() { - return false; - } - - @Override - public int read(byte[] b) throws IOException { - int result = mStream.read(b); - mPosition += result; - return result; - } - - @Override - public int read(byte[] b, int offset, int length) throws IOException { - int result = mStream.read(b, offset, length); - mPosition += result; - return result; - } - - @Override - public synchronized void reset() throws IOException { - mStream.reset(); - mPosition = 0; - } - - @Override - public long skip(long n) throws IOException { - long result = mStream.skip(n); - mPosition += result; - return result; - } - - public long position() { - return mPosition; - } -} diff --git a/org_apg/src/org/apg/Preferences.java b/org_apg/src/org/apg/Preferences.java deleted file mode 100644 index 60d9db8e8..000000000 --- a/org_apg/src/org/apg/Preferences.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -import org.spongycastle.bcpg.HashAlgorithmTags; -import org.spongycastle.openpgp.PGPEncryptedData; - -import android.content.Context; -import android.content.SharedPreferences; - -import java.util.Vector; - -public class Preferences { - private static Preferences mPreferences; - private SharedPreferences mSharedPreferences; - - public static synchronized Preferences getPreferences(Context context) { - return getPreferences(context, false); - } - - public static synchronized Preferences getPreferences(Context context, boolean force_new) { - if (mPreferences == null || force_new) { - mPreferences = new Preferences(context); - } - return mPreferences; - } - - private Preferences(Context context) { - mSharedPreferences = context.getSharedPreferences("APG.main", Context.MODE_PRIVATE); - } - - public String getLanguage() { - return mSharedPreferences.getString(Constants.pref.LANGUAGE, ""); - } - - public void setLanguage(String value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putString(Constants.pref.LANGUAGE, value); - editor.commit(); - } - - public int getPassPhraseCacheTtl() { - int ttl = mSharedPreferences.getInt(Constants.pref.PASS_PHRASE_CACHE_TTL, 180); - // fix the value if it was set to "never" in previous versions, which currently is not - // supported - if (ttl == 0) { - ttl = 180; - } - return ttl; - } - - public void setPassPhraseCacheTtl(int value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putInt(Constants.pref.PASS_PHRASE_CACHE_TTL, value); - editor.commit(); - } - - public int getDefaultEncryptionAlgorithm() { - return mSharedPreferences.getInt(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM, - PGPEncryptedData.AES_256); - } - - public void setDefaultEncryptionAlgorithm(int value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putInt(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM, value); - editor.commit(); - } - - public int getDefaultHashAlgorithm() { - return mSharedPreferences.getInt(Constants.pref.DEFAULT_HASH_ALGORITHM, - HashAlgorithmTags.SHA256); - } - - public void setDefaultHashAlgorithm(int value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putInt(Constants.pref.DEFAULT_HASH_ALGORITHM, value); - editor.commit(); - } - - public int getDefaultMessageCompression() { - return mSharedPreferences.getInt(Constants.pref.DEFAULT_MESSAGE_COMPRESSION, - Id.choice.compression.zlib); - } - - public void setDefaultMessageCompression(int value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putInt(Constants.pref.DEFAULT_MESSAGE_COMPRESSION, value); - editor.commit(); - } - - public int getDefaultFileCompression() { - return mSharedPreferences.getInt(Constants.pref.DEFAULT_FILE_COMPRESSION, - Id.choice.compression.none); - } - - public void setDefaultFileCompression(int value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putInt(Constants.pref.DEFAULT_FILE_COMPRESSION, value); - editor.commit(); - } - - public boolean getDefaultAsciiArmour() { - return mSharedPreferences.getBoolean(Constants.pref.DEFAULT_ASCII_ARMOUR, false); - } - - public void setDefaultAsciiArmour(boolean value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(Constants.pref.DEFAULT_ASCII_ARMOUR, value); - editor.commit(); - } - - public boolean getForceV3Signatures() { - return mSharedPreferences.getBoolean(Constants.pref.FORCE_V3_SIGNATURES, false); - } - - public void setForceV3Signatures(boolean value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(Constants.pref.FORCE_V3_SIGNATURES, value); - editor.commit(); - } - - public boolean hasSeenChangeLog(String version) { - return mSharedPreferences.getBoolean(Constants.pref.HAS_SEEN_CHANGE_LOG + version, false); - } - - public void setHasSeenChangeLog(String version, boolean value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(Constants.pref.HAS_SEEN_CHANGE_LOG + version, value); - editor.commit(); - } - - public boolean hasSeenHelp() { - return mSharedPreferences.getBoolean(Constants.pref.HAS_SEEN_HELP, false); - } - - public void setHasSeenHelp(boolean value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - editor.putBoolean(Constants.pref.HAS_SEEN_HELP, value); - editor.commit(); - } - - public String[] getKeyServers() { - String rawData = mSharedPreferences.getString(Constants.pref.KEY_SERVERS, - Constants.defaults.KEY_SERVERS); - Vector servers = new Vector(); - String chunks[] = rawData.split(","); - for (int i = 0; i < chunks.length; ++i) { - String tmp = chunks[i].trim(); - if (tmp.length() > 0) { - servers.add(tmp); - } - } - return servers.toArray(chunks); - } - - public void setKeyServers(String[] value) { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - String rawData = ""; - for (int i = 0; i < value.length; ++i) { - String tmp = value[i].trim(); - if (tmp.length() == 0) { - continue; - } - if (!"".equals(rawData)) { - rawData += ","; - } - rawData += tmp; - } - editor.putString(Constants.pref.KEY_SERVERS, rawData); - editor.commit(); - } -} diff --git a/org_apg/src/org/apg/Primes.java b/org_apg/src/org/apg/Primes.java deleted file mode 100644 index 1fd6482b2..000000000 --- a/org_apg/src/org/apg/Primes.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -import java.math.BigInteger; - -public final class Primes { - // taken from http://www.ietf.org/rfc/rfc3526.txt - public static final String P1536 = - "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + - "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + - "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + - "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + - "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + - "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + - "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + - "670C354E 4ABC9804 F1746C08 CA237327 FFFFFFFF FFFFFFFF"; - - public static final String P2048 = - "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + - "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + - "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + - "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + - "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + - "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + - "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + - "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + - "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + - "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + - "15728E5A 8AACAA68 FFFFFFFF FFFFFFFF"; - - public static final String P3072 = - "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + - "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + - "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + - "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + - "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + - "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + - "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + - "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + - "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + - "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + - "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + - "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + - "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + - "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + - "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + - "43DB5BFC E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF"; - - public static final String P4096 = - "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + - "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + - "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + - "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + - "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + - "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + - "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + - "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + - "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + - "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + - "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + - "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + - "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + - "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + - "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + - "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" + - "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" + - "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" + - "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" + - "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" + - "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199" + - "FFFFFFFF FFFFFFFF"; - - public static final String P6144 = - "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + - "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + - "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + - "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + - "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + - "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + - "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + - "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + - "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + - "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + - "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + - "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + - "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + - "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + - "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + - "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" + - "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" + - "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" + - "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" + - "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" + - "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" + - "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD" + - "F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831" + - "179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B" + - "DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF" + - "5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6" + - "D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3" + - "23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" + - "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328" + - "06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C" + - "DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE" + - "12BF2D5B 0B7474D6 E694F91E 6DCC4024 FFFFFFFF FFFFFFFF"; - - public static final String P8192 = - "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + - "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + - "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + - "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + - "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + - "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + - "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + - "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + - "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + - "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + - "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + - "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + - "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + - "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + - "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + - "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" + - "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" + - "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" + - "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" + - "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" + - "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" + - "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD" + - "F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831" + - "179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B" + - "DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF" + - "5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6" + - "D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3" + - "23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" + - "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328" + - "06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C" + - "DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE" + - "12BF2D5B 0B7474D6 E694F91E 6DBE1159 74A3926F 12FEE5E4" + - "38777CB6 A932DF8C D8BEC4D0 73B931BA 3BC832B6 8D9DD300" + - "741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C 5AE4F568" + - "3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9" + - "22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B" + - "4BCBC886 2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A" + - "062B3CF5 B3A278A6 6D2A13F8 3F44F82D DF310EE0 74AB6A36" + - "4597E899 A0255DC1 64F31CC5 0846851D F9AB4819 5DED7EA1" + - "B1D510BD 7EE74D73 FAF36BC3 1ECFA268 359046F4 EB879F92" + - "4009438B 481C6CD7 889A002E D5EE382B C9190DA6 FC026E47" + - "9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71" + - "60C980DD 98EDD3DF FFFFFFFF FFFFFFFF"; - - public static BigInteger getBestPrime(int keySize) { - String primeString; - if (keySize >= (8192 + 6144) / 2) { - primeString = P8192; - } else if (keySize >= (6144 + 4096) / 2) { - primeString = P6144; - } else if (keySize >= (4096 + 3072) / 2) { - primeString = P4096; - } else if (keySize >= (3072 + 2048) / 2) { - primeString = P3072; - } else if (keySize >= (2048 + 1536) / 2) { - primeString = P2048; - } else { - primeString = P1536; - } - - return new BigInteger(primeString.replaceAll(" ", ""), 16); - } -} diff --git a/org_apg/src/org/apg/ProgressDialogUpdater.java b/org_apg/src/org/apg/ProgressDialogUpdater.java deleted file mode 100644 index 043cca906..000000000 --- a/org_apg/src/org/apg/ProgressDialogUpdater.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -public interface ProgressDialogUpdater { - void setProgress(String message, int current, int total); - - void setProgress(int resourceId, int current, int total); - - void setProgress(int current, int total); -} diff --git a/org_apg/src/org/apg/Service.java b/org_apg/src/org/apg/Service.java deleted file mode 100644 index 58677b737..000000000 --- a/org_apg/src/org/apg/Service.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg; - -import android.content.Intent; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; - -public class Service extends android.app.Service { - private final IBinder mBinder = new LocalBinder(); - - public static final String EXTRA_TTL = "ttl"; - - private int mPassPhraseCacheTtl = 15; - private Handler mCacheHandler = new Handler(); - private Runnable mCacheTask = new Runnable() { - public void run() { - // check every ttl/2 seconds, which shouldn't be heavy on the device (even if ttl = 15), - // and makes sure the longest a pass phrase survives in the cache is 1.5 * ttl - int delay = mPassPhraseCacheTtl * 1000 / 2; - // also make sure the delay is not longer than one minute - if (delay > 60000) { - delay = 60000; - } - - delay = Apg.cleanUpCache(mPassPhraseCacheTtl, delay); - // don't check too often, even if we were close - if (delay < 5000) { - delay = 5000; - } - - mCacheHandler.postDelayed(this, delay); - } - }; - - static private boolean mIsRunning = false; - - @Override - public void onCreate() { - super.onCreate(); - - mIsRunning = true; - } - - @Override - public void onDestroy() { - super.onDestroy(); - mIsRunning = false; - } - - @Override - public void onStart(Intent intent, int startId) { - super.onStart(intent, startId); - - if (intent != null) { - mPassPhraseCacheTtl = intent.getIntExtra(EXTRA_TTL, 15); - } - if (mPassPhraseCacheTtl < 15) { - mPassPhraseCacheTtl = 15; - } - mCacheHandler.removeCallbacks(mCacheTask); - mCacheHandler.postDelayed(mCacheTask, 1000); - } - - static public boolean isRunning() { - return mIsRunning; - } - - public class LocalBinder extends Binder { - Service getService() { - return Service.this; - } - } - - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } -} diff --git a/org_apg/src/org/apg/provider/ApgServiceBlobDatabase.java b/org_apg/src/org/apg/provider/ApgServiceBlobDatabase.java deleted file mode 100644 index 2dab048a3..000000000 --- a/org_apg/src/org/apg/provider/ApgServiceBlobDatabase.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.provider; - -import org.apg.ApgService; - -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.net.Uri; -import android.util.Log; - -public class ApgServiceBlobDatabase extends SQLiteOpenHelper { - - private static final String TAG = "ApgServiceBlobDatabase"; - - private static final int VERSION = 1; - private static final String NAME = "apg_service_blob_data"; - private static final String TABLE = "data"; - - public ApgServiceBlobDatabase(Context context) { - super(context, NAME, null, VERSION); - if(ApgService.LOCAL_LOGD) Log.d(TAG, "constructor called"); - } - - @Override - public void onCreate(SQLiteDatabase db) { - if(ApgService.LOCAL_LOGD) Log.d(TAG, "onCreate() called"); - db.execSQL("create table " + TABLE + " ( _id integer primary key autoincrement," + - "key text not null)"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if(ApgService.LOCAL_LOGD) Log.d(TAG, "onUpgrade() called"); - // no upgrade necessary yet - } - - public Uri insert(ContentValues vals) { - if(ApgService.LOCAL_LOGD) Log.d(TAG, "insert() called"); - SQLiteDatabase db = this.getWritableDatabase(); - long newId = db.insert(TABLE, null, vals); - return ContentUris.withAppendedId(ApgServiceBlobProvider.CONTENT_URI, newId); - } - - public Cursor query(String id, String key) { - if(ApgService.LOCAL_LOGD) Log.d(TAG, "query() called"); - SQLiteDatabase db = this.getReadableDatabase(); - return db.query(TABLE, new String[] {"_id"}, - "_id = ? and key = ?", new String[] {id, key}, - null, null, null); - } -} diff --git a/org_apg/src/org/apg/provider/ApgServiceBlobProvider.java b/org_apg/src/org/apg/provider/ApgServiceBlobProvider.java deleted file mode 100644 index 72f70a590..000000000 --- a/org_apg/src/org/apg/provider/ApgServiceBlobProvider.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.provider; - -import org.apg.ApgService; -import org.apg.Constants; - -import android.content.ContentProvider; -import android.content.ContentValues; -import android.database.Cursor; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.util.Log; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.List; -import java.util.UUID; - -public class ApgServiceBlobProvider extends ContentProvider { - - private static final String TAG = "ApgServiceBlobProvider"; - - public static final Uri CONTENT_URI = Uri.parse("content://org.apg.provider.apgserviceblobprovider"); - - private static final String COLUMN_KEY = "key"; - - private static final String STORE_PATH = Constants.path.APP_DIR+"/ApgServiceBlobs"; - - private ApgServiceBlobDatabase mDb = null; - - public ApgServiceBlobProvider() { - if(ApgService.LOCAL_LOGD) Log.d(TAG, "Constructor called"); - File dir = new File(STORE_PATH); - dir.mkdirs(); - if(ApgService.LOCAL_LOGD) Log.d(TAG, "Constructor finished"); - } - - @Override - public int delete(Uri arg0, String arg1, String[] arg2) { - if(ApgService.LOCAL_LOGD) Log.d(TAG, "delete() called"); - // TODO Auto-generated method stub - return 0; - } - - @Override - public String getType(Uri arg0) { - if(ApgService.LOCAL_LOGD) Log.d(TAG, "getType() called"); - // not needed for now - return null; - } - - @Override - public Uri insert(Uri uri, ContentValues ignored) { - if(ApgService.LOCAL_LOGD) Log.d(TAG, "insert() called"); - // ContentValues are actually ignored, because we want to store a blob with no more information - // but have to create an record with the password generated here first - - ContentValues vals = new ContentValues(); - - // Insert a random key in the database. This has to provided by the caller when updating or - // getting the blob - String password = UUID.randomUUID().toString(); - vals.put(COLUMN_KEY, password); - - Uri insertedUri = mDb.insert(vals); - return Uri.withAppendedPath(insertedUri, password); - } - - @Override - public boolean onCreate() { - if(ApgService.LOCAL_LOGD) Log.d(TAG, "onCreate() called"); - mDb = new ApgServiceBlobDatabase(getContext()); - // TODO Auto-generated method stub - return true; - } - - @Override - public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) { - if(ApgService.LOCAL_LOGD) Log.d(TAG, "query() called"); - // TODO Auto-generated method stub - return null; - } - - @Override - public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) { - if(ApgService.LOCAL_LOGD) Log.d(TAG, "update() called"); - // TODO Auto-generated method stub - return 0; - } - - @Override - public ParcelFileDescriptor openFile(Uri uri, String mode) throws SecurityException, FileNotFoundException { - if(ApgService.LOCAL_LOGD) Log.d(TAG, "openFile() called"); - if(ApgService.LOCAL_LOGD) Log.d(TAG, "... with uri: "+uri.toString()); - if(ApgService.LOCAL_LOGD) Log.d(TAG, "... with mode: "+mode); - - List segments = uri.getPathSegments(); - if(segments.size() < 2) { - throw new SecurityException("Password not found in URI"); - } - String id = segments.get(0); - String key = segments.get(1); - - if(ApgService.LOCAL_LOGD) Log.d(TAG, "... got id: "+id); - if(ApgService.LOCAL_LOGD) Log.d(TAG, "... and key: "+key); - - // get the data - Cursor result = mDb.query(id, key); - - if(result.getCount() == 0) { - // either the key is wrong or no id exists - throw new FileNotFoundException("No file found with that ID and/or password"); - } - - File targetFile = new File(STORE_PATH, id); - if(mode.equals("w")) { - if(ApgService.LOCAL_LOGD) Log.d(TAG, "... will try to open file w"); - if( !targetFile.exists() ) { - try { - targetFile.createNewFile(); - } catch (IOException e) { - Log.e(TAG, "... got IEOException on creating new file", e); - throw new FileNotFoundException("Could not create file to write to"); - } - } - return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE ); - } else if(mode.equals("r")) { - if(ApgService.LOCAL_LOGD) Log.d(TAG, "... will try to open file r"); - if( !targetFile.exists() ) { - throw new FileNotFoundException("Error: Could not find the file requested"); - } - return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_READ_ONLY); - } - - return null; - } - -} diff --git a/org_apg/src/org/apg/provider/DataProvider.java b/org_apg/src/org/apg/provider/DataProvider.java deleted file mode 100644 index bafcc3c28..000000000 --- a/org_apg/src/org/apg/provider/DataProvider.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.provider; - -import java.io.File; -import java.io.FileNotFoundException; -import java.util.HashMap; - -import org.apg.Id; - -import android.content.ContentProvider; -import android.content.ContentValues; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteQueryBuilder; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.text.TextUtils; - -public class DataProvider extends ContentProvider { - public static final String AUTHORITY = "org.apg.provider"; - - private static final int PUBLIC_KEY_RING = 101; - private static final int PUBLIC_KEY_RING_ID = 102; - private static final int PUBLIC_KEY_RING_BY_KEY_ID = 103; - private static final int PUBLIC_KEY_RING_BY_EMAILS = 104; - private static final int PUBLIC_KEY_RING_KEY = 111; - private static final int PUBLIC_KEY_RING_KEY_RANK = 112; - private static final int PUBLIC_KEY_RING_USER_ID = 121; - private static final int PUBLIC_KEY_RING_USER_ID_RANK = 122; - - private static final int SECRET_KEY_RING = 201; - private static final int SECRET_KEY_RING_ID = 202; - private static final int SECRET_KEY_RING_BY_KEY_ID = 203; - private static final int SECRET_KEY_RING_BY_EMAILS = 204; - private static final int SECRET_KEY_RING_KEY = 211; - private static final int SECRET_KEY_RING_KEY_RANK = 212; - private static final int SECRET_KEY_RING_USER_ID = 221; - private static final int SECRET_KEY_RING_USER_ID_RANK = 222; - - private static final int DATA_STREAM = 301; - - private static final String PUBLIC_KEY_RING_CONTENT_DIR_TYPE = - "vnd.android.cursor.dir/vnd.thialfihar.apg.public.key_ring"; - private static final String PUBLIC_KEY_RING_CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.thialfihar.apg.public.key_ring"; - - private static final String PUBLIC_KEY_CONTENT_DIR_TYPE = - "vnd.android.cursor.dir/vnd.thialfihar.apg.public.key"; - private static final String PUBLIC_KEY_CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.thialfihar.apg.public.key"; - - private static final String SECRET_KEY_RING_CONTENT_DIR_TYPE = - "vnd.android.cursor.dir/vnd.thialfihar.apg.secret.key_ring"; - private static final String SECRET_KEY_RING_CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.thialfihar.apg.secret.key_ring"; - - private static final String SECRET_KEY_CONTENT_DIR_TYPE = - "vnd.android.cursor.dir/vnd.thialfihar.apg.secret.key"; - private static final String SECRET_KEY_CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.thialfihar.apg.secret.key"; - - private static final String USER_ID_CONTENT_DIR_TYPE = - "vnd.android.cursor.dir/vnd.thialfihar.apg.user_id"; - private static final String USER_ID_CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/vnd.thialfihar.apg.user_id"; - - public static final String _ID = "_id"; - public static final String MASTER_KEY_ID = "master_key_id"; - public static final String KEY_ID = "key_id"; - public static final String USER_ID = "user_id"; - - private static final UriMatcher mUriMatcher; - - private Database mDb; - - static { - mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - mUriMatcher.addURI(AUTHORITY, "key_rings/public/key_id/*", PUBLIC_KEY_RING_BY_KEY_ID); - mUriMatcher.addURI(AUTHORITY, "key_rings/public/emails/*", PUBLIC_KEY_RING_BY_EMAILS); - - mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/keys", PUBLIC_KEY_RING_KEY); - mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/keys/#", PUBLIC_KEY_RING_KEY_RANK); - - mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/user_ids", PUBLIC_KEY_RING_USER_ID); - mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/user_ids/#", PUBLIC_KEY_RING_USER_ID_RANK); - - mUriMatcher.addURI(AUTHORITY, "key_rings/public", PUBLIC_KEY_RING); - mUriMatcher.addURI(AUTHORITY, "key_rings/public/*", PUBLIC_KEY_RING_ID); - - mUriMatcher.addURI(AUTHORITY, "key_rings/secret/key_id/*", SECRET_KEY_RING_BY_KEY_ID); - mUriMatcher.addURI(AUTHORITY, "key_rings/secret/emails/*", SECRET_KEY_RING_BY_EMAILS); - - mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/keys", SECRET_KEY_RING_KEY); - mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/keys/#", SECRET_KEY_RING_KEY_RANK); - - mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/user_ids", SECRET_KEY_RING_USER_ID); - mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/user_ids/#", SECRET_KEY_RING_USER_ID_RANK); - - mUriMatcher.addURI(AUTHORITY, "key_rings/secret", SECRET_KEY_RING); - mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*", SECRET_KEY_RING_ID); - - mUriMatcher.addURI(AUTHORITY, "data/*", DATA_STREAM); - } - - @Override - public boolean onCreate() { - mDb = new Database(getContext()); - return true; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, - String[] selectionArgs, String sortOrder) { - // TODO: implement the others, then use them for the lists - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - HashMap projectionMap = new HashMap(); - - int match = mUriMatcher.match(uri); - int type; - switch (match) { - case PUBLIC_KEY_RING: - case PUBLIC_KEY_RING_ID: - case PUBLIC_KEY_RING_BY_KEY_ID: - case PUBLIC_KEY_RING_BY_EMAILS: - case PUBLIC_KEY_RING_KEY: - case PUBLIC_KEY_RING_KEY_RANK: - case PUBLIC_KEY_RING_USER_ID: - case PUBLIC_KEY_RING_USER_ID_RANK: - type = Id.database.type_public; - break; - - case SECRET_KEY_RING: - case SECRET_KEY_RING_ID: - case SECRET_KEY_RING_BY_KEY_ID: - case SECRET_KEY_RING_BY_EMAILS: - case SECRET_KEY_RING_KEY: - case SECRET_KEY_RING_KEY_RANK: - case SECRET_KEY_RING_USER_ID: - case SECRET_KEY_RING_USER_ID_RANK: - type = Id.database.type_secret; - break; - - default: { - throw new IllegalArgumentException("Unknown URI " + uri); - } - } - - qb.appendWhere(KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = " + type); - - switch (match) { - case PUBLIC_KEY_RING_ID: - case SECRET_KEY_RING_ID: { - qb.appendWhere(" AND " + - KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(2)); - - // break omitted intentionally - } - - case PUBLIC_KEY_RING: - case SECRET_KEY_RING: { - qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + - "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + - Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + - Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + - ") " + - " INNER JOIN " + UserIds.TABLE_NAME + " ON " + - "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + - UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + - UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); - - projectionMap.put(_ID, - KeyRings.TABLE_NAME + "." + KeyRings._ID); - projectionMap.put(MASTER_KEY_ID, - KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID); - projectionMap.put(USER_ID, - UserIds.TABLE_NAME + "." + UserIds.USER_ID); - - if (TextUtils.isEmpty(sortOrder)) { - sortOrder = UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC"; - } - - break; - } - - case SECRET_KEY_RING_BY_KEY_ID: - case PUBLIC_KEY_RING_BY_KEY_ID: { - qb.setTables(Keys.TABLE_NAME + " AS tmp INNER JOIN " + - KeyRings.TABLE_NAME + " ON (" + - KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + - "tmp." + Keys.KEY_RING_ID + ")" + - " INNER JOIN " + Keys.TABLE_NAME + " ON " + - "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + - Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + - Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + - ") " + - " INNER JOIN " + UserIds.TABLE_NAME + " ON " + - "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + - UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + - UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); - - projectionMap.put(_ID, - KeyRings.TABLE_NAME + "." + KeyRings._ID); - projectionMap.put(MASTER_KEY_ID, - KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID); - projectionMap.put(USER_ID, - UserIds.TABLE_NAME + "." + UserIds.USER_ID); - - qb.appendWhere(" AND tmp." + Keys.KEY_ID + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(3)); - - break; - } - - case SECRET_KEY_RING_BY_EMAILS: - case PUBLIC_KEY_RING_BY_EMAILS: { - qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + - "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + - Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + - Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + - ") " + - " INNER JOIN " + UserIds.TABLE_NAME + " ON " + - "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + - UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + - UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); - - projectionMap.put(_ID, - KeyRings.TABLE_NAME + "." + KeyRings._ID); - projectionMap.put(MASTER_KEY_ID, - KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID); - projectionMap.put(USER_ID, - UserIds.TABLE_NAME + "." + UserIds.USER_ID); - - String emails = uri.getPathSegments().get(3); - String chunks[] = emails.split(" *, *"); - boolean gotCondition = false; - String emailWhere = ""; - for (int i = 0; i < chunks.length; ++i) { - if (chunks[i].length() == 0) { - continue; - } - if (i != 0) { - emailWhere += " OR "; - } - emailWhere += "tmp." + UserIds.USER_ID + " LIKE "; - // match '*', so it has to be at the *end* of the user id - emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">"); - gotCondition = true; - } - - if (gotCondition) { - qb.appendWhere(" AND EXISTS (SELECT tmp." + UserIds._ID + - " FROM " + UserIds.TABLE_NAME + - " AS tmp WHERE tmp." + UserIds.KEY_ID + " = " + - Keys.TABLE_NAME + "." + Keys._ID + - " AND (" + emailWhere + "))"); - } - - break; - } - - default: { - throw new IllegalArgumentException("Unknown URI " + uri); - } - } - - qb.setProjectionMap(projectionMap); - - // If no sort order is specified use the default - String orderBy; - if (TextUtils.isEmpty(sortOrder)) { - orderBy = null; - } else { - orderBy = sortOrder; - } - - //System.out.println(qb.buildQuery(projection, selection, selectionArgs, null, null, sortOrder, null).replace("WHERE", "WHERE\n")); - Cursor c = qb.query(mDb.db(), projection, selection, selectionArgs, null, null, orderBy); - - // Tell the cursor what uri to watch, so it knows when its source data changes - c.setNotificationUri(getContext().getContentResolver(), uri); - return c; - } - - @Override - public String getType(Uri uri) { - switch (mUriMatcher.match(uri)) { - case PUBLIC_KEY_RING: - case PUBLIC_KEY_RING_BY_EMAILS: - return PUBLIC_KEY_RING_CONTENT_DIR_TYPE; - - case PUBLIC_KEY_RING_ID: - return PUBLIC_KEY_RING_CONTENT_ITEM_TYPE; - - case PUBLIC_KEY_RING_BY_KEY_ID: - return PUBLIC_KEY_RING_CONTENT_ITEM_TYPE; - - case PUBLIC_KEY_RING_KEY: - return PUBLIC_KEY_CONTENT_DIR_TYPE; - - case PUBLIC_KEY_RING_KEY_RANK: - return PUBLIC_KEY_CONTENT_ITEM_TYPE; - - case PUBLIC_KEY_RING_USER_ID: - return USER_ID_CONTENT_DIR_TYPE; - - case PUBLIC_KEY_RING_USER_ID_RANK: - return USER_ID_CONTENT_ITEM_TYPE; - - case SECRET_KEY_RING: - case SECRET_KEY_RING_BY_EMAILS: - return SECRET_KEY_RING_CONTENT_DIR_TYPE; - - case SECRET_KEY_RING_ID: - return SECRET_KEY_RING_CONTENT_ITEM_TYPE; - - case SECRET_KEY_RING_BY_KEY_ID: - return SECRET_KEY_RING_CONTENT_ITEM_TYPE; - - case SECRET_KEY_RING_KEY: - return SECRET_KEY_CONTENT_DIR_TYPE; - - case SECRET_KEY_RING_KEY_RANK: - return SECRET_KEY_CONTENT_ITEM_TYPE; - - case SECRET_KEY_RING_USER_ID: - return USER_ID_CONTENT_DIR_TYPE; - - case SECRET_KEY_RING_USER_ID_RANK: - return USER_ID_CONTENT_ITEM_TYPE; - - default: - throw new IllegalArgumentException("Unknown URI " + uri); - } - } - - @Override - public Uri insert(Uri uri, ContentValues initialValues) { - // not supported - return null; - } - - @Override - public int delete(Uri uri, String where, String[] whereArgs) { - // not supported - return 0; - } - - @Override - public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { - // not supported - return 0; - } - - @Override - public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - int match = mUriMatcher.match(uri); - if (match != DATA_STREAM) { - throw new FileNotFoundException(); - } - String fileName = uri.getPathSegments().get(1); - File file = new File(getContext().getFilesDir().getAbsolutePath(), fileName); - return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); - } -} diff --git a/org_apg/src/org/apg/provider/Database.java b/org_apg/src/org/apg/provider/Database.java deleted file mode 100644 index a41df2ced..000000000 --- a/org_apg/src/org/apg/provider/Database.java +++ /dev/null @@ -1,616 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.provider; - -import org.apg.Apg; -import org.apg.Id; -import org.apg.util.IterableIterator; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSecretKey; -import org.spongycastle.openpgp.PGPSecretKeyRing; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; - -import java.io.IOException; -import java.util.Date; -import java.util.HashMap; -import java.util.Vector; - -public class Database extends SQLiteOpenHelper { - public static class GeneralException extends Exception { - static final long serialVersionUID = 0xf812773343L; - - public GeneralException(String message) { - super(message); - } - } - - private static final String DATABASE_NAME = "apg"; - private static final int DATABASE_VERSION = 2; - - public static final String AUTHORITY = "org.apg.database"; - - public static HashMap sKeyRingsProjection; - public static HashMap sKeysProjection; - public static HashMap sUserIdsProjection; - - private SQLiteDatabase mDb = null; - private int mStatus = 0; - - static { - sKeyRingsProjection = new HashMap(); - sKeyRingsProjection.put(KeyRings._ID, KeyRings._ID); - sKeyRingsProjection.put(KeyRings.MASTER_KEY_ID, KeyRings.MASTER_KEY_ID); - sKeyRingsProjection.put(KeyRings.TYPE, KeyRings.TYPE); - sKeyRingsProjection.put(KeyRings.WHO_ID, KeyRings.WHO_ID); - sKeyRingsProjection.put(KeyRings.KEY_RING_DATA, KeyRings.KEY_RING_DATA); - - sKeysProjection = new HashMap(); - sKeysProjection.put(Keys._ID, Keys._ID); - sKeysProjection.put(Keys.KEY_ID, Keys.KEY_ID); - sKeysProjection.put(Keys.TYPE, Keys.TYPE); - sKeysProjection.put(Keys.IS_MASTER_KEY, Keys.IS_MASTER_KEY); - sKeysProjection.put(Keys.ALGORITHM, Keys.ALGORITHM); - sKeysProjection.put(Keys.KEY_SIZE, Keys.KEY_SIZE); - sKeysProjection.put(Keys.CAN_SIGN, Keys.CAN_SIGN); - sKeysProjection.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT); - sKeysProjection.put(Keys.IS_REVOKED, Keys.IS_REVOKED); - sKeysProjection.put(Keys.CREATION, Keys.CREATION); - sKeysProjection.put(Keys.EXPIRY, Keys.EXPIRY); - sKeysProjection.put(Keys.KEY_DATA, Keys.KEY_DATA); - sKeysProjection.put(Keys.RANK, Keys.RANK); - - sUserIdsProjection = new HashMap(); - sUserIdsProjection.put(UserIds._ID, UserIds._ID); - sUserIdsProjection.put(UserIds.KEY_ID, UserIds.KEY_ID); - sUserIdsProjection.put(UserIds.USER_ID, UserIds.USER_ID); - sUserIdsProjection.put(UserIds.RANK, UserIds.RANK); - } - - public Database(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - // force upgrade to test things - //onUpgrade(getWritableDatabase(), 1, 2); - mDb = getWritableDatabase(); - } - - @Override - protected void finalize() throws Throwable { - mDb.close(); - super.finalize(); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + KeyRings.TABLE_NAME + " (" + - KeyRings._ID + " " + KeyRings._ID_type + "," + - KeyRings.MASTER_KEY_ID + " " + KeyRings.MASTER_KEY_ID_type + ", " + - KeyRings.TYPE + " " + KeyRings.TYPE_type + ", " + - KeyRings.WHO_ID + " " + KeyRings.WHO_ID_type + ", " + - KeyRings.KEY_RING_DATA + " " + KeyRings.KEY_RING_DATA_type + ");"); - - db.execSQL("CREATE TABLE " + Keys.TABLE_NAME + " (" + - Keys._ID + " " + Keys._ID_type + "," + - Keys.KEY_ID + " " + Keys.KEY_ID_type + ", " + - Keys.TYPE + " " + Keys.TYPE_type + ", " + - Keys.IS_MASTER_KEY + " " + Keys.IS_MASTER_KEY_type + ", " + - Keys.ALGORITHM + " " + Keys.ALGORITHM_type + ", " + - Keys.KEY_SIZE + " " + Keys.KEY_SIZE_type + ", " + - Keys.CAN_SIGN + " " + Keys.CAN_SIGN_type + ", " + - Keys.CAN_ENCRYPT + " " + Keys.CAN_ENCRYPT_type + ", " + - Keys.IS_REVOKED + " " + Keys.IS_REVOKED_type + ", " + - Keys.CREATION + " " + Keys.CREATION_type + ", " + - Keys.EXPIRY + " " + Keys.EXPIRY_type + ", " + - Keys.KEY_RING_ID + " " + Keys.KEY_RING_ID_type + ", " + - Keys.KEY_DATA + " " + Keys.KEY_DATA_type + - Keys.RANK + " " + Keys.RANK_type + ");"); - - db.execSQL("CREATE TABLE " + UserIds.TABLE_NAME + " (" + - UserIds._ID + " " + UserIds._ID_type + "," + - UserIds.KEY_ID + " " + UserIds.KEY_ID_type + "," + - UserIds.USER_ID + " " + UserIds.USER_ID_type + "," + - UserIds.RANK + " " + UserIds.RANK_type + ");"); - - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - mDb = db; - for (int version = oldVersion; version < newVersion; ++version) { - switch (version) { - case 1: { // upgrade 1 to 2 - db.execSQL("DROP TABLE IF EXISTS " + KeyRings.TABLE_NAME + ";"); - db.execSQL("DROP TABLE IF EXISTS " + Keys.TABLE_NAME + ";"); - db.execSQL("DROP TABLE IF EXISTS " + UserIds.TABLE_NAME + ";"); - - db.execSQL("CREATE TABLE " + KeyRings.TABLE_NAME + " (" + - KeyRings._ID + " " + KeyRings._ID_type + "," + - KeyRings.MASTER_KEY_ID + " " + KeyRings.MASTER_KEY_ID_type + ", " + - KeyRings.TYPE + " " + KeyRings.TYPE_type + ", " + - KeyRings.WHO_ID + " " + KeyRings.WHO_ID_type + ", " + - KeyRings.KEY_RING_DATA + " " + KeyRings.KEY_RING_DATA_type + ");"); - - db.execSQL("CREATE TABLE " + Keys.TABLE_NAME + " (" + - Keys._ID + " " + Keys._ID_type + "," + - Keys.KEY_ID + " " + Keys.KEY_ID_type + ", " + - Keys.TYPE + " " + Keys.TYPE_type + ", " + - Keys.IS_MASTER_KEY + " " + Keys.IS_MASTER_KEY_type + ", " + - Keys.ALGORITHM + " " + Keys.ALGORITHM_type + ", " + - Keys.KEY_SIZE + " " + Keys.KEY_SIZE_type + ", " + - Keys.CAN_SIGN + " " + Keys.CAN_SIGN_type + ", " + - Keys.CAN_ENCRYPT + " " + Keys.CAN_ENCRYPT_type + ", " + - Keys.IS_REVOKED + " " + Keys.IS_REVOKED_type + ", " + - Keys.CREATION + " " + Keys.CREATION_type + ", " + - Keys.EXPIRY + " " + Keys.EXPIRY_type + ", " + - Keys.KEY_RING_ID + " " + Keys.KEY_RING_ID_type + ", " + - Keys.KEY_DATA + " " + Keys.KEY_DATA_type + - Keys.RANK + " " + Keys.RANK_type + ");"); - - db.execSQL("CREATE TABLE " + UserIds.TABLE_NAME + " (" + - UserIds._ID + " " + UserIds._ID_type + "," + - UserIds.KEY_ID + " " + UserIds.KEY_ID_type + "," + - UserIds.USER_ID + " " + UserIds.USER_ID_type + "," + - UserIds.RANK + " " + UserIds.RANK_type + ");"); - - Cursor cursor = db.query("public_keys", new String[] { "c_key_data" }, - null, null, null, null, null); - if (cursor != null && cursor.moveToFirst()) { - do { - byte[] data = cursor.getBlob(0); - try { - PGPPublicKeyRing keyRing = new PGPPublicKeyRing(data); - saveKeyRing(keyRing); - } catch (IOException e) { - Log.e("apg.db.upgrade", "key import failed: " + e); - } catch (GeneralException e) { - Log.e("apg.db.upgrade", "key import failed: " + e); - } - } while (cursor.moveToNext()); - } - - if (cursor != null) { - cursor.close(); - } - - cursor = db.query("secret_keys", new String[]{ "c_key_data" }, - null, null, null, null, null); - if (cursor != null && cursor.moveToFirst()) { - do { - byte[] data = cursor.getBlob(0); - try { - PGPSecretKeyRing keyRing = new PGPSecretKeyRing(data); - saveKeyRing(keyRing); - } catch (IOException e) { - Log.e("apg.db.upgrade", "key import failed: " + e); - } catch (PGPException e) { - Log.e("apg.db.upgrade", "key import failed: " + e); - } catch (GeneralException e) { - Log.e("apg.db.upgrade", "key import failed: " + e); - } - } while (cursor.moveToNext()); - } - - if (cursor != null) { - cursor.close(); - } - - db.execSQL("DROP TABLE IF EXISTS public_keys;"); - db.execSQL("DROP TABLE IF EXISTS secret_keys;"); - - break; - } - - default: { - break; - } - } - } - mDb = null; - } - - public int saveKeyRing(PGPPublicKeyRing keyRing) throws IOException, GeneralException { - mDb.beginTransaction(); - ContentValues values = new ContentValues(); - PGPPublicKey masterKey = keyRing.getPublicKey(); - long masterKeyId = masterKey.getKeyID(); - - values.put(KeyRings.MASTER_KEY_ID, masterKeyId); - values.put(KeyRings.TYPE, Id.database.type_public); - values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded()); - - long rowId = insertOrUpdateKeyRing(values); - int returnValue = mStatus; - - if (rowId == -1) { - throw new GeneralException("saving public key ring " + masterKeyId + " failed"); - } - - Vector seenIds = new Vector(); - int rank = 0; - for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { - seenIds.add(saveKey(rowId, key, rank)); - ++rank; - } - - String seenIdsStr = ""; - for (Integer id : seenIds) { - if (seenIdsStr.length() > 0) { - seenIdsStr += ","; - } - seenIdsStr += id; - } - mDb.delete(Keys.TABLE_NAME, - Keys.KEY_RING_ID + " = ? AND " + - Keys._ID + " NOT IN (" + seenIdsStr + ")", - new String[] { "" + rowId }); - - mDb.setTransactionSuccessful(); - mDb.endTransaction(); - return returnValue; - } - - public int saveKeyRing(PGPSecretKeyRing keyRing) throws IOException, GeneralException { - mDb.beginTransaction(); - ContentValues values = new ContentValues(); - PGPSecretKey masterKey = keyRing.getSecretKey(); - long masterKeyId = masterKey.getKeyID(); - - values.put(KeyRings.MASTER_KEY_ID, masterKeyId); - values.put(KeyRings.TYPE, Id.database.type_secret); - values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded()); - - long rowId = insertOrUpdateKeyRing(values); - int returnValue = mStatus; - - if (rowId == -1) { - throw new GeneralException("saving secret key ring " + masterKeyId + " failed"); - } - - Vector seenIds = new Vector(); - int rank = 0; - for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { - seenIds.add(saveKey(rowId, key, rank)); - ++rank; - } - - String seenIdsStr = ""; - for (Integer id : seenIds) { - if (seenIdsStr.length() > 0) { - seenIdsStr += ","; - } - seenIdsStr += id; - } - mDb.delete(Keys.TABLE_NAME, - Keys.KEY_RING_ID + " = ? AND " + - Keys._ID + " NOT IN (" + seenIdsStr + ")", - new String[] { "" + rowId }); - - mDb.setTransactionSuccessful(); - mDb.endTransaction(); - return returnValue; - } - - private int saveKey(long keyRingId, PGPPublicKey key, int rank) - throws IOException, GeneralException { - ContentValues values = new ContentValues(); - - values.put(Keys.KEY_ID, key.getKeyID()); - values.put(Keys.TYPE, Id.database.type_public); - values.put(Keys.IS_MASTER_KEY, key.isMasterKey()); - values.put(Keys.ALGORITHM, key.getAlgorithm()); - values.put(Keys.KEY_SIZE, key.getBitStrength()); - values.put(Keys.CAN_SIGN, Apg.isSigningKey(key)); - values.put(Keys.CAN_ENCRYPT, Apg.isEncryptionKey(key)); - values.put(Keys.IS_REVOKED, key.isRevoked()); - values.put(Keys.CREATION, Apg.getCreationDate(key).getTime() / 1000); - Date expiryDate = Apg.getExpiryDate(key); - if (expiryDate != null) { - values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); - } - values.put(Keys.KEY_RING_ID, keyRingId); - values.put(Keys.KEY_DATA, key.getEncoded()); - values.put(Keys.RANK, rank); - - long rowId = insertOrUpdateKey(values); - - if (rowId == -1) { - throw new GeneralException("saving public key " + key.getKeyID() + " failed"); - } - - Vector seenIds = new Vector(); - int userIdRank = 0; - for (String userId : new IterableIterator(key.getUserIDs())) { - seenIds.add(saveUserId(rowId, userId, userIdRank)); - ++userIdRank; - } - - String seenIdsStr = ""; - for (Integer id : seenIds) { - if (seenIdsStr.length() > 0) { - seenIdsStr += ","; - } - seenIdsStr += id; - } - mDb.delete(UserIds.TABLE_NAME, - UserIds.KEY_ID + " = ? AND " + - UserIds._ID + " NOT IN (" + seenIdsStr + ")", - new String[] { "" + rowId }); - - return (int)rowId; - } - - private int saveKey(long keyRingId, PGPSecretKey key, int rank) - throws IOException, GeneralException { - ContentValues values = new ContentValues(); - - values.put(Keys.KEY_ID, key.getPublicKey().getKeyID()); - values.put(Keys.TYPE, Id.database.type_secret); - values.put(Keys.IS_MASTER_KEY, key.isMasterKey()); - values.put(Keys.ALGORITHM, key.getPublicKey().getAlgorithm()); - values.put(Keys.KEY_SIZE, key.getPublicKey().getBitStrength()); - values.put(Keys.CAN_SIGN, Apg.isSigningKey(key)); - values.put(Keys.CAN_ENCRYPT, Apg.isEncryptionKey(key)); - values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked()); - values.put(Keys.CREATION, Apg.getCreationDate(key).getTime() / 1000); - Date expiryDate = Apg.getExpiryDate(key); - if (expiryDate != null) { - values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); - } - values.put(Keys.KEY_RING_ID, keyRingId); - values.put(Keys.KEY_DATA, key.getEncoded()); - values.put(Keys.RANK, rank); - - long rowId = insertOrUpdateKey(values); - - if (rowId == -1) { - throw new GeneralException("saving secret key " + key.getPublicKey().getKeyID() + " failed"); - } - - Vector seenIds = new Vector(); - int userIdRank = 0; - for (String userId : new IterableIterator(key.getUserIDs())) { - seenIds.add(saveUserId(rowId, userId, userIdRank)); - ++userIdRank; - } - - String seenIdsStr = ""; - for (Integer id : seenIds) { - if (seenIdsStr.length() > 0) { - seenIdsStr += ","; - } - seenIdsStr += id; - } - mDb.delete(UserIds.TABLE_NAME, - UserIds.KEY_ID + " = ? AND " + - UserIds._ID + " NOT IN (" + seenIdsStr + ")", - new String[] { "" + rowId }); - - return (int)rowId; - } - - private int saveUserId(long keyId, String userId, int rank) throws GeneralException { - ContentValues values = new ContentValues(); - - values.put(UserIds.KEY_ID, keyId); - values.put(UserIds.USER_ID, userId); - values.put(UserIds.RANK, rank); - - long rowId = insertOrUpdateUserId(values); - - if (rowId == -1) { - throw new GeneralException("saving user id " + userId + " failed"); - } - - return (int)rowId; - } - - private long insertOrUpdateKeyRing(ContentValues values) { - Cursor c = mDb.query(KeyRings.TABLE_NAME, new String[] { KeyRings._ID }, - KeyRings.MASTER_KEY_ID + " = ? AND " + KeyRings.TYPE + " = ?", - new String[] { - values.getAsString(KeyRings.MASTER_KEY_ID), - values.getAsString(KeyRings.TYPE), - }, - null, null, null); - long rowId = -1; - if (c != null && c.moveToFirst()) { - rowId = c.getLong(0); - mDb.update(KeyRings.TABLE_NAME, values, - KeyRings._ID + " = ?", new String[] { "" + rowId }); - mStatus = Id.return_value.updated; - } else { - rowId = mDb.insert(KeyRings.TABLE_NAME, KeyRings.WHO_ID, values); - mStatus = Id.return_value.ok; - } - - if (c != null) { - c.close(); - } - - return rowId; - } - - private long insertOrUpdateKey(ContentValues values) { - Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID }, - Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", - new String[] { - values.getAsString(Keys.KEY_ID), - values.getAsString(Keys.TYPE), - }, - null, null, null); - long rowId = -1; - if (c != null && c.moveToFirst()) { - rowId = c.getLong(0); - mDb.update(Keys.TABLE_NAME, values, - Keys._ID + " = ?", new String[] { "" + rowId }); - } else { - rowId = mDb.insert(Keys.TABLE_NAME, Keys.KEY_DATA, values); - } - - if (c != null) { - c.close(); - } - - return rowId; - } - - private long insertOrUpdateUserId(ContentValues values) { - Cursor c = mDb.query(UserIds.TABLE_NAME, new String[] { UserIds._ID }, - UserIds.KEY_ID + " = ? AND " + UserIds.USER_ID + " = ?", - new String[] { - values.getAsString(UserIds.KEY_ID), - values.getAsString(UserIds.USER_ID), - }, - null, null, null); - long rowId = -1; - if (c != null && c.moveToFirst()) { - rowId = c.getLong(0); - mDb.update(UserIds.TABLE_NAME, values, - UserIds._ID + " = ?", new String[] { "" + rowId }); - } else { - rowId = mDb.insert(UserIds.TABLE_NAME, UserIds.USER_ID, values); - } - - if (c != null) { - c.close(); - } - - return rowId; - } - - public Object getKeyRing(int keyRingId) { - Cursor c = mDb.query(KeyRings.TABLE_NAME, - new String[] { KeyRings.KEY_RING_DATA, KeyRings.TYPE }, - KeyRings._ID + " = ?", - new String[] { - "" + keyRingId, - }, - null, null, null); - byte[] data = null; - Object keyRing = null; - if (c != null && c.moveToFirst()) { - data = c.getBlob(0); - if (data != null) { - try { - if (c.getInt(1) == Id.database.type_public) { - keyRing = new PGPPublicKeyRing(data); - } else { - keyRing = new PGPSecretKeyRing(data); - } - } catch (IOException e) { - // can't load it, then - } catch (PGPException e) { - // can't load it, then - } - } - } - - if (c != null) { - c.close(); - } - - return keyRing; - } - - public byte[] getKeyRingDataFromKeyId(int type, long keyId) { - Cursor c = mDb.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON (" + - KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + - Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + ")", - new String[] { KeyRings.TABLE_NAME + "." + KeyRings.KEY_RING_DATA }, - Keys.TABLE_NAME + "." + Keys.KEY_ID + " = ? AND " + - KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", - new String[] { - "" + keyId, - "" + type, - }, - null, null, null); - - byte[] data = null; - if (c != null && c.moveToFirst()) { - data = c.getBlob(0); - } - - if (c != null) { - c.close(); - } - - return data; - } - - public byte[] getKeyDataFromKeyId(int type, long keyId) { - Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys.KEY_DATA }, - Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", - new String[] { - "" + keyId, - "" + type, - }, - null, null, null); - byte[] data = null; - if (c != null && c.moveToFirst()) { - data = c.getBlob(0); - } - - if (c != null) { - c.close(); - } - - return data; - } - - public void deleteKeyRing(int keyRingId) { - mDb.beginTransaction(); - mDb.delete(KeyRings.TABLE_NAME, - KeyRings._ID + " = ?", new String[] { "" + keyRingId }); - - Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID }, - Keys.KEY_RING_ID + " = ?", - new String[] { - "" + keyRingId, - }, - null, null, null); - if (c != null && c.moveToFirst()) { - do { - int keyId = c.getInt(0); - deleteKey(keyId); - } while (c.moveToNext()); - } - - if (c != null) { - c.close(); - } - - mDb.setTransactionSuccessful(); - mDb.endTransaction(); - } - - private void deleteKey(int keyId) { - mDb.delete(Keys.TABLE_NAME, - Keys._ID + " = ?", new String[] { "" + keyId }); - - mDb.delete(UserIds.TABLE_NAME, - UserIds.KEY_ID + " = ?", new String[] { "" + keyId }); - } - - public SQLiteDatabase db() { - return mDb; - } -} diff --git a/org_apg/src/org/apg/provider/KeyRings.java b/org_apg/src/org/apg/provider/KeyRings.java deleted file mode 100644 index 304afd24f..000000000 --- a/org_apg/src/org/apg/provider/KeyRings.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.provider; - -import android.provider.BaseColumns; - -public class KeyRings implements BaseColumns { - public static final String TABLE_NAME = "key_rings"; - - public static final String _ID_type = "INTEGER PRIMARY KEY"; - public static final String MASTER_KEY_ID = "c_master_key_id"; - public static final String MASTER_KEY_ID_type = "INT64"; - public static final String TYPE = "c_type"; - public static final String TYPE_type = "INTEGER"; - public static final String WHO_ID = "c_who_id"; - public static final String WHO_ID_type = "INTEGER"; - public static final String KEY_RING_DATA = "c_key_ring_data"; - public static final String KEY_RING_DATA_type = "BLOB"; -} diff --git a/org_apg/src/org/apg/provider/Keys.java b/org_apg/src/org/apg/provider/Keys.java deleted file mode 100644 index 63eaee54f..000000000 --- a/org_apg/src/org/apg/provider/Keys.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.provider; - -import android.provider.BaseColumns; - -public class Keys implements BaseColumns { - public static final String TABLE_NAME = "keys"; - - public static final String _ID_type = "INTEGER PRIMARY KEY"; - public static final String KEY_ID = "c_key_id"; - public static final String KEY_ID_type = "INT64"; - public static final String TYPE = "c_type"; - public static final String TYPE_type = "INTEGER"; - public static final String IS_MASTER_KEY = "c_is_master_key"; - public static final String IS_MASTER_KEY_type = "INTEGER"; - public static final String ALGORITHM = "c_algorithm"; - public static final String ALGORITHM_type = "INTEGER"; - public static final String KEY_SIZE = "c_key_size"; - public static final String KEY_SIZE_type = "INTEGER"; - public static final String CAN_SIGN = "c_can_sign"; - public static final String CAN_SIGN_type = "INTEGER"; - public static final String CAN_ENCRYPT = "c_can_encrypt"; - public static final String CAN_ENCRYPT_type = "INTEGER"; - public static final String IS_REVOKED = "c_is_revoked"; - public static final String IS_REVOKED_type = "INTEGER"; - public static final String CREATION = "c_creation"; - public static final String CREATION_type = "INTEGER"; - public static final String EXPIRY = "c_expiry"; - public static final String EXPIRY_type = "INTEGER"; - public static final String KEY_RING_ID = "c_key_ring_id"; - public static final String KEY_RING_ID_type = "INTEGER"; - public static final String KEY_DATA = "c_key_data"; - public static final String KEY_DATA_type = "BLOB"; - public static final String RANK = "c_key_data"; - public static final String RANK_type = "INTEGER"; -} diff --git a/org_apg/src/org/apg/provider/UserIds.java b/org_apg/src/org/apg/provider/UserIds.java deleted file mode 100644 index e8ddc677d..000000000 --- a/org_apg/src/org/apg/provider/UserIds.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.provider; - -import android.provider.BaseColumns; - -public class UserIds implements BaseColumns { - public static final String TABLE_NAME = "user_ids"; - - public static final String _ID_type = "INTEGER PRIMARY KEY"; - public static final String KEY_ID = "c_key_id"; - public static final String KEY_ID_type = "INTEGER"; - public static final String USER_ID = "c_user_id"; - public static final String USER_ID_type = "TEXT"; - public static final String RANK = "c_rank"; - public static final String RANK_type = "INTEGER"; -} diff --git a/org_apg/src/org/apg/ui/AboutActivity.java b/org_apg/src/org/apg/ui/AboutActivity.java deleted file mode 100644 index e9a97d370..000000000 --- a/org_apg/src/org/apg/ui/AboutActivity.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import org.apg.Constants; -import org.apg.R; - -import android.app.Activity; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Bundle; -import android.util.Log; -import android.widget.TextView; - -public class AboutActivity extends Activity { - Activity mActivity; - - /** - * Instantiate View for this Activity - */ - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.about_activity); - - mActivity = this; - - TextView versionText = (TextView) findViewById(R.id.about_version); - versionText.setText(getString(R.string.about_version) + " " + getVersion()); - } - - /** - * Get the current package version. - * - * @return The current version. - */ - private String getVersion() { - String result = ""; - try { - PackageManager manager = mActivity.getPackageManager(); - PackageInfo info = manager.getPackageInfo(mActivity.getPackageName(), 0); - - result = String.format("%s (%s)", info.versionName, info.versionCode); - } catch (NameNotFoundException e) { - Log.w(Constants.TAG, "Unable to get application version: " + e.getMessage()); - result = "Unable to get application version."; - } - - return result; - } -} diff --git a/org_apg/src/org/apg/ui/BaseActivity.java b/org_apg/src/org/apg/ui/BaseActivity.java deleted file mode 100644 index 43c5c0bfe..000000000 --- a/org_apg/src/org/apg/ui/BaseActivity.java +++ /dev/null @@ -1,408 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; - -import org.apg.Apg; -import org.apg.AskForSecretKeyPassPhrase; -import org.apg.Constants; -import org.apg.Id; -import org.apg.PausableThread; -import org.apg.Preferences; -import org.apg.ProgressDialogUpdater; -import org.apg.Service; -import org.apg.R; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.SherlockActivity; -import com.actionbarsherlock.view.MenuItem; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; -import android.os.Message; -import android.widget.Toast; - -public class BaseActivity extends SherlockActivity implements Runnable, ProgressDialogUpdater, - AskForSecretKeyPassPhrase.PassPhraseCallbackInterface { - - private ProgressDialog mProgressDialog = null; - private PausableThread mRunningThread = null; - private Thread mDeletingThread = null; - - private long mSecretKeyId = 0; - private String mDeleteFile = null; - - protected Preferences mPreferences; - - private Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - handlerCallback(msg); - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - final ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(true); - - mPreferences = Preferences.getPreferences(this); - - Apg.initialize(this); - - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - File dir = new File(Constants.path.APP_DIR); - if (!dir.exists() && !dir.mkdirs()) { - // ignore this for now, it's not crucial - // that the directory doesn't exist at this point - } - } - - startCacheService(this, mPreferences); - } - - public static void startCacheService(Activity activity, Preferences preferences) { - Intent intent = new Intent(activity, Service.class); - intent.putExtra(Service.EXTRA_TTL, preferences.getPassPhraseCacheTtl()); - activity.startService(intent); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case android.R.id.home: - startActivity(new Intent(this, MainActivity.class)); - return true; - - // TODO: needed?: - case Id.menu.option.search: - startSearch("", false, null, false); - return true; - - default: - break; - - } - return false; - } - - @Override - protected Dialog onCreateDialog(int id) { - // in case it is a progress dialog - mProgressDialog = new ProgressDialog(this); - mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - mProgressDialog.setCancelable(false); - switch (id) { - case Id.dialog.encrypting: { - mProgressDialog.setMessage(this.getString(R.string.progress_initializing)); - return mProgressDialog; - } - - case Id.dialog.decrypting: { - mProgressDialog.setMessage(this.getString(R.string.progress_initializing)); - return mProgressDialog; - } - - case Id.dialog.saving: { - mProgressDialog.setMessage(this.getString(R.string.progress_saving)); - return mProgressDialog; - } - - case Id.dialog.importing: { - mProgressDialog.setMessage(this.getString(R.string.progress_importing)); - return mProgressDialog; - } - - case Id.dialog.exporting: { - mProgressDialog.setMessage(this.getString(R.string.progress_exporting)); - return mProgressDialog; - } - - case Id.dialog.deleting: { - mProgressDialog.setMessage(this.getString(R.string.progress_initializing)); - return mProgressDialog; - } - - case Id.dialog.querying: { - mProgressDialog.setMessage(this.getString(R.string.progress_querying)); - mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); - mProgressDialog.setCancelable(false); - return mProgressDialog; - } - - case Id.dialog.signing: { - mProgressDialog.setMessage(this.getString(R.string.progress_signing)); - mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); - mProgressDialog.setCancelable(false); - return mProgressDialog; - } - - default: { - break; - } - } - mProgressDialog = null; - - switch (id) { - - case Id.dialog.pass_phrase: { - return AskForSecretKeyPassPhrase.createDialog(this, getSecretKeyId(), this); - } - - case Id.dialog.pass_phrases_do_not_match: { - AlertDialog.Builder alert = new AlertDialog.Builder(this); - - alert.setIcon(android.R.drawable.ic_dialog_alert); - alert.setTitle(R.string.error); - alert.setMessage(R.string.passPhrasesDoNotMatch); - - alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - removeDialog(Id.dialog.pass_phrases_do_not_match); - } - }); - alert.setCancelable(false); - - return alert.create(); - } - - case Id.dialog.no_pass_phrase: { - AlertDialog.Builder alert = new AlertDialog.Builder(this); - - alert.setIcon(android.R.drawable.ic_dialog_alert); - alert.setTitle(R.string.error); - alert.setMessage(R.string.passPhraseMustNotBeEmpty); - - alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - removeDialog(Id.dialog.no_pass_phrase); - } - }); - alert.setCancelable(false); - - return alert.create(); - } - - case Id.dialog.delete_file: { - AlertDialog.Builder alert = new AlertDialog.Builder(this); - - alert.setIcon(android.R.drawable.ic_dialog_alert); - alert.setTitle(R.string.warning); - alert.setMessage(this.getString(R.string.fileDeleteConfirmation, getDeleteFile())); - - alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - removeDialog(Id.dialog.delete_file); - final File file = new File(getDeleteFile()); - showDialog(Id.dialog.deleting); - mDeletingThread = new Thread(new Runnable() { - public void run() { - Bundle data = new Bundle(); - data.putInt(Constants.extras.STATUS, Id.message.delete_done); - try { - Apg.deleteFileSecurely(BaseActivity.this, file, BaseActivity.this); - } catch (FileNotFoundException e) { - data.putString(Apg.EXTRA_ERROR, BaseActivity.this.getString( - R.string.error_fileNotFound, file)); - } catch (IOException e) { - data.putString(Apg.EXTRA_ERROR, BaseActivity.this.getString( - R.string.error_fileDeleteFailed, file)); - } - Message msg = new Message(); - msg.setData(data); - sendMessage(msg); - } - }); - mDeletingThread.start(); - } - }); - alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - removeDialog(Id.dialog.delete_file); - } - }); - alert.setCancelable(true); - - return alert.create(); - } - - default: { - break; - } - } - - return super.onCreateDialog(id); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case Id.request.secret_keys: { - if (resultCode == RESULT_OK) { - Bundle bundle = data.getExtras(); - setSecretKeyId(bundle.getLong(Apg.EXTRA_KEY_ID)); - } else { - setSecretKeyId(Id.key.none); - } - break; - } - - default: { - break; - } - } - - super.onActivityResult(requestCode, resultCode, data); - } - - public void setProgress(int resourceId, int progress, int max) { - setProgress(getString(resourceId), progress, max); - } - - public void setProgress(int progress, int max) { - Message msg = new Message(); - Bundle data = new Bundle(); - data.putInt(Constants.extras.STATUS, Id.message.progress_update); - data.putInt(Constants.extras.PROGRESS, progress); - data.putInt(Constants.extras.PROGRESS_MAX, max); - msg.setData(data); - mHandler.sendMessage(msg); - } - - public void setProgress(String message, int progress, int max) { - Message msg = new Message(); - Bundle data = new Bundle(); - data.putInt(Constants.extras.STATUS, Id.message.progress_update); - data.putString(Constants.extras.MESSAGE, message); - data.putInt(Constants.extras.PROGRESS, progress); - data.putInt(Constants.extras.PROGRESS_MAX, max); - msg.setData(data); - mHandler.sendMessage(msg); - } - - public void handlerCallback(Message msg) { - Bundle data = msg.getData(); - if (data == null) { - return; - } - - int type = data.getInt(Constants.extras.STATUS); - switch (type) { - case Id.message.progress_update: { - String message = data.getString(Constants.extras.MESSAGE); - if (mProgressDialog != null) { - if (message != null) { - mProgressDialog.setMessage(message); - } - mProgressDialog.setMax(data.getInt(Constants.extras.PROGRESS_MAX)); - mProgressDialog.setProgress(data.getInt(Constants.extras.PROGRESS)); - } - break; - } - - case Id.message.delete_done: { - mProgressDialog = null; - deleteDoneCallback(msg); - break; - } - - case Id.message.import_done: // intentionally no break - case Id.message.export_done: // intentionally no break - case Id.message.query_done: // intentionally no break - case Id.message.done: { - mProgressDialog = null; - doneCallback(msg); - break; - } - - default: { - break; - } - } - } - - public void doneCallback(Message msg) { - - } - - public void deleteDoneCallback(Message msg) { - removeDialog(Id.dialog.deleting); - mDeletingThread = null; - - Bundle data = msg.getData(); - String error = data.getString(Apg.EXTRA_ERROR); - String message; - if (error != null) { - message = getString(R.string.errorMessage, error); - } else { - message = getString(R.string.fileDeleteSuccessful); - } - - Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); - } - - public void passPhraseCallback(long keyId, String passPhrase) { - Apg.setCachedPassPhrase(keyId, passPhrase); - } - - public void sendMessage(Message msg) { - mHandler.sendMessage(msg); - } - - public PausableThread getRunningThread() { - return mRunningThread; - } - - public void startThread() { - mRunningThread = new PausableThread(this); - mRunningThread.start(); - } - - public void run() { - - } - - public void setSecretKeyId(long id) { - mSecretKeyId = id; - } - - public long getSecretKeyId() { - return mSecretKeyId; - } - - protected void setDeleteFile(String deleteFile) { - mDeleteFile = deleteFile; - } - - protected String getDeleteFile() { - return mDeleteFile; - } -} diff --git a/org_apg/src/org/apg/ui/DecryptActivity.java b/org_apg/src/org/apg/ui/DecryptActivity.java deleted file mode 100644 index 575a08c8c..000000000 --- a/org_apg/src/org/apg/ui/DecryptActivity.java +++ /dev/null @@ -1,864 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import org.apg.Apg; -import org.apg.Constants; -import org.apg.DataDestination; -import org.apg.DataSource; -import org.apg.FileDialog; -import org.apg.Id; -import org.apg.InputData; -import org.apg.PausableThread; -import org.apg.provider.DataProvider; -import org.apg.util.Compatibility; -import org.spongycastle.jce.provider.BouncyCastleProvider; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.apg.R; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuItem; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.ActivityNotFoundException; -import android.content.DialogInterface; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.Message; -import android.util.Log; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.animation.AnimationUtils; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; -import android.widget.ViewFlipper; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.Security; -import java.security.SignatureException; -import java.util.regex.Matcher; - -public class DecryptActivity extends BaseActivity { - private long mSignatureKeyId = 0; - - private Intent mIntent; - - private boolean mReturnResult = false; - private String mReplyTo = null; - private String mSubject = null; - private boolean mSignedOnly = false; - private boolean mAssumeSymmetricEncryption = false; - - private EditText mMessage = null; - private LinearLayout mSignatureLayout = null; - private ImageView mSignatureStatusImage = null; - private TextView mUserId = null; - private TextView mUserIdRest = null; - - private ViewFlipper mSource = null; - private TextView mSourceLabel = null; - private ImageView mSourcePrevious = null; - private ImageView mSourceNext = null; - - // private Button mDecryptButton = null; - // private Button mReplyButton = null; - - private boolean mDecryptEnabled = true; - private String mDecryptString = ""; - private boolean mReplyEnabled = true; - private String mReplyString = ""; - - private int mDecryptTarget; - - private EditText mFilename = null; - private CheckBox mDeleteAfter = null; - private ImageButton mBrowse = null; - - private String mInputFilename = null; - private String mOutputFilename = null; - - private Uri mContentUri = null; - private byte[] mData = null; - private boolean mReturnBinary = false; - - private DataSource mDataSource = null; - private DataDestination mDataDestination = null; - - private long mUnknownSignatureKeyId = 0; - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - - if (mDecryptEnabled) { - menu.add(1, Id.menu.option.decrypt, 0, mDecryptString).setShowAsAction( - MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - } - if (mReplyEnabled) { - menu.add(1, Id.menu.option.reply, 1, mReplyString).setShowAsAction( - MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - } - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case Id.menu.option.decrypt: { - decryptClicked(); - - return true; - } - case Id.menu.option.reply: { - replyClicked(); - - return true; - } - - default: { - return super.onOptionsItemSelected(item); - } - } - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.decrypt); - - mSource = (ViewFlipper) findViewById(R.id.source); - mSourceLabel = (TextView) findViewById(R.id.sourceLabel); - mSourcePrevious = (ImageView) findViewById(R.id.sourcePrevious); - mSourceNext = (ImageView) findViewById(R.id.sourceNext); - - mSourcePrevious.setClickable(true); - mSourcePrevious.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this, - R.anim.push_right_in)); - mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this, - R.anim.push_right_out)); - mSource.showPrevious(); - updateSource(); - } - }); - - mSourceNext.setClickable(true); - OnClickListener nextSourceClickListener = new OnClickListener() { - public void onClick(View v) { - mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this, - R.anim.push_left_in)); - mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this, - R.anim.push_left_out)); - mSource.showNext(); - updateSource(); - } - }; - mSourceNext.setOnClickListener(nextSourceClickListener); - - mSourceLabel.setClickable(true); - mSourceLabel.setOnClickListener(nextSourceClickListener); - - mMessage = (EditText) findViewById(R.id.message); - mSignatureLayout = (LinearLayout) findViewById(R.id.signature); - mSignatureStatusImage = (ImageView) findViewById(R.id.ic_signature_status); - mUserId = (TextView) findViewById(R.id.mainUserId); - mUserIdRest = (TextView) findViewById(R.id.mainUserIdRest); - - // measure the height of the source_file view and set the message view's min height to that, - // so it fills mSource fully... bit of a hack. - View tmp = findViewById(R.id.sourceFile); - tmp.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - int height = tmp.getMeasuredHeight(); - mMessage.setMinimumHeight(height); - - mFilename = (EditText) findViewById(R.id.filename); - mBrowse = (ImageButton) findViewById(R.id.btn_browse); - mBrowse.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - openFile(); - } - }); - - mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterDecryption); - - // default: message source - mSource.setInAnimation(null); - mSource.setOutAnimation(null); - while (mSource.getCurrentView().getId() != R.id.sourceMessage) { - mSource.showNext(); - } - - mIntent = getIntent(); - if (Intent.ACTION_VIEW.equals(mIntent.getAction())) { - Uri uri = mIntent.getData(); - try { - InputStream attachment = getContentResolver().openInputStream(uri); - ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); - byte bytes[] = new byte[1 << 16]; - int length; - while ((length = attachment.read(bytes)) > 0) { - byteOut.write(bytes, 0, length); - } - byteOut.close(); - String data = new String(byteOut.toByteArray()); - mMessage.setText(data); - } catch (FileNotFoundException e) { - // ignore, then - } catch (IOException e) { - // ignore, then - } - } else if (Apg.Intent.DECRYPT.equals(mIntent.getAction())) { - Log.d(Constants.TAG, "Apg Intent DECRYPT startet"); - Bundle extras = mIntent.getExtras(); - if (extras == null) { - Log.d(Constants.TAG, "extra bundle was null"); - extras = new Bundle(); - } else { - Log.d(Constants.TAG, "got extras"); - } - - mData = extras.getByteArray(Apg.EXTRA_DATA); - String textData = null; - if (mData == null) { - Log.d(Constants.TAG, "EXTRA_DATA was null"); - textData = extras.getString(Apg.EXTRA_TEXT); - } else { - Log.d(Constants.TAG, "Got data from EXTRA_DATA"); - } - if (textData != null) { - Log.d(Constants.TAG, "textData null, matching text ..."); - Matcher matcher = Apg.PGP_MESSAGE.matcher(textData); - if (matcher.matches()) { - Log.d(Constants.TAG, "PGP_MESSAGE matched"); - textData = matcher.group(1); - // replace non breakable spaces - textData = textData.replaceAll("\\xa0", " "); - mMessage.setText(textData); - } else { - matcher = Apg.PGP_SIGNED_MESSAGE.matcher(textData); - if (matcher.matches()) { - Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched"); - textData = matcher.group(1); - // replace non breakable spaces - textData = textData.replaceAll("\\xa0", " "); - mMessage.setText(textData); - - mDecryptString = getString(R.string.btn_verify); - // build new action bar - invalidateOptionsMenu(); - } else { - Log.d(Constants.TAG, "Nothing matched!"); - } - } - } - mReplyTo = extras.getString(Apg.EXTRA_REPLY_TO); - mSubject = extras.getString(Apg.EXTRA_SUBJECT); - } else if (Apg.Intent.DECRYPT_FILE.equals(mIntent.getAction())) { - mInputFilename = mIntent.getDataString(); - if ("file".equals(mIntent.getScheme())) { - mInputFilename = Uri.decode(mInputFilename.substring(7)); - } - mFilename.setText(mInputFilename); - guessOutputFilename(); - mSource.setInAnimation(null); - mSource.setOutAnimation(null); - while (mSource.getCurrentView().getId() != R.id.sourceFile) { - mSource.showNext(); - } - } else if (Apg.Intent.DECRYPT_AND_RETURN.equals(mIntent.getAction())) { - mContentUri = mIntent.getData(); - Bundle extras = mIntent.getExtras(); - if (extras == null) { - extras = new Bundle(); - } - - // disable home button on actionbar because this activity is run from another app - final ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(false); - actionBar.setHomeButtonEnabled(false); - - mReturnBinary = extras.getBoolean(Apg.EXTRA_BINARY, false); - - if (mContentUri == null) { - mData = extras.getByteArray(Apg.EXTRA_DATA); - String data = extras.getString(Apg.EXTRA_TEXT); - if (data != null) { - Matcher matcher = Apg.PGP_MESSAGE.matcher(data); - if (matcher.matches()) { - data = matcher.group(1); - // replace non breakable spaces - data = data.replaceAll("\\xa0", " "); - mMessage.setText(data); - } else { - matcher = Apg.PGP_SIGNED_MESSAGE.matcher(data); - if (matcher.matches()) { - data = matcher.group(1); - // replace non breakable spaces - data = data.replaceAll("\\xa0", " "); - mMessage.setText(data); - mDecryptString = getString(R.string.btn_verify); - - // build new action bar - invalidateOptionsMenu(); - } - } - } - } - mReturnResult = true; - } - - if (mSource.getCurrentView().getId() == R.id.sourceMessage - && mMessage.getText().length() == 0) { - - CharSequence clipboardText = Compatibility.getClipboardText(this); - - String data = ""; - if (clipboardText != null) { - Matcher matcher = Apg.PGP_MESSAGE.matcher(clipboardText); - if (!matcher.matches()) { - matcher = Apg.PGP_SIGNED_MESSAGE.matcher(clipboardText); - } - if (matcher.matches()) { - data = matcher.group(1); - mMessage.setText(data); - Toast.makeText(this, R.string.usingClipboardContent, Toast.LENGTH_SHORT).show(); - } - } - } - - mSignatureLayout.setVisibility(View.GONE); - mSignatureLayout.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - if (mSignatureKeyId == 0) { - return; - } - PGPPublicKeyRing key = Apg.getPublicKeyRing(mSignatureKeyId); - if (key != null) { - Intent intent = new Intent(DecryptActivity.this, KeyServerQueryActivity.class); - intent.setAction(Apg.Intent.LOOK_UP_KEY_ID); - intent.putExtra(Apg.EXTRA_KEY_ID, mSignatureKeyId); - startActivity(intent); - } - } - }); - - mReplyEnabled = false; - - // build new actionbar - invalidateOptionsMenu(); - - if (mReturnResult) { - mSourcePrevious.setClickable(false); - mSourcePrevious.setEnabled(false); - mSourcePrevious.setVisibility(View.INVISIBLE); - - mSourceNext.setClickable(false); - mSourceNext.setEnabled(false); - mSourceNext.setVisibility(View.INVISIBLE); - - mSourceLabel.setClickable(false); - mSourceLabel.setEnabled(false); - } - - updateSource(); - - if (mSource.getCurrentView().getId() == R.id.sourceMessage - && (mMessage.getText().length() > 0 || mData != null || mContentUri != null)) { - decryptClicked(); - } - } - - private void openFile() { - String filename = mFilename.getText().toString(); - - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - - intent.setData(Uri.parse("file://" + filename)); - intent.setType("*/*"); - - try { - startActivityForResult(intent, Id.request.filename); - } catch (ActivityNotFoundException e) { - // No compatible file manager was found. - Toast.makeText(this, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show(); - } - } - - private void guessOutputFilename() { - mInputFilename = mFilename.getText().toString(); - File file = new File(mInputFilename); - String filename = file.getName(); - if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) { - filename = filename.substring(0, filename.length() - 4); - } - mOutputFilename = Constants.path.APP_DIR + "/" + filename; - } - - private void updateSource() { - switch (mSource.getCurrentView().getId()) { - case R.id.sourceFile: { - mSourceLabel.setText(R.string.label_file); - mDecryptString = getString(R.string.btn_decrypt); - - // build new action bar - invalidateOptionsMenu(); - break; - } - - case R.id.sourceMessage: { - mSourceLabel.setText(R.string.label_message); - mDecryptString = getString(R.string.btn_decrypt); - - // build new action bar - invalidateOptionsMenu(); - break; - } - - default: { - break; - } - } - } - - private void decryptClicked() { - if (mSource.getCurrentView().getId() == R.id.sourceFile) { - mDecryptTarget = Id.target.file; - } else { - mDecryptTarget = Id.target.message; - } - initiateDecryption(); - } - - private void initiateDecryption() { - if (mDecryptTarget == Id.target.file) { - String currentFilename = mFilename.getText().toString(); - if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { - guessOutputFilename(); - } - - if (mInputFilename.equals("")) { - Toast.makeText(this, R.string.noFileSelected, Toast.LENGTH_SHORT).show(); - return; - } - - if (mInputFilename.startsWith("file")) { - File file = new File(mInputFilename); - if (!file.exists() || !file.isFile()) { - Toast.makeText( - this, - getString(R.string.errorMessage, getString(R.string.error_fileNotFound)), - Toast.LENGTH_SHORT).show(); - return; - } - } - } - - if (mDecryptTarget == Id.target.message) { - String messageData = mMessage.getText().toString(); - Matcher matcher = Apg.PGP_SIGNED_MESSAGE.matcher(messageData); - if (matcher.matches()) { - mSignedOnly = true; - decryptStart(); - return; - } - } - - // else treat it as an decrypted message/file - mSignedOnly = false; - String error = null; - fillDataSource(); - try { - InputData in = mDataSource.getInputData(this, false); - try { - setSecretKeyId(Apg.getDecryptionKeyId(this, in)); - if (getSecretKeyId() == Id.key.none) { - throw new Apg.GeneralException(getString(R.string.error_noSecretKeyFound)); - } - mAssumeSymmetricEncryption = false; - } catch (Apg.NoAsymmetricEncryptionException e) { - setSecretKeyId(Id.key.symmetric); - in = mDataSource.getInputData(this, false); - if (!Apg.hasSymmetricEncryption(this, in)) { - throw new Apg.GeneralException(getString(R.string.error_noKnownEncryptionFound)); - } - mAssumeSymmetricEncryption = true; - } - - if (getSecretKeyId() == Id.key.symmetric - || Apg.getCachedPassPhrase(getSecretKeyId()) == null) { - showDialog(Id.dialog.pass_phrase); - } else { - if (mDecryptTarget == Id.target.file) { - askForOutputFilename(); - } else { - decryptStart(); - } - } - } catch (FileNotFoundException e) { - error = getString(R.string.error_fileNotFound); - } catch (IOException e) { - error = "" + e; - } catch (Apg.GeneralException e) { - error = "" + e; - } - if (error != null) { - Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT) - .show(); - } - } - - private void replyClicked() { - Intent intent = new Intent(this, EncryptActivity.class); - intent.setAction(Apg.Intent.ENCRYPT); - String data = mMessage.getText().toString(); - data = data.replaceAll("(?m)^", "> "); - data = "\n\n" + data; - intent.putExtra(Apg.EXTRA_TEXT, data); - intent.putExtra(Apg.EXTRA_SUBJECT, "Re: " + mSubject); - intent.putExtra(Apg.EXTRA_SEND_TO, mReplyTo); - intent.putExtra(Apg.EXTRA_SIGNATURE_KEY_ID, getSecretKeyId()); - intent.putExtra(Apg.EXTRA_ENCRYPTION_KEY_IDS, new long[] { mSignatureKeyId }); - startActivity(intent); - } - - private void askForOutputFilename() { - showDialog(Id.dialog.output_filename); - } - - @Override - public void passPhraseCallback(long keyId, String passPhrase) { - super.passPhraseCallback(keyId, passPhrase); - if (mDecryptTarget == Id.target.file) { - askForOutputFilename(); - } else { - decryptStart(); - } - } - - private void decryptStart() { - showDialog(Id.dialog.decrypting); - startThread(); - } - - @Override - public void run() { - String error = null; - Security.addProvider(new BouncyCastleProvider()); - - Bundle data = new Bundle(); - Message msg = new Message(); - fillDataSource(); - fillDataDestination(); - try { - InputData in = mDataSource.getInputData(this, true); - OutputStream out = mDataDestination.getOutputStream(this); - - if (mSignedOnly) { - data = Apg.verifyText(this, in, out, this); - } else { - data = Apg.decrypt(this, in, out, Apg.getCachedPassPhrase(getSecretKeyId()), this, - mAssumeSymmetricEncryption); - } - - out.close(); - - if (mDataDestination.getStreamFilename() != null) { - data.putString(Apg.EXTRA_RESULT_URI, "content://" + DataProvider.AUTHORITY - + "/data/" + mDataDestination.getStreamFilename()); - } else if (mDecryptTarget == Id.target.message) { - if (mReturnBinary) { - data.putByteArray(Apg.EXTRA_DECRYPTED_DATA, - ((ByteArrayOutputStream) out).toByteArray()); - } else { - data.putString(Apg.EXTRA_DECRYPTED_MESSAGE, new String( - ((ByteArrayOutputStream) out).toByteArray())); - } - } - } catch (PGPException e) { - error = "" + e; - } catch (IOException e) { - error = "" + e; - } catch (SignatureException e) { - error = "" + e; - } catch (Apg.GeneralException e) { - error = "" + e; - } - - data.putInt(Constants.extras.STATUS, Id.message.done); - - if (error != null) { - data.putString(Apg.EXTRA_ERROR, error); - } - - msg.setData(data); - sendMessage(msg); - } - - @Override - public void handlerCallback(Message msg) { - Bundle data = msg.getData(); - if (data == null) { - return; - } - - if (data.getInt(Constants.extras.STATUS) == Id.message.unknown_signature_key) { - mUnknownSignatureKeyId = data.getLong(Constants.extras.KEY_ID); - showDialog(Id.dialog.lookup_unknown_key); - return; - } - - super.handlerCallback(msg); - } - - @Override - public void doneCallback(Message msg) { - super.doneCallback(msg); - - Bundle data = msg.getData(); - removeDialog(Id.dialog.decrypting); - mSignatureKeyId = 0; - mSignatureLayout.setVisibility(View.GONE); - mReplyEnabled = false; - - // build new action bar - invalidateOptionsMenu(); - - String error = data.getString(Apg.EXTRA_ERROR); - if (error != null) { - Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT) - .show(); - return; - } - - Toast.makeText(this, R.string.decryptionSuccessful, Toast.LENGTH_SHORT).show(); - if (mReturnResult) { - Intent intent = new Intent(); - intent.putExtras(data); - setResult(RESULT_OK, intent); - finish(); - return; - } - - switch (mDecryptTarget) { - case Id.target.message: { - String decryptedMessage = data.getString(Apg.EXTRA_DECRYPTED_MESSAGE); - mMessage.setText(decryptedMessage); - mMessage.setHorizontallyScrolling(false); - mReplyEnabled = false; - - // build new action bar - invalidateOptionsMenu(); - break; - } - - case Id.target.file: { - if (mDeleteAfter.isChecked()) { - setDeleteFile(mInputFilename); - showDialog(Id.dialog.delete_file); - } - break; - } - - default: { - // shouldn't happen - break; - } - } - - if (data.getBoolean(Apg.EXTRA_SIGNATURE)) { - String userId = data.getString(Apg.EXTRA_SIGNATURE_USER_ID); - mSignatureKeyId = data.getLong(Apg.EXTRA_SIGNATURE_KEY_ID); - mUserIdRest.setText("id: " + Apg.getSmallFingerPrint(mSignatureKeyId)); - if (userId == null) { - userId = getResources().getString(R.string.unknownUserId); - } - String chunks[] = userId.split(" <", 2); - userId = chunks[0]; - if (chunks.length > 1) { - mUserIdRest.setText("<" + chunks[1]); - } - mUserId.setText(userId); - - if (data.getBoolean(Apg.EXTRA_SIGNATURE_SUCCESS)) { - mSignatureStatusImage.setImageResource(R.drawable.overlay_ok); - } else if (data.getBoolean(Apg.EXTRA_SIGNATURE_UNKNOWN)) { - mSignatureStatusImage.setImageResource(R.drawable.overlay_error); - Toast.makeText(this, R.string.unknownSignatureKeyTouchToLookUp, Toast.LENGTH_LONG) - .show(); - } else { - mSignatureStatusImage.setImageResource(R.drawable.overlay_error); - } - mSignatureLayout.setVisibility(View.VISIBLE); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case Id.request.filename: { - if (resultCode == RESULT_OK && data != null) { - String filename = data.getDataString(); - if (filename != null) { - // Get rid of URI prefix: - if (filename.startsWith("file://")) { - filename = filename.substring(7); - } - // replace %20 and so on - filename = Uri.decode(filename); - - mFilename.setText(filename); - } - } - return; - } - - case Id.request.output_filename: { - if (resultCode == RESULT_OK && data != null) { - String filename = data.getDataString(); - if (filename != null) { - // Get rid of URI prefix: - if (filename.startsWith("file://")) { - filename = filename.substring(7); - } - // replace %20 and so on - filename = Uri.decode(filename); - - FileDialog.setFilename(filename); - } - } - return; - } - - case Id.request.look_up_key_id: { - PausableThread thread = getRunningThread(); - if (thread != null && thread.isPaused()) { - thread.unpause(); - } - return; - } - - default: { - break; - } - } - - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - protected Dialog onCreateDialog(int id) { - switch (id) { - case Id.dialog.output_filename: { - return FileDialog.build(this, getString(R.string.title_decryptToFile), - getString(R.string.specifyFileToDecryptTo), mOutputFilename, - new FileDialog.OnClickListener() { - public void onOkClick(String filename, boolean checked) { - removeDialog(Id.dialog.output_filename); - mOutputFilename = filename; - decryptStart(); - } - - public void onCancelClick() { - removeDialog(Id.dialog.output_filename); - } - }, getString(R.string.filemanager_titleSave), - getString(R.string.filemanager_btnSave), null, Id.request.output_filename); - } - - case Id.dialog.lookup_unknown_key: { - AlertDialog.Builder alert = new AlertDialog.Builder(this); - - alert.setIcon(android.R.drawable.ic_dialog_alert); - alert.setTitle(R.string.title_unknownSignatureKey); - alert.setMessage(getString(R.string.lookupUnknownKey, - Apg.getSmallFingerPrint(mUnknownSignatureKeyId))); - - alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - removeDialog(Id.dialog.lookup_unknown_key); - Intent intent = new Intent(DecryptActivity.this, KeyServerQueryActivity.class); - intent.setAction(Apg.Intent.LOOK_UP_KEY_ID); - intent.putExtra(Apg.EXTRA_KEY_ID, mUnknownSignatureKeyId); - startActivityForResult(intent, Id.request.look_up_key_id); - } - }); - alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - removeDialog(Id.dialog.lookup_unknown_key); - PausableThread thread = getRunningThread(); - if (thread != null && thread.isPaused()) { - thread.unpause(); - } - } - }); - alert.setCancelable(true); - - return alert.create(); - } - - default: { - break; - } - } - - return super.onCreateDialog(id); - } - - protected void fillDataSource() { - mDataSource = new DataSource(); - if (mContentUri != null) { - mDataSource.setUri(mContentUri); - } else if (mDecryptTarget == Id.target.file) { - mDataSource.setUri(mInputFilename); - } else { - if (mData != null) { - mDataSource.setData(mData); - } else { - mDataSource.setText(mMessage.getText().toString()); - } - } - } - - protected void fillDataDestination() { - mDataDestination = new DataDestination(); - if (mContentUri != null) { - mDataDestination.setMode(Id.mode.stream); - } else if (mDecryptTarget == Id.target.file) { - mDataDestination.setFilename(mOutputFilename); - mDataDestination.setMode(Id.mode.file); - } else { - mDataDestination.setMode(Id.mode.byte_array); - } - } -} diff --git a/org_apg/src/org/apg/ui/EditKeyActivity.java b/org_apg/src/org/apg/ui/EditKeyActivity.java deleted file mode 100644 index 27a94fd1f..000000000 --- a/org_apg/src/org/apg/ui/EditKeyActivity.java +++ /dev/null @@ -1,414 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import org.apg.Apg; -import org.apg.Constants; -import org.apg.Id; -import org.apg.provider.Database; -import org.apg.ui.widget.KeyEditor; -import org.apg.ui.widget.SectionView; -import org.apg.util.IterableIterator; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPSecretKey; -import org.spongycastle.openpgp.PGPSecretKeyRing; -import org.apg.R; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuItem; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.os.Message; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.Toast; -import android.widget.CompoundButton.OnCheckedChangeListener; - -import java.io.IOException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidParameterException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SignatureException; -import java.util.Vector; - -public class EditKeyActivity extends BaseActivity { - private Intent mIntent = null; - private ActionBar mActionBar; - - private PGPSecretKeyRing mKeyRing = null; - - private SectionView mUserIds; - private SectionView mKeys; - - private String mCurrentPassPhrase = null; - private String mNewPassPhrase = null; - - private Button mChangePassPhrase; - - private CheckBox mNoPassphrase; - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(1, Id.menu.option.cancel, 0, R.string.btn_doNotSave).setShowAsAction( - MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - menu.add(1, Id.menu.option.save, 1, R.string.btn_save).setShowAsAction( - MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case android.R.id.home: - startActivity(new Intent(this, SecretKeyListActivity.class)); - return true; - - case Id.menu.option.save: - saveClicked(); - return true; - - case Id.menu.option.cancel: - finish(); - return true; - - default: - break; - - } - return false; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.edit_key); - - mActionBar = getSupportActionBar(); - - // find views - mChangePassPhrase = (Button) findViewById(R.id.edit_key_btn_change_pass_phrase); - mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase); - - Vector userIds = new Vector(); - Vector keys = new Vector(); - Vector keysUsages = new Vector(); - - // Catch Intents opened from other apps - mIntent = getIntent(); - Bundle extras = mIntent.getExtras(); - if (Apg.Intent.CREATE_KEY.equals(mIntent.getAction())) { - - mActionBar.setTitle(R.string.title_createKey); - - mCurrentPassPhrase = ""; - - if (extras != null) { - - // disable home button on actionbar because this activity is run from another app - mActionBar.setDisplayShowTitleEnabled(true); - mActionBar.setDisplayHomeAsUpEnabled(false); - mActionBar.setHomeButtonEnabled(false); - - // if userId is given, prefill the fields - if (extras.containsKey(Apg.EXTRA_USER_IDS)) { - Log.d(Constants.TAG, "UserIds are given!"); - userIds.add(extras.getString(Apg.EXTRA_USER_IDS)); - } - - // if no passphrase is given - if (extras.containsKey(Apg.EXTRA_NO_PASSPHRASE)) { - boolean noPassphrase = extras.getBoolean(Apg.EXTRA_NO_PASSPHRASE); - if (noPassphrase) { - // check "no passphrase" checkbox and remove button - mNoPassphrase.setChecked(true); - mChangePassPhrase.setVisibility(View.GONE); - } - } - - // generate key - if (extras.containsKey(Apg.EXTRA_GENERATE_DEFAULT_KEYS)) { - boolean generateDefaultKeys = extras - .getBoolean(Apg.EXTRA_GENERATE_DEFAULT_KEYS); - if (generateDefaultKeys) { - - // generate a RSA 2048 key for encryption and signing! - try { - PGPSecretKey masterKey = Apg.createKey(this, Id.choice.algorithm.rsa, - 2048, mCurrentPassPhrase, null); - - // add new masterKey to keys array, which is then added to view - keys.add(masterKey); - keysUsages.add(Id.choice.usage.sign_only); - - PGPSecretKey subKey = Apg.createKey(this, Id.choice.algorithm.rsa, - 2048, mCurrentPassPhrase, masterKey); - - keys.add(subKey); - keysUsages.add(Id.choice.usage.encrypt_only); - - // define usage of this key - } catch (Exception e) { - Log.e(Constants.TAG, "Creating initial key failed: +" + e); - } - } - - } - } - } else if (Apg.Intent.EDIT_KEY.equals(mIntent.getAction())) { - - mActionBar.setTitle(R.string.title_editKey); - - mCurrentPassPhrase = Apg.getEditPassPhrase(); - if (mCurrentPassPhrase == null) { - mCurrentPassPhrase = ""; - } - - if (mCurrentPassPhrase.equals("")) { - // check "no passphrase" checkbox and remove button - mNoPassphrase.setChecked(true); - mChangePassPhrase.setVisibility(View.GONE); - } - - if (extras != null) { - - if (extras.containsKey(Apg.EXTRA_KEY_ID)) { - long keyId = mIntent.getExtras().getLong(Apg.EXTRA_KEY_ID); - - if (keyId != 0) { - PGPSecretKey masterKey = null; - mKeyRing = Apg.getSecretKeyRing(keyId); - if (mKeyRing != null) { - masterKey = Apg.getMasterKey(mKeyRing); - for (PGPSecretKey key : new IterableIterator( - mKeyRing.getSecretKeys())) { - keys.add(key); - keysUsages.add(-1); // get usage when view is created - } - } - if (masterKey != null) { - for (String userId : new IterableIterator( - masterKey.getUserIDs())) { - userIds.add(userId); - } - } - } - } - } - } - - mChangePassPhrase.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - showDialog(Id.dialog.new_pass_phrase); - } - }); - - // disable passphrase when no passphrase checkobox is checked! - mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() { - - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) { - // remove passphrase - mNewPassPhrase = null; - - mChangePassPhrase.setVisibility(View.GONE); - } else { - mChangePassPhrase.setVisibility(View.VISIBLE); - } - - } - }); - - // Build layout based on given userIds and keys - LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - LinearLayout container = (LinearLayout) findViewById(R.id.edit_key_container); - mUserIds = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false); - mUserIds.setType(Id.type.user_id); - mUserIds.setUserIds(userIds); - container.addView(mUserIds); - mKeys = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false); - mKeys.setType(Id.type.key); - mKeys.setKeys(keys, keysUsages); - container.addView(mKeys); - - updatePassPhraseButtonText(); - } - - private long getMasterKeyId() { - if (mKeys.getEditors().getChildCount() == 0) { - return 0; - } - return ((KeyEditor) mKeys.getEditors().getChildAt(0)).getValue().getKeyID(); - } - - public boolean isPassphraseSet() { - if (mNoPassphrase.isChecked()) { - return true; - } else if ((!mCurrentPassPhrase.equals("")) - || (mNewPassPhrase != null && !mNewPassPhrase.equals(""))) { - return true; - } else { - return false; - } - } - - @Override - protected Dialog onCreateDialog(int id) { - switch (id) { - case Id.dialog.new_pass_phrase: { - AlertDialog.Builder alert = new AlertDialog.Builder(this); - - if (isPassphraseSet()) { - alert.setTitle(R.string.title_changePassPhrase); - } else { - alert.setTitle(R.string.title_setPassPhrase); - } - alert.setMessage(R.string.enterPassPhraseTwice); - - LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View view = inflater.inflate(R.layout.passphrase, null); - final EditText input1 = (EditText) view.findViewById(R.id.passphrase_passphrase); - final EditText input2 = (EditText) view.findViewById(R.id.passphrase_passphrase_again); - - alert.setView(view); - - alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - removeDialog(Id.dialog.new_pass_phrase); - - String passPhrase1 = "" + input1.getText(); - String passPhrase2 = "" + input2.getText(); - if (!passPhrase1.equals(passPhrase2)) { - showDialog(Id.dialog.pass_phrases_do_not_match); - return; - } - - if (passPhrase1.equals("")) { - showDialog(Id.dialog.no_pass_phrase); - return; - } - - mNewPassPhrase = passPhrase1; - updatePassPhraseButtonText(); - } - }); - - alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - removeDialog(Id.dialog.new_pass_phrase); - } - }); - - return alert.create(); - } - - default: { - return super.onCreateDialog(id); - } - } - } - - private void saveClicked() { - if (!isPassphraseSet()) { - Toast.makeText(this, R.string.setAPassPhrase, Toast.LENGTH_SHORT).show(); - return; - } - showDialog(Id.dialog.saving); - startThread(); - } - - @Override - public void run() { - String error = null; - Bundle data = new Bundle(); - Message msg = new Message(); - - try { - String oldPassPhrase = mCurrentPassPhrase; - String newPassPhrase = mNewPassPhrase; - if (newPassPhrase == null) { - newPassPhrase = oldPassPhrase; - } - Apg.buildSecretKey(this, mUserIds, mKeys, oldPassPhrase, newPassPhrase, this); - Apg.setCachedPassPhrase(getMasterKeyId(), newPassPhrase); - } catch (NoSuchProviderException e) { - error = "" + e; - } catch (NoSuchAlgorithmException e) { - error = "" + e; - } catch (PGPException e) { - error = "" + e; - } catch (SignatureException e) { - error = "" + e; - } catch (Apg.GeneralException e) { - error = "" + e; - } catch (Database.GeneralException e) { - error = "" + e; - } catch (IOException e) { - error = "" + e; - } - - data.putInt(Constants.extras.STATUS, Id.message.done); - - if (error != null) { - data.putString(Apg.EXTRA_ERROR, error); - } - - msg.setData(data); - sendMessage(msg); - } - - @Override - public void doneCallback(Message msg) { - super.doneCallback(msg); - - Bundle data = msg.getData(); - removeDialog(Id.dialog.saving); - - String error = data.getString(Apg.EXTRA_ERROR); - if (error != null) { - Toast.makeText(EditKeyActivity.this, getString(R.string.errorMessage, error), - Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(EditKeyActivity.this, R.string.keySaved, Toast.LENGTH_SHORT).show(); - setResult(RESULT_OK); - finish(); - } - } - - private void updatePassPhraseButtonText() { - mChangePassPhrase.setText(isPassphraseSet() ? R.string.btn_changePassPhrase - : R.string.btn_setPassPhrase); - } -} diff --git a/org_apg/src/org/apg/ui/EncryptActivity.java b/org_apg/src/org/apg/ui/EncryptActivity.java deleted file mode 100644 index 68b2c893b..000000000 --- a/org_apg/src/org/apg/ui/EncryptActivity.java +++ /dev/null @@ -1,1042 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import org.apg.Apg; -import org.apg.Constants; -import org.apg.DataDestination; -import org.apg.DataSource; -import org.apg.FileDialog; -import org.apg.Id; -import org.apg.InputData; -import org.apg.provider.DataProvider; -import org.apg.util.Choice; -import org.apg.util.Compatibility; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSecretKey; -import org.spongycastle.openpgp.PGPSecretKeyRing; -import org.apg.R; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuItem; - -import android.app.Dialog; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.Message; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.animation.AnimationUtils; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.Spinner; -import android.widget.TextView; -import android.widget.Toast; -import android.widget.ViewFlipper; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SignatureException; -import java.util.Vector; - -public class EncryptActivity extends BaseActivity { - private Intent mIntent = null; - private String mSubject = null; - private String mSendTo = null; - - private long mEncryptionKeyIds[] = null; - - private boolean mReturnResult = false; - private EditText mMessage = null; - private Button mSelectKeysButton = null; - - private boolean mEncryptEnabled = false; - private String mEncryptString = ""; - private boolean mEncryptToClipboardEnabled = false; - private String mEncryptToClipboardString = ""; - - private CheckBox mSign = null; - private TextView mMainUserId = null; - private TextView mMainUserIdRest = null; - - private ViewFlipper mSource = null; - private TextView mSourceLabel = null; - private ImageView mSourcePrevious = null; - private ImageView mSourceNext = null; - - private ViewFlipper mMode = null; - private TextView mModeLabel = null; - private ImageView mModePrevious = null; - private ImageView mModeNext = null; - - private int mEncryptTarget; - - private EditText mPassPhrase = null; - private EditText mPassPhraseAgain = null; - private CheckBox mAsciiArmour = null; - private Spinner mFileCompression = null; - - private EditText mFilename = null; - private CheckBox mDeleteAfter = null; - private ImageButton mBrowse = null; - - private String mInputFilename = null; - private String mOutputFilename = null; - - private boolean mAsciiArmourDemand = false; - private boolean mOverrideAsciiArmour = false; - private Uri mContentUri = null; - private byte[] mData = null; - - private DataSource mDataSource = null; - private DataDestination mDataDestination = null; - - private boolean mGenerateSignature = false; - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - - if (mEncryptToClipboardEnabled) { - menu.add(1, Id.menu.option.encrypt_to_clipboard, 0, mEncryptToClipboardString) - .setShowAsAction( - MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - } - if (mEncryptEnabled) { - menu.add(1, Id.menu.option.encrypt, 1, mEncryptString).setShowAsAction( - MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - } - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case Id.menu.option.encrypt_to_clipboard: { - encryptToClipboardClicked(); - - return true; - } - case Id.menu.option.encrypt: { - encryptClicked(); - - return true; - } - - default: { - return super.onOptionsItemSelected(item); - } - } - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.encrypt); - - mGenerateSignature = false; - - mSource = (ViewFlipper) findViewById(R.id.source); - mSourceLabel = (TextView) findViewById(R.id.sourceLabel); - mSourcePrevious = (ImageView) findViewById(R.id.sourcePrevious); - mSourceNext = (ImageView) findViewById(R.id.sourceNext); - - mSourcePrevious.setClickable(true); - mSourcePrevious.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - mSource.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_right_in)); - mSource.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_right_out)); - mSource.showPrevious(); - updateSource(); - } - }); - - mSourceNext.setClickable(true); - OnClickListener nextSourceClickListener = new OnClickListener() { - public void onClick(View v) { - mSource.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_left_in)); - mSource.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_left_out)); - mSource.showNext(); - updateSource(); - } - }; - mSourceNext.setOnClickListener(nextSourceClickListener); - - mSourceLabel.setClickable(true); - mSourceLabel.setOnClickListener(nextSourceClickListener); - - mMode = (ViewFlipper) findViewById(R.id.mode); - mModeLabel = (TextView) findViewById(R.id.modeLabel); - mModePrevious = (ImageView) findViewById(R.id.modePrevious); - mModeNext = (ImageView) findViewById(R.id.modeNext); - - mModePrevious.setClickable(true); - mModePrevious.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - mMode.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_right_in)); - mMode.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_right_out)); - mMode.showPrevious(); - updateMode(); - } - }); - - OnClickListener nextModeClickListener = new OnClickListener() { - public void onClick(View v) { - mMode.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_left_in)); - mMode.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, - R.anim.push_left_out)); - mMode.showNext(); - updateMode(); - } - }; - mModeNext.setOnClickListener(nextModeClickListener); - - mModeLabel.setClickable(true); - mModeLabel.setOnClickListener(nextModeClickListener); - - mMessage = (EditText) findViewById(R.id.message); - mSelectKeysButton = (Button) findViewById(R.id.btn_selectEncryptKeys); - mSign = (CheckBox) findViewById(R.id.sign); - mMainUserId = (TextView) findViewById(R.id.mainUserId); - mMainUserIdRest = (TextView) findViewById(R.id.mainUserIdRest); - - mPassPhrase = (EditText) findViewById(R.id.passPhrase); - mPassPhraseAgain = (EditText) findViewById(R.id.passPhraseAgain); - - // measure the height of the source_file view and set the message view's min height to that, - // so it fills mSource fully... bit of a hack. - View tmp = findViewById(R.id.sourceFile); - tmp.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - int height = tmp.getMeasuredHeight(); - mMessage.setMinimumHeight(height); - - mFilename = (EditText) findViewById(R.id.filename); - mBrowse = (ImageButton) findViewById(R.id.btn_browse); - mBrowse.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - openFile(); - } - }); - - mFileCompression = (Spinner) findViewById(R.id.fileCompression); - Choice[] choices = new Choice[] { - new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " (" - + getString(R.string.fast) + ")"), - new Choice(Id.choice.compression.zip, "ZIP (" + getString(R.string.fast) + ")"), - new Choice(Id.choice.compression.zlib, "ZLIB (" + getString(R.string.fast) + ")"), - new Choice(Id.choice.compression.bzip2, "BZIP2 (" + getString(R.string.very_slow) - + ")"), }; - ArrayAdapter adapter = new ArrayAdapter(this, - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mFileCompression.setAdapter(adapter); - - int defaultFileCompression = mPreferences.getDefaultFileCompression(); - for (int i = 0; i < choices.length; ++i) { - if (choices[i].getId() == defaultFileCompression) { - mFileCompression.setSelection(i); - break; - } - } - - mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterEncryption); - - mAsciiArmour = (CheckBox) findViewById(R.id.asciiArmour); - mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour()); - mAsciiArmour.setOnClickListener(new OnClickListener() { - public void onClick(View view) { - guessOutputFilename(); - } - }); - - mSelectKeysButton.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - selectPublicKeys(); - } - }); - - mSign.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - CheckBox checkBox = (CheckBox) v; - if (checkBox.isChecked()) { - selectSecretKey(); - } else { - setSecretKeyId(Id.key.none); - updateView(); - } - } - }); - - mIntent = getIntent(); - if (Apg.Intent.ENCRYPT.equals(mIntent.getAction()) - || Apg.Intent.ENCRYPT_FILE.equals(mIntent.getAction()) - || Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction()) - || Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) { - mContentUri = mIntent.getData(); - Bundle extras = mIntent.getExtras(); - if (extras == null) { - extras = new Bundle(); - } - - // disable home button on actionbar because this activity is run from another app - if (Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction())) { - final ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(false); - actionBar.setHomeButtonEnabled(false); - } - - if (Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction()) - || Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) { - mReturnResult = true; - } - - if (Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) { - mGenerateSignature = true; - mOverrideAsciiArmour = true; - mAsciiArmourDemand = false; - } - - if (extras.containsKey(Apg.EXTRA_ASCII_ARMOUR)) { - mAsciiArmourDemand = extras.getBoolean(Apg.EXTRA_ASCII_ARMOUR, true); - mOverrideAsciiArmour = true; - mAsciiArmour.setChecked(mAsciiArmourDemand); - } - - mData = extras.getByteArray(Apg.EXTRA_DATA); - String textData = null; - if (mData == null) { - textData = extras.getString(Apg.EXTRA_TEXT); - } - mSendTo = extras.getString(Apg.EXTRA_SEND_TO); - mSubject = extras.getString(Apg.EXTRA_SUBJECT); - long signatureKeyId = extras.getLong(Apg.EXTRA_SIGNATURE_KEY_ID); - long encryptionKeyIds[] = extras.getLongArray(Apg.EXTRA_ENCRYPTION_KEY_IDS); - if (signatureKeyId != 0) { - PGPSecretKeyRing keyRing = Apg.getSecretKeyRing(signatureKeyId); - PGPSecretKey masterKey = null; - if (keyRing != null) { - masterKey = Apg.getMasterKey(keyRing); - if (masterKey != null) { - Vector signKeys = Apg.getUsableSigningKeys(keyRing); - if (signKeys.size() > 0) { - setSecretKeyId(masterKey.getKeyID()); - } - } - } - } - - if (encryptionKeyIds != null) { - Vector goodIds = new Vector(); - for (int i = 0; i < encryptionKeyIds.length; ++i) { - PGPPublicKeyRing keyRing = Apg.getPublicKeyRing(encryptionKeyIds[i]); - PGPPublicKey masterKey = null; - if (keyRing == null) { - continue; - } - masterKey = Apg.getMasterKey(keyRing); - if (masterKey == null) { - continue; - } - Vector encryptKeys = Apg.getUsableEncryptKeys(keyRing); - if (encryptKeys.size() == 0) { - continue; - } - goodIds.add(masterKey.getKeyID()); - } - if (goodIds.size() > 0) { - mEncryptionKeyIds = new long[goodIds.size()]; - for (int i = 0; i < goodIds.size(); ++i) { - mEncryptionKeyIds[i] = goodIds.get(i); - } - } - } - - if (Apg.Intent.ENCRYPT.equals(mIntent.getAction()) - || Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction()) - || Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) { - if (textData != null) { - mMessage.setText(textData); - } - mSource.setInAnimation(null); - mSource.setOutAnimation(null); - while (mSource.getCurrentView().getId() != R.id.sourceMessage) { - mSource.showNext(); - } - } else if (Apg.Intent.ENCRYPT_FILE.equals(mIntent.getAction())) { - if ("file".equals(mIntent.getScheme())) { - mInputFilename = Uri.decode(mIntent.getDataString().replace("file://", "")); - mFilename.setText(mInputFilename); - guessOutputFilename(); - } - mSource.setInAnimation(null); - mSource.setOutAnimation(null); - while (mSource.getCurrentView().getId() != R.id.sourceFile) { - mSource.showNext(); - } - } - } - - updateView(); - updateSource(); - updateMode(); - - if (mReturnResult) { - mSourcePrevious.setClickable(false); - mSourcePrevious.setEnabled(false); - mSourcePrevious.setVisibility(View.INVISIBLE); - - mSourceNext.setClickable(false); - mSourceNext.setEnabled(false); - mSourceNext.setVisibility(View.INVISIBLE); - - mSourceLabel.setClickable(false); - mSourceLabel.setEnabled(false); - } - - updateButtons(); - - if (mReturnResult - && (mMessage.getText().length() > 0 || mData != null || mContentUri != null) - && ((mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) || getSecretKeyId() != 0)) { - encryptClicked(); - } - } - - private void openFile() { - String filename = mFilename.getText().toString(); - - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - - intent.setData(Uri.parse("file://" + filename)); - intent.setType("*/*"); - - try { - startActivityForResult(intent, Id.request.filename); - } catch (ActivityNotFoundException e) { - // No compatible file manager was found. - Toast.makeText(this, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show(); - } - } - - private void guessOutputFilename() { - mInputFilename = mFilename.getText().toString(); - File file = new File(mInputFilename); - String ending = (mAsciiArmour.isChecked() ? ".asc" : ".gpg"); - mOutputFilename = Constants.path.APP_DIR + "/" + file.getName() + ending; - } - - private void updateSource() { - switch (mSource.getCurrentView().getId()) { - case R.id.sourceFile: { - mSourceLabel.setText(R.string.label_file); - break; - } - - case R.id.sourceMessage: { - mSourceLabel.setText(R.string.label_message); - break; - } - - default: { - break; - } - } - updateButtons(); - } - - private void updateButtons() { - switch (mSource.getCurrentView().getId()) { - case R.id.sourceFile: { - mEncryptEnabled = true; - mEncryptToClipboardEnabled = false; - - break; - } - - case R.id.sourceMessage: { - mSourceLabel.setText(R.string.label_message); - if (mReturnResult) { - mEncryptToClipboardEnabled = false; - } else { - mEncryptToClipboardEnabled = true; - } - if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { - if (mReturnResult) { - mEncryptString = getString(R.string.btn_encrypt); - } else { - mEncryptString = getString(R.string.btn_encryptAndEmail); - } - mEncryptEnabled = true; - mEncryptToClipboardString = getString(R.string.btn_encryptToClipboard); - mEncryptToClipboardEnabled = true; - } else { - if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) { - if (getSecretKeyId() == 0) { - if (mReturnResult) { - mEncryptString = getString(R.string.btn_encrypt); - } else { - mEncryptString = getString(R.string.btn_encryptAndEmail); - } - mEncryptEnabled = false; - mEncryptToClipboardString = getString(R.string.btn_encryptToClipboard); - mEncryptToClipboardEnabled = false; - } else { - if (mReturnResult) { - mEncryptString = getString(R.string.btn_sign); - } else { - mEncryptString = getString(R.string.btn_signAndEmail); - } - - mEncryptEnabled = true; - mEncryptToClipboardString = getString(R.string.btn_signToClipboard); - mEncryptToClipboardEnabled = true; - } - } else { - if (mReturnResult) { - mEncryptString = getString(R.string.btn_encrypt); - } else { - mEncryptString = getString(R.string.btn_encryptAndEmail); - } - - mEncryptEnabled = true; - mEncryptToClipboardString = getString(R.string.btn_encryptToClipboard); - mEncryptToClipboardEnabled = true; - } - } - break; - } - - default: { - break; - } - } - - // build new action bar - invalidateOptionsMenu(); - } - - private void updateMode() { - switch (mMode.getCurrentView().getId()) { - case R.id.modeAsymmetric: { - mModeLabel.setText(R.string.label_asymmetric); - break; - } - - case R.id.modeSymmetric: { - mModeLabel.setText(R.string.label_symmetric); - break; - } - - default: { - break; - } - } - updateButtons(); - } - - private void encryptToClipboardClicked() { - mEncryptTarget = Id.target.clipboard; - initiateEncryption(); - } - - private void encryptClicked() { - if (mSource.getCurrentView().getId() == R.id.sourceFile) { - mEncryptTarget = Id.target.file; - } else { - mEncryptTarget = Id.target.email; - } - initiateEncryption(); - } - - private void initiateEncryption() { - if (mEncryptTarget == Id.target.file) { - String currentFilename = mFilename.getText().toString(); - if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { - guessOutputFilename(); - } - - if (mInputFilename.equals("")) { - Toast.makeText(this, R.string.noFileSelected, Toast.LENGTH_SHORT).show(); - return; - } - - if (!mInputFilename.startsWith("content")) { - File file = new File(mInputFilename); - if (!file.exists() || !file.isFile()) { - Toast.makeText( - this, - getString(R.string.errorMessage, getString(R.string.error_fileNotFound)), - Toast.LENGTH_SHORT).show(); - return; - } - } - } - - // symmetric encryption - if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { - boolean gotPassPhrase = false; - String passPhrase = mPassPhrase.getText().toString(); - String passPhraseAgain = mPassPhraseAgain.getText().toString(); - if (!passPhrase.equals(passPhraseAgain)) { - Toast.makeText(this, R.string.passPhrasesDoNotMatch, Toast.LENGTH_SHORT).show(); - return; - } - - gotPassPhrase = (passPhrase.length() != 0); - if (!gotPassPhrase) { - Toast.makeText(this, R.string.passPhraseMustNotBeEmpty, Toast.LENGTH_SHORT).show(); - return; - } - } else { - boolean encryptIt = (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0); - // for now require at least one form of encryption for files - if (!encryptIt && mEncryptTarget == Id.target.file) { - Toast.makeText(this, R.string.selectEncryptionKey, Toast.LENGTH_SHORT).show(); - return; - } - - if (!encryptIt && getSecretKeyId() == 0) { - Toast.makeText(this, R.string.selectEncryptionOrSignatureKey, Toast.LENGTH_SHORT) - .show(); - return; - } - - if (getSecretKeyId() != 0 && Apg.getCachedPassPhrase(getSecretKeyId()) == null) { - showDialog(Id.dialog.pass_phrase); - return; - } - } - - if (mEncryptTarget == Id.target.file) { - askForOutputFilename(); - } else { - encryptStart(); - } - } - - private void askForOutputFilename() { - showDialog(Id.dialog.output_filename); - } - - @Override - public void passPhraseCallback(long keyId, String passPhrase) { - super.passPhraseCallback(keyId, passPhrase); - if (mEncryptTarget == Id.target.file) { - askForOutputFilename(); - } else { - encryptStart(); - } - } - - private void encryptStart() { - showDialog(Id.dialog.encrypting); - startThread(); - } - - @Override - public void run() { - String error = null; - Bundle data = new Bundle(); - Message msg = new Message(); - - try { - InputData in; - OutputStream out; - boolean useAsciiArmour = true; - long encryptionKeyIds[] = null; - long signatureKeyId = 0; - int compressionId = 0; - boolean signOnly = false; - - String passPhrase = null; - if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { - passPhrase = mPassPhrase.getText().toString(); - if (passPhrase.length() == 0) { - passPhrase = null; - } - } else { - encryptionKeyIds = mEncryptionKeyIds; - signatureKeyId = getSecretKeyId(); - signOnly = (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0); - } - - fillDataSource(signOnly && !mReturnResult); - fillDataDestination(); - - // streams - in = mDataSource.getInputData(this, true); - out = mDataDestination.getOutputStream(this); - - if (mEncryptTarget == Id.target.file) { - useAsciiArmour = mAsciiArmour.isChecked(); - compressionId = ((Choice) mFileCompression.getSelectedItem()).getId(); - } else { - useAsciiArmour = true; - compressionId = mPreferences.getDefaultMessageCompression(); - } - - if (mOverrideAsciiArmour) { - useAsciiArmour = mAsciiArmourDemand; - } - - if (mGenerateSignature) { - Apg.generateSignature(this, in, out, useAsciiArmour, mDataSource.isBinary(), - getSecretKeyId(), Apg.getCachedPassPhrase(getSecretKeyId()), - mPreferences.getDefaultHashAlgorithm(), - mPreferences.getForceV3Signatures(), this); - } else if (signOnly) { - Apg.signText(this, in, out, getSecretKeyId(), - Apg.getCachedPassPhrase(getSecretKeyId()), - mPreferences.getDefaultHashAlgorithm(), - mPreferences.getForceV3Signatures(), this); - } else { - Apg.encrypt(this, in, out, useAsciiArmour, encryptionKeyIds, signatureKeyId, - Apg.getCachedPassPhrase(signatureKeyId), this, - mPreferences.getDefaultEncryptionAlgorithm(), - mPreferences.getDefaultHashAlgorithm(), compressionId, - mPreferences.getForceV3Signatures(), passPhrase); - } - - out.close(); - if (mEncryptTarget != Id.target.file) { - - if (out instanceof ByteArrayOutputStream) { - if (useAsciiArmour) { - String extraData = new String(((ByteArrayOutputStream) out).toByteArray()); - if (mGenerateSignature) { - data.putString(Apg.EXTRA_SIGNATURE_TEXT, extraData); - } else { - data.putString(Apg.EXTRA_ENCRYPTED_MESSAGE, extraData); - } - } else { - byte extraData[] = ((ByteArrayOutputStream) out).toByteArray(); - if (mGenerateSignature) { - data.putByteArray(Apg.EXTRA_SIGNATURE_DATA, extraData); - } else { - data.putByteArray(Apg.EXTRA_ENCRYPTED_DATA, extraData); - } - } - } else if (out instanceof FileOutputStream) { - String fileName = mDataDestination.getStreamFilename(); - String uri = "content://" + DataProvider.AUTHORITY + "/data/" + fileName; - data.putString(Apg.EXTRA_RESULT_URI, uri); - } else { - throw new Apg.GeneralException("No output-data found."); - } - } - } catch (IOException e) { - error = "" + e; - } catch (PGPException e) { - error = "" + e; - } catch (NoSuchProviderException e) { - error = "" + e; - } catch (NoSuchAlgorithmException e) { - error = "" + e; - } catch (SignatureException e) { - error = "" + e; - } catch (Apg.GeneralException e) { - error = "" + e; - } - - data.putInt(Constants.extras.STATUS, Id.message.done); - - if (error != null) { - data.putString(Apg.EXTRA_ERROR, error); - } - - msg.setData(data); - sendMessage(msg); - } - - private void updateView() { - if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) { - mSelectKeysButton.setText(R.string.noKeysSelected); - } else if (mEncryptionKeyIds.length == 1) { - mSelectKeysButton.setText(R.string.oneKeySelected); - } else { - mSelectKeysButton.setText("" + mEncryptionKeyIds.length + " " - + getResources().getString(R.string.nKeysSelected)); - } - - if (getSecretKeyId() == 0) { - mSign.setChecked(false); - mMainUserId.setText(""); - mMainUserIdRest.setText(""); - } else { - String uid = getResources().getString(R.string.unknownUserId); - String uidExtra = ""; - PGPSecretKeyRing keyRing = Apg.getSecretKeyRing(getSecretKeyId()); - if (keyRing != null) { - PGPSecretKey key = Apg.getMasterKey(keyRing); - if (key != null) { - String userId = Apg.getMainUserIdSafe(this, key); - String chunks[] = userId.split(" <", 2); - uid = chunks[0]; - if (chunks.length > 1) { - uidExtra = "<" + chunks[1]; - } - } - } - mMainUserId.setText(uid); - mMainUserIdRest.setText(uidExtra); - mSign.setChecked(true); - } - - updateButtons(); - } - - private void selectPublicKeys() { - Intent intent = new Intent(this, SelectPublicKeyListActivity.class); - Vector keyIds = new Vector(); - if (getSecretKeyId() != 0) { - keyIds.add(getSecretKeyId()); - } - if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) { - for (int i = 0; i < mEncryptionKeyIds.length; ++i) { - keyIds.add(mEncryptionKeyIds[i]); - } - } - long[] initialKeyIds = null; - if (keyIds.size() > 0) { - initialKeyIds = new long[keyIds.size()]; - for (int i = 0; i < keyIds.size(); ++i) { - initialKeyIds[i] = keyIds.get(i); - } - } - intent.putExtra(Apg.EXTRA_SELECTION, initialKeyIds); - startActivityForResult(intent, Id.request.public_keys); - } - - private void selectSecretKey() { - Intent intent = new Intent(this, SelectSecretKeyListActivity.class); - startActivityForResult(intent, Id.request.secret_keys); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case Id.request.filename: { - if (resultCode == RESULT_OK && data != null) { - String filename = data.getDataString(); - if (filename != null) { - // Get rid of URI prefix: - if (filename.startsWith("file://")) { - filename = filename.substring(7); - } - // replace %20 and so on - filename = Uri.decode(filename); - - mFilename.setText(filename); - } - } - return; - } - - case Id.request.output_filename: { - if (resultCode == RESULT_OK && data != null) { - String filename = data.getDataString(); - if (filename != null) { - // Get rid of URI prefix: - if (filename.startsWith("file://")) { - filename = filename.substring(7); - } - // replace %20 and so on - filename = Uri.decode(filename); - - FileDialog.setFilename(filename); - } - } - return; - } - - case Id.request.secret_keys: { - if (resultCode == RESULT_OK) { - super.onActivityResult(requestCode, resultCode, data); - } - updateView(); - break; - } - - case Id.request.public_keys: { - if (resultCode == RESULT_OK) { - Bundle bundle = data.getExtras(); - mEncryptionKeyIds = bundle.getLongArray(Apg.EXTRA_SELECTION); - } - updateView(); - break; - } - - default: { - break; - } - } - - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - public void doneCallback(Message msg) { - super.doneCallback(msg); - - removeDialog(Id.dialog.encrypting); - - Bundle data = msg.getData(); - String error = data.getString(Apg.EXTRA_ERROR); - if (error != null) { - Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT) - .show(); - return; - } - switch (mEncryptTarget) { - case Id.target.clipboard: { - String message = data.getString(Apg.EXTRA_ENCRYPTED_MESSAGE); - Compatibility.copyToClipboard(this, message); - Toast.makeText(this, R.string.encryptionToClipboardSuccessful, Toast.LENGTH_SHORT) - .show(); - break; - } - - case Id.target.email: { - if (mReturnResult) { - Intent intent = new Intent(); - intent.putExtras(data); - setResult(RESULT_OK, intent); - finish(); - return; - } - - String message = data.getString(Apg.EXTRA_ENCRYPTED_MESSAGE); - Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND); - emailIntent.setType("text/plain; charset=utf-8"); - emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, message); - if (mSubject != null) { - emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, mSubject); - } - if (mSendTo != null) { - emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[] { mSendTo }); - } - EncryptActivity.this.startActivity(Intent.createChooser(emailIntent, - getString(R.string.title_sendEmail))); - break; - } - - case Id.target.file: { - Toast.makeText(this, R.string.encryptionSuccessful, Toast.LENGTH_SHORT).show(); - if (mDeleteAfter.isChecked()) { - setDeleteFile(mInputFilename); - showDialog(Id.dialog.delete_file); - } - break; - } - - default: { - // shouldn't happen - break; - } - } - } - - @Override - protected Dialog onCreateDialog(int id) { - switch (id) { - case Id.dialog.output_filename: { - return FileDialog.build(this, getString(R.string.title_encryptToFile), - getString(R.string.specifyFileToEncryptTo), mOutputFilename, - new FileDialog.OnClickListener() { - public void onOkClick(String filename, boolean checked) { - removeDialog(Id.dialog.output_filename); - mOutputFilename = filename; - encryptStart(); - } - - public void onCancelClick() { - removeDialog(Id.dialog.output_filename); - } - }, getString(R.string.filemanager_titleSave), - getString(R.string.filemanager_btnSave), null, Id.request.output_filename); - } - - default: { - break; - } - } - - return super.onCreateDialog(id); - } - - protected void fillDataSource(boolean fixContent) { - mDataSource = new DataSource(); - if (mContentUri != null) { - mDataSource.setUri(mContentUri); - } else if (mEncryptTarget == Id.target.file) { - mDataSource.setUri(mInputFilename); - } else { - if (mData != null) { - mDataSource.setData(mData); - } else { - String message = mMessage.getText().toString(); - if (fixContent) { - // fix the message a bit, trailing spaces and newlines break stuff, - // because GMail sends as HTML and such things fuck up the - // signature, - // TODO: things like "<" and ">" also fuck up the signature - message = message.replaceAll(" +\n", "\n"); - message = message.replaceAll("\n\n+", "\n\n"); - message = message.replaceFirst("^\n+", ""); - // make sure there'll be exactly one newline at the end - message = message.replaceFirst("\n*$", "\n"); - } - mDataSource.setText(message); - } - } - } - - protected void fillDataDestination() { - mDataDestination = new DataDestination(); - if (mContentUri != null) { - mDataDestination.setMode(Id.mode.stream); - } else if (mEncryptTarget == Id.target.file) { - mDataDestination.setFilename(mOutputFilename); - mDataDestination.setMode(Id.mode.file); - } else { - mDataDestination.setMode(Id.mode.byte_array); - } - } -} diff --git a/org_apg/src/org/apg/ui/GeneralActivity.java b/org_apg/src/org/apg/ui/GeneralActivity.java deleted file mode 100644 index e165dd140..000000000 --- a/org_apg/src/org/apg/ui/GeneralActivity.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import java.io.ByteArrayInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.Vector; - -import org.apg.Apg; -import org.apg.Id; -import org.apg.util.Choice; -import org.apg.R; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.ListView; -import android.widget.Toast; - -public class GeneralActivity extends BaseActivity { - private Intent mIntent; - private ArrayAdapter mAdapter; - private ListView mList; - private Button mCancelButton; - private String mDataString; - private Uri mDataUri; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.general); - - mIntent = getIntent(); - - InputStream inStream = null; - { - String data = mIntent.getStringExtra(Intent.EXTRA_TEXT); - if (data != null) { - mDataString = data; - inStream = new ByteArrayInputStream(data.getBytes()); - } - } - - if (inStream == null) { - Uri data = mIntent.getData(); - if (data != null) { - mDataUri = data; - try { - inStream = getContentResolver().openInputStream(data); - } catch (FileNotFoundException e) { - // didn't work - Toast.makeText(this, "failed to open stream", Toast.LENGTH_SHORT).show(); - } - } - } - - if (inStream == null) { - Toast.makeText(this, "no data found", Toast.LENGTH_SHORT).show(); - finish(); - return; - } - - int contentType = Id.content.unknown; - try { - contentType = Apg.getStreamContent(this, inStream); - inStream.close(); - } catch (IOException e) { - // just means that there's no PGP data in there - } - - mList = (ListView) findViewById(R.id.options); - Vector choices = new Vector(); - - if (contentType == Id.content.keys) { - choices.add(new Choice(Id.choice.action.import_public, - getString(R.string.action_importPublic))); - choices.add(new Choice(Id.choice.action.import_secret, - getString(R.string.action_importSecret))); - } - - if (contentType == Id.content.encrypted_data) { - choices.add(new Choice(Id.choice.action.decrypt, getString(R.string.action_decrypt))); - } - - if (contentType == Id.content.unknown) { - choices.add(new Choice(Id.choice.action.encrypt, getString(R.string.action_encrypt))); - } - - mAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, choices); - mList.setAdapter(mAdapter); - - mList.setOnItemClickListener(new OnItemClickListener() { - public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) { - clicked(mAdapter.getItem(arg2).getId()); - } - }); - - mCancelButton = (Button) findViewById(R.id.btn_cancel); - mCancelButton.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - GeneralActivity.this.finish(); - } - }); - - if (choices.size() == 1) { - clicked(choices.get(0).getId()); - } - } - - private void clicked(int id) { - Intent intent = new Intent(); - switch (id) { - case Id.choice.action.encrypt: { - intent.setClass(this, EncryptActivity.class); - if (mDataString != null) { - intent.setAction(Apg.Intent.ENCRYPT); - intent.putExtra(Apg.EXTRA_TEXT, mDataString); - } else if (mDataUri != null) { - intent.setAction(Apg.Intent.ENCRYPT_FILE); - intent.setDataAndType(mDataUri, mIntent.getType()); - } - - break; - } - - case Id.choice.action.decrypt: { - intent.setClass(this, DecryptActivity.class); - if (mDataString != null) { - intent.setAction(Apg.Intent.DECRYPT); - intent.putExtra(Apg.EXTRA_TEXT, mDataString); - } else if (mDataUri != null) { - intent.setAction(Apg.Intent.DECRYPT_FILE); - intent.setDataAndType(mDataUri, mIntent.getType()); - } - - break; - } - - case Id.choice.action.import_public: { - intent.setClass(this, PublicKeyListActivity.class); - intent.setAction(Apg.Intent.IMPORT); - if (mDataString != null) { - intent.putExtra(Apg.EXTRA_TEXT, mDataString); - } else if (mDataUri != null) { - intent.setDataAndType(mDataUri, mIntent.getType()); - } - break; - } - - case Id.choice.action.import_secret: { - intent.setClass(this, SecretKeyListActivity.class); - intent.setAction(Apg.Intent.IMPORT); - if (mDataString != null) { - intent.putExtra(Apg.EXTRA_TEXT, mDataString); - } else if (mDataUri != null) { - intent.setDataAndType(mDataUri, mIntent.getType()); - } - break; - } - - default: { - // shouldn't happen - return; - } - } - - startActivity(intent); - finish(); - } -} diff --git a/org_apg/src/org/apg/ui/HelpActivity.java b/org_apg/src/org/apg/ui/HelpActivity.java deleted file mode 100644 index 9a4dc7f76..000000000 --- a/org_apg/src/org/apg/ui/HelpActivity.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import org.apg.R; -import org.apg.util.Utils; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.SherlockActivity; -import com.actionbarsherlock.view.MenuItem; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.text.Html; -import android.text.method.LinkMovementMethod; -import android.widget.TextView; - -public class HelpActivity extends SherlockActivity { - Activity mActivity; - TextView mHelpText; - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case android.R.id.home: - startActivity(new Intent(this, MainActivity.class)); - return true; - - default: - break; - - } - return false; - } - - /** - * Instantiate View for this Activity - */ - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.help_activity); - - final ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(true); - - mActivity = this; - - mHelpText = (TextView) findViewById(R.id.help_text); - - // load html from html file from /res/raw - String helpText = Utils.readContentFromResource(mActivity, R.raw.help); - - // set text from resources with html markup - mHelpText.setText(Html.fromHtml(helpText)); - // make links work - mHelpText.setMovementMethod(LinkMovementMethod.getInstance()); - - } - -} diff --git a/org_apg/src/org/apg/ui/ImportFromQRCodeActivity.java b/org_apg/src/org/apg/ui/ImportFromQRCodeActivity.java deleted file mode 100644 index 27e2be621..000000000 --- a/org_apg/src/org/apg/ui/ImportFromQRCodeActivity.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import java.io.ByteArrayInputStream; -import java.io.IOException; - -import org.apg.Apg; -import org.apg.Constants; -import org.apg.HkpKeyServer; -import org.apg.Id; -import org.apg.KeyServer.QueryException; -import org.spongycastle.openpgp.PGPKeyRing; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.apg.R; - -import android.content.Intent; -import android.os.Bundle; -import android.os.Message; -import android.util.Log; -import android.widget.Toast; - -import com.google.zxing.integration.android.IntentIntegrator; -import com.google.zxing.integration.android.IntentResult; - -public class ImportFromQRCodeActivity extends BaseActivity { - private static final String TAG = "ImportFromQRCodeActivity"; - - private final Bundle status = new Bundle(); - private final Message msg = new Message(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - new IntentIntegrator(this).initiateScan(); - } - - private void importAndSign(final long keyId, final String expectedFingerprint) { - if (expectedFingerprint != null && expectedFingerprint.length() > 0) { - - Thread t = new Thread() { - @Override - public void run() { - try { - // TODO: display some sort of spinner here while the user waits - - HkpKeyServer server = new HkpKeyServer(mPreferences.getKeyServers()[0]); // TODO: there should be only 1 - String encodedKey = server.get(keyId); - - PGPKeyRing keyring = Apg.decodeKeyRing(new ByteArrayInputStream(encodedKey.getBytes())); - if (keyring != null && keyring instanceof PGPPublicKeyRing) { - PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring; - - // make sure the fingerprints match before we cache this thing - String actualFingerprint = Apg.convertToHex(publicKeyRing.getPublicKey().getFingerprint()); - if (expectedFingerprint.equals(actualFingerprint)) { - // store the signed key in our local cache - int retval = Apg.storeKeyRingInCache(publicKeyRing); - if (retval != Id.return_value.ok && retval != Id.return_value.updated) { - status.putString(Apg.EXTRA_ERROR, "Failed to store signed key in local cache"); - } else { - Intent intent = new Intent(ImportFromQRCodeActivity.this, SignKeyActivity.class); - intent.putExtra(Apg.EXTRA_KEY_ID, keyId); - startActivityForResult(intent, Id.request.sign_key); - } - } else { - status.putString(Apg.EXTRA_ERROR, "Scanned fingerprint does NOT match the fingerprint of the received key. You shouldnt trust this key."); - } - } - } catch (QueryException e) { - Log.e(TAG, "Failed to query KeyServer", e); - status.putString(Apg.EXTRA_ERROR, "Failed to query KeyServer"); - status.putInt(Constants.extras.STATUS, Id.message.done); - } catch (IOException e) { - Log.e(TAG, "Failed to query KeyServer", e); - status.putString(Apg.EXTRA_ERROR, "Failed to query KeyServer"); - status.putInt(Constants.extras.STATUS, Id.message.done); - } - } - }; - - t.setName("KeyExchange Download Thread"); - t.setDaemon(true); - t.start(); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case IntentIntegrator.REQUEST_CODE: { - boolean debug = true; // TODO: remove this!!! - IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data); - if (debug || (scanResult != null && scanResult.getFormatName() != null)) { - String[] bits = debug ? new String[] { "5993515643896327656", "0816 F68A 6816 68FB 01BF 2CA5 532D 3EB9 1E2F EDE8" } : scanResult.getContents().split(","); - if (bits.length != 2) { - return; // dont know how to handle this. Not a valid code - } - - long keyId = Long.parseLong(bits[0]); - String expectedFingerprint = bits[1]; - - importAndSign(keyId, expectedFingerprint); - - break; - } - } - - case Id.request.sign_key: { - // signals the end of processing. Signature was either applied, or it wasnt - status.putInt(Constants.extras.STATUS, Id.message.done); - - msg.setData(status); - sendMessage(msg); - - break; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - } - } - } - - @Override - public void doneCallback(Message msg) { - super.doneCallback(msg); - - Bundle data = msg.getData(); - String error = data.getString(Apg.EXTRA_ERROR); - if (error != null) { - Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT).show(); - return; - } - - Toast.makeText(this, R.string.keySignSuccess, Toast.LENGTH_SHORT).show(); // TODO - finish(); - } -} diff --git a/org_apg/src/org/apg/ui/KeyListActivity.java b/org_apg/src/org/apg/ui/KeyListActivity.java deleted file mode 100644 index 74942a797..000000000 --- a/org_apg/src/org/apg/ui/KeyListActivity.java +++ /dev/null @@ -1,769 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import org.apg.Apg; -import org.apg.Constants; -import org.apg.FileDialog; -import org.apg.Id; -import org.apg.InputData; -import org.apg.provider.KeyRings; -import org.apg.provider.Keys; -import org.apg.provider.UserIds; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSecretKeyRing; -import org.apg.R; - -import com.actionbarsherlock.view.MenuItem; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.SearchManager; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; -import android.net.Uri; -import android.os.Bundle; -import android.os.Message; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.BaseExpandableListAdapter; -import android.widget.Button; -import android.widget.ExpandableListView; -import android.widget.ExpandableListView.ExpandableListContextMenuInfo; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Vector; - -public class KeyListActivity extends BaseActivity { - protected ExpandableListView mList; - protected KeyListAdapter mListAdapter; - protected View mFilterLayout; - protected Button mClearFilterButton; - protected TextView mFilterInfo; - - protected int mSelectedItem = -1; - protected int mTask = 0; - - protected String mImportFilename = Constants.path.APP_DIR + "/"; - protected String mExportFilename = Constants.path.APP_DIR + "/"; - - protected String mImportData; - protected boolean mDeleteAfterImport = false; - - protected int mKeyType = Id.type.public_key; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.key_list); - - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); - - mList = (ExpandableListView) findViewById(R.id.list); - registerForContextMenu(mList); - - mFilterLayout = findViewById(R.id.layout_filter); - mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo); - mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear); - - mClearFilterButton.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - handleIntent(new Intent()); - } - }); - - handleIntent(getIntent()); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - handleIntent(intent); - } - - protected void handleIntent(Intent intent) { - String searchString = null; - if (Intent.ACTION_SEARCH.equals(intent.getAction())) { - searchString = intent.getStringExtra(SearchManager.QUERY); - if (searchString != null && searchString.trim().length() == 0) { - searchString = null; - } - } - - if (searchString == null) { - mFilterLayout.setVisibility(View.GONE); - } else { - mFilterLayout.setVisibility(View.VISIBLE); - mFilterInfo.setText(getString(R.string.filterInfo, searchString)); - } - - if (mListAdapter != null) { - mListAdapter.cleanup(); - } - mListAdapter = new KeyListAdapter(this, searchString); - mList.setAdapter(mListAdapter); - - if (Apg.Intent.IMPORT.equals(intent.getAction())) { - if ("file".equals(intent.getScheme()) && intent.getDataString() != null) { - mImportFilename = Uri.decode(intent.getDataString().replace("file://", "")); - } else { - mImportData = intent.getStringExtra(Apg.EXTRA_TEXT); - } - importKeys(); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case Id.menu.option.import_keys: { - showDialog(Id.dialog.import_keys); - return true; - } - - case Id.menu.option.export_keys: { - showDialog(Id.dialog.export_keys); - return true; - } - - default: { - return super.onOptionsItemSelected(item); - } - } - } - - @Override - public boolean onContextItemSelected(android.view.MenuItem menuItem) { - ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo(); - int type = ExpandableListView.getPackedPositionType(info.packedPosition); - int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); - - if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) { - return super.onContextItemSelected(menuItem); - } - - switch (menuItem.getItemId()) { - case Id.menu.export: { - mSelectedItem = groupPosition; - showDialog(Id.dialog.export_key); - return true; - } - - case Id.menu.delete: { - mSelectedItem = groupPosition; - showDialog(Id.dialog.delete_key); - return true; - } - - default: { - return super.onContextItemSelected(menuItem); - } - } - } - - @Override - protected Dialog onCreateDialog(int id) { - boolean singleKeyExport = false; - - switch (id) { - case Id.dialog.delete_key: { - final int keyRingId = mListAdapter.getKeyRingId(mSelectedItem); - mSelectedItem = -1; - // TODO: better way to do this? - String userId = ""; - Object keyRing = Apg.getKeyRing(keyRingId); - if (keyRing != null) { - if (keyRing instanceof PGPPublicKeyRing) { - userId = Apg.getMainUserIdSafe(this, - Apg.getMasterKey((PGPPublicKeyRing) keyRing)); - } else { - userId = Apg.getMainUserIdSafe(this, - Apg.getMasterKey((PGPSecretKeyRing) keyRing)); - } - } - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.warning); - builder.setMessage(getString( - mKeyType == Id.type.public_key ? R.string.keyDeletionConfirmation - : R.string.secretKeyDeletionConfirmation, userId)); - builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - deleteKey(keyRingId); - removeDialog(Id.dialog.delete_key); - } - }); - builder.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - removeDialog(Id.dialog.delete_key); - } - }); - return builder.create(); - } - - case Id.dialog.import_keys: { - return FileDialog.build(this, getString(R.string.title_importKeys), - getString(R.string.specifyFileToImportFrom), mImportFilename, - new FileDialog.OnClickListener() { - public void onOkClick(String filename, boolean checked) { - removeDialog(Id.dialog.import_keys); - mDeleteAfterImport = checked; - mImportFilename = filename; - importKeys(); - } - - public void onCancelClick() { - removeDialog(Id.dialog.import_keys); - } - }, getString(R.string.filemanager_titleOpen), - getString(R.string.filemanager_btnOpen), - getString(R.string.label_deleteAfterImport), Id.request.filename); - } - - case Id.dialog.export_key: { - singleKeyExport = true; - // break intentionally omitted, to use the Id.dialog.export_keys dialog - } - - case Id.dialog.export_keys: { - String title = (singleKeyExport ? getString(R.string.title_exportKey) - : getString(R.string.title_exportKeys)); - - final int thisDialogId = (singleKeyExport ? Id.dialog.export_key - : Id.dialog.export_keys); - - return FileDialog.build(this, title, - getString(mKeyType == Id.type.public_key ? R.string.specifyFileToExportTo - : R.string.specifyFileToExportSecretKeysTo), mExportFilename, - new FileDialog.OnClickListener() { - public void onOkClick(String filename, boolean checked) { - removeDialog(thisDialogId); - mExportFilename = filename; - exportKeys(); - } - - public void onCancelClick() { - removeDialog(thisDialogId); - } - }, getString(R.string.filemanager_titleSave), - getString(R.string.filemanager_btnSave), null, Id.request.filename); - } - - default: { - return super.onCreateDialog(id); - } - } - } - - public void importKeys() { - showDialog(Id.dialog.importing); - mTask = Id.task.import_keys; - startThread(); - } - - public void exportKeys() { - showDialog(Id.dialog.exporting); - mTask = Id.task.export_keys; - startThread(); - } - - @Override - public void run() { - String error = null; - Bundle data = new Bundle(); - Message msg = new Message(); - - try { - InputStream importInputStream = null; - OutputStream exportOutputStream = null; - long size = 0; - if (mTask == Id.task.import_keys) { - if (mImportData != null) { - byte[] bytes = mImportData.getBytes(); - size = bytes.length; - importInputStream = new ByteArrayInputStream(bytes); - } else { - File file = new File(mImportFilename); - size = file.length(); - importInputStream = new FileInputStream(file); - } - } else { - exportOutputStream = new FileOutputStream(mExportFilename); - } - - if (mTask == Id.task.import_keys) { - data = Apg.importKeyRings(this, mKeyType, new InputData(importInputStream, size), - this); - } else { - Vector keyRingIds = new Vector(); - if (mSelectedItem == -1) { - keyRingIds = Apg - .getKeyRingIds(mKeyType == Id.type.public_key ? Id.database.type_public - : Id.database.type_secret); - } else { - int keyRingId = mListAdapter.getKeyRingId(mSelectedItem); - keyRingIds.add(keyRingId); - mSelectedItem = -1; - } - data = Apg.exportKeyRings(this, keyRingIds, exportOutputStream, this); - } - } catch (FileNotFoundException e) { - error = getString(R.string.error_fileNotFound); - } catch (IOException e) { - error = "" + e; - } catch (PGPException e) { - error = "" + e; - } catch (Apg.GeneralException e) { - error = "" + e; - } - - mImportData = null; - - if (mTask == Id.task.import_keys) { - data.putInt(Constants.extras.STATUS, Id.message.import_done); - } else { - data.putInt(Constants.extras.STATUS, Id.message.export_done); - } - - if (error != null) { - data.putString(Apg.EXTRA_ERROR, error); - } - - msg.setData(data); - sendMessage(msg); - } - - protected void deleteKey(int keyRingId) { - Apg.deleteKey(keyRingId); - refreshList(); - } - - protected void refreshList() { - mListAdapter.rebuild(true); - mListAdapter.notifyDataSetChanged(); - } - - @Override - public void doneCallback(Message msg) { - super.doneCallback(msg); - - Bundle data = msg.getData(); - if (data != null) { - int type = data.getInt(Constants.extras.STATUS); - switch (type) { - case Id.message.import_done: { - removeDialog(Id.dialog.importing); - - String error = data.getString(Apg.EXTRA_ERROR); - if (error != null) { - Toast.makeText(KeyListActivity.this, getString(R.string.errorMessage, error), - Toast.LENGTH_SHORT).show(); - } else { - int added = data.getInt("added"); - int updated = data.getInt("updated"); - int bad = data.getInt("bad"); - String message; - if (added > 0 && updated > 0) { - message = getString(R.string.keysAddedAndUpdated, added, updated); - } else if (added > 0) { - message = getString(R.string.keysAdded, added); - } else if (updated > 0) { - message = getString(R.string.keysUpdated, updated); - } else { - message = getString(R.string.noKeysAddedOrUpdated); - } - Toast.makeText(KeyListActivity.this, message, Toast.LENGTH_SHORT).show(); - if (bad > 0) { - AlertDialog.Builder alert = new AlertDialog.Builder(this); - - alert.setIcon(android.R.drawable.ic_dialog_alert); - alert.setTitle(R.string.warning); - alert.setMessage(this.getString(R.string.badKeysEncountered, bad)); - - alert.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - alert.setCancelable(true); - alert.create().show(); - } else if (mDeleteAfterImport) { - // everything went well, so now delete, if that was turned on - setDeleteFile(mImportFilename); - showDialog(Id.dialog.delete_file); - } - } - refreshList(); - break; - } - - case Id.message.export_done: { - removeDialog(Id.dialog.exporting); - - String error = data.getString(Apg.EXTRA_ERROR); - if (error != null) { - Toast.makeText(KeyListActivity.this, getString(R.string.errorMessage, error), - Toast.LENGTH_SHORT).show(); - } else { - int exported = data.getInt("exported"); - String message; - if (exported == 1) { - message = getString(R.string.keyExported); - } else if (exported > 0) { - message = getString(R.string.keysExported, exported); - } else { - message = getString(R.string.noKeysExported); - } - Toast.makeText(KeyListActivity.this, message, Toast.LENGTH_SHORT).show(); - } - break; - } - - default: { - break; - } - } - } - } - - protected class KeyListAdapter extends BaseExpandableListAdapter { - private LayoutInflater mInflater; - private Vector> mChildren; - private SQLiteDatabase mDatabase; - private Cursor mCursor; - private String mSearchString; - - private class KeyChild { - public static final int KEY = 0; - public static final int USER_ID = 1; - public static final int FINGER_PRINT = 2; - - public int type; - public String userId; - public long keyId; - public boolean isMasterKey; - public int algorithm; - public int keySize; - public boolean canSign; - public boolean canEncrypt; - public String fingerPrint; - - public KeyChild(long keyId, boolean isMasterKey, int algorithm, int keySize, - boolean canSign, boolean canEncrypt) { - this.type = KEY; - this.keyId = keyId; - this.isMasterKey = isMasterKey; - this.algorithm = algorithm; - this.keySize = keySize; - this.canSign = canSign; - this.canEncrypt = canEncrypt; - } - - public KeyChild(String userId) { - type = USER_ID; - this.userId = userId; - } - - public KeyChild(String fingerPrint, boolean isFingerPrint) { - type = FINGER_PRINT; - this.fingerPrint = fingerPrint; - } - } - - public KeyListAdapter(Context context, String searchString) { - mSearchString = searchString; - - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mDatabase = Apg.getDatabase().db(); - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + "(" - + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "." - + Keys.KEY_RING_ID + " AND " + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY - + " = '1'" + ") " + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + "(" - + Keys.TABLE_NAME + "." + Keys._ID + " = " + UserIds.TABLE_NAME + "." - + UserIds.KEY_ID + " AND " + UserIds.TABLE_NAME + "." + UserIds.RANK - + " = '0')"); - - if (searchString != null && searchString.trim().length() > 0) { - String[] chunks = searchString.trim().split(" +"); - qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " + UserIds.TABLE_NAME - + " AS tmp WHERE " + "tmp." + UserIds.KEY_ID + " = " + Keys.TABLE_NAME - + "." + Keys._ID); - for (int i = 0; i < chunks.length; ++i) { - qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE "); - qb.appendWhereEscapeString("%" + chunks[i] + "%"); - } - qb.appendWhere(")"); - } - - mCursor = qb.query(mDatabase, new String[] { KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0 - KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1 - UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2 - }, KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", new String[] { "" - + (mKeyType == Id.type.public_key ? Id.database.type_public - : Id.database.type_secret) }, null, null, UserIds.TABLE_NAME + "." - + UserIds.USER_ID + " ASC"); - - // content provider way for reference, might have to go back to it sometime: - /* - * Uri contentUri = null; if (mKeyType == Id.type.secret_key) { contentUri = - * Apg.CONTENT_URI_SECRET_KEY_RINGS; } else { contentUri = - * Apg.CONTENT_URI_PUBLIC_KEY_RINGS; } mCursor = getContentResolver().query( contentUri, - * new String[] { DataProvider._ID, // 0 DataProvider.MASTER_KEY_ID, // 1 - * DataProvider.USER_ID, // 2 }, null, null, null); - */ - - startManagingCursor(mCursor); - rebuild(false); - } - - public void cleanup() { - if (mCursor != null) { - stopManagingCursor(mCursor); - mCursor.close(); - } - } - - public void rebuild(boolean requery) { - if (requery) { - mCursor.requery(); - } - mChildren = new Vector>(); - for (int i = 0; i < mCursor.getCount(); ++i) { - mChildren.add(null); - } - } - - protected Vector getChildrenOfGroup(int groupPosition) { - Vector children = mChildren.get(groupPosition); - if (children != null) { - return children; - } - - mCursor.moveToPosition(groupPosition); - children = new Vector(); - Cursor c = mDatabase.query(Keys.TABLE_NAME, new String[] { Keys._ID, // 0 - Keys.KEY_ID, // 1 - Keys.IS_MASTER_KEY, // 2 - Keys.ALGORITHM, // 3 - Keys.KEY_SIZE, // 4 - Keys.CAN_SIGN, // 5 - Keys.CAN_ENCRYPT, // 6 - }, Keys.KEY_RING_ID + " = ?", new String[] { mCursor.getString(0) }, null, null, - Keys.RANK + " ASC"); - - int masterKeyId = -1; - long fingerPrintId = -1; - for (int i = 0; i < c.getCount(); ++i) { - c.moveToPosition(i); - children.add(new KeyChild(c.getLong(1), c.getInt(2) == 1, c.getInt(3), c.getInt(4), - c.getInt(5) == 1, c.getInt(6) == 1)); - if (i == 0) { - masterKeyId = c.getInt(0); - fingerPrintId = c.getLong(1); - } - } - c.close(); - - if (masterKeyId != -1) { - children.insertElementAt(new KeyChild(Apg.getFingerPrint(fingerPrintId), true), 0); - c = mDatabase.query(UserIds.TABLE_NAME, new String[] { UserIds.USER_ID, // 0 - }, UserIds.KEY_ID + " = ? AND " + UserIds.RANK + " > 0", new String[] { "" - + masterKeyId }, null, null, UserIds.RANK + " ASC"); - - for (int i = 0; i < c.getCount(); ++i) { - c.moveToPosition(i); - children.add(new KeyChild(c.getString(0))); - } - c.close(); - } - - mChildren.set(groupPosition, children); - return children; - } - - public boolean hasStableIds() { - return true; - } - - public boolean isChildSelectable(int groupPosition, int childPosition) { - return true; - } - - public int getGroupCount() { - return mCursor.getCount(); - } - - public Object getChild(int groupPosition, int childPosition) { - return null; - } - - public long getChildId(int groupPosition, int childPosition) { - return childPosition; - } - - public int getChildrenCount(int groupPosition) { - return getChildrenOfGroup(groupPosition).size(); - } - - public Object getGroup(int position) { - return position; - } - - public long getGroupId(int position) { - mCursor.moveToPosition(position); - return mCursor.getLong(1); // MASTER_KEY_ID - } - - public int getKeyRingId(int position) { - mCursor.moveToPosition(position); - return mCursor.getInt(0); // _ID - } - - public View getGroupView(int groupPosition, boolean isExpanded, View convertView, - ViewGroup parent) { - mCursor.moveToPosition(groupPosition); - - View view = mInflater.inflate(R.layout.key_list_group_item, null); - view.setBackgroundResource(android.R.drawable.list_selector_background); - - TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); - mainUserId.setText(""); - TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - mainUserIdRest.setText(""); - - String userId = mCursor.getString(2); // USER_ID - if (userId != null) { - String chunks[] = userId.split(" <", 2); - userId = chunks[0]; - if (chunks.length > 1) { - mainUserIdRest.setText("<" + chunks[1]); - } - mainUserId.setText(userId); - } - - if (mainUserId.getText().length() == 0) { - mainUserId.setText(R.string.unknownUserId); - } - - if (mainUserIdRest.getText().length() == 0) { - mainUserIdRest.setVisibility(View.GONE); - } - return view; - } - - public View getChildView(int groupPosition, int childPosition, boolean isLastChild, - View convertView, ViewGroup parent) { - mCursor.moveToPosition(groupPosition); - - Vector children = getChildrenOfGroup(groupPosition); - - KeyChild child = children.get(childPosition); - View view = null; - switch (child.type) { - case KeyChild.KEY: { - if (child.isMasterKey) { - view = mInflater.inflate(R.layout.key_list_child_item_master_key, null); - } else { - view = mInflater.inflate(R.layout.key_list_child_item_sub_key, null); - } - - TextView keyId = (TextView) view.findViewById(R.id.keyId); - String keyIdStr = Apg.getSmallFingerPrint(child.keyId); - keyId.setText(keyIdStr); - TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails); - String algorithmStr = Apg.getAlgorithmInfo(child.algorithm, child.keySize); - keyDetails.setText("(" + algorithmStr + ")"); - - ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey); - if (!child.canEncrypt) { - encryptIcon.setVisibility(View.GONE); - } - - ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey); - if (!child.canSign) { - signIcon.setVisibility(View.GONE); - } - break; - } - - case KeyChild.USER_ID: { - view = mInflater.inflate(R.layout.key_list_child_item_user_id, null); - TextView userId = (TextView) view.findViewById(R.id.userId); - userId.setText(child.userId); - break; - } - - case KeyChild.FINGER_PRINT: { - view = mInflater.inflate(R.layout.key_list_child_item_user_id, null); - TextView userId = (TextView) view.findViewById(R.id.userId); - userId.setText(getString(R.string.fingerprint) + ":\n" - + child.fingerPrint.replace(" ", "\n")); - break; - } - } - return view; - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case Id.request.filename: { - if (resultCode == RESULT_OK && data != null) { - String filename = data.getDataString(); - if (filename != null) { - // Get rid of URI prefix: - if (filename.startsWith("file://")) { - filename = filename.substring(7); - } - // replace %20 and so on - filename = Uri.decode(filename); - - FileDialog.setFilename(filename); - } - } - return; - } - - default: { - break; - } - } - super.onActivityResult(requestCode, resultCode, data); - } -} diff --git a/org_apg/src/org/apg/ui/KeyServerExportActivity.java b/org_apg/src/org/apg/ui/KeyServerExportActivity.java deleted file mode 100644 index 999c45b16..000000000 --- a/org_apg/src/org/apg/ui/KeyServerExportActivity.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import org.apg.Apg; -import org.apg.Constants; -import org.apg.HkpKeyServer; -import org.apg.Id; -import org.spongycastle.openpgp.PGPKeyRing; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.apg.R; - -import com.actionbarsherlock.view.MenuItem; - -import android.content.Intent; -import android.os.Bundle; -import android.os.Message; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.Spinner; -import android.widget.Toast; - -/** - * gpg --send-key activity - * - * Sends the selected public key to a key server - */ -public class KeyServerExportActivity extends BaseActivity { - - private Button export; - private Spinner keyServer; - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case android.R.id.home: - startActivity(new Intent(this, PublicKeyListActivity.class)); - return true; - - default: - break; - - } - return false; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.key_server_export_layout); - - export = (Button) findViewById(R.id.btn_export_to_server); - keyServer = (Spinner) findViewById(R.id.keyServer); - - ArrayAdapter adapter = new ArrayAdapter(this, - android.R.layout.simple_spinner_item, mPreferences.getKeyServers()); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - keyServer.setAdapter(adapter); - if (adapter.getCount() > 0) { - keyServer.setSelection(0); - } else { - export.setEnabled(false); - } - - export.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - startThread(); - } - }); - } - - @Override - public void run() { - String error = null; - Bundle data = new Bundle(); - Message msg = new Message(); - - HkpKeyServer server = new HkpKeyServer((String) keyServer.getSelectedItem()); - - int keyRingId = getIntent().getIntExtra(Apg.EXTRA_KEY_ID, -1); - - PGPKeyRing keyring = Apg.getKeyRing(keyRingId); - if (keyring != null && keyring instanceof PGPPublicKeyRing) { - boolean uploaded = Apg.uploadKeyRingToServer(server, (PGPPublicKeyRing) keyring); - if (!uploaded) { - error = "Unable to export key to selected server"; - } - } - - data.putInt(Constants.extras.STATUS, Id.message.export_done); - - if (error != null) { - data.putString(Apg.EXTRA_ERROR, error); - } - - msg.setData(data); - sendMessage(msg); - } - - @Override - public void doneCallback(Message msg) { - super.doneCallback(msg); - - Bundle data = msg.getData(); - String error = data.getString(Apg.EXTRA_ERROR); - if (error != null) { - Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT) - .show(); - return; - } - - Toast.makeText(this, R.string.keySendSuccess, Toast.LENGTH_SHORT).show(); - finish(); - } -} diff --git a/org_apg/src/org/apg/ui/KeyServerPreferenceActivity.java b/org_apg/src/org/apg/ui/KeyServerPreferenceActivity.java deleted file mode 100644 index 69f9772ef..000000000 --- a/org_apg/src/org/apg/ui/KeyServerPreferenceActivity.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import java.util.Vector; - -import org.apg.Apg; -import org.apg.ui.widget.Editor; -import org.apg.ui.widget.KeyServerEditor; -import org.apg.ui.widget.Editor.EditorListener; -import org.apg.R; - -import com.actionbarsherlock.view.Menu; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -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 KeyServerPreferenceActivity extends BaseActivity implements OnClickListener, - EditorListener { - private LayoutInflater mInflater; - private ViewGroup mEditors; - private View mAdd; - private TextView mTitle; - private TextView mSummary; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.key_server_preference); - - mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - mTitle = (TextView) findViewById(R.id.title); - mSummary = (TextView) findViewById(R.id.summary); - - mTitle.setText(R.string.label_keyServers); - - mEditors = (ViewGroup) findViewById(R.id.editors); - mAdd = findViewById(R.id.add); - mAdd.setOnClickListener(this); - - Intent intent = getIntent(); - String servers[] = intent.getStringArrayExtra(Apg.EXTRA_KEY_SERVERS); - if (servers != null) { - for (int i = 0; i < servers.length; ++i) { - KeyServerEditor view = (KeyServerEditor) mInflater.inflate( - R.layout.key_server_editor, mEditors, false); - view.setEditorListener(this); - view.setValue(servers[i]); - mEditors.addView(view); - } - } - - Button okButton = (Button) findViewById(R.id.btn_ok); - okButton.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - okClicked(); - } - }); - - Button cancelButton = (Button) findViewById(R.id.btn_cancel); - cancelButton.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - cancelClicked(); - } - }); - } - - public void onDeleted(Editor editor) { - // nothing to do - } - - public void onClick(View v) { - KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor, - mEditors, false); - view.setEditorListener(this); - mEditors.addView(view); - } - - private void cancelClicked() { - setResult(RESULT_CANCELED, null); - finish(); - } - - private void okClicked() { - Intent data = new Intent(); - Vector servers = new Vector(); - for (int i = 0; i < mEditors.getChildCount(); ++i) { - KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i); - String tmp = editor.getValue(); - if (tmp.length() > 0) { - servers.add(tmp); - } - } - String[] dummy = new String[0]; - data.putExtra(Apg.EXTRA_KEY_SERVERS, servers.toArray(dummy)); - setResult(RESULT_OK, data); - finish(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // override this, so no option menu is added (as would be in BaseActivity), since - // we're still in preferences - return true; - } -} diff --git a/org_apg/src/org/apg/ui/KeyServerQueryActivity.java b/org_apg/src/org/apg/ui/KeyServerQueryActivity.java deleted file mode 100644 index cc65769de..000000000 --- a/org_apg/src/org/apg/ui/KeyServerQueryActivity.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import java.util.List; -import java.util.Vector; - -import org.apg.Apg; -import org.apg.Constants; -import org.apg.HkpKeyServer; -import org.apg.Id; -import org.apg.KeyServer.InsufficientQuery; -import org.apg.KeyServer.KeyInfo; -import org.apg.KeyServer.QueryException; -import org.apg.KeyServer.TooManyResponses; -import org.apg.R; - -import com.actionbarsherlock.view.MenuItem; - -import android.app.Activity; -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.Message; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ArrayAdapter; -import android.widget.BaseAdapter; -import android.widget.Button; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.LinearLayout.LayoutParams; -import android.widget.ListView; -import android.widget.Spinner; -import android.widget.TextView; -import android.widget.Toast; - -public class KeyServerQueryActivity extends BaseActivity { - private ListView mList; - private EditText mQuery; - private Button mSearch; - private KeyInfoListAdapter mAdapter; - private Spinner mKeyServer; - - private int mQueryType; - private String mQueryString; - private long mQueryId; - private volatile List mSearchResult; - private volatile String mKeyData; - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case android.R.id.home: - startActivity(new Intent(this, PublicKeyListActivity.class)); - return true; - - default: - break; - - } - return false; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.key_server_query_layout); - - mQuery = (EditText) findViewById(R.id.query); - mSearch = (Button) findViewById(R.id.btn_search); - mList = (ListView) findViewById(R.id.list); - mAdapter = new KeyInfoListAdapter(this); - mList.setAdapter(mAdapter); - - mKeyServer = (Spinner) findViewById(R.id.keyServer); - ArrayAdapter adapter = new ArrayAdapter(this, - android.R.layout.simple_spinner_item, mPreferences.getKeyServers()); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mKeyServer.setAdapter(adapter); - if (adapter.getCount() > 0) { - mKeyServer.setSelection(0); - } else { - mSearch.setEnabled(false); - } - - mList.setOnItemClickListener(new OnItemClickListener() { - public void onItemClick(AdapterView adapter, View view, int position, long keyId) { - get(keyId); - } - }); - - mSearch.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - String query = mQuery.getText().toString(); - search(query); - } - }); - - Intent intent = getIntent(); - if (Apg.Intent.LOOK_UP_KEY_ID.equals(intent.getAction()) - || Apg.Intent.LOOK_UP_KEY_ID_AND_RETURN.equals(intent.getAction())) { - long keyId = intent.getLongExtra(Apg.EXTRA_KEY_ID, 0); - if (keyId != 0) { - String query = "0x" + Apg.keyToHex(keyId); - mQuery.setText(query); - search(query); - } - } - } - - private void search(String query) { - showDialog(Id.dialog.querying); - mQueryType = Id.keyserver.search; - mQueryString = query; - mAdapter.setKeys(new Vector()); - startThread(); - } - - private void get(long keyId) { - showDialog(Id.dialog.querying); - mQueryType = Id.keyserver.get; - mQueryId = keyId; - startThread(); - } - - @Override - protected Dialog onCreateDialog(int id) { - ProgressDialog progress = (ProgressDialog) super.onCreateDialog(id); - progress.setMessage(this.getString(R.string.progress_queryingServer, - (String) mKeyServer.getSelectedItem())); - return progress; - } - - @Override - public void run() { - String error = null; - Bundle data = new Bundle(); - Message msg = new Message(); - - try { - HkpKeyServer server = new HkpKeyServer((String) mKeyServer.getSelectedItem()); - if (mQueryType == Id.keyserver.search) { - mSearchResult = server.search(mQueryString); - } else if (mQueryType == Id.keyserver.get) { - mKeyData = server.get(mQueryId); - } - } catch (QueryException e) { - error = "" + e; - } catch (InsufficientQuery e) { - error = "Insufficient query."; - } catch (TooManyResponses e) { - error = "Too many responses."; - } - - data.putInt(Constants.extras.STATUS, Id.message.done); - - if (error != null) { - data.putString(Apg.EXTRA_ERROR, error); - } - - msg.setData(data); - sendMessage(msg); - } - - @Override - public void doneCallback(Message msg) { - super.doneCallback(msg); - - removeDialog(Id.dialog.querying); - - Bundle data = msg.getData(); - String error = data.getString(Apg.EXTRA_ERROR); - if (error != null) { - Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT) - .show(); - return; - } - - if (mQueryType == Id.keyserver.search) { - if (mSearchResult != null) { - Toast.makeText(this, getString(R.string.keysFound, mSearchResult.size()), - Toast.LENGTH_SHORT).show(); - mAdapter.setKeys(mSearchResult); - } - } else if (mQueryType == Id.keyserver.get) { - Intent orgIntent = getIntent(); - if (Apg.Intent.LOOK_UP_KEY_ID_AND_RETURN.equals(orgIntent.getAction())) { - if (mKeyData != null) { - Intent intent = new Intent(); - intent.putExtra(Apg.EXTRA_TEXT, mKeyData); - setResult(RESULT_OK, intent); - } else { - setResult(RESULT_CANCELED); - } - finish(); - } else { - if (mKeyData != null) { - Intent intent = new Intent(this, PublicKeyListActivity.class); - intent.setAction(Apg.Intent.IMPORT); - intent.putExtra(Apg.EXTRA_TEXT, mKeyData); - startActivity(intent); - } - } - } - } - - public class KeyInfoListAdapter extends BaseAdapter { - protected LayoutInflater mInflater; - protected Activity mActivity; - protected List mKeys; - - public KeyInfoListAdapter(Activity activity) { - mActivity = activity; - mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mKeys = new Vector(); - } - - public void setKeys(List keys) { - mKeys = keys; - notifyDataSetChanged(); - } - - @Override - public boolean hasStableIds() { - return true; - } - - public int getCount() { - return mKeys.size(); - } - - public Object getItem(int position) { - return mKeys.get(position); - } - - public long getItemId(int position) { - return mKeys.get(position).keyId; - } - - public View getView(int position, View convertView, ViewGroup parent) { - KeyInfo keyInfo = mKeys.get(position); - - View view = mInflater.inflate(R.layout.key_server_query_result_item, null); - - TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); - mainUserId.setText(R.string.unknownUserId); - TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - mainUserIdRest.setText(""); - TextView keyId = (TextView) view.findViewById(R.id.keyId); - keyId.setText(R.string.noKey); - TextView algorithm = (TextView) view.findViewById(R.id.algorithm); - algorithm.setText(""); - TextView status = (TextView) view.findViewById(R.id.status); - status.setText(""); - - String userId = keyInfo.userIds.get(0); - if (userId != null) { - String chunks[] = userId.split(" <", 2); - userId = chunks[0]; - if (chunks.length > 1) { - mainUserIdRest.setText("<" + chunks[1]); - } - mainUserId.setText(userId); - } - - keyId.setText(Apg.getSmallFingerPrint(keyInfo.keyId)); - - if (mainUserIdRest.getText().length() == 0) { - mainUserIdRest.setVisibility(View.GONE); - } - - algorithm.setText("" + keyInfo.size + "/" + keyInfo.algorithm); - - if (keyInfo.revoked != null) { - status.setText("revoked"); - } else { - status.setVisibility(View.GONE); - } - - LinearLayout ll = (LinearLayout) view.findViewById(R.id.list); - if (keyInfo.userIds.size() == 1) { - ll.setVisibility(View.GONE); - } else { - boolean first = true; - boolean second = true; - for (String uid : keyInfo.userIds) { - if (first) { - first = false; - continue; - } - if (!second) { - View sep = new View(mActivity); - sep.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, 1)); - sep.setBackgroundResource(android.R.drawable.divider_horizontal_dark); - ll.addView(sep); - } - TextView uidView = (TextView) mInflater.inflate( - R.layout.key_server_query_result_user_id, null); - uidView.setText(uid); - ll.addView(uidView); - second = false; - } - } - - return view; - } - } -} diff --git a/org_apg/src/org/apg/ui/MailListActivity.java b/org_apg/src/org/apg/ui/MailListActivity.java deleted file mode 100644 index aee0d2967..000000000 --- a/org_apg/src/org/apg/ui/MailListActivity.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import java.util.Vector; -import java.util.regex.Matcher; - -import org.apg.Apg; -import org.apg.Preferences; -import org.apg.R; - -import android.app.ListActivity; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.TextView; - -public class MailListActivity extends ListActivity { - LayoutInflater mInflater = null; - - public static final String EXTRA_ACCOUNT = "account"; - - private static class Conversation { - public long id; - public String subject; - public Vector messages; - - public Conversation(long id, String subject) { - this.id = id; - this.subject = subject; - } - } - - private static class Message { - public Conversation parent; - public long id; - public String subject; - public String fromAddress; - public String data; - public String replyTo; - public boolean signedOnly; - - public Message(Conversation parent, long id, String subject, - String fromAddress, String replyTo, - String data, boolean signedOnly) { - this.parent = parent; - this.id = id; - this.subject = subject; - this.fromAddress = fromAddress; - this.replyTo = replyTo; - this.data = data; - if (this.replyTo == null || this.replyTo.equals("")) { - this.replyTo = this.fromAddress; - } - this.signedOnly = signedOnly; - } - } - - private Vector mConversations; - private Vector mMessages; - - @Override - protected void onCreate(Bundle savedInstanceState) { - Preferences prefs = Preferences.getPreferences(this); - - super.onCreate(savedInstanceState); - - mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - mConversations = new Vector(); - mMessages = new Vector(); - - String account = getIntent().getExtras().getString(EXTRA_ACCOUNT); - // TODO: what if account is null? - Uri uri = Uri.parse("content://gmail-ls/conversations/" + account); - Cursor cursor = - managedQuery(uri, new String[] { "conversation_id", "subject" }, null, null, null); - for (int i = 0; i < cursor.getCount(); ++i) { - cursor.moveToPosition(i); - - int idIndex = cursor.getColumnIndex("conversation_id"); - int subjectIndex = cursor.getColumnIndex("subject"); - long conversationId = cursor.getLong(idIndex); - Conversation conversation = - new Conversation(conversationId, cursor.getString(subjectIndex)); - Uri messageUri = Uri.withAppendedPath(uri, "" + conversationId + "/messages"); - Cursor messageCursor = - managedQuery(messageUri, new String[] { - "messageId", - "subject", - "fromAddress", - "replyToAddresses", - "body" }, null, null, null); - Vector messages = new Vector(); - for (int j = 0; j < messageCursor.getCount(); ++j) { - messageCursor.moveToPosition(j); - idIndex = messageCursor.getColumnIndex("messageId"); - subjectIndex = messageCursor.getColumnIndex("subject"); - int fromAddressIndex = messageCursor.getColumnIndex("fromAddress"); - int replyToIndex = messageCursor.getColumnIndex("replyToAddresses"); - int bodyIndex = messageCursor.getColumnIndex("body"); - String data = messageCursor.getString(bodyIndex); - data = Html.fromHtml(data).toString(); - boolean signedOnly = false; - Matcher matcher = Apg.PGP_MESSAGE.matcher(data); - if (matcher.matches()) { - data = matcher.group(1); - } else { - matcher = Apg.PGP_SIGNED_MESSAGE.matcher(data); - if (matcher.matches()) { - data = matcher.group(1); - signedOnly = true; - } else { - data = null; - } - } - Message message = - new Message(conversation, - messageCursor.getLong(idIndex), - messageCursor.getString(subjectIndex), - messageCursor.getString(fromAddressIndex), - messageCursor.getString(replyToIndex), - data, signedOnly); - - messages.add(message); - mMessages.add(message); - } - conversation.messages = messages; - mConversations.add(conversation); - } - - setListAdapter(new MailboxAdapter()); - getListView().setOnItemClickListener(new OnItemClickListener() { - public void onItemClick(AdapterView arg0, View v, int position, long id) { - Intent intent = new Intent(MailListActivity.this, DecryptActivity.class); - intent.setAction(Apg.Intent.DECRYPT); - Message message = (Message) ((MailboxAdapter) getListAdapter()).getItem(position); - intent.putExtra(Apg.EXTRA_TEXT, message.data); - intent.putExtra(Apg.EXTRA_SUBJECT, message.subject); - intent.putExtra(Apg.EXTRA_REPLY_TO, message.replyTo); - startActivity(intent); - } - }); - } - - private class MailboxAdapter extends BaseAdapter implements ListAdapter { - - @Override - public boolean isEnabled(int position) { - Message message = (Message) getItem(position); - return message.data != null; - } - - @Override - public boolean hasStableIds() { - return true; - } - - public int getCount() { - return mMessages.size(); - } - - public Object getItem(int position) { - return mMessages.get(position); - } - - public long getItemId(int position) { - return mMessages.get(position).id; - } - - public View getView(int position, View convertView, ViewGroup parent) { - View view = mInflater.inflate(R.layout.mailbox_message_item, null); - - Message message = (Message) getItem(position); - - TextView subject = (TextView) view.findViewById(R.id.subject); - TextView email = (TextView) view.findViewById(R.id.emailAddress); - ImageView status = (ImageView) view.findViewById(R.id.ic_status); - - subject.setText(message.subject); - email.setText(message.fromAddress); - if (message.data != null) { - if (message.signedOnly) { - status.setImageResource(R.drawable.signed); - } else { - status.setImageResource(R.drawable.encrypted); - } - status.setVisibility(View.VISIBLE); - } else { - status.setVisibility(View.INVISIBLE); - } - - return view; - } - } -} diff --git a/org_apg/src/org/apg/ui/MainActivity.java b/org_apg/src/org/apg/ui/MainActivity.java deleted file mode 100644 index d53757e12..000000000 --- a/org_apg/src/org/apg/ui/MainActivity.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import java.security.Security; - -import org.apg.Apg; -import org.apg.Id; -import org.spongycastle.jce.provider.BouncyCastleProvider; -import org.apg.R; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuItem; - -import android.app.Dialog; -import android.content.Intent; -import android.os.Bundle; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.View; -import android.widget.TextView; - -public class MainActivity extends BaseActivity { - static { - Security.addProvider(new BouncyCastleProvider()); - } - - public void manageKeysOnClick(View view) { - startActivity(new Intent(this, PublicKeyListActivity.class)); - } - - public void myKeysOnClick(View view) { - startActivity(new Intent(this, SecretKeyListActivity.class)); - } - - public void encryptOnClick(View view) { - Intent intent = new Intent(MainActivity.this, EncryptActivity.class); - intent.setAction(Apg.Intent.ENCRYPT); - startActivity(intent); - } - - public void decryptOnClick(View view) { - Intent intent = new Intent(MainActivity.this, DecryptActivity.class); - intent.setAction(Apg.Intent.DECRYPT); - startActivity(intent); - } - - public void scanQrcodeOnClick(View view) { - Intent intent = new Intent(this, ImportFromQRCodeActivity.class); - intent.setAction(Apg.Intent.IMPORT_FROM_QR_CODE); - startActivityForResult(intent, Id.request.import_from_qr_code); - } - - public void helpOnClick(View view) { - startActivity(new Intent(this, HelpActivity.class)); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - - final ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(false); - actionBar.setHomeButtonEnabled(false); - - // if (!mPreferences.hasSeenHelp()) { - // showDialog(Id.dialog.help); - // } - // - // if (Apg.isReleaseVersion(this) && !mPreferences.hasSeenChangeLog(Apg.getVersion(this))) { - // showDialog(Id.dialog.change_log); - // } - } - - @Override - protected Dialog onCreateDialog(int id) { - switch (id) { - - // case Id.dialog.change_log: { - // AlertDialog.Builder alert = new AlertDialog.Builder(this); - // - // alert.setTitle("Changes " + Apg.getFullVersion(this)); - // LayoutInflater inflater = (LayoutInflater) this - // .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - // View layout = inflater.inflate(R.layout.info, null); - // TextView message = (TextView) layout.findViewById(R.id.message); - // - // message.setText("Changes:\n" + "* \n" + "\n" - // + "WARNING: be careful editing your existing keys, as they " - // + "WILL be stripped of certificates right now.\n" + "\n" - // + "Also: key cross-certification is NOT supported, so signing " - // + "with those keys will get a warning when the signature is " + "checked.\n" - // + "\n" + "I hope APG continues to be useful to you, please send " - // + "bug reports, feature wishes, feedback."); - // alert.setView(layout); - // - // alert.setCancelable(false); - // alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - // public void onClick(DialogInterface dialog, int id) { - // MainActivity.this.removeDialog(Id.dialog.change_log); - // mPreferences.setHasSeenChangeLog(Apg.getVersion(MainActivity.this), true); - // } - // }); - // - // return alert.create(); - // } - - // case Id.dialog.help: { - // AlertDialog.Builder alert = new AlertDialog.Builder(this); - // - // alert.setTitle(R.string.title_help); - // - // LayoutInflater inflater = (LayoutInflater) this - // .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - // View layout = inflater.inflate(R.layout.info, null); - // TextView message = (TextView) layout.findViewById(R.id.message); - // message.setText(R.string.text_help); - // - // TransformFilter packageNames = new TransformFilter() { - // public final String transformUrl(final Matcher match, String url) { - // String name = match.group(1).toLowerCase(); - // if (name.equals("astro")) { - // return "com.metago.astro"; - // } else if (name.equals("k-9 mail")) { - // return "com.fsck.k9"; - // } else { - // return "org.openintents.filemanager"; - // } - // } - // }; - // - // Pattern pattern = Pattern.compile("(OI File Manager|ASTRO|K-9 Mail)"); - // String scheme = "market://search?q=pname:"; - // message.setAutoLinkMask(0); - // Linkify.addLinks(message, pattern, scheme, null, packageNames); - // - // alert.setView(layout); - // - // alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - // public void onClick(DialogInterface dialog, int id) { - // MainActivity.this.removeDialog(Id.dialog.help); - // mPreferences.setHasSeenHelp(true); - // } - // }); - // - // return alert.create(); - // } - - default: { - return super.onCreateDialog(id); - } - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, Id.menu.option.preferences, 0, R.string.menu_preferences) - .setIcon(R.drawable.ic_menu_settings) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - menu.add(0, Id.menu.option.about, 1, R.string.menu_about).setIcon(R.drawable.ic_menu_about) - .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case Id.menu.option.about: { - startActivity(new Intent(this, AboutActivity.class)); - return true; - } - - case Id.menu.option.preferences: { - startActivity(new Intent(this, PreferencesActivity.class)); - return true; - } - - default: { - break; - } - } - return false; - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - - TextView nameTextView = (TextView) v.findViewById(R.id.accountName); - if (nameTextView != null) { - menu.setHeaderTitle(nameTextView.getText()); - menu.add(0, Id.menu.delete, 0, R.string.menu_deleteAccount); - } - } - -} \ No newline at end of file diff --git a/org_apg/src/org/apg/ui/PreferencesActivity.java b/org_apg/src/org/apg/ui/PreferencesActivity.java deleted file mode 100644 index 2e2c8fa29..000000000 --- a/org_apg/src/org/apg/ui/PreferencesActivity.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import org.apg.Apg; -import org.apg.Constants; -import org.apg.Id; -import org.apg.Preferences; -import org.apg.ui.widget.IntegerListPreference; -import org.spongycastle.bcpg.HashAlgorithmTags; -import org.spongycastle.openpgp.PGPEncryptedData; -import org.apg.R; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.SherlockPreferenceActivity; -import com.actionbarsherlock.view.MenuItem; - -import android.content.Intent; -import android.os.Bundle; -import android.preference.CheckBoxPreference; -import android.preference.Preference; -import android.preference.PreferenceScreen; - - -public class PreferencesActivity extends SherlockPreferenceActivity { - private IntegerListPreference mPassPhraseCacheTtl = null; - private IntegerListPreference mEncryptionAlgorithm = null; - private IntegerListPreference mHashAlgorithm = null; - private IntegerListPreference mMessageCompression = null; - private IntegerListPreference mFileCompression = null; - private CheckBoxPreference mAsciiArmour = null; - private CheckBoxPreference mForceV3Signatures = null; - private PreferenceScreen mKeyServerPreference = null; - private Preferences mPreferences; - - @Override - protected void onCreate(Bundle savedInstanceState) { - mPreferences = Preferences.getPreferences(this); - super.onCreate(savedInstanceState); - - final ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(true); - - addPreferencesFromResource(R.xml.apg_preferences); - - mPassPhraseCacheTtl = (IntegerListPreference) findPreference(Constants.pref.PASS_PHRASE_CACHE_TTL); - mPassPhraseCacheTtl.setValue("" + mPreferences.getPassPhraseCacheTtl()); - mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry()); - mPassPhraseCacheTtl - .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mPassPhraseCacheTtl.setValue(newValue.toString()); - mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry()); - mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString())); - BaseActivity.startCacheService(PreferencesActivity.this, mPreferences); - return false; - } - }); - - mEncryptionAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM); - int valueIds[] = { PGPEncryptedData.AES_128, PGPEncryptedData.AES_192, - PGPEncryptedData.AES_256, PGPEncryptedData.BLOWFISH, PGPEncryptedData.TWOFISH, - PGPEncryptedData.CAST5, PGPEncryptedData.DES, PGPEncryptedData.TRIPLE_DES, - PGPEncryptedData.IDEA, }; - String entries[] = { "AES-128", "AES-192", "AES-256", "Blowfish", "Twofish", "CAST5", - "DES", "Triple DES", "IDEA", }; - String values[] = new String[valueIds.length]; - for (int i = 0; i < values.length; ++i) { - values[i] = "" + valueIds[i]; - } - mEncryptionAlgorithm.setEntries(entries); - mEncryptionAlgorithm.setEntryValues(values); - mEncryptionAlgorithm.setValue("" + mPreferences.getDefaultEncryptionAlgorithm()); - mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry()); - mEncryptionAlgorithm - .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mEncryptionAlgorithm.setValue(newValue.toString()); - mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry()); - mPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue - .toString())); - return false; - } - }); - - mHashAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_HASH_ALGORITHM); - valueIds = new int[] { HashAlgorithmTags.MD5, HashAlgorithmTags.RIPEMD160, - HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256, - HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512, }; - entries = new String[] { "MD5", "RIPEMD-160", "SHA-1", "SHA-224", "SHA-256", "SHA-384", - "SHA-512", }; - values = new String[valueIds.length]; - for (int i = 0; i < values.length; ++i) { - values[i] = "" + valueIds[i]; - } - mHashAlgorithm.setEntries(entries); - mHashAlgorithm.setEntryValues(values); - mHashAlgorithm.setValue("" + mPreferences.getDefaultHashAlgorithm()); - mHashAlgorithm.setSummary(mHashAlgorithm.getEntry()); - mHashAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mHashAlgorithm.setValue(newValue.toString()); - mHashAlgorithm.setSummary(mHashAlgorithm.getEntry()); - mPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString())); - return false; - } - }); - - mMessageCompression = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_MESSAGE_COMPRESSION); - valueIds = new int[] { Id.choice.compression.none, Id.choice.compression.zip, - Id.choice.compression.zlib, Id.choice.compression.bzip2, }; - entries = new String[] { - getString(R.string.choice_none) + " (" + getString(R.string.fast) + ")", - "ZIP (" + getString(R.string.fast) + ")", - "ZLIB (" + getString(R.string.fast) + ")", - "BZIP2 (" + getString(R.string.very_slow) + ")", }; - values = new String[valueIds.length]; - for (int i = 0; i < values.length; ++i) { - values[i] = "" + valueIds[i]; - } - mMessageCompression.setEntries(entries); - mMessageCompression.setEntryValues(values); - mMessageCompression.setValue("" + mPreferences.getDefaultMessageCompression()); - mMessageCompression.setSummary(mMessageCompression.getEntry()); - mMessageCompression - .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mMessageCompression.setValue(newValue.toString()); - mMessageCompression.setSummary(mMessageCompression.getEntry()); - mPreferences.setDefaultMessageCompression(Integer.parseInt(newValue - .toString())); - return false; - } - }); - - mFileCompression = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_FILE_COMPRESSION); - mFileCompression.setEntries(entries); - mFileCompression.setEntryValues(values); - mFileCompression.setValue("" + mPreferences.getDefaultFileCompression()); - mFileCompression.setSummary(mFileCompression.getEntry()); - mFileCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mFileCompression.setValue(newValue.toString()); - mFileCompression.setSummary(mFileCompression.getEntry()); - mPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString())); - return false; - } - }); - - mAsciiArmour = (CheckBoxPreference) findPreference(Constants.pref.DEFAULT_ASCII_ARMOUR); - mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour()); - mAsciiArmour.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mAsciiArmour.setChecked((Boolean) newValue); - mPreferences.setDefaultAsciiArmour((Boolean) newValue); - return false; - } - }); - - mForceV3Signatures = (CheckBoxPreference) findPreference(Constants.pref.FORCE_V3_SIGNATURES); - mForceV3Signatures.setChecked(mPreferences.getForceV3Signatures()); - mForceV3Signatures - .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - public boolean onPreferenceChange(Preference preference, Object newValue) { - mForceV3Signatures.setChecked((Boolean) newValue); - mPreferences.setForceV3Signatures((Boolean) newValue); - return false; - } - }); - - mKeyServerPreference = (PreferenceScreen) findPreference(Constants.pref.KEY_SERVERS); - String servers[] = mPreferences.getKeyServers(); - mKeyServerPreference.setSummary(getResources().getString(R.string.nKeyServers, - servers.length)); - mKeyServerPreference - .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - public boolean onPreferenceClick(Preference preference) { - Intent intent = new Intent(PreferencesActivity.this, - KeyServerPreferenceActivity.class); - intent.putExtra(Apg.EXTRA_KEY_SERVERS, mPreferences.getKeyServers()); - startActivityForResult(intent, Id.request.key_server_preference); - return false; - } - }); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case Id.request.key_server_preference: { - if (resultCode == RESULT_CANCELED || data == null) { - return; - } - String servers[] = data.getStringArrayExtra(Apg.EXTRA_KEY_SERVERS); - mPreferences.setKeyServers(servers); - mKeyServerPreference.setSummary(getResources().getString(R.string.nKeyServers, - servers.length)); - break; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - break; - } - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case android.R.id.home: - startActivity(new Intent(this, MainActivity.class)); - return true; - - default: - break; - - } - return false; - } -} diff --git a/org_apg/src/org/apg/ui/PublicKeyListActivity.java b/org_apg/src/org/apg/ui/PublicKeyListActivity.java deleted file mode 100644 index 5759350ec..000000000 --- a/org_apg/src/org/apg/ui/PublicKeyListActivity.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import org.apg.Apg; -import org.apg.Constants; -import org.apg.Id; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.apg.R; - -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuItem; - -import android.content.Intent; -import android.os.Bundle; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.View; -import android.widget.ExpandableListView; -import android.widget.ExpandableListView.ExpandableListContextMenuInfo; - -public class PublicKeyListActivity extends KeyListActivity { - @Override - public void onCreate(Bundle savedInstanceState) { - mExportFilename = Constants.path.APP_DIR + "/pubexport.asc"; - mKeyType = Id.type.public_key; - super.onCreate(savedInstanceState); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(1, Id.menu.option.search, 0, R.string.menu_search) - .setIcon(R.drawable.ic_menu_search).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - menu.add(1, Id.menu.option.scanQRCode, 1, R.string.menu_scanQRCode) - .setIcon(R.drawable.ic_menu_scan_qrcode) - .setShowAsAction( - MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - menu.add(1, Id.menu.option.key_server, 2, R.string.menu_keyServer) - .setIcon(R.drawable.ic_menu_search_list) - .setShowAsAction( - MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - menu.add(0, Id.menu.option.import_keys, 3, R.string.menu_importKeys).setShowAsAction( - MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - menu.add(0, Id.menu.option.export_keys, 4, R.string.menu_exportKeys).setShowAsAction( - MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case Id.menu.option.key_server: { - startActivity(new Intent(this, KeyServerQueryActivity.class)); - - return true; - } - case Id.menu.option.scanQRCode: { - Intent intent = new Intent(this, ImportFromQRCodeActivity.class); - intent.setAction(Apg.Intent.IMPORT_FROM_QR_CODE); - startActivityForResult(intent, Id.request.import_from_qr_code); - - return true; - } - - default: { - return super.onOptionsItemSelected(item); - } - } - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo; - int type = ExpandableListView.getPackedPositionType(info.packedPosition); - - if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { - // TODO: user id? menu.setHeaderTitle("Key"); - menu.add(0, Id.menu.export, 0, R.string.menu_exportKey); - menu.add(0, Id.menu.delete, 1, R.string.menu_deleteKey); - menu.add(0, Id.menu.update, 1, R.string.menu_updateKey); - menu.add(0, Id.menu.exportToServer, 1, R.string.menu_exportKeyToServer); - menu.add(0, Id.menu.signKey, 1, R.string.menu_signKey); - } - } - - @Override - public boolean onContextItemSelected(android.view.MenuItem menuItem) { - ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo(); - int type = ExpandableListView.getPackedPositionType(info.packedPosition); - int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); - - if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) { - return super.onContextItemSelected(menuItem); - } - - switch (menuItem.getItemId()) { - case Id.menu.update: { - mSelectedItem = groupPosition; - final int keyRingId = mListAdapter.getKeyRingId(groupPosition); - long keyId = 0; - Object keyRing = Apg.getKeyRing(keyRingId); - if (keyRing != null && keyRing instanceof PGPPublicKeyRing) { - keyId = Apg.getMasterKey((PGPPublicKeyRing) keyRing).getKeyID(); - } - if (keyId == 0) { - // this shouldn't happen - return true; - } - - Intent intent = new Intent(this, KeyServerQueryActivity.class); - intent.setAction(Apg.Intent.LOOK_UP_KEY_ID_AND_RETURN); - intent.putExtra(Apg.EXTRA_KEY_ID, keyId); - startActivityForResult(intent, Id.request.look_up_key_id); - - return true; - } - - case Id.menu.exportToServer: { - mSelectedItem = groupPosition; - final int keyRingId = mListAdapter.getKeyRingId(groupPosition); - - Intent intent = new Intent(this, KeyServerExportActivity.class); - intent.setAction(Apg.Intent.EXPORT_KEY_TO_SERVER); - intent.putExtra(Apg.EXTRA_KEY_ID, keyRingId); - startActivityForResult(intent, Id.request.export_to_server); - - return true; - } - - case Id.menu.signKey: { - mSelectedItem = groupPosition; - final int keyRingId = mListAdapter.getKeyRingId(groupPosition); - long keyId = 0; - Object keyRing = Apg.getKeyRing(keyRingId); - if (keyRing != null && keyRing instanceof PGPPublicKeyRing) { - keyId = Apg.getMasterKey((PGPPublicKeyRing) keyRing).getKeyID(); - } - - if (keyId == 0) { - // this shouldn't happen - return true; - } - - Intent intent = new Intent(this, SignKeyActivity.class); - intent.putExtra(Apg.EXTRA_KEY_ID, keyId); - startActivity(intent); - - return true; - } - - default: { - return super.onContextItemSelected(menuItem); - } - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case Id.request.look_up_key_id: { - if (resultCode == RESULT_CANCELED || data == null - || data.getStringExtra(Apg.EXTRA_TEXT) == null) { - return; - } - - Intent intent = new Intent(this, PublicKeyListActivity.class); - intent.setAction(Apg.Intent.IMPORT); - intent.putExtra(Apg.EXTRA_TEXT, data.getStringExtra(Apg.EXTRA_TEXT)); - handleIntent(intent); - break; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - break; - } - } - } -} diff --git a/org_apg/src/org/apg/ui/SecretKeyListActivity.java b/org_apg/src/org/apg/ui/SecretKeyListActivity.java deleted file mode 100644 index 23ec694ce..000000000 --- a/org_apg/src/org/apg/ui/SecretKeyListActivity.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import org.apg.Apg; -import org.apg.AskForSecretKeyPassPhrase; -import org.apg.Constants; -import org.apg.Id; -import org.apg.R; - -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuItem; - -import android.app.Dialog; -import android.content.Intent; -import android.os.Bundle; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.View; -import android.widget.ExpandableListView; -import android.widget.ExpandableListView.ExpandableListContextMenuInfo; -import android.widget.ExpandableListView.OnChildClickListener; - -import com.google.zxing.integration.android.IntentIntegrator; - -public class SecretKeyListActivity extends KeyListActivity implements OnChildClickListener { - @Override - public void onCreate(Bundle savedInstanceState) { - mExportFilename = Constants.path.APP_DIR + "/secexport.asc"; - mKeyType = Id.type.secret_key; - super.onCreate(savedInstanceState); - mList.setOnChildClickListener(this); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(3, Id.menu.option.search, 0, R.string.menu_search) - .setIcon(R.drawable.ic_menu_search).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); - menu.add(1, Id.menu.option.create, 1, R.string.menu_createKey).setShowAsAction( - MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - menu.add(0, Id.menu.option.import_keys, 2, R.string.menu_importKeys).setShowAsAction( - MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - menu.add(0, Id.menu.option.export_keys, 3, R.string.menu_exportKeys).setShowAsAction( - MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case Id.menu.option.create: { - createKey(); - return true; - } - - default: { - return super.onOptionsItemSelected(item); - } - } - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo; - int type = ExpandableListView.getPackedPositionType(info.packedPosition); - - if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { - // TODO: user id? menu.setHeaderTitle("Key"); - menu.add(0, Id.menu.edit, 0, R.string.menu_editKey); - menu.add(0, Id.menu.export, 1, R.string.menu_exportKey); - menu.add(0, Id.menu.delete, 2, R.string.menu_deleteKey); - menu.add(0, Id.menu.share, 2, R.string.menu_share); - } - } - - @Override - public boolean onContextItemSelected(android.view.MenuItem menuItem) { - ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo(); - int type = ExpandableListView.getPackedPositionType(info.packedPosition); - int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); - - if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) { - return super.onContextItemSelected(menuItem); - } - - switch (menuItem.getItemId()) { - case Id.menu.edit: { - mSelectedItem = groupPosition; - checkPassPhraseAndEdit(); - return true; - } - - case Id.menu.share: { - mSelectedItem = groupPosition; - - long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()) - .getGroupId(mSelectedItem); - String msg = keyId + "," + Apg.getFingerPrint(keyId); - - new IntentIntegrator(this).shareText(msg); - } - - default: { - return super.onContextItemSelected(menuItem); - } - } - } - - public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, - int childPosition, long id) { - mSelectedItem = groupPosition; - checkPassPhraseAndEdit(); - return true; - } - - @Override - protected Dialog onCreateDialog(int id) { - switch (id) { - case Id.dialog.pass_phrase: { - long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()) - .getGroupId(mSelectedItem); - return AskForSecretKeyPassPhrase.createDialog(this, keyId, this); - } - - default: { - return super.onCreateDialog(id); - } - } - } - - public void checkPassPhraseAndEdit() { - long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem); - String passPhrase = Apg.getCachedPassPhrase(keyId); - if (passPhrase == null) { - showDialog(Id.dialog.pass_phrase); - } else { - Apg.setEditPassPhrase(passPhrase); - editKey(); - } - } - - @Override - public void passPhraseCallback(long keyId, String passPhrase) { - super.passPhraseCallback(keyId, passPhrase); - Apg.setEditPassPhrase(passPhrase); - editKey(); - } - - private void createKey() { - Apg.setEditPassPhrase(""); - Intent intent = new Intent(Apg.Intent.CREATE_KEY); - startActivityForResult(intent, Id.message.create_key); - } - - private void editKey() { - long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem); - Intent intent = new Intent(Apg.Intent.EDIT_KEY); - intent.putExtra(Apg.EXTRA_KEY_ID, keyId); - startActivityForResult(intent, Id.message.edit_key); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case Id.message.create_key: // intentionally no break - case Id.message.edit_key: { - if (resultCode == RESULT_OK) { - refreshList(); - } - break; - } - - default: { - break; - } - } - - super.onActivityResult(requestCode, resultCode, data); - } -} diff --git a/org_apg/src/org/apg/ui/SelectPublicKeyListActivity.java b/org_apg/src/org/apg/ui/SelectPublicKeyListActivity.java deleted file mode 100644 index e2dcc8d4d..000000000 --- a/org_apg/src/org/apg/ui/SelectPublicKeyListActivity.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import java.util.Vector; - -import org.apg.Apg; -import org.apg.Id; -import org.apg.R; -import org.apg.ui.widget.SelectPublicKeyListAdapter; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuItem; - -import android.app.SearchManager; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.ListView; -import android.widget.TextView; - -public class SelectPublicKeyListActivity extends BaseActivity { - protected ListView mList; - protected SelectPublicKeyListAdapter mListAdapter; - protected View mFilterLayout; - protected Button mClearFilterButton; - protected TextView mFilterInfo; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - final ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(false); - actionBar.setHomeButtonEnabled(false); - - setContentView(R.layout.select_public_key); - - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); - - mList = (ListView) findViewById(R.id.list); - // needed in Android 1.5, where the XML attribute gets ignored - mList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - - mFilterLayout = findViewById(R.id.layout_filter); - mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo); - mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear); - - mClearFilterButton.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - handleIntent(new Intent()); - } - }); - - handleIntent(getIntent()); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - handleIntent(intent); - } - - private void handleIntent(Intent intent) { - String searchString = null; - if (Intent.ACTION_SEARCH.equals(intent.getAction())) { - searchString = intent.getStringExtra(SearchManager.QUERY); - if (searchString != null && searchString.trim().length() == 0) { - searchString = null; - } - } - - long selectedKeyIds[] = null; - selectedKeyIds = intent.getLongArrayExtra(Apg.EXTRA_SELECTION); - - if (selectedKeyIds == null) { - Vector vector = new Vector(); - for (int i = 0; i < mList.getCount(); ++i) { - if (mList.isItemChecked(i)) { - vector.add(mList.getItemIdAtPosition(i)); - } - } - selectedKeyIds = new long[vector.size()]; - for (int i = 0; i < vector.size(); ++i) { - selectedKeyIds[i] = vector.get(i); - } - } - - if (searchString == null) { - mFilterLayout.setVisibility(View.GONE); - } else { - mFilterLayout.setVisibility(View.VISIBLE); - mFilterInfo.setText(getString(R.string.filterInfo, searchString)); - } - - if (mListAdapter != null) { - mListAdapter.cleanup(); - } - - mListAdapter = new SelectPublicKeyListAdapter(this, mList, searchString, selectedKeyIds); - mList.setAdapter(mListAdapter); - - if (selectedKeyIds != null) { - for (int i = 0; i < mListAdapter.getCount(); ++i) { - long keyId = mListAdapter.getItemId(i); - for (int j = 0; j < selectedKeyIds.length; ++j) { - if (keyId == selectedKeyIds[j]) { - mList.setItemChecked(i, true); - break; - } - } - } - } - } - - private void cancelClicked() { - setResult(RESULT_CANCELED, null); - finish(); - } - - private void okClicked() { - Intent data = new Intent(); - Vector keys = new Vector(); - Vector userIds = new Vector(); - for (int i = 0; i < mList.getCount(); ++i) { - if (mList.isItemChecked(i)) { - keys.add(mList.getItemIdAtPosition(i)); - userIds.add((String) mList.getItemAtPosition(i)); - } - } - long selectedKeyIds[] = new long[keys.size()]; - for (int i = 0; i < keys.size(); ++i) { - selectedKeyIds[i] = keys.get(i); - } - String userIdArray[] = new String[0]; - data.putExtra(Apg.EXTRA_SELECTION, selectedKeyIds); - data.putExtra(Apg.EXTRA_USER_IDS, userIds.toArray(userIdArray)); - setResult(RESULT_OK, data); - finish(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, Id.menu.option.search, 0, R.string.menu_search).setIcon( - android.R.drawable.ic_menu_search); - menu.add(1, Id.menu.option.cancel, 0, R.string.btn_doNotSave).setShowAsAction( - MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - menu.add(1, Id.menu.option.okay, 1, R.string.btn_okay).setShowAsAction( - MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case Id.menu.option.okay: - okClicked(); - return true; - - case Id.menu.option.cancel: - cancelClicked(); - return true; - - default: - break; - - } - return false; - } -} diff --git a/org_apg/src/org/apg/ui/SelectSecretKeyListActivity.java b/org_apg/src/org/apg/ui/SelectSecretKeyListActivity.java deleted file mode 100644 index dadc6f5bf..000000000 --- a/org_apg/src/org/apg/ui/SelectSecretKeyListActivity.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import org.apg.Apg; -import org.apg.Id; -import org.apg.R; -import org.apg.ui.widget.SelectSecretKeyListAdapter; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.view.Menu; - -import android.app.SearchManager; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.Button; -import android.widget.ListView; -import android.widget.TextView; - -public class SelectSecretKeyListActivity extends BaseActivity { - protected ListView mList; - protected SelectSecretKeyListAdapter mListAdapter; - protected View mFilterLayout; - protected Button mClearFilterButton; - protected TextView mFilterInfo; - - protected long mSelectedKeyId = 0; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - final ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(false); - actionBar.setHomeButtonEnabled(false); - - setContentView(R.layout.select_secret_key); - - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); - - mList = (ListView) findViewById(R.id.list); - - mList.setOnItemClickListener(new OnItemClickListener() { - public void onItemClick(AdapterView adapterView, View view, int position, long id) { - Intent data = new Intent(); - data.putExtra(Apg.EXTRA_KEY_ID, id); - data.putExtra(Apg.EXTRA_USER_ID, (String) mList.getItemAtPosition(position)); - setResult(RESULT_OK, data); - finish(); - } - }); - - mFilterLayout = findViewById(R.id.layout_filter); - mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo); - mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear); - - mClearFilterButton.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - handleIntent(new Intent()); - } - }); - - handleIntent(getIntent()); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - handleIntent(intent); - } - - private void handleIntent(Intent intent) { - String searchString = null; - if (Intent.ACTION_SEARCH.equals(intent.getAction())) { - searchString = intent.getStringExtra(SearchManager.QUERY); - if (searchString != null && searchString.trim().length() == 0) { - searchString = null; - } - } - - if (searchString == null) { - mFilterLayout.setVisibility(View.GONE); - } else { - mFilterLayout.setVisibility(View.VISIBLE); - mFilterInfo.setText(getString(R.string.filterInfo, searchString)); - } - - if (mListAdapter != null) { - mListAdapter.cleanup(); - } - - mListAdapter = new SelectSecretKeyListAdapter(this, mList, searchString); - mList.setAdapter(mListAdapter); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add(0, Id.menu.option.search, 0, R.string.menu_search).setIcon( - android.R.drawable.ic_menu_search); - return true; - } -} diff --git a/org_apg/src/org/apg/ui/SignKeyActivity.java b/org_apg/src/org/apg/ui/SignKeyActivity.java deleted file mode 100644 index 012118cac..000000000 --- a/org_apg/src/org/apg/ui/SignKeyActivity.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui; - -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SignatureException; -import java.util.Iterator; - -import org.apg.Apg; -import org.apg.Constants; -import org.apg.HkpKeyServer; -import org.apg.Id; -import org.spongycastle.jce.provider.BouncyCastleProvider; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPPrivateKey; -import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSecretKey; -import org.spongycastle.openpgp.PGPSignature; -import org.spongycastle.openpgp.PGPSignatureGenerator; -import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; -import org.spongycastle.openpgp.PGPSignatureSubpacketVector; -import org.spongycastle.openpgp.PGPUtil; -import org.apg.R; - -import com.actionbarsherlock.view.MenuItem; - -import android.content.Intent; -import android.os.Bundle; -import android.os.Message; -import android.util.Log; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.Spinner; -import android.widget.Toast; - -/** - * gpg --sign-key - * - * signs the specified public key with the specified secret master key - */ -public class SignKeyActivity extends BaseActivity { - private static final String TAG = "SignKeyActivity"; - - private long pubKeyId = 0; - private long masterKeyId = 0; - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - - case android.R.id.home: - startActivity(new Intent(this, PublicKeyListActivity.class)); - return true; - - default: - break; - - } - return false; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // check we havent already signed it - setContentView(R.layout.sign_key_layout); - - final Spinner keyServer = (Spinner) findViewById(R.id.keyServer); - ArrayAdapter adapter = new ArrayAdapter(this, - android.R.layout.simple_spinner_item, mPreferences.getKeyServers()); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - keyServer.setAdapter(adapter); - - final CheckBox sendKey = (CheckBox) findViewById(R.id.sendKey); - if (!sendKey.isChecked()) { - keyServer.setEnabled(false); - } else { - keyServer.setEnabled(true); - } - - sendKey.setOnCheckedChangeListener(new OnCheckedChangeListener() { - - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (!isChecked) { - keyServer.setEnabled(false); - } else { - keyServer.setEnabled(true); - } - } - }); - - Button sign = (Button) findViewById(R.id.sign); - sign.setEnabled(false); // disabled until the user selects a key to sign with - sign.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if (pubKeyId != 0) { - initiateSigning(); - } - } - }); - - pubKeyId = getIntent().getLongExtra(Apg.EXTRA_KEY_ID, 0); - if (pubKeyId == 0) { - finish(); // nothing to do if we dont know what key to sign - } else { - // kick off the SecretKey selection activity so the user chooses which key to sign with - // first - Intent intent = new Intent(this, SelectSecretKeyListActivity.class); - startActivityForResult(intent, Id.request.secret_keys); - } - } - - /** - * handles the UI bits of the signing process on the UI thread - */ - private void initiateSigning() { - PGPPublicKeyRing pubring = Apg.getPublicKeyRing(pubKeyId); - if (pubring != null) { - // if we have already signed this key, dont bother doing it again - boolean alreadySigned = false; - - @SuppressWarnings("unchecked") - Iterator itr = pubring.getPublicKey(pubKeyId).getSignatures(); - while (itr.hasNext()) { - PGPSignature sig = itr.next(); - if (sig.getKeyID() == masterKeyId) { - alreadySigned = true; - break; - } - } - - if (!alreadySigned) { - /* - * get the user's passphrase for this key (if required) - */ - String passphrase = Apg.getCachedPassPhrase(masterKeyId); - if (passphrase == null) { - showDialog(Id.dialog.pass_phrase); - return; // bail out; need to wait until the user has entered the passphrase - // before trying again - } else { - startSigning(); - } - } else { - final Bundle status = new Bundle(); - Message msg = new Message(); - - status.putString(Apg.EXTRA_ERROR, "Key has already been signed"); - - status.putInt(Constants.extras.STATUS, Id.message.done); - - msg.setData(status); - sendMessage(msg); - - setResult(Id.return_value.error); - finish(); - } - } - } - - @Override - public long getSecretKeyId() { - return masterKeyId; - } - - @Override - public void passPhraseCallback(long keyId, String passPhrase) { - super.passPhraseCallback(keyId, passPhrase); - startSigning(); - } - - /** - * kicks off the actual signing process on a background thread - */ - private void startSigning() { - showDialog(Id.dialog.signing); - startThread(); - } - - @Override - public void run() { - final Bundle status = new Bundle(); - Message msg = new Message(); - - try { - String passphrase = Apg.getCachedPassPhrase(masterKeyId); - if (passphrase == null || passphrase.length() <= 0) { - status.putString(Apg.EXTRA_ERROR, "Unable to obtain passphrase"); - } else { - PGPPublicKeyRing pubring = Apg.getPublicKeyRing(pubKeyId); - - /* - * sign the incoming key - */ - PGPSecretKey secretKey = Apg.getSecretKey(masterKeyId); - PGPPrivateKey signingKey = secretKey.extractPrivateKey(passphrase.toCharArray(), - BouncyCastleProvider.PROVIDER_NAME); - PGPSignatureGenerator sGen = new PGPSignatureGenerator(secretKey.getPublicKey() - .getAlgorithm(), PGPUtil.SHA256, BouncyCastleProvider.PROVIDER_NAME); - sGen.initSign(PGPSignature.DIRECT_KEY, signingKey); - - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - - PGPSignatureSubpacketVector packetVector = spGen.generate(); - sGen.setHashedSubpackets(packetVector); - - PGPPublicKey signedKey = PGPPublicKey.addCertification( - pubring.getPublicKey(pubKeyId), sGen.generate()); - pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey); - - // check if we need to send the key to the server or not - CheckBox sendKey = (CheckBox) findViewById(R.id.sendKey); - if (sendKey.isChecked()) { - Spinner keyServer = (Spinner) findViewById(R.id.keyServer); - HkpKeyServer server = new HkpKeyServer((String) keyServer.getSelectedItem()); - - /* - * upload the newly signed key to the key server - */ - - Apg.uploadKeyRingToServer(server, pubring); - } - - // store the signed key in our local cache - int retval = Apg.storeKeyRingInCache(pubring); - if (retval != Id.return_value.ok && retval != Id.return_value.updated) { - status.putString(Apg.EXTRA_ERROR, "Failed to store signed key in local cache"); - } - } - } catch (PGPException e) { - Log.e(TAG, "Failed to sign key", e); - status.putString(Apg.EXTRA_ERROR, "Failed to sign key"); - status.putInt(Constants.extras.STATUS, Id.message.done); - return; - } catch (NoSuchAlgorithmException e) { - Log.e(TAG, "Failed to sign key", e); - status.putString(Apg.EXTRA_ERROR, "Failed to sign key"); - status.putInt(Constants.extras.STATUS, Id.message.done); - return; - } catch (NoSuchProviderException e) { - Log.e(TAG, "Failed to sign key", e); - status.putString(Apg.EXTRA_ERROR, "Failed to sign key"); - status.putInt(Constants.extras.STATUS, Id.message.done); - return; - } catch (SignatureException e) { - Log.e(TAG, "Failed to sign key", e); - status.putString(Apg.EXTRA_ERROR, "Failed to sign key"); - status.putInt(Constants.extras.STATUS, Id.message.done); - return; - } - - status.putInt(Constants.extras.STATUS, Id.message.done); - - msg.setData(status); - sendMessage(msg); - - if (status.containsKey(Apg.EXTRA_ERROR)) { - setResult(Id.return_value.error); - } else { - setResult(Id.return_value.ok); - } - - finish(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case Id.request.secret_keys: { - if (resultCode == RESULT_OK) { - masterKeyId = data.getLongExtra(Apg.EXTRA_KEY_ID, 0); - - // re-enable the sign button so the user can initiate the sign process - Button sign = (Button) findViewById(R.id.sign); - sign.setEnabled(true); - } - - break; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - } - } - } - - @Override - public void doneCallback(Message msg) { - super.doneCallback(msg); - - removeDialog(Id.dialog.signing); - - Bundle data = msg.getData(); - String error = data.getString(Apg.EXTRA_ERROR); - if (error != null) { - Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT) - .show(); - return; - } - - Toast.makeText(this, R.string.keySignSuccess, Toast.LENGTH_SHORT).show(); - finish(); - } -} diff --git a/org_apg/src/org/apg/ui/widget/DashboardLayout.java b/org_apg/src/org/apg/ui/widget/DashboardLayout.java deleted file mode 100644 index 8cd97ca05..000000000 --- a/org_apg/src/org/apg/ui/widget/DashboardLayout.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2011 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; - -/** - * Custom layout that arranges children in a grid-like manner, optimizing for even horizontal and - * vertical whitespace. - */ -public class DashboardLayout extends ViewGroup { - private static final int UNEVEN_GRID_PENALTY_MULTIPLIER = 10; - - private int mMaxChildWidth = 0; - private int mMaxChildHeight = 0; - - public DashboardLayout(Context context) { - super(context, null); - } - - public DashboardLayout(Context context, AttributeSet attrs) { - super(context, attrs, 0); - } - - public DashboardLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - mMaxChildWidth = 0; - mMaxChildHeight = 0; - - // Measure once to find the maximum child size. - - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST); - int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.AT_MOST); - - final int count = getChildCount(); - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - - mMaxChildWidth = Math.max(mMaxChildWidth, child.getMeasuredWidth()); - mMaxChildHeight = Math.max(mMaxChildHeight, child.getMeasuredHeight()); - } - - // Measure again for each child to be exactly the same size. - - childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxChildWidth, MeasureSpec.EXACTLY); - childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxChildHeight, MeasureSpec.EXACTLY); - - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - } - - setMeasuredDimension(resolveSize(mMaxChildWidth, widthMeasureSpec), - resolveSize(mMaxChildHeight, heightMeasureSpec)); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - int width = r - l; - int height = b - t; - - final int count = getChildCount(); - - // Calculate the number of visible children. - int visibleCount = 0; - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - ++visibleCount; - } - - if (visibleCount == 0) { - return; - } - - // Calculate what number of rows and columns will optimize for even horizontal and - // vertical whitespace between items. Start with a 1 x N grid, then try 2 x N, and so on. - int bestSpaceDifference = Integer.MAX_VALUE; - int spaceDifference; - - // Horizontal and vertical space between items - int hSpace = 0; - int vSpace = 0; - - int cols = 1; - int rows; - - while (true) { - rows = (visibleCount - 1) / cols + 1; - - hSpace = ((width - mMaxChildWidth * cols) / (cols + 1)); - vSpace = ((height - mMaxChildHeight * rows) / (rows + 1)); - - spaceDifference = Math.abs(vSpace - hSpace); - if (rows * cols != visibleCount) { - spaceDifference *= UNEVEN_GRID_PENALTY_MULTIPLIER; - } else if (rows * mMaxChildHeight > height || cols * mMaxChildWidth > width) { - spaceDifference *= UNEVEN_GRID_PENALTY_MULTIPLIER; - } - - if (spaceDifference < bestSpaceDifference) { - // Found a better whitespace squareness/ratio - bestSpaceDifference = spaceDifference; - - // If we found a better whitespace squareness and there's only 1 row, this is - // the best we can do. - if (rows == 1) { - break; - } - } else { - // This is a worse whitespace ratio, use the previous value of cols and exit. - --cols; - rows = (visibleCount - 1) / cols + 1; - hSpace = ((width - mMaxChildWidth * cols) / (cols + 1)); - vSpace = ((height - mMaxChildHeight * rows) / (rows + 1)); - break; - } - - ++cols; - } - - // Lay out children based on calculated best-fit number of rows and cols. - - // If we chose a layout that has negative horizontal or vertical space, force it to zero. - hSpace = Math.max(0, hSpace); - vSpace = Math.max(0, vSpace); - - // Re-use width/height variables to be child width/height. - width = (width - hSpace * (cols + 1)) / cols; - height = (height - vSpace * (rows + 1)) / rows; - - int left, top; - int col, row; - int visibleIndex = 0; - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - if (child.getVisibility() == GONE) { - continue; - } - - row = visibleIndex / cols; - col = visibleIndex % cols; - - left = hSpace * (col + 1) + width * col; - top = vSpace * (row + 1) + height * row; - - child.layout(left, top, (hSpace == 0 && col == cols - 1) ? r : (left + width), - (vSpace == 0 && row == rows - 1) ? b : (top + height)); - ++visibleIndex; - } - } -} \ No newline at end of file diff --git a/org_apg/src/org/apg/ui/widget/Editor.java b/org_apg/src/org/apg/ui/widget/Editor.java deleted file mode 100644 index be95ad656..000000000 --- a/org_apg/src/org/apg/ui/widget/Editor.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui.widget; - -public interface Editor { - public interface EditorListener { - public void onDeleted(Editor editor); - } - - public void setEditorListener(EditorListener listener); -} diff --git a/org_apg/src/org/apg/ui/widget/IntegerListPreference.java b/org_apg/src/org/apg/ui/widget/IntegerListPreference.java deleted file mode 100644 index fa411a786..000000000 --- a/org_apg/src/org/apg/ui/widget/IntegerListPreference.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package org.apg.ui.widget; - -import android.content.Context; -import android.preference.ListPreference; -import android.util.AttributeSet; - -/** - * A list preference which persists its values as integers instead of strings. - * Code reading the values should use - * {@link android.content.SharedPreferences#getInt}. - * When using XML-declared arrays for entry values, the arrays should be regular - * string arrays containing valid integer values. - * - * @author Rodrigo Damazio - */ -public class IntegerListPreference extends ListPreference { - - public IntegerListPreference(Context context) { - super(context); - - verifyEntryValues(null); - } - - public IntegerListPreference(Context context, AttributeSet attrs) { - super(context, attrs); - - verifyEntryValues(null); - } - - @Override - public void setEntryValues(CharSequence[] entryValues) { - CharSequence[] oldValues = getEntryValues(); - super.setEntryValues(entryValues); - verifyEntryValues(oldValues); - } - - @Override - public void setEntryValues(int entryValuesResId) { - CharSequence[] oldValues = getEntryValues(); - super.setEntryValues(entryValuesResId); - verifyEntryValues(oldValues); - } - - @Override - protected String getPersistedString(String defaultReturnValue) { - // During initial load, there's no known default value - int defaultIntegerValue = Integer.MIN_VALUE; - if (defaultReturnValue != null) { - defaultIntegerValue = Integer.parseInt(defaultReturnValue); - } - - // When the list preference asks us to read a string, instead read an - // integer. - int value = getPersistedInt(defaultIntegerValue); - return Integer.toString(value); - } - - @Override - protected boolean persistString(String value) { - // When asked to save a string, instead save an integer - return persistInt(Integer.parseInt(value)); - } - - private void verifyEntryValues(CharSequence[] oldValues) { - CharSequence[] entryValues = getEntryValues(); - if (entryValues == null) { - return; - } - - for (CharSequence entryValue : entryValues) { - try { - Integer.parseInt(entryValue.toString()); - } catch (NumberFormatException nfe) { - super.setEntryValues(oldValues); - throw nfe; - } - } - } -} diff --git a/org_apg/src/org/apg/ui/widget/KeyEditor.java b/org_apg/src/org/apg/ui/widget/KeyEditor.java deleted file mode 100644 index 0e2bd145f..000000000 --- a/org_apg/src/org/apg/ui/widget/KeyEditor.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui.widget; - -import org.apg.Apg; -import org.apg.Id; -import org.apg.util.Choice; -import org.spongycastle.openpgp.PGPPublicKey; -import org.spongycastle.openpgp.PGPSecretKey; -import org.apg.R; - -import android.app.DatePickerDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.DatePicker; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.Spinner; -import android.widget.TextView; - -import java.text.DateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.Vector; - -public class KeyEditor extends LinearLayout implements Editor, OnClickListener { - private PGPSecretKey mKey; - - private EditorListener mEditorListener = null; - - private boolean mIsMasterKey; - ImageButton mDeleteButton; - TextView mAlgorithm; - TextView mKeyId; - Spinner mUsage; - TextView mCreationDate; - Button mExpiryDateButton; - GregorianCalendar mExpiryDate; - - private DatePickerDialog.OnDateSetListener mExpiryDateSetListener = new DatePickerDialog.OnDateSetListener() { - public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { - GregorianCalendar date = new GregorianCalendar(year, monthOfYear, dayOfMonth); - setExpiryDate(date); - } - }; - - public KeyEditor(Context context) { - super(context); - } - - public KeyEditor(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - setDrawingCacheEnabled(true); - setAlwaysDrawnWithCacheEnabled(true); - - mAlgorithm = (TextView) findViewById(R.id.algorithm); - mKeyId = (TextView) findViewById(R.id.keyId); - mCreationDate = (TextView) findViewById(R.id.creation); - mExpiryDateButton = (Button) findViewById(R.id.expiry); - mUsage = (Spinner) findViewById(R.id.usage); - Choice choices[] = { - new Choice(Id.choice.usage.sign_only, getResources().getString( - R.string.choice_signOnly)), - new Choice(Id.choice.usage.encrypt_only, getResources().getString( - R.string.choice_encryptOnly)), - new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString( - R.string.choice_signAndEncrypt)), }; - ArrayAdapter adapter = new ArrayAdapter(getContext(), - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mUsage.setAdapter(adapter); - - mDeleteButton = (ImageButton) findViewById(R.id.delete); - mDeleteButton.setOnClickListener(this); - - setExpiryDate(null); - - mExpiryDateButton.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - GregorianCalendar date = mExpiryDate; - if (date == null) { - date = new GregorianCalendar(); - } - - DatePickerDialog dialog = new DatePickerDialog(getContext(), - mExpiryDateSetListener, date.get(Calendar.YEAR), date.get(Calendar.MONTH), - date.get(Calendar.DAY_OF_MONTH)); - dialog.setCancelable(true); - dialog.setButton(Dialog.BUTTON_NEGATIVE, getContext() - .getString(R.string.btn_noDate), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - setExpiryDate(null); - } - }); - dialog.show(); - } - }); - - super.onFinishInflate(); - } - - public void setValue(PGPSecretKey key, boolean isMasterKey, int usage) { - mKey = key; - - mIsMasterKey = isMasterKey; - if (mIsMasterKey) { - mDeleteButton.setVisibility(View.INVISIBLE); - } - - mAlgorithm.setText(Apg.getAlgorithmInfo(key)); - String keyId1Str = Apg.getSmallFingerPrint(key.getKeyID()); - String keyId2Str = Apg.getSmallFingerPrint(key.getKeyID() >> 32); - mKeyId.setText(keyId1Str + " " + keyId2Str); - - Vector choices = new Vector(); - boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT); - if (!isElGamalKey) { - choices.add(new Choice(Id.choice.usage.sign_only, getResources().getString( - R.string.choice_signOnly))); - } - if (!mIsMasterKey) { - choices.add(new Choice(Id.choice.usage.encrypt_only, getResources().getString( - R.string.choice_encryptOnly))); - } - if (!isElGamalKey) { - choices.add(new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString( - R.string.choice_signAndEncrypt))); - } - - ArrayAdapter adapter = new ArrayAdapter(getContext(), - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mUsage.setAdapter(adapter); - - // Set value in choice dropdown to key - int selectId = 0; - if (Apg.isEncryptionKey(key)) { - if (Apg.isSigningKey(key)) { - selectId = Id.choice.usage.sign_and_encrypt; - } else { - selectId = Id.choice.usage.encrypt_only; - } - } else { - // set usage if it is predefined - if (usage != -1) { - selectId = usage; - } else { - selectId = Id.choice.usage.sign_only; - } - - } - - for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == selectId) { - mUsage.setSelection(i); - break; - } - } - - GregorianCalendar cal = new GregorianCalendar(); - cal.setTime(Apg.getCreationDate(key)); - mCreationDate.setText(DateFormat.getDateInstance().format(cal.getTime())); - cal = new GregorianCalendar(); - Date date = Apg.getExpiryDate(key); - if (date == null) { - setExpiryDate(null); - } else { - cal.setTime(Apg.getExpiryDate(key)); - setExpiryDate(cal); - } - - } - - public PGPSecretKey getValue() { - return mKey; - } - - public void onClick(View v) { - final ViewGroup parent = (ViewGroup) getParent(); - if (v == mDeleteButton) { - parent.removeView(this); - if (mEditorListener != null) { - mEditorListener.onDeleted(this); - } - } - } - - public void setEditorListener(EditorListener listener) { - mEditorListener = listener; - } - - private void setExpiryDate(GregorianCalendar date) { - mExpiryDate = date; - if (date == null) { - mExpiryDateButton.setText(R.string.none); - } else { - mExpiryDateButton.setText(DateFormat.getDateInstance().format(date.getTime())); - } - } - - public GregorianCalendar getExpiryDate() { - return mExpiryDate; - } - - public int getUsage() { - return ((Choice) mUsage.getSelectedItem()).getId(); - } - -} diff --git a/org_apg/src/org/apg/ui/widget/KeyServerEditor.java b/org_apg/src/org/apg/ui/widget/KeyServerEditor.java deleted file mode 100644 index 3d8634c76..000000000 --- a/org_apg/src/org/apg/ui/widget/KeyServerEditor.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui.widget; - -import org.apg.R; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.TextView; - -public class KeyServerEditor extends LinearLayout implements Editor, OnClickListener { - private EditorListener mEditorListener = null; - - ImageButton mDeleteButton; - TextView mServer; - - public KeyServerEditor(Context context) { - super(context); - } - - public KeyServerEditor(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - setDrawingCacheEnabled(true); - setAlwaysDrawnWithCacheEnabled(true); - - mServer = (TextView) findViewById(R.id.server); - - mDeleteButton = (ImageButton) findViewById(R.id.delete); - mDeleteButton.setOnClickListener(this); - - super.onFinishInflate(); - } - - public void setValue(String value) { - mServer.setText(value); - } - - public String getValue() { - return mServer.getText().toString().trim(); - } - - public void onClick(View v) { - final ViewGroup parent = (ViewGroup)getParent(); - if (v == mDeleteButton) { - parent.removeView(this); - if (mEditorListener != null) { - mEditorListener.onDeleted(this); - } - } - } - - public void setEditorListener(EditorListener listener) { - mEditorListener = listener; - } -} diff --git a/org_apg/src/org/apg/ui/widget/SectionView.java b/org_apg/src/org/apg/ui/widget/SectionView.java deleted file mode 100644 index 556164ca8..000000000 --- a/org_apg/src/org/apg/ui/widget/SectionView.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui.widget; - -import org.apg.Apg; -import org.apg.Id; -import org.apg.ui.widget.Editor.EditorListener; -import org.apg.util.Choice; -import org.spongycastle.openpgp.PGPException; -import org.spongycastle.openpgp.PGPSecretKey; -import org.apg.R; - -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.Spinner; -import android.widget.TableRow; -import android.widget.TextView; -import android.widget.Toast; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidParameterException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.util.Vector; - -public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Runnable { - private LayoutInflater mInflater; - private View mAdd; - private ViewGroup mEditors; - private TextView mTitle; - private int mType = 0; - - private Choice mNewKeyAlgorithmChoice; - private int mNewKeySize; - - volatile private PGPSecretKey mNewKey; - private ProgressDialog mProgressDialog; - private Thread mRunningThread = null; - - private Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - Bundle data = msg.getData(); - if (data != null) { - boolean closeProgressDialog = data.getBoolean("closeProgressDialog"); - if (closeProgressDialog) { - if (mProgressDialog != null) { - mProgressDialog.dismiss(); - mProgressDialog = null; - } - } - - String error = data.getString(Apg.EXTRA_ERROR); - if (error != null) { - Toast.makeText(getContext(), - getContext().getString(R.string.errorMessage, error), - Toast.LENGTH_SHORT).show(); - } - - boolean gotNewKey = data.getBoolean("gotNewKey"); - if (gotNewKey) { - KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, - mEditors, false); - view.setEditorListener(SectionView.this); - boolean isMasterKey = (mEditors.getChildCount() == 0); - view.setValue(mNewKey, isMasterKey, -1); - mEditors.addView(view); - SectionView.this.updateEditorsVisible(); - } - } - } - }; - - public SectionView(Context context) { - super(context); - } - - public SectionView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public ViewGroup getEditors() { - return mEditors; - } - - public void setType(int type) { - mType = type; - switch (type) { - case Id.type.user_id: { - mTitle.setText(R.string.section_userIds); - break; - } - - case Id.type.key: { - mTitle.setText(R.string.section_keys); - break; - } - - default: { - break; - } - } - } - - /** {@inheritDoc} */ - @Override - protected void onFinishInflate() { - mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - setDrawingCacheEnabled(true); - setAlwaysDrawnWithCacheEnabled(true); - - mAdd = findViewById(R.id.header); - mAdd.setOnClickListener(this); - - mEditors = (ViewGroup) findViewById(R.id.editors); - mTitle = (TextView) findViewById(R.id.title); - - updateEditorsVisible(); - super.onFinishInflate(); - } - - /** {@inheritDoc} */ - public void onDeleted(Editor editor) { - this.updateEditorsVisible(); - } - - protected void updateEditorsVisible() { - final boolean hasChildren = mEditors.getChildCount() > 0; - mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE); - } - - /** {@inheritDoc} */ - public void onClick(View v) { - switch (mType) { - case Id.type.user_id: { - UserIdEditor view = (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item, - mEditors, false); - view.setEditorListener(this); - if (mEditors.getChildCount() == 0) { - view.setIsMainUserId(true); - } - mEditors.addView(view); - break; - } - - case Id.type.key: { - AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); - - View view = mInflater.inflate(R.layout.create_key, null); - dialog.setView(view); - dialog.setTitle(R.string.title_createKey); - - boolean wouldBeMasterKey = (mEditors.getChildCount() == 0); - - final Spinner algorithm = (Spinner) view.findViewById(R.id.create_key_algorithm); - Vector choices = new Vector(); - choices.add(new Choice(Id.choice.algorithm.dsa, getResources().getString(R.string.dsa))); - if (!wouldBeMasterKey) { - choices.add(new Choice(Id.choice.algorithm.elgamal, getResources().getString( - R.string.elgamal))); - } - - choices.add(new Choice(Id.choice.algorithm.rsa, getResources().getString(R.string.rsa))); - - ArrayAdapter adapter = new ArrayAdapter(getContext(), - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - algorithm.setAdapter(adapter); - // make RSA the default - for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == Id.choice.algorithm.rsa) { - algorithm.setSelection(i); - break; - } - } - - final EditText keySize = (EditText) view.findViewById(R.id.create_key_size); - - dialog.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface di, int id) { - di.dismiss(); - try { - mNewKeySize = Integer.parseInt("" + keySize.getText()); - } catch (NumberFormatException e) { - mNewKeySize = 0; - } - - mNewKeyAlgorithmChoice = (Choice) algorithm.getSelectedItem(); - createKey(); - } - }); - - dialog.setCancelable(true); - dialog.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface di, int id) { - di.dismiss(); - } - }); - - dialog.create().show(); - break; - } - - default: { - break; - } - } - this.updateEditorsVisible(); - } - - public void setUserIds(Vector list) { - if (mType != Id.type.user_id) { - return; - } - - mEditors.removeAllViews(); - for (String userId : list) { - UserIdEditor view = (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item, - mEditors, false); - view.setEditorListener(this); - view.setValue(userId); - if (mEditors.getChildCount() == 0) { - view.setIsMainUserId(true); - } - mEditors.addView(view); - } - - this.updateEditorsVisible(); - } - - public void setKeys(Vector list, Vector usages) { - if (mType != Id.type.key) { - return; - } - - mEditors.removeAllViews(); - - // go through all keys and set view based on them - for (int i = 0; i < list.size(); i++) { - KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, mEditors, - false); - view.setEditorListener(this); - boolean isMasterKey = (mEditors.getChildCount() == 0); - view.setValue(list.get(i), isMasterKey, usages.get(i)); - mEditors.addView(view); - } - - this.updateEditorsVisible(); - } - - private void createKey() { - mProgressDialog = new ProgressDialog(getContext()); - mProgressDialog.setMessage(getContext().getString(R.string.progress_generating)); - mProgressDialog.setCancelable(false); - mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); - mProgressDialog.show(); - mRunningThread = new Thread(this); - mRunningThread.start(); - } - - public void run() { - String error = null; - try { - PGPSecretKey masterKey = null; - String passPhrase; - if (mEditors.getChildCount() > 0) { - masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue(); - passPhrase = Apg.getCachedPassPhrase(masterKey.getKeyID()); - } else { - passPhrase = ""; - } - mNewKey = Apg.createKey(getContext(), mNewKeyAlgorithmChoice.getId(), mNewKeySize, - passPhrase, masterKey); - } catch (NoSuchProviderException e) { - error = "" + e; - } catch (NoSuchAlgorithmException e) { - error = "" + e; - } catch (PGPException e) { - error = "" + e; - } catch (InvalidParameterException e) { - error = "" + e; - } catch (InvalidAlgorithmParameterException e) { - error = "" + e; - } catch (Apg.GeneralException e) { - error = "" + e; - } - - Message message = new Message(); - Bundle data = new Bundle(); - data.putBoolean("closeProgressDialog", true); - if (error != null) { - data.putString(Apg.EXTRA_ERROR, error); - } else { - data.putBoolean("gotNewKey", true); - } - message.setData(data); - mHandler.sendMessage(message); - } -} diff --git a/org_apg/src/org/apg/ui/widget/SelectPublicKeyListAdapter.java b/org_apg/src/org/apg/ui/widget/SelectPublicKeyListAdapter.java deleted file mode 100644 index b98a76661..000000000 --- a/org_apg/src/org/apg/ui/widget/SelectPublicKeyListAdapter.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui.widget; - -import java.util.Date; - -import org.apg.Apg; -import org.apg.Id; -import org.apg.provider.KeyRings; -import org.apg.provider.Keys; -import org.apg.provider.UserIds; -import org.apg.R; - -import android.app.Activity; -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.CheckBox; -import android.widget.ListView; -import android.widget.TextView; - -public class SelectPublicKeyListAdapter extends BaseAdapter { - protected LayoutInflater mInflater; - protected ListView mParent; - protected SQLiteDatabase mDatabase; - protected Cursor mCursor; - protected String mSearchString; - protected Activity mActivity; - - public SelectPublicKeyListAdapter(Activity activity, ListView parent, String searchString, - long selectedKeyIds[]) { - mSearchString = searchString; - - mActivity = activity; - mParent = parent; - mDatabase = Apg.getDatabase().db(); - mInflater = (LayoutInflater) parent.getContext().getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - long now = new Date().getTime() / 1000; - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + "(" - + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "." - + Keys.KEY_RING_ID + " AND " + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY - + " = '1'" + ") " + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + "(" - + Keys.TABLE_NAME + "." + Keys._ID + " = " + UserIds.TABLE_NAME + "." - + UserIds.KEY_ID + " AND " + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); - - String inIdList = null; - - if (selectedKeyIds != null && selectedKeyIds.length > 0) { - inIdList = KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID + " IN ("; - for (int i = 0; i < selectedKeyIds.length; ++i) { - if (i != 0) { - inIdList += ", "; - } - inIdList += DatabaseUtils.sqlEscapeString("" + selectedKeyIds[i]); - } - inIdList += ")"; - } - - if (searchString != null && searchString.trim().length() > 0) { - String[] chunks = searchString.trim().split(" +"); - qb.appendWhere("(EXISTS (SELECT tmp." + UserIds._ID + " FROM " + UserIds.TABLE_NAME - + " AS tmp WHERE " + "tmp." + UserIds.KEY_ID + " = " + Keys.TABLE_NAME + "." - + Keys._ID); - for (int i = 0; i < chunks.length; ++i) { - qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE "); - qb.appendWhereEscapeString("%" + chunks[i] + "%"); - } - qb.appendWhere("))"); - - if (inIdList != null) { - qb.appendWhere(" OR (" + inIdList + ")"); - } - } - - String orderBy = UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC"; - if (inIdList != null) { - orderBy = inIdList + " DESC, " + orderBy; - } - - mCursor = qb.query(mDatabase, new String[] { - KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0 - KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1 - UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2 - "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " - + "tmp." + Keys.KEY_RING_ID + " = " + KeyRings.TABLE_NAME + "." - + KeyRings._ID + " AND " + "tmp." + Keys.IS_REVOKED + " = '0' AND " - + "tmp." + Keys.CAN_ENCRYPT + " = '1')", // 3 - "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " - + "tmp." + Keys.KEY_RING_ID + " = " + KeyRings.TABLE_NAME + "." - + KeyRings._ID + " AND " + "tmp." + Keys.IS_REVOKED + " = '0' AND " - + "tmp." + Keys.CAN_ENCRYPT + " = '1' AND " + "tmp." + Keys.CREATION - + " <= '" + now + "' AND " + "(tmp." + Keys.EXPIRY + " IS NULL OR " - + "tmp." + Keys.EXPIRY + " >= '" + now + "'))", // 4 - }, KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", new String[] { "" - + Id.database.type_public }, null, null, orderBy); - - activity.startManagingCursor(mCursor); - } - - public void cleanup() { - if (mCursor != null) { - mActivity.stopManagingCursor(mCursor); - mCursor.close(); - } - } - - @Override - public boolean isEnabled(int position) { - mCursor.moveToPosition(position); - return mCursor.getInt(4) > 0; // valid CAN_ENCRYPT - } - - @Override - public boolean hasStableIds() { - return true; - } - - public int getCount() { - return mCursor.getCount(); - } - - public Object getItem(int position) { - mCursor.moveToPosition(position); - return mCursor.getString(2); // USER_ID - } - - public long getItemId(int position) { - mCursor.moveToPosition(position); - return mCursor.getLong(1); // MASTER_KEY_ID - } - - public View getView(int position, View convertView, ViewGroup parent) { - mCursor.moveToPosition(position); - - View view = mInflater.inflate(R.layout.select_public_key_item, null); - boolean enabled = isEnabled(position); - - TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); - mainUserId.setText(R.string.unknownUserId); - TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - mainUserIdRest.setText(""); - TextView keyId = (TextView) view.findViewById(R.id.keyId); - keyId.setText(R.string.noKey); - TextView status = (TextView) view.findViewById(R.id.status); - status.setText(R.string.unknownStatus); - - String userId = mCursor.getString(2); // USER_ID - if (userId != null) { - String chunks[] = userId.split(" <", 2); - userId = chunks[0]; - if (chunks.length > 1) { - mainUserIdRest.setText("<" + chunks[1]); - } - mainUserId.setText(userId); - } - - long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID - keyId.setText(Apg.getSmallFingerPrint(masterKeyId)); - - if (mainUserIdRest.getText().length() == 0) { - mainUserIdRest.setVisibility(View.GONE); - } - - if (enabled) { - status.setText(R.string.canEncrypt); - } else { - if (mCursor.getInt(3) > 0) { - // has some CAN_ENCRYPT keys, but col(4) = 0, so must be revoked or expired - status.setText(R.string.expired); - } else { - status.setText(R.string.noKey); - } - } - - status.setText(status.getText() + " "); - - CheckBox selected = (CheckBox) view.findViewById(R.id.selected); - - if (!enabled) { - mParent.setItemChecked(position, false); - } - - selected.setChecked(mParent.isItemChecked(position)); - - view.setEnabled(enabled); - mainUserId.setEnabled(enabled); - mainUserIdRest.setEnabled(enabled); - keyId.setEnabled(enabled); - selected.setEnabled(enabled); - status.setEnabled(enabled); - - return view; - } -} diff --git a/org_apg/src/org/apg/ui/widget/SelectSecretKeyListAdapter.java b/org_apg/src/org/apg/ui/widget/SelectSecretKeyListAdapter.java deleted file mode 100644 index 1b6d115b7..000000000 --- a/org_apg/src/org/apg/ui/widget/SelectSecretKeyListAdapter.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui.widget; - -import java.util.Date; - -import org.apg.Apg; -import org.apg.Id; -import org.apg.provider.KeyRings; -import org.apg.provider.Keys; -import org.apg.provider.UserIds; -import org.apg.R; - -import android.app.Activity; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ListView; -import android.widget.TextView; - -public class SelectSecretKeyListAdapter extends BaseAdapter { - protected LayoutInflater mInflater; - protected ListView mParent; - protected SQLiteDatabase mDatabase; - protected Cursor mCursor; - protected String mSearchString; - protected Activity mActivity; - - public SelectSecretKeyListAdapter(Activity activity, ListView parent, String searchString) { - mSearchString = searchString; - - mActivity = activity; - mParent = parent; - mDatabase = Apg.getDatabase().db(); - mInflater = (LayoutInflater) parent.getContext().getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - long now = new Date().getTime() / 1000; - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + "(" - + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "." - + Keys.KEY_RING_ID + " AND " + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY - + " = '1'" + ") " + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + "(" - + Keys.TABLE_NAME + "." + Keys._ID + " = " + UserIds.TABLE_NAME + "." - + UserIds.KEY_ID + " AND " + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); - - if (searchString != null && searchString.trim().length() > 0) { - String[] chunks = searchString.trim().split(" +"); - qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " + UserIds.TABLE_NAME - + " AS tmp WHERE " + "tmp." + UserIds.KEY_ID + " = " + Keys.TABLE_NAME + "." - + Keys._ID); - for (int i = 0; i < chunks.length; ++i) { - qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE "); - qb.appendWhereEscapeString("%" + chunks[i] + "%"); - } - qb.appendWhere(")"); - } - - mCursor = qb.query(mDatabase, new String[] { - KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0 - KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1 - UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2 - "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " - + "tmp." + Keys.KEY_RING_ID + " = " + KeyRings.TABLE_NAME + "." - + KeyRings._ID + " AND " + "tmp." + Keys.IS_REVOKED + " = '0' AND " - + "tmp." + Keys.CAN_SIGN + " = '1')", // 3, - "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " - + "tmp." + Keys.KEY_RING_ID + " = " + KeyRings.TABLE_NAME + "." - + KeyRings._ID + " AND " + "tmp." + Keys.IS_REVOKED + " = '0' AND " - + "tmp." + Keys.CAN_SIGN + " = '1' AND " + "tmp." + Keys.CREATION + " <= '" - + now + "' AND " + "(tmp." + Keys.EXPIRY + " IS NULL OR " + "tmp." - + Keys.EXPIRY + " >= '" + now + "'))", // 4 - }, KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", new String[] { "" - + Id.database.type_secret }, null, null, UserIds.TABLE_NAME + "." + UserIds.USER_ID - + " ASC"); - - activity.startManagingCursor(mCursor); - } - - public void cleanup() { - if (mCursor != null) { - mActivity.stopManagingCursor(mCursor); - mCursor.close(); - } - } - - @Override - public boolean isEnabled(int position) { - mCursor.moveToPosition(position); - return mCursor.getInt(4) > 0; // valid CAN_SIGN - } - - @Override - public boolean hasStableIds() { - return true; - } - - public int getCount() { - return mCursor.getCount(); - } - - public Object getItem(int position) { - mCursor.moveToPosition(position); - return mCursor.getString(2); // USER_ID - } - - public long getItemId(int position) { - mCursor.moveToPosition(position); - return mCursor.getLong(1); // MASTER_KEY_ID - } - - public View getView(int position, View convertView, ViewGroup parent) { - mCursor.moveToPosition(position); - - View view = mInflater.inflate(R.layout.select_secret_key_item, null); - boolean enabled = isEnabled(position); - - TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); - mainUserId.setText(R.string.unknownUserId); - TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); - mainUserIdRest.setText(""); - TextView keyId = (TextView) view.findViewById(R.id.keyId); - keyId.setText(R.string.noKey); - TextView status = (TextView) view.findViewById(R.id.status); - status.setText(R.string.unknownStatus); - - String userId = mCursor.getString(2); // USER_ID - if (userId != null) { - String chunks[] = userId.split(" <", 2); - userId = chunks[0]; - if (chunks.length > 1) { - mainUserIdRest.setText("<" + chunks[1]); - } - mainUserId.setText(userId); - } - - long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID - keyId.setText(Apg.getSmallFingerPrint(masterKeyId)); - - if (mainUserIdRest.getText().length() == 0) { - mainUserIdRest.setVisibility(View.GONE); - } - - if (enabled) { - status.setText(R.string.canSign); - } else { - if (mCursor.getInt(3) > 0) { - // has some CAN_SIGN keys, but col(4) = 0, so must be revoked or expired - status.setText(R.string.expired); - } else { - status.setText(R.string.noKey); - } - } - - status.setText(status.getText() + " "); - - view.setEnabled(enabled); - mainUserId.setEnabled(enabled); - mainUserIdRest.setEnabled(enabled); - keyId.setEnabled(enabled); - status.setEnabled(enabled); - - return view; - } -} \ No newline at end of file diff --git a/org_apg/src/org/apg/ui/widget/UserIdEditor.java b/org_apg/src/org/apg/ui/widget/UserIdEditor.java deleted file mode 100644 index ce5594c8e..000000000 --- a/org_apg/src/org/apg/ui/widget/UserIdEditor.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.ui.widget; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apg.R; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.RadioButton; - -public class UserIdEditor extends LinearLayout implements Editor, OnClickListener { - private EditorListener mEditorListener = null; - - private ImageButton mDeleteButton; - private RadioButton mIsMainUserId; - private EditText mName; - private EditText mEmail; - private EditText mComment; - - // see http://www.regular-expressions.info/email.html - // RFC 2822 if we omit the syntax using double quotes and square brackets - private static final Pattern EMAIL_PATTERN = Pattern - .compile( - "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", - Pattern.CASE_INSENSITIVE); - - public static class NoNameException extends Exception { - static final long serialVersionUID = 0xf812773343L; - - public NoNameException(String message) { - super(message); - } - } - - public static class NoEmailException extends Exception { - static final long serialVersionUID = 0xf812773344L; - - public NoEmailException(String message) { - super(message); - } - } - - public static class InvalidEmailException extends Exception { - static final long serialVersionUID = 0xf812773345L; - - public InvalidEmailException(String message) { - super(message); - } - } - - public UserIdEditor(Context context) { - super(context); - } - - public UserIdEditor(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - setDrawingCacheEnabled(true); - setAlwaysDrawnWithCacheEnabled(true); - - mDeleteButton = (ImageButton) findViewById(R.id.delete); - mDeleteButton.setOnClickListener(this); - mIsMainUserId = (RadioButton) findViewById(R.id.isMainUserId); - mIsMainUserId.setOnClickListener(this); - - mName = (EditText) findViewById(R.id.name); - mEmail = (EditText) findViewById(R.id.email); - mComment = (EditText) findViewById(R.id.comment); - - super.onFinishInflate(); - } - - public void setValue(String userId) { - mName.setText(""); - mComment.setText(""); - mEmail.setText(""); - - Pattern withComment = Pattern.compile("^(.*) [(](.*)[)] <(.*)>$"); - Matcher matcher = withComment.matcher(userId); - if (matcher.matches()) { - mName.setText(matcher.group(1)); - mComment.setText(matcher.group(2)); - mEmail.setText(matcher.group(3)); - return; - } - - Pattern withoutComment = Pattern.compile("^(.*) <(.*)>$"); - matcher = withoutComment.matcher(userId); - if (matcher.matches()) { - mName.setText(matcher.group(1)); - mEmail.setText(matcher.group(2)); - return; - } - } - - public String getValue() throws NoNameException, NoEmailException, InvalidEmailException { - String name = ("" + mName.getText()).trim(); - String email = ("" + mEmail.getText()).trim(); - String comment = ("" + mComment.getText()).trim(); - - if (email.length() > 0) { - Matcher emailMatcher = EMAIL_PATTERN.matcher(email); - if (!emailMatcher.matches()) { - throw new InvalidEmailException(getContext().getString(R.string.error_invalidEmail, - email)); - } - } - - String userId = name; - if (comment.length() > 0) { - userId += " (" + comment + ")"; - } - if (email.length() > 0) { - userId += " <" + email + ">"; - } - - if (userId.equals("")) { - // ok, empty one... - return userId; - } - - // otherwise make sure that name and email exist - if (name.equals("")) { - throw new NoNameException("need a name"); - } - - if (email.equals("")) { - throw new NoEmailException("need an email"); - } - - return userId; - } - - public void onClick(View v) { - final ViewGroup parent = (ViewGroup) getParent(); - if (v == mDeleteButton) { - boolean wasMainUserId = mIsMainUserId.isChecked(); - parent.removeView(this); - if (mEditorListener != null) { - mEditorListener.onDeleted(this); - } - if (wasMainUserId && parent.getChildCount() > 0) { - UserIdEditor editor = (UserIdEditor) parent.getChildAt(0); - editor.setIsMainUserId(true); - } - } else if (v == mIsMainUserId) { - for (int i = 0; i < parent.getChildCount(); ++i) { - UserIdEditor editor = (UserIdEditor) parent.getChildAt(i); - if (editor == this) { - editor.setIsMainUserId(true); - } else { - editor.setIsMainUserId(false); - } - } - } - } - - public void setIsMainUserId(boolean value) { - mIsMainUserId.setChecked(value); - } - - public boolean isMainUserId() { - return mIsMainUserId.isChecked(); - } - - public void setEditorListener(EditorListener listener) { - mEditorListener = listener; - } -} diff --git a/org_apg/src/org/apg/util/ApgCon.java b/org_apg/src/org/apg/util/ApgCon.java deleted file mode 100644 index fb57f7eea..000000000 --- a/org_apg/src/org/apg/util/ApgCon.java +++ /dev/null @@ -1,836 +0,0 @@ -/* - * Copyright (C) 2011 Markus Doits - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.util; - -import org.apg.util.ApgConInterface.OnCallFinishListener; -import org.apg.IApgService; - -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.PackageManager; -import android.content.pm.ServiceInfo; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.IBinder; -import android.util.Log; - -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; - -/** - * A APG-AIDL-Wrapper - * - *

- * This class can be used by other projects to simplify connecting to the - * APG-AIDL-Service. Kind of wrapper of for AIDL. - *

- * - *

- * It is not used in this project. - *

- * - * @author Markus Doits - * @version 1.1rc1 - * - */ -public class ApgCon { - private static final boolean LOCAL_LOGV = true; - private static final boolean LOCAL_LOGD = true; - - private final static String TAG = "ApgCon"; - private final static int API_VERSION = 2; // aidl api-version it expects - private final static String BLOB_URI = "content://org.apg.provider.apgserviceblobprovider"; - - /** - * How many seconds to wait for a connection to AGP when connecting. - * Being unsuccessful for this number of seconds, a connection - * is assumed to be failed. - */ - public int secondsToWaitForConnection = 15; - - private class CallAsync extends AsyncTask { - - @Override - protected Void doInBackground(String... arg) { - if( LOCAL_LOGD ) Log.d(TAG, "Async execution starting"); - call(arg[0]); - return null; - } - - protected void onPostExecute(Void res) { - if( LOCAL_LOGD ) Log.d(TAG, "Async execution finished"); - mAsyncRunning = false; - - } - - } - - private final Context mContext; - private final error mConnectionStatus; - private boolean mAsyncRunning = false; - private OnCallFinishListener mOnCallFinishListener; - - private final Bundle mResult = new Bundle(); - private final Bundle mArgs = new Bundle(); - private final ArrayList mErrorList = new ArrayList(); - private final ArrayList mWarningList = new ArrayList(); - - /** Remote service for decrypting and encrypting data */ - private IApgService mApgService = null; - - /** Set apgService accordingly to connection status */ - private ServiceConnection mApgConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - if( LOCAL_LOGD ) Log.d(TAG, "IApgService bound to apgService"); - mApgService = IApgService.Stub.asInterface(service); - } - - public void onServiceDisconnected(ComponentName className) { - if( LOCAL_LOGD ) Log.d(TAG, "IApgService disconnected"); - mApgService = null; - } - }; - - /** - * Different types of local errors - */ - public static enum error { - /** - * no error - */ - NO_ERROR, - /** - * generic error - */ - GENERIC, - /** - * connection to apg service not possible - */ - CANNOT_BIND_TO_APG, - /** - * function to call not provided - */ - CALL_MISSING, - /** - * apg service does not know what to do - */ - CALL_NOT_KNOWN, - /** - * could not find APG being installed - */ - APG_NOT_FOUND, - /** - * found APG but without AIDL interface - */ - APG_AIDL_MISSING, - /** - * found APG but with wrong API - */ - APG_API_MISSMATCH - } - - private static enum ret { - ERROR, // returned from AIDL - RESULT, // returned from AIDL - WARNINGS, // mixed AIDL and LOCAL - ERRORS, // mixed AIDL and LOCAL - } - - /** - * Constructor - * - *

- * Creates a new ApgCon object and searches for the right APG version on - * initialization. If not found, errors are printed to the error log. - *

- * - * @param ctx - * the running context - */ - public ApgCon(Context ctx) { - if( LOCAL_LOGV ) Log.v(TAG, "EncryptionService created"); - mContext = ctx; - - error tmpError = null; - try { - if( LOCAL_LOGV ) Log.v(TAG, "Searching for the right APG version"); - ServiceInfo apgServices[] = ctx.getPackageManager().getPackageInfo("org.apg", - PackageManager.GET_SERVICES | PackageManager.GET_META_DATA).services; - if (apgServices == null) { - Log.e(TAG, "Could not fetch services"); - tmpError = error.GENERIC; - } else { - boolean apgServiceFound = false; - for (ServiceInfo inf : apgServices) { - if( LOCAL_LOGV ) Log.v(TAG, "Found service of APG: " + inf.name); - if (inf.name.equals("org.apg.ApgService")) { - apgServiceFound = true; - if (inf.metaData == null) { - Log.w(TAG, "Could not determine ApgService API"); - Log.w(TAG, "This probably won't work!"); - mWarningList.add("(LOCAL) Could not determine ApgService API"); - tmpError = error.APG_API_MISSMATCH; - } else if (inf.metaData.getInt("api_version") != API_VERSION) { - Log.w(TAG, "Found ApgService API version " + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION); - Log.w(TAG, "This probably won't work!"); - mWarningList.add("(LOCAL) Found ApgService API version " + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION); - tmpError = error.APG_API_MISSMATCH; - } else { - if( LOCAL_LOGV ) Log.v(TAG, "Found api_version " + API_VERSION + ", everything should work"); - tmpError = error.NO_ERROR; - } - } - } - - if (!apgServiceFound) { - Log.e(TAG, "Could not find APG with AIDL interface, this probably won't work"); - mErrorList.add("(LOCAL) Could not find APG with AIDL interface, this probably won't work"); - mResult.putInt(ret.ERROR.name(), error.APG_AIDL_MISSING.ordinal()); - tmpError = error.APG_NOT_FOUND; - } - } - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Could not find APG, is it installed?", e); - mErrorList.add("(LOCAL) Could not find APG, is it installed?"); - mResult.putInt(ret.ERROR.name(), error.APG_NOT_FOUND.ordinal()); - tmpError = error.APG_NOT_FOUND; - } - - mConnectionStatus = tmpError; - - } - - /** try to connect to the apg service */ - private boolean connect() { - if( LOCAL_LOGV ) Log.v(TAG, "trying to bind the apgService to context"); - - if (mApgService != null) { - if( LOCAL_LOGV ) Log.v(TAG, "allready connected"); - return true; - } - - try { - mContext.bindService(new Intent(IApgService.class.getName()), mApgConnection, Context.BIND_AUTO_CREATE); - } catch (Exception e) { - Log.e(TAG, "could not bind APG service", e); - return false; - } - - int waitCount = 0; - while (mApgService == null && waitCount++ < secondsToWaitForConnection) { - if( LOCAL_LOGV ) Log.v(TAG, "sleeping 1 second to wait for apg"); - android.os.SystemClock.sleep(1000); - } - - if (waitCount >= secondsToWaitForConnection) { - if( LOCAL_LOGV ) Log.v(TAG, "slept waiting for nothing!"); - return false; - } - - return true; - } - - /** - * Disconnects ApgCon from Apg - * - *

- * This should be called whenever all work with APG is done (e.g. everything - * you wanted to encrypt is encrypted), since connections with AIDL should - * not be upheld indefinitely. - *

- * - *

- * Also, if you destroy you end using your ApgCon-instance, this must be - * called or else the connection to APG is leaked - *

- */ - public void disconnect() { - if( LOCAL_LOGV ) Log.v(TAG, "disconnecting apgService"); - if (mApgService != null) { - mContext.unbindService(mApgConnection); - mApgService = null; - } - } - - private boolean initialize() { - if (mApgService == null) { - if (!connect()) { - if( LOCAL_LOGV ) Log.v(TAG, "connection to apg service failed"); - return false; - } - } - return true; - } - - /** - * Calls a function from APG's AIDL-interface - * - *

- * After you have set up everything with {@link #setArg(String, String)} - * (and variants), you can call a function of the AIDL-interface. This - * will: - *

    - *
  • start connection to the remote interface (if not already connected)
  • - *
  • call the function passed with all parameters synchronously
  • - *
  • set up everything to retrieve the result and/or warnings/errors
  • - *
  • call the callback if provided - *
- *

- * - *

- * Note your thread will be blocked during execution - if you want to call - * the function asynchronously, see {@link #callAsync(String)}. - *

- * - * @param function - * a remote function to call - * @return true, if call successful (= no errors), else false - * - * @see #callAsync(String) - * @see #setArg(String, String) - * @see #setOnCallFinishListener(OnCallFinishListener) - */ - public boolean call(String function) { - boolean success = this.call(function, mArgs, mResult); - if (mOnCallFinishListener != null) { - try { - if( LOCAL_LOGD ) Log.d(TAG, "About to execute callback"); - mOnCallFinishListener.onCallFinish(mResult); - if( LOCAL_LOGD ) Log.d(TAG, "Callback executed"); - } catch (Exception e) { - Log.w(TAG, "Exception on callback: (" + e.getClass() + ") " + e.getMessage(), e); - mWarningList.add("(LOCAL) Could not execute callback (" + e.getClass() + "): " + e.getMessage()); - } - } - return success; - } - - /** - * Calls a function of remote interface asynchronously - * - *

- * This does exactly the same as {@link #call(String)}, but asynchronously. - * While connection to APG and work are done in background, your thread can - * go on executing. - *

- * - *

- * To see whether the task is finished, you have two possibilities: - *

    - *
  • In your thread, poll {@link #isRunning()}
  • - *
  • Supply a callback with {@link #setOnCallFinishListener(OnCallFinishListener)}
  • - *
- *

- * - * @param function - * a remote function to call - * - * @see #call(String) - * @see #isRunning() - * @see #setOnCallFinishListener(OnCallFinishListener) - */ - public void callAsync(String function) { - mAsyncRunning = true; - new CallAsync().execute(function); - } - - private boolean call(String function, Bundle pArgs, Bundle pReturn) { - - if (!initialize()) { - mErrorList.add("(LOCAL) Cannot bind to ApgService"); - mResult.putInt(ret.ERROR.name(), error.CANNOT_BIND_TO_APG.ordinal()); - return false; - } - - if (function == null || function.length() == 0) { - mErrorList.add("(LOCAL) Function to call missing"); - mResult.putInt(ret.ERROR.name(), error.CALL_MISSING.ordinal()); - return false; - } - - try { - Boolean success = (Boolean) IApgService.class.getMethod(function, Bundle.class, Bundle.class).invoke(mApgService, pArgs, pReturn); - mErrorList.addAll(pReturn.getStringArrayList(ret.ERRORS.name())); - mWarningList.addAll(pReturn.getStringArrayList(ret.WARNINGS.name())); - return success; - } catch (NoSuchMethodException e) { - Log.e(TAG, "Remote call not known (" + function + "): " + e.getMessage(), e); - mErrorList.add("(LOCAL) Remote call not known (" + function + "): " + e.getMessage()); - mResult.putInt(ret.ERROR.name(), error.CALL_NOT_KNOWN.ordinal()); - return false; - } catch (InvocationTargetException e) { - Throwable orig = e.getTargetException(); - Log.w(TAG, "Exception of type '" + orig.getClass() + "' on AIDL call '" + function + "': " + orig.getMessage(), orig); - mErrorList.add("(LOCAL) Exception of type '" + orig.getClass() + "' on AIDL call '" + function + "': " + orig.getMessage()); - return false; - } catch (Exception e) { - Log.e(TAG, "Generic error (" + e.getClass() + "): " + e.getMessage(), e); - mErrorList.add("(LOCAL) Generic error (" + e.getClass() + "): " + e.getMessage()); - mResult.putInt(ret.ERROR.name(), error.GENERIC.ordinal()); - return false; - } - - } - - /** - * Set a string argument for APG - * - *

- * This defines a string argument for APG's AIDL-interface. - *

- * - *

- * To know what key-value-pairs are possible (or required), take a look into - * the IApgService.aidl - *

- * - *

- * Note that parameters are not reseted after a call, so you have to - * reset ({@link #clearArgs()}) them manually if you want to. - *

- * - * - * @param key - * the key - * @param val - * the value - * - * @see #clearArgs() - */ - public void setArg(String key, String val) { - mArgs.putString(key, val); - } - - /** - * Set a string-array argument for APG - * - *

- * If the AIDL-parameter is an {@literal ArrayList}, you have to use - * this function. - *

- * - * - *
-     * setArg("a key", new String[]{ "entry 1", "entry 2" });
-     * 
- *
- * - * @param key - * the key - * @param vals - * the value - * - * @see #setArg(String, String) - */ - public void setArg(String key, String vals[]) { - ArrayList list = new ArrayList(); - for (String val : vals) { - list.add(val); - } - mArgs.putStringArrayList(key, list); - } - - /** - * Set up a boolean argument for APG - * - * @param key - * the key - * @param vals - * the value - * - * @see #setArg(String, String) - */ - public void setArg(String key, boolean val) { - mArgs.putBoolean(key, val); - } - - /** - * Set up a int argument for APG - * - * @param key - * the key - * @param vals - * the value - * - * @see #setArg(String, String) - */ - public void setArg(String key, int val) { - mArgs.putInt(key, val); - } - - /** - * Set up a int-array argument for APG - *

- * If the AIDL-parameter is an {@literal ArrayList}, you have to - * use this function. - *

- * - * @param key - * the key - * @param vals - * the value - * - * @see #setArg(String, String) - */ - public void setArg(String key, int vals[]) { - ArrayList list = new ArrayList(); - for (int val : vals) { - list.add(val); - } - mArgs.putIntegerArrayList(key, list); - } - - /** - * Set up binary data to en/decrypt - * - * @param is - * InputStream to get the data from - */ - public void setBlob(InputStream is) { - if( LOCAL_LOGD ) Log.d(TAG, "setBlob() called"); - // 1. get the new contentUri - ContentResolver cr = mContext.getContentResolver(); - Uri contentUri = cr.insert(Uri.parse(BLOB_URI), new ContentValues()); - - // 2. insert binary data - OutputStream os = null; - try { - os = cr.openOutputStream(contentUri, "w"); - } catch( Exception e ) { - Log.e(TAG, "... exception on setBlob", e); - } - - byte[] buffer = new byte[8]; - int len = 0; - try { - while( (len = is.read(buffer)) != -1) { - os.write(buffer, 0, len); - } - if(LOCAL_LOGD) Log.d(TAG, "... write finished, now closing"); - os.close(); - } catch (Exception e) { - Log.e(TAG, "... error on writing buffer", e); - } - - mArgs.putString("BLOB", contentUri.toString() ); - } - - /** - * Clears all arguments - * - *

- * Anything the has been set up with the various - * {@link #setArg(String, String)} functions is cleared. - *

- * - *

- * Note that any warning, error, callback, result, etc. is NOT cleared with - * this. - *

- * - * @see #reset() - */ - public void clearArgs() { - mArgs.clear(); - } - - /** - * Return the object associated with the key - * - * @param key - * the object's key you want to return - * @return an object at position key, or null if not set - */ - public Object getArg(String key) { - return mArgs.get(key); - } - - /** - * Iterates through the errors - * - *

- * With this method you can iterate through all errors. The errors are only - * returned once and deleted immediately afterwards, so you can only return - * each error once. - *

- * - * @return a human readable description of a error that happened, or null if - * no more errors - * - * @see #hasNextError() - * @see #clearErrors() - */ - public String getNextError() { - if (mErrorList.size() != 0) - return mErrorList.remove(0); - else - return null; - } - - /** - * Check if there are any new errors - * - * @return true, if there are unreturned errors, false otherwise - * - * @see #getNextError() - */ - public boolean hasNextError() { - return mErrorList.size() != 0; - } - - /** - * Get the numeric representation of the last error - * - *

- * Values <100 mean the error happened locally, values >=100 mean the error - * happened at the remote side (APG). See the IApgService.aidl (or get the - * human readable description with {@link #getNextError()}) for what - * errors >=100 mean. - *

- * - * @return the id of the error that happened - */ - public int getError() { - if (mResult.containsKey(ret.ERROR.name())) - return mResult.getInt(ret.ERROR.name()); - else - return -1; - } - - /** - * Iterates through the warnings - * - *

- * With this method you can iterate through all warnings. Warnings are - * only returned once and deleted immediately afterwards, so you can only - * return each warning once. - *

- * - * @return a human readable description of a warning that happened, or null - * if no more warnings - * - * @see #hasNextWarning() - * @see #clearWarnings() - */ - public String getNextWarning() { - if (mWarningList.size() != 0) - return mWarningList.remove(0); - else - return null; - } - - /** - * Check if there are any new warnings - * - * @return true, if there are unreturned warnings, false otherwise - * - * @see #getNextWarning() - */ - public boolean hasNextWarning() { - return mWarningList.size() != 0; - } - - /** - * Get the result - * - *

- * This gets your result. After doing an encryption or decryption with APG, - * you get the output with this function. - *

- * - *

- * Note when your last remote call is unsuccessful, the result will - * still have the same value like the last successful call (or null, if no - * call was successful). To ensure you do not work with old call's results, - * either be sure to {@link #reset()} (or at least {@link #clearResult()}) - * your instance before each new call or always check that - * {@link #hasNextError()} is false. - *

- * - *

- * Note: When handling binary data with {@link #setBlob(InputStream)}, you - * get your result with {@link #getBlobResult()}. - *

- * - * @return the mResult of the last {@link #call(String)} or - * {@link #callAsync(String)}. - * - * @see #reset() - * @see #clearResult() - * @see #getResultBundle() - * @see #getBlobResult() - */ - public String getResult() { - return mResult.getString(ret.RESULT.name()); - } - - /** - * Get the binary result - * - *

- * This gets your binary result. It only works if you called {@link #setBlob(InputStream)} before. - * - * If you did not call encrypt nor decrypt, this will be the same data as you inputed. - *

- * - * @return InputStream of the binary data which was en/decrypted - * - * @see #setBlob(InputStream) - * @see #getResult() - */ - public InputStream getBlobResult() { - if(mArgs.containsKey("BLOB")) { - ContentResolver cr = mContext.getContentResolver(); - InputStream in = null; - try { - in = cr.openInputStream(Uri.parse(mArgs.getString("BLOB"))); - } catch( Exception e ) { - Log.e(TAG, "Could not return blob in result", e); - } - return in; - } else { - return null; - } - } - - /** - * Get the result bundle - * - *

- * Unlike {@link #getResult()}, which only returns any en-/decrypted - * message, this function returns the complete information that was returned - * by Apg. This also includes the "RESULT", but additionally the warnings, - * errors and any other information. - *

- *

- * For warnings and errors it is suggested to use the functions that are - * provided here, namely {@link #getError()}, {@link #getNextError()}, - * {@link #get_next_Warning()} etc.), but if any call returns something non - * standard, you have access to the complete result bundle to extract the - * information. - *

- * - * @return the complete result bundle of the last call to apg - */ - public Bundle getResultBundle() { - return mResult; - } - - public error getConnectionStatus() { - return mConnectionStatus; - } - - /** - * Clears all unfetched errors - * - * @see #getNextError() - * @see #hasNextError() - */ - public void clearErrors() { - mErrorList.clear(); - mResult.remove(ret.ERROR.name()); - } - - /** - * Clears all unfetched warnings - * - * @see #getNextWarning() - * @see #hasNextWarning() - */ - public void clearWarnings() { - mWarningList.clear(); - } - - /** - * Clears the last mResult - * - * @see #getResult() - */ - public void clearResult() { - mResult.remove(ret.RESULT.name()); - } - - /** - * Set a callback listener when call to AIDL finishes - * - * @param obj - * a object to call back after async execution - * @see ApgConInterface - */ - public void setOnCallFinishListener(OnCallFinishListener lis) { - mOnCallFinishListener = lis; - } - - /** - * Clears any callback object - * - * @see #setOnCallFinishListener(OnCallFinishListener) - */ - public void clearOnCallFinishListener() { - mOnCallFinishListener = null; - } - - /** - * Checks if an async execution is running - * - *

- * If you started something with {@link #callAsync(String)}, this will - * return true if the task is still running - *

- * - * @return true, if an async task is still running, false otherwise - * - * @see #callAsync(String) - * - */ - public boolean isRunning() { - return mAsyncRunning; - } - - /** - * Completely resets your instance - * - *

- * This currently resets everything in this instance. Errors, warnings, - * results, callbacks, ... are removed. Any connection to the remote - * interface is upheld, though. - *

- * - *

- * Note when an async execution ({@link #callAsync(String)}) is - * running, it's result, warnings etc. will still be evaluated (which might - * be not what you want). Also mind that any callback you set is also - * reseted, so when finishing the execution any before defined callback will - * NOT BE TRIGGERED. - *

- */ - public void reset() { - clearErrors(); - clearWarnings(); - clearArgs(); - clearOnCallFinishListener(); - mResult.clear(); - } - -} diff --git a/org_apg/src/org/apg/util/ApgConInterface.java b/org_apg/src/org/apg/util/ApgConInterface.java deleted file mode 100644 index 54d6af9b8..000000000 --- a/org_apg/src/org/apg/util/ApgConInterface.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.util; - -public interface ApgConInterface { - public static interface OnCallFinishListener { - public abstract void onCallFinish(android.os.Bundle result); - } -} diff --git a/org_apg/src/org/apg/util/Choice.java b/org_apg/src/org/apg/util/Choice.java deleted file mode 100644 index 1dbd9b215..000000000 --- a/org_apg/src/org/apg/util/Choice.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.util; - -public class Choice { - private String mName; - private int mId; - - public Choice() { - mId = -1; - mName = ""; - } - - public Choice(int id, String name) { - mId = id; - mName = name; - } - - public int getId() { - return mId; - } - - public String getName() { - return mName; - } - - @Override - public String toString() { - return mName; - } -} diff --git a/org_apg/src/org/apg/util/Compatibility.java b/org_apg/src/org/apg/util/Compatibility.java deleted file mode 100644 index 03053335b..000000000 --- a/org_apg/src/org/apg/util/Compatibility.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.util; - -import java.lang.reflect.Method; - -import android.content.Context; -import android.util.Log; - -public class Compatibility { - - private static final String clipboardLabel = "APG"; - - /** - * Wrapper around ClipboardManager based on Android version using Reflection API, from - * http://www.projectsexception.com/blog/?p=87 - * - * @param context - * @param text - */ - public static void copyToClipboard(Context context, String text) { - Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE); - try { - if ("android.text.ClipboardManager".equals(clipboard.getClass().getName())) { - Method method = clipboard.getClass().getMethod("setText", CharSequence.class); - method.invoke(clipboard, text); - } else if ("android.content.ClipboardManager".equals(clipboard.getClass().getName())) { - Class clazz = Class.forName("android.content.ClipData"); - Method method = clazz.getMethod("newPlainText", CharSequence.class, - CharSequence.class); - Object clip = method.invoke(null, clipboardLabel, text); - method = clipboard.getClass().getMethod("setPrimaryClip", clazz); - method.invoke(clipboard, clip); - } - } catch (Exception e) { - Log.e("ProjectsException", "There was and error copying the text to the clipboard: " - + e.getMessage()); - } - } - - /** - * Wrapper around ClipboardManager based on Android version using Reflection API - * - * @param context - * @param text - */ - public static CharSequence getClipboardText(Context context) { - Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE); - try { - if ("android.text.ClipboardManager".equals(clipboard.getClass().getName())) { - // CharSequence text = clipboard.getText(); - Method method = clipboard.getClass().getMethod("getText"); - Object text = method.invoke(clipboard); - - return (CharSequence) text; - } else if ("android.content.ClipboardManager".equals(clipboard.getClass().getName())) { - // ClipData clipData = clipboard.getPrimaryClip(); - Method methodGetPrimaryClip = clipboard.getClass().getMethod("getPrimaryClip"); - Object clipData = methodGetPrimaryClip.invoke(clipboard); - - // ClipData.Item clipDataItem = clipData.getItemAt(0); - Method methodGetItemAt = clipData.getClass().getMethod("getItemAt", Integer.TYPE); - Object clipDataItem = methodGetItemAt.invoke(clipData, 0); - - // CharSequence text = clipDataItem.coerceToText(context); - Method methodGetString = clipDataItem.getClass().getMethod("coerceToText", - Context.class); - Object text = methodGetString.invoke(clipDataItem, context); - - return (CharSequence) text; - } else { - return null; - } - } catch (Exception e) { - Log.e("ProjectsException", "There was and error getting the text from the clipboard: " - + e.getMessage()); - - return null; - } - } -} diff --git a/org_apg/src/org/apg/util/Constants.java b/org_apg/src/org/apg/util/Constants.java deleted file mode 100644 index 819d13700..000000000 --- a/org_apg/src/org/apg/util/Constants.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.apg.util; - -public class Constants { - public static final String TAG = "APG"; - -} diff --git a/org_apg/src/org/apg/util/IterableIterator.java b/org_apg/src/org/apg/util/IterableIterator.java deleted file mode 100644 index be6632ac0..000000000 --- a/org_apg/src/org/apg/util/IterableIterator.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2010 Thialfihar - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.util; - -import java.util.Iterator; - -public class IterableIterator implements Iterable { - private Iterator mIter; - - public IterableIterator(Iterator iter) { - mIter = iter; - } - - public Iterator iterator() { - return mIter; - } -} diff --git a/org_apg/src/org/apg/util/Utils.java b/org_apg/src/org/apg/util/Utils.java deleted file mode 100644 index 83a65213d..000000000 --- a/org_apg/src/org/apg/util/Utils.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2012 Dominik Schürmann - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apg.util; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Calendar; -import java.util.GregorianCalendar; - -import android.content.Context; - -public class Utils { - - /** - * Reads html files from /res/raw/example.html to output them as string. See - * http://www.monocube.com/2011/02/08/android-tutorial-html-file-in-webview/ - * - * @param context - * current context - * @param resourceID - * of html file to read - * @return content of html file with formatting - */ - public static String readContentFromResource(Context context, int resourceID) { - InputStream raw = context.getResources().openRawResource(resourceID); - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - int i; - try { - i = raw.read(); - while (i != -1) { - stream.write(i); - i = raw.read(); - } - raw.close(); - } catch (IOException e) { - e.printStackTrace(); - } - return stream.toString(); - } - - /** - * Return the number if days between two dates - * - * @param first - * @param second - * @return number of days - */ - public static long getNumDaysBetween(GregorianCalendar first, GregorianCalendar second) { - GregorianCalendar tmp = new GregorianCalendar(); - tmp.setTime(first.getTime()); - long numDays = (second.getTimeInMillis() - first.getTimeInMillis()) / 1000 / 86400; - tmp.add(Calendar.DAY_OF_MONTH, (int) numDays); - while (tmp.before(second)) { - tmp.add(Calendar.DAY_OF_MONTH, 1); - ++numDays; - } - return numDays; - } - -} diff --git a/org_apg/src/org/thialfihar/android/apg/Apg.java b/org_apg/src/org/thialfihar/android/apg/Apg.java new file mode 100644 index 000000000..b742f9159 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/Apg.java @@ -0,0 +1,2280 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import org.spongycastle.bcpg.ArmoredInputStream; +import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.BCPGOutputStream; +import org.spongycastle.bcpg.CompressionAlgorithmTags; +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.spongycastle.bcpg.sig.KeyFlags; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.jce.spec.ElGamalParameterSpec; +import org.spongycastle.openpgp.PGPCompressedData; +import org.spongycastle.openpgp.PGPCompressedDataGenerator; +import org.spongycastle.openpgp.PGPEncryptedData; +import org.spongycastle.openpgp.PGPEncryptedDataGenerator; +import org.spongycastle.openpgp.PGPEncryptedDataList; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPKeyPair; +import org.spongycastle.openpgp.PGPKeyRing; +import org.spongycastle.openpgp.PGPKeyRingGenerator; +import org.spongycastle.openpgp.PGPLiteralData; +import org.spongycastle.openpgp.PGPLiteralDataGenerator; +import org.spongycastle.openpgp.PGPObjectFactory; +import org.spongycastle.openpgp.PGPOnePassSignature; +import org.spongycastle.openpgp.PGPOnePassSignatureList; +import org.spongycastle.openpgp.PGPPBEEncryptedData; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyEncryptedData; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureGenerator; +import org.spongycastle.openpgp.PGPSignatureList; +import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.spongycastle.openpgp.PGPSignatureSubpacketVector; +import org.spongycastle.openpgp.PGPUtil; +import org.spongycastle.openpgp.PGPV3SignatureGenerator; +import org.thialfihar.android.apg.KeyServer.AddKeyException; +import org.thialfihar.android.apg.provider.DataProvider; +import org.thialfihar.android.apg.provider.Database; +import org.thialfihar.android.apg.provider.KeyRings; +import org.thialfihar.android.apg.provider.Keys; +import org.thialfihar.android.apg.provider.UserIds; +import org.thialfihar.android.apg.ui.BaseActivity; +import org.thialfihar.android.apg.ui.widget.KeyEditor; +import org.thialfihar.android.apg.ui.widget.SectionView; +import org.thialfihar.android.apg.ui.widget.UserIdEditor; +import org.thialfihar.android.apg.util.IterableIterator; +import org.thialfihar.android.apg.util.Utils; +import org.thialfihar.android.apg.R; + +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.Message; +import android.view.ViewGroup; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.Security; +import java.security.SignatureException; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Vector; +import java.util.regex.Pattern; + +public class Apg { + private static final String PACKAGE_NAME = "org.thialfihar.android.apg"; + private static final String INTENT_PREFIX = "org.thialfihar.android.apg.intent."; + + public static class Intent { + public static final String DECRYPT = INTENT_PREFIX + "DECRYPT"; + public static final String ENCRYPT = INTENT_PREFIX + "ENCRYPT"; + public static final String DECRYPT_FILE = INTENT_PREFIX + "DECRYPT_FILE"; + public static final String ENCRYPT_FILE = INTENT_PREFIX + "ENCRYPT_FILE"; + public static final String DECRYPT_AND_RETURN = INTENT_PREFIX + "DECRYPT_AND_RETURN"; + public static final String ENCRYPT_AND_RETURN = INTENT_PREFIX + "ENCRYPT_AND_RETURN"; + public static final String SELECT_PUBLIC_KEYS = INTENT_PREFIX + "SELECT_PUBLIC_KEYS"; + public static final String SELECT_SECRET_KEY = INTENT_PREFIX + "SELECT_SECRET_KEY"; + public static final String IMPORT = INTENT_PREFIX + "IMPORT"; + public static final String LOOK_UP_KEY_ID = INTENT_PREFIX + "LOOK_UP_KEY_ID"; + public static final String LOOK_UP_KEY_ID_AND_RETURN = INTENT_PREFIX + + "LOOK_UP_KEY_ID_AND_RETURN"; + public static final String GENERATE_SIGNATURE = INTENT_PREFIX + "GENERATE_SIGNATURE"; + public static final String EXPORT_KEY_TO_SERVER = INTENT_PREFIX + "EXPORT_KEY_TO_SERVER"; + public static final String IMPORT_FROM_QR_CODE = INTENT_PREFIX + "IMPORT_FROM_QR_CODE"; + public static final String CREATE_KEY = INTENT_PREFIX + "CREATE_KEY"; + public static final String EDIT_KEY = INTENT_PREFIX + "EDIT_KEY"; + } + + public static final String EXTRA_TEXT = "text"; + public static final String EXTRA_DATA = "data"; + public static final String EXTRA_ERROR = "error"; + public static final String EXTRA_DECRYPTED_MESSAGE = "decryptedMessage"; + public static final String EXTRA_DECRYPTED_DATA = "decryptedData"; + public static final String EXTRA_ENCRYPTED_MESSAGE = "encryptedMessage"; + public static final String EXTRA_ENCRYPTED_DATA = "encryptedData"; + public static final String EXTRA_RESULT_URI = "resultUri"; + public static final String EXTRA_SIGNATURE = "signature"; + public static final String EXTRA_SIGNATURE_KEY_ID = "signatureKeyId"; + public static final String EXTRA_SIGNATURE_USER_ID = "signatureUserId"; + public static final String EXTRA_SIGNATURE_SUCCESS = "signatureSuccess"; + public static final String EXTRA_SIGNATURE_UNKNOWN = "signatureUnknown"; + public static final String EXTRA_SIGNATURE_DATA = "signatureData"; + public static final String EXTRA_SIGNATURE_TEXT = "signatureText"; + public static final String EXTRA_USER_ID = "userId"; + public static final String EXTRA_USER_IDS = "userIds"; + public static final String EXTRA_KEY_ID = "keyId"; + public static final String EXTRA_REPLY_TO = "replyTo"; + public static final String EXTRA_SEND_TO = "sendTo"; + public static final String EXTRA_SUBJECT = "subject"; + public static final String EXTRA_ENCRYPTION_KEY_IDS = "encryptionKeyIds"; + public static final String EXTRA_SELECTION = "selection"; + public static final String EXTRA_ASCII_ARMOUR = "asciiArmour"; + public static final String EXTRA_BINARY = "binary"; + public static final String EXTRA_KEY_SERVERS = "keyServers"; + public static final String EXTRA_EXPECTED_FINGERPRINT = "expectedFingerprint"; + public static final String EXTRA_NO_PASSPHRASE = "noPassphrase"; + public static final String EXTRA_GENERATE_DEFAULT_KEYS = "generateDefaultKeys"; + + public static final String AUTHORITY = DataProvider.AUTHORITY; + + public static final Uri CONTENT_URI_SECRET_KEY_RINGS = Uri.parse("content://" + AUTHORITY + + "/key_rings/secret/"); + public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_KEY_ID = Uri.parse("content://" + + AUTHORITY + "/key_rings/secret/key_id/"); + public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_EMAILS = Uri.parse("content://" + + AUTHORITY + "/key_rings/secret/emails/"); + + public static final Uri CONTENT_URI_PUBLIC_KEY_RINGS = Uri.parse("content://" + AUTHORITY + + "/key_rings/public/"); + public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_KEY_ID = Uri.parse("content://" + + AUTHORITY + "/key_rings/public/key_id/"); + public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_EMAILS = Uri.parse("content://" + + AUTHORITY + "/key_rings/public/emails/"); + + private static String VERSION = null; + + private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[] { + SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, + SymmetricKeyAlgorithmTags.AES_128, SymmetricKeyAlgorithmTags.CAST5, + SymmetricKeyAlgorithmTags.TRIPLE_DES }; + private static final int[] PREFERRED_HASH_ALGORITHMS = new int[] { HashAlgorithmTags.SHA1, + HashAlgorithmTags.SHA256, HashAlgorithmTags.RIPEMD160 }; + private static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[] { + CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2, + CompressionAlgorithmTags.ZIP }; + + public static Pattern PGP_MESSAGE = Pattern.compile( + ".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL); + + public static Pattern PGP_SIGNED_MESSAGE = Pattern + .compile( + ".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*", + Pattern.DOTALL); + + public static Pattern PGP_PUBLIC_KEY = Pattern.compile( + ".*?(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----).*", + Pattern.DOTALL); + + private static HashMap mPassPhraseCache = new HashMap(); + private static String mEditPassPhrase = null; + + private static Database mDatabase = null; + + public static class GeneralException extends Exception { + static final long serialVersionUID = 0xf812773342L; + + public GeneralException(String message) { + super(message); + } + } + + public static class NoAsymmetricEncryptionException extends Exception { + static final long serialVersionUID = 0xf812773343L; + + public NoAsymmetricEncryptionException() { + super(); + } + } + + public static void initialize(Context context) { + if (mDatabase == null) { + mDatabase = new Database(context); + } + } + + public static Database getDatabase() { + return mDatabase; + } + + public static void setEditPassPhrase(String passPhrase) { + mEditPassPhrase = passPhrase; + } + + public static String getEditPassPhrase() { + return mEditPassPhrase; + } + + public static void setCachedPassPhrase(long keyId, String passPhrase) { + mPassPhraseCache.put(keyId, new CachedPassPhrase(new Date().getTime(), passPhrase)); + } + + public static String getCachedPassPhrase(long keyId) { + long realId = keyId; + if (realId != Id.key.symmetric) { + PGPSecretKeyRing keyRing = getSecretKeyRing(keyId); + if (keyRing == null) { + return null; + } + PGPSecretKey masterKey = getMasterKey(keyRing); + if (masterKey == null) { + return null; + } + realId = masterKey.getKeyID(); + } + CachedPassPhrase cpp = mPassPhraseCache.get(realId); + if (cpp == null) { + return null; + } + // set it again to reset the cache life cycle + setCachedPassPhrase(realId, cpp.passPhrase); + return cpp.passPhrase; + } + + public static int cleanUpCache(int ttl, int initialDelay) { + int delay = initialDelay; + long realTtl = ttl * 1000; + long now = new Date().getTime(); + Vector oldKeys = new Vector(); + for (Map.Entry pair : mPassPhraseCache.entrySet()) { + long lived = now - pair.getValue().timestamp; + if (lived >= realTtl) { + oldKeys.add(pair.getKey()); + } else { + // see, whether the remaining time for this cache entry improves our + // check delay + long nextCheck = realTtl - lived + 1000; + if (nextCheck < delay) { + delay = (int) nextCheck; + } + } + } + + for (long keyId : oldKeys) { + mPassPhraseCache.remove(keyId); + } + + return delay; + } + + public static PGPSecretKey createKey(Context context, int algorithmChoice, int keySize, + String passPhrase, PGPSecretKey masterKey) throws NoSuchAlgorithmException, + PGPException, NoSuchProviderException, GeneralException, + InvalidAlgorithmParameterException { + + if (keySize < 512) { + throw new GeneralException(context.getString(R.string.error_keySizeMinimum512bit)); + } + + Security.addProvider(new BouncyCastleProvider()); + + if (passPhrase == null) { + passPhrase = ""; + } + + int algorithm = 0; + KeyPairGenerator keyGen = null; + + switch (algorithmChoice) { + case Id.choice.algorithm.dsa: { + keyGen = KeyPairGenerator.getInstance("DSA", new BouncyCastleProvider()); + keyGen.initialize(keySize, new SecureRandom()); + algorithm = PGPPublicKey.DSA; + break; + } + + case Id.choice.algorithm.elgamal: { + if (masterKey == null) { + throw new GeneralException( + context.getString(R.string.error_masterKeyMustNotBeElGamal)); + } + keyGen = KeyPairGenerator.getInstance("ELGAMAL", new BouncyCastleProvider()); + BigInteger p = Primes.getBestPrime(keySize); + BigInteger g = new BigInteger("2"); + + ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g); + + keyGen.initialize(elParams); + algorithm = PGPPublicKey.ELGAMAL_ENCRYPT; + break; + } + + case Id.choice.algorithm.rsa: { + keyGen = KeyPairGenerator.getInstance("RSA", new BouncyCastleProvider()); + keyGen.initialize(keySize, new SecureRandom()); + + algorithm = PGPPublicKey.RSA_GENERAL; + break; + } + + default: { + throw new GeneralException(context.getString(R.string.error_unknownAlgorithmChoice)); + } + } + + PGPKeyPair keyPair = new PGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date()); + + PGPSecretKey secretKey = null; + if (masterKey == null) { + // enough for now, as we assemble the key again later anyway + secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, keyPair, "", + PGPEncryptedData.CAST5, passPhrase.toCharArray(), null, null, + new SecureRandom(), new BouncyCastleProvider().getName()); + + } else { + PGPPublicKey tmpKey = masterKey.getPublicKey(); + PGPPublicKey masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(), + tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime()); + PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(passPhrase.toCharArray(), + new BouncyCastleProvider()); + + PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); + PGPKeyRingGenerator ringGen = new PGPKeyRingGenerator( + PGPSignature.POSITIVE_CERTIFICATION, masterKeyPair, "", PGPEncryptedData.CAST5, + passPhrase.toCharArray(), null, null, new SecureRandom(), + new BouncyCastleProvider().getName()); + ringGen.addSubKey(keyPair); + PGPSecretKeyRing secKeyRing = ringGen.generateSecretKeyRing(); + Iterator it = secKeyRing.getSecretKeys(); + // first one is the master key + it.next(); + secretKey = it.next(); + } + + return secretKey; + } + + public static void buildSecretKey(Activity context, SectionView userIdsView, + SectionView keysView, String oldPassPhrase, String newPassPhrase, + ProgressDialogUpdater progress) throws Apg.GeneralException, NoSuchProviderException, + PGPException, NoSuchAlgorithmException, SignatureException, IOException, + Database.GeneralException { + + if (progress != null) + progress.setProgress(R.string.progress_buildingKey, 0, 100); + + Security.addProvider(new BouncyCastleProvider()); + + if (oldPassPhrase == null || oldPassPhrase.equals("")) { + oldPassPhrase = ""; + } + + if (newPassPhrase == null || newPassPhrase.equals("")) { + newPassPhrase = ""; + } + + Vector userIds = new Vector(); + Vector keys = new Vector(); + + ViewGroup userIdEditors = userIdsView.getEditors(); + ViewGroup keyEditors = keysView.getEditors(); + + boolean gotMainUserId = false; + for (int i = 0; i < userIdEditors.getChildCount(); ++i) { + UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i); + String userId = null; + try { + userId = editor.getValue(); + } catch (UserIdEditor.NoNameException e) { + throw new Apg.GeneralException(context.getString(R.string.error_userIdNeedsAName)); + } catch (UserIdEditor.NoEmailException e) { + throw new Apg.GeneralException( + context.getString(R.string.error_userIdNeedsAnEmailAddress)); + } catch (UserIdEditor.InvalidEmailException e) { + throw new Apg.GeneralException("" + e); + } + + if (userId.equals("")) { + continue; + } + + if (editor.isMainUserId()) { + userIds.insertElementAt(userId, 0); + gotMainUserId = true; + } else { + userIds.add(userId); + } + } + + if (userIds.size() == 0) { + throw new Apg.GeneralException(context.getString(R.string.error_keyNeedsAUserId)); + } + + if (!gotMainUserId) { + throw new Apg.GeneralException( + context.getString(R.string.error_mainUserIdMustNotBeEmpty)); + } + + if (keyEditors.getChildCount() == 0) { + throw new Apg.GeneralException(context.getString(R.string.error_keyNeedsMasterKey)); + } + + for (int i = 0; i < keyEditors.getChildCount(); ++i) { + KeyEditor editor = (KeyEditor) keyEditors.getChildAt(i); + keys.add(editor.getValue()); + } + + if (progress != null) + progress.setProgress(R.string.progress_preparingMasterKey, 10, 100); + KeyEditor keyEditor = (KeyEditor) keyEditors.getChildAt(0); + int usageId = keyEditor.getUsage(); + boolean canSign = (usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt); + boolean canEncrypt = (usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt); + + String mainUserId = userIds.get(0); + + PGPSecretKey masterKey = keys.get(0); + PGPPublicKey tmpKey = masterKey.getPublicKey(); + PGPPublicKey masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(), + tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime()); + PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(oldPassPhrase.toCharArray(), + new BouncyCastleProvider()); + + if (progress != null) + progress.setProgress(R.string.progress_certifyingMasterKey, 20, 100); + for (int i = 0; i < userIds.size(); ++i) { + String userId = userIds.get(i); + + PGPSignatureGenerator sGen = new PGPSignatureGenerator(masterPublicKey.getAlgorithm(), + HashAlgorithmTags.SHA1, new BouncyCastleProvider()); + + sGen.initSign(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey); + + PGPSignature certification = sGen.generateCertification(userId, masterPublicKey); + + masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification); + } + + // TODO: cross-certify the master key with every sub key + + PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey); + + PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); + + int keyFlags = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA; + if (canEncrypt) { + keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; + } + hashedPacketsGen.setKeyFlags(true, keyFlags); + + hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS); + hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS); + hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS); + + // TODO: this doesn't work quite right yet + if (keyEditor.getExpiryDate() != null) { + GregorianCalendar creationDate = new GregorianCalendar(); + creationDate.setTime(getCreationDate(masterKey)); + GregorianCalendar expiryDate = keyEditor.getExpiryDate(); + long numDays = Utils.getNumDaysBetween(creationDate, expiryDate); + if (numDays <= 0) { + throw new GeneralException( + context.getString(R.string.error_expiryMustComeAfterCreation)); + } + hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400); + } + + if (progress != null) { + progress.setProgress(R.string.progress_buildingMasterKeyRing, 30, 100); + } + + PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, + masterKeyPair, mainUserId, PGPEncryptedData.CAST5, newPassPhrase.toCharArray(), + hashedPacketsGen.generate(), unhashedPacketsGen.generate(), new SecureRandom(), + new BouncyCastleProvider().getName()); + + if (progress != null) + progress.setProgress(R.string.progress_addingSubKeys, 40, 100); + for (int i = 1; i < keys.size(); ++i) { + if (progress != null) + progress.setProgress(40 + 50 * (i - 1) / (keys.size() - 1), 100); + PGPSecretKey subKey = keys.get(i); + keyEditor = (KeyEditor) keyEditors.getChildAt(i); + PGPPublicKey subPublicKey = subKey.getPublicKey(); + PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(oldPassPhrase.toCharArray(), + new BouncyCastleProvider()); + PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey.getAlgorithm(), + subPublicKey.getKey(new BouncyCastleProvider()), subPrivateKey.getKey(), + subPublicKey.getCreationTime()); + + hashedPacketsGen = new PGPSignatureSubpacketGenerator(); + unhashedPacketsGen = new PGPSignatureSubpacketGenerator(); + + keyFlags = 0; + usageId = keyEditor.getUsage(); + canSign = (usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt); + canEncrypt = (usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt); + if (canSign) { + keyFlags |= KeyFlags.SIGN_DATA; + } + if (canEncrypt) { + keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE; + } + hashedPacketsGen.setKeyFlags(true, keyFlags); + + // TODO: this doesn't work quite right yet + if (keyEditor.getExpiryDate() != null) { + GregorianCalendar creationDate = new GregorianCalendar(); + creationDate.setTime(getCreationDate(masterKey)); + GregorianCalendar expiryDate = keyEditor.getExpiryDate(); + long numDays = Utils.getNumDaysBetween(creationDate, expiryDate); + if (numDays <= 0) { + throw new GeneralException( + context.getString(R.string.error_expiryMustComeAfterCreation)); + } + hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400); + } + + keyGen.addSubKey(subKeyPair, hashedPacketsGen.generate(), unhashedPacketsGen.generate()); + } + + PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing(); + PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing(); + + if (progress != null) + progress.setProgress(R.string.progress_savingKeyRing, 90, 100); + mDatabase.saveKeyRing(secretKeyRing); + mDatabase.saveKeyRing(publicKeyRing); + + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + } + + public static PGPKeyRing decodeKeyRing(InputStream is) throws IOException { + InputStream in = PGPUtil.getDecoderStream(is); + PGPObjectFactory objectFactory = new PGPObjectFactory(in); + Object obj = objectFactory.nextObject(); + + if (obj instanceof PGPKeyRing) { + return (PGPKeyRing) obj; + } + + return null; + } + + public static int storeKeyRingInCache(PGPKeyRing keyring) { + int status = Integer.MIN_VALUE; // out of bounds value (Id.retrun_value.*) + try { + if (keyring instanceof PGPSecretKeyRing) { + PGPSecretKeyRing secretKeyRing = (PGPSecretKeyRing) keyring; + boolean save = true; + try { + PGPPrivateKey testKey = secretKeyRing.getSecretKey().extractPrivateKey( + new char[] {}, new BouncyCastleProvider()); + if (testKey == null) { + // this is bad, something is very wrong... likely a --export-secret-subkeys + // export + save = false; + status = Id.return_value.bad; + } + } catch (PGPException e) { + // all good if this fails, we likely didn't use the right password + } + + if (save) { + status = mDatabase.saveKeyRing(secretKeyRing); + } + } else if (keyring instanceof PGPPublicKeyRing) { + PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring; + status = mDatabase.saveKeyRing(publicKeyRing); + } + } catch (IOException e) { + status = Id.return_value.error; + } catch (Database.GeneralException e) { + status = Id.return_value.error; + } + + return status; + } + + public static boolean uploadKeyRingToServer(HkpKeyServer server, PGPPublicKeyRing keyring) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ArmoredOutputStream aos = new ArmoredOutputStream(bos); + try { + aos.write(keyring.getEncoded()); + aos.close(); + + String armouredKey = bos.toString("UTF-8"); + server.add(armouredKey); + + return true; + } catch (IOException e) { + return false; + } catch (AddKeyException e) { + // TODO: tell the user? + return false; + } finally { + try { + bos.close(); + } catch (IOException e) { + } + } + } + + public static Bundle importKeyRings(Activity context, int type, InputData data, + ProgressDialogUpdater progress) throws GeneralException, FileNotFoundException, + PGPException, IOException { + Bundle returnData = new Bundle(); + + if (type == Id.type.secret_key) { + if (progress != null) + progress.setProgress(R.string.progress_importingSecretKeys, 0, 100); + } else { + if (progress != null) + progress.setProgress(R.string.progress_importingPublicKeys, 0, 100); + } + + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + throw new GeneralException(context.getString(R.string.error_externalStorageNotReady)); + } + + PositionAwareInputStream progressIn = new PositionAwareInputStream(data.getInputStream()); + // need to have access to the bufferedInput, so we can reuse it for the possible + // PGPObject chunks after the first one, e.g. files with several consecutive ASCII + // armour blocks + BufferedInputStream bufferedInput = new BufferedInputStream(progressIn); + int newKeys = 0; + int oldKeys = 0; + int badKeys = 0; + try { + PGPKeyRing keyring = decodeKeyRing(bufferedInput); + while (keyring != null) { + int status = Integer.MIN_VALUE; // out of bounds value + + // if this key is what we expect it to be, save it + if ((type == Id.type.secret_key && keyring instanceof PGPSecretKeyRing) + || (type == Id.type.public_key && keyring instanceof PGPPublicKeyRing)) { + status = storeKeyRingInCache(keyring); + } + + if (status == Id.return_value.error) { + throw new GeneralException(context.getString(R.string.error_savingKeys)); + } + + // update the counts to display to the user at the end + if (status == Id.return_value.updated) { + ++oldKeys; + } else if (status == Id.return_value.ok) { + ++newKeys; + } else if (status == Id.return_value.bad) { + ++badKeys; + } + + if (progress != null) { + progress.setProgress((int) (100 * progressIn.position() / data.getSize()), 100); + } + // TODO: needed? + // obj = objectFactory.nextObject(); + + keyring = decodeKeyRing(bufferedInput); + } + } catch (EOFException e) { + // nothing to do, we are done + } + + returnData.putInt("added", newKeys); + returnData.putInt("updated", oldKeys); + returnData.putInt("bad", badKeys); + + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + + return returnData; + } + + public static Bundle exportKeyRings(Activity context, Vector keyRingIds, + OutputStream outStream, ProgressDialogUpdater progress) throws GeneralException, + FileNotFoundException, PGPException, IOException { + Bundle returnData = new Bundle(); + + if (keyRingIds.size() == 1) { + if (progress != null) + progress.setProgress(R.string.progress_exportingKey, 0, 100); + } else { + if (progress != null) + progress.setProgress(R.string.progress_exportingKeys, 0, 100); + } + + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + throw new GeneralException(context.getString(R.string.error_externalStorageNotReady)); + } + ArmoredOutputStream out = new ArmoredOutputStream(outStream); + + int numKeys = 0; + for (int i = 0; i < keyRingIds.size(); ++i) { + if (progress != null) + progress.setProgress(i * 100 / keyRingIds.size(), 100); + Object obj = mDatabase.getKeyRing(keyRingIds.get(i)); + PGPPublicKeyRing publicKeyRing; + PGPSecretKeyRing secretKeyRing; + + if (obj instanceof PGPSecretKeyRing) { + secretKeyRing = (PGPSecretKeyRing) obj; + secretKeyRing.encode(out); + } else if (obj instanceof PGPPublicKeyRing) { + publicKeyRing = (PGPPublicKeyRing) obj; + publicKeyRing.encode(out); + } else { + continue; + } + ++numKeys; + } + out.close(); + returnData.putInt("exported", numKeys); + + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + + return returnData; + } + + public static Date getCreationDate(PGPPublicKey key) { + return key.getCreationTime(); + } + + public static Date getCreationDate(PGPSecretKey key) { + return key.getPublicKey().getCreationTime(); + } + + @SuppressWarnings("unchecked") + public static PGPPublicKey getMasterKey(PGPPublicKeyRing keyRing) { + if (keyRing == null) { + return null; + } + for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { + if (key.isMasterKey()) { + return key; + } + } + + return null; + } + + @SuppressWarnings("unchecked") + public static PGPSecretKey getMasterKey(PGPSecretKeyRing keyRing) { + if (keyRing == null) { + return null; + } + for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { + if (key.isMasterKey()) { + return key; + } + } + + return null; + } + + @SuppressWarnings("unchecked") + public static Vector getEncryptKeys(PGPPublicKeyRing keyRing) { + Vector encryptKeys = new Vector(); + + for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { + if (isEncryptionKey(key)) { + encryptKeys.add(key); + } + } + + return encryptKeys; + } + + @SuppressWarnings("unchecked") + public static Vector getSigningKeys(PGPSecretKeyRing keyRing) { + Vector signingKeys = new Vector(); + + for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { + if (isSigningKey(key)) { + signingKeys.add(key); + } + } + + return signingKeys; + } + + public static Vector getUsableEncryptKeys(PGPPublicKeyRing keyRing) { + Vector usableKeys = new Vector(); + Vector encryptKeys = getEncryptKeys(keyRing); + PGPPublicKey masterKey = null; + for (int i = 0; i < encryptKeys.size(); ++i) { + PGPPublicKey key = encryptKeys.get(i); + if (!isExpired(key)) { + if (key.isMasterKey()) { + masterKey = key; + } else { + usableKeys.add(key); + } + } + } + if (masterKey != null) { + usableKeys.add(masterKey); + } + return usableKeys; + } + + public static boolean isExpired(PGPPublicKey key) { + Date creationDate = getCreationDate(key); + Date expiryDate = getExpiryDate(key); + Date now = new Date(); + if (now.compareTo(creationDate) >= 0 + && (expiryDate == null || now.compareTo(expiryDate) <= 0)) { + return false; + } + return true; + } + + public static boolean isExpired(PGPSecretKey key) { + return isExpired(key.getPublicKey()); + } + + public static Vector getUsableSigningKeys(PGPSecretKeyRing keyRing) { + Vector usableKeys = new Vector(); + Vector signingKeys = getSigningKeys(keyRing); + PGPSecretKey masterKey = null; + for (int i = 0; i < signingKeys.size(); ++i) { + PGPSecretKey key = signingKeys.get(i); + if (key.isMasterKey()) { + masterKey = key; + } else { + usableKeys.add(key); + } + } + if (masterKey != null) { + usableKeys.add(masterKey); + } + return usableKeys; + } + + public static Date getExpiryDate(PGPPublicKey key) { + Date creationDate = getCreationDate(key); + if (key.getValidDays() == 0) { + // no expiry + return null; + } + Calendar calendar = GregorianCalendar.getInstance(); + calendar.setTime(creationDate); + calendar.add(Calendar.DATE, key.getValidDays()); + Date expiryDate = calendar.getTime(); + + return expiryDate; + } + + public static Date getExpiryDate(PGPSecretKey key) { + return getExpiryDate(key.getPublicKey()); + } + + public static PGPPublicKey getEncryptPublicKey(long masterKeyId) { + PGPPublicKeyRing keyRing = getPublicKeyRing(masterKeyId); + if (keyRing == null) { + return null; + } + Vector encryptKeys = getUsableEncryptKeys(keyRing); + if (encryptKeys.size() == 0) { + return null; + } + return encryptKeys.get(0); + } + + public static PGPSecretKey getSigningKey(long masterKeyId) { + PGPSecretKeyRing keyRing = getSecretKeyRing(masterKeyId); + if (keyRing == null) { + return null; + } + Vector signingKeys = getUsableSigningKeys(keyRing); + if (signingKeys.size() == 0) { + return null; + } + return signingKeys.get(0); + } + + @SuppressWarnings("unchecked") + public static String getMainUserId(PGPPublicKey key) { + for (String userId : new IterableIterator(key.getUserIDs())) { + return userId; + } + return null; + } + + @SuppressWarnings("unchecked") + public static String getMainUserId(PGPSecretKey key) { + for (String userId : new IterableIterator(key.getUserIDs())) { + return userId; + } + return null; + } + + public static String getMainUserIdSafe(Context context, PGPPublicKey key) { + String userId = getMainUserId(key); + if (userId == null) { + userId = context.getResources().getString(R.string.unknownUserId); + } + return userId; + } + + public static String getMainUserIdSafe(Context context, PGPSecretKey key) { + String userId = getMainUserId(key); + if (userId == null) { + userId = context.getResources().getString(R.string.unknownUserId); + } + return userId; + } + + @SuppressWarnings("unchecked") + public static boolean isEncryptionKey(PGPPublicKey key) { + if (!key.isEncryptionKey()) { + return false; + } + + if (key.getVersion() <= 3) { + // this must be true now + return key.isEncryptionKey(); + } + + // special cases + if (key.getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT) { + return true; + } + + if (key.getAlgorithm() == PGPPublicKey.RSA_ENCRYPT) { + return true; + } + + for (PGPSignature sig : new IterableIterator(key.getSignatures())) { + if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) { + continue; + } + PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); + + if (hashed != null + && (hashed.getKeyFlags() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0) { + return true; + } + + PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); + + if (unhashed != null + && (unhashed.getKeyFlags() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0) { + return true; + } + } + return false; + } + + public static boolean isEncryptionKey(PGPSecretKey key) { + return isEncryptionKey(key.getPublicKey()); + } + + @SuppressWarnings("unchecked") + public static boolean isSigningKey(PGPPublicKey key) { + if (key.getVersion() <= 3) { + return true; + } + + // special case + if (key.getAlgorithm() == PGPPublicKey.RSA_SIGN) { + return true; + } + + for (PGPSignature sig : new IterableIterator(key.getSignatures())) { + if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) { + continue; + } + PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets(); + + if (hashed != null && (hashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) { + return true; + } + + PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets(); + + if (unhashed != null && (unhashed.getKeyFlags() & KeyFlags.SIGN_DATA) != 0) { + return true; + } + } + + return false; + } + + public static boolean isSigningKey(PGPSecretKey key) { + return isSigningKey(key.getPublicKey()); + } + + public static String getAlgorithmInfo(PGPPublicKey key) { + return getAlgorithmInfo(key.getAlgorithm(), key.getBitStrength()); + } + + public static String getAlgorithmInfo(PGPSecretKey key) { + return getAlgorithmInfo(key.getPublicKey()); + } + + public static String getAlgorithmInfo(int algorithm, int keySize) { + String algorithmStr = null; + + switch (algorithm) { + case PGPPublicKey.RSA_ENCRYPT: + case PGPPublicKey.RSA_GENERAL: + case PGPPublicKey.RSA_SIGN: { + algorithmStr = "RSA"; + break; + } + + case PGPPublicKey.DSA: { + algorithmStr = "DSA"; + break; + } + + case PGPPublicKey.ELGAMAL_ENCRYPT: + case PGPPublicKey.ELGAMAL_GENERAL: { + algorithmStr = "ElGamal"; + break; + } + + default: { + algorithmStr = "???"; + break; + } + } + return algorithmStr + ", " + keySize + "bit"; + } + + public static String convertToHex(byte[] fp) { + String fingerPrint = ""; + for (int i = 0; i < fp.length; ++i) { + if (i != 0 && i % 10 == 0) { + fingerPrint += " "; + } else if (i != 0 && i % 2 == 0) { + fingerPrint += " "; + } + String chunk = Integer.toHexString((fp[i] + 256) % 256).toUpperCase(); + while (chunk.length() < 2) { + chunk = "0" + chunk; + } + fingerPrint += chunk; + } + + return fingerPrint; + + } + + public static String getFingerPrint(long keyId) { + PGPPublicKey key = Apg.getPublicKey(keyId); + if (key == null) { + PGPSecretKey secretKey = Apg.getSecretKey(keyId); + if (secretKey == null) { + return ""; + } + key = secretKey.getPublicKey(); + } + + return convertToHex(key.getFingerprint()); + } + + public static String getSmallFingerPrint(long keyId) { + String fingerPrint = Long.toHexString(keyId & 0xffffffffL).toUpperCase(); + while (fingerPrint.length() < 8) { + fingerPrint = "0" + fingerPrint; + } + return fingerPrint; + } + + public static String keyToHex(long keyId) { + return getSmallFingerPrint(keyId >> 32) + getSmallFingerPrint(keyId); + } + + public static long keyFromHex(String data) { + int len = data.length(); + String s2 = data.substring(len - 8); + String s1 = data.substring(0, len - 8); + return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16); + } + + public static void deleteKey(int keyRingId) { + mDatabase.deleteKeyRing(keyRingId); + } + + public static PGPKeyRing getKeyRing(int keyRingId) { + return (PGPKeyRing) mDatabase.getKeyRing(keyRingId); + } + + public static PGPSecretKeyRing getSecretKeyRing(long keyId) { + byte[] data = mDatabase.getKeyRingDataFromKeyId(Id.database.type_secret, keyId); + if (data == null) { + return null; + } + try { + return new PGPSecretKeyRing(data); + } catch (IOException e) { + // no good way to handle this, return null + // TODO: some info? + } catch (PGPException e) { + // no good way to handle this, return null + // TODO: some info? + } + return null; + } + + public static PGPPublicKeyRing getPublicKeyRing(long keyId) { + byte[] data = mDatabase.getKeyRingDataFromKeyId(Id.database.type_public, keyId); + if (data == null) { + return null; + } + try { + return new PGPPublicKeyRing(data); + } catch (IOException e) { + // no good way to handle this, return null + // TODO: some info? + } + return null; + } + + public static PGPSecretKey getSecretKey(long keyId) { + PGPSecretKeyRing keyRing = getSecretKeyRing(keyId); + if (keyRing == null) { + return null; + } + return keyRing.getSecretKey(keyId); + } + + public static PGPPublicKey getPublicKey(long keyId) { + PGPPublicKeyRing keyRing = getPublicKeyRing(keyId); + if (keyRing == null) { + return null; + } + + return keyRing.getPublicKey(keyId); + } + + public static Vector getKeyRingIds(int type) { + SQLiteDatabase db = mDatabase.db(); + Vector keyIds = new Vector(); + Cursor c = db.query(KeyRings.TABLE_NAME, new String[] { KeyRings._ID }, KeyRings.TYPE + + " = ?", new String[] { "" + type }, null, null, null); + if (c != null && c.moveToFirst()) { + do { + keyIds.add(c.getInt(0)); + } while (c.moveToNext()); + } + + if (c != null) { + c.close(); + } + + return keyIds; + } + + public static String getMainUserId(long keyId, int type) { + SQLiteDatabase db = mDatabase.db(); + Cursor c = db.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON (" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "." + + Keys.KEY_RING_ID + ") " + " INNER JOIN " + Keys.TABLE_NAME + " AS masterKey ON (" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + "masterKey." + + Keys.KEY_RING_ID + " AND " + "masterKey." + Keys.IS_MASTER_KEY + " = '1') " + + " INNER JOIN " + UserIds.TABLE_NAME + " ON (" + UserIds.TABLE_NAME + "." + + UserIds.KEY_ID + " = " + "masterKey." + Keys._ID + " AND " + UserIds.TABLE_NAME + + "." + UserIds.RANK + " = '0')", new String[] { UserIds.USER_ID }, Keys.TABLE_NAME + + "." + Keys.KEY_ID + " = ? AND " + KeyRings.TABLE_NAME + "." + KeyRings.TYPE + + " = ?", new String[] { "" + keyId, "" + type, }, null, null, null); + String userId = ""; + if (c != null && c.moveToFirst()) { + do { + userId = c.getString(0); + } while (c.moveToNext()); + } + + if (c != null) { + c.close(); + } + + return userId; + } + + public static void encrypt(Context context, InputData data, OutputStream outStream, + boolean armored, long encryptionKeyIds[], long signatureKeyId, + String signaturePassPhrase, ProgressDialogUpdater progress, int symmetricAlgorithm, + int hashAlgorithm, int compression, boolean forceV3Signature, String passPhrase) + throws IOException, GeneralException, PGPException, NoSuchProviderException, + NoSuchAlgorithmException, SignatureException { + Security.addProvider(new BouncyCastleProvider()); + + if (encryptionKeyIds == null) { + encryptionKeyIds = new long[0]; + } + + ArmoredOutputStream armorOut = null; + OutputStream out = null; + OutputStream encryptOut = null; + if (armored) { + armorOut = new ArmoredOutputStream(outStream); + armorOut.setHeader("Version", getFullVersion(context)); + out = armorOut; + } else { + out = outStream; + } + PGPSecretKey signingKey = null; + PGPSecretKeyRing signingKeyRing = null; + PGPPrivateKey signaturePrivateKey = null; + + if (encryptionKeyIds.length == 0 && passPhrase == null) { + throw new GeneralException( + context.getString(R.string.error_noEncryptionKeysOrPassPhrase)); + } + + if (signatureKeyId != 0) { + signingKeyRing = getSecretKeyRing(signatureKeyId); + signingKey = getSigningKey(signatureKeyId); + if (signingKey == null) { + throw new GeneralException(context.getString(R.string.error_signatureFailed)); + } + + if (signaturePassPhrase == null) { + throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase)); + } + if (progress != null) + progress.setProgress(R.string.progress_extractingSignatureKey, 0, 100); + signaturePrivateKey = signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(), + new BouncyCastleProvider()); + if (signaturePrivateKey == null) { + throw new GeneralException( + context.getString(R.string.error_couldNotExtractPrivateKey)); + } + } + if (progress != null) + progress.setProgress(R.string.progress_preparingStreams, 5, 100); + + // encrypt and compress input file content + PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(symmetricAlgorithm, true, + new SecureRandom(), new BouncyCastleProvider()); + + if (encryptionKeyIds.length == 0) { + // symmetric encryption + cPk.addMethod(passPhrase.toCharArray()); + } + for (int i = 0; i < encryptionKeyIds.length; ++i) { + PGPPublicKey key = getEncryptPublicKey(encryptionKeyIds[i]); + if (key != null) { + cPk.addMethod(key); + } + } + encryptOut = cPk.open(out, new byte[1 << 16]); + + PGPSignatureGenerator signatureGenerator = null; + PGPV3SignatureGenerator signatureV3Generator = null; + + if (signatureKeyId != 0) { + if (progress != null) + progress.setProgress(R.string.progress_preparingSignature, 10, 100); + if (forceV3Signature) { + signatureV3Generator = new PGPV3SignatureGenerator(signingKey.getPublicKey() + .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider()); + signatureV3Generator.initSign(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey); + } else { + signatureGenerator = new PGPSignatureGenerator(signingKey.getPublicKey() + .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider()); + signatureGenerator.initSign(PGPSignature.BINARY_DOCUMENT, signaturePrivateKey); + + String userId = getMainUserId(getMasterKey(signingKeyRing)); + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + spGen.setSignerUserID(false, userId); + signatureGenerator.setHashedSubpackets(spGen.generate()); + } + } + + PGPCompressedDataGenerator compressGen = null; + BCPGOutputStream bcpgOut = null; + if (compression == Id.choice.compression.none) { + bcpgOut = new BCPGOutputStream(encryptOut); + } else { + compressGen = new PGPCompressedDataGenerator(compression); + bcpgOut = new BCPGOutputStream(compressGen.open(encryptOut)); + } + if (signatureKeyId != 0) { + if (forceV3Signature) { + signatureV3Generator.generateOnePassVersion(false).encode(bcpgOut); + } else { + signatureGenerator.generateOnePassVersion(false).encode(bcpgOut); + } + } + + PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator(); + // file name not needed, so empty string + OutputStream pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, "", new Date(), + new byte[1 << 16]); + if (progress != null) + progress.setProgress(R.string.progress_encrypting, 20, 100); + + long done = 0; + int n = 0; + byte[] buffer = new byte[1 << 16]; + InputStream in = data.getInputStream(); + while ((n = in.read(buffer)) > 0) { + pOut.write(buffer, 0, n); + if (signatureKeyId != 0) { + if (forceV3Signature) { + signatureV3Generator.update(buffer, 0, n); + } else { + signatureGenerator.update(buffer, 0, n); + } + } + done += n; + if (data.getSize() != 0) { + if (progress != null) + progress.setProgress((int) (20 + (95 - 20) * done / data.getSize()), 100); + } + } + + literalGen.close(); + + if (signatureKeyId != 0) { + if (progress != null) + progress.setProgress(R.string.progress_generatingSignature, 95, 100); + if (forceV3Signature) { + signatureV3Generator.generate().encode(pOut); + } else { + signatureGenerator.generate().encode(pOut); + } + } + if (compressGen != null) { + compressGen.close(); + } + encryptOut.close(); + if (armored) { + armorOut.close(); + } + + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + } + + public static void signText(Context context, InputData data, OutputStream outStream, + long signatureKeyId, String signaturePassPhrase, int hashAlgorithm, + boolean forceV3Signature, ProgressDialogUpdater progress) throws GeneralException, + PGPException, IOException, NoSuchAlgorithmException, SignatureException { + Security.addProvider(new BouncyCastleProvider()); + + ArmoredOutputStream armorOut = new ArmoredOutputStream(outStream); + armorOut.setHeader("Version", getFullVersion(context)); + + PGPSecretKey signingKey = null; + PGPSecretKeyRing signingKeyRing = null; + PGPPrivateKey signaturePrivateKey = null; + + if (signatureKeyId == 0) { + throw new GeneralException(context.getString(R.string.error_noSignatureKey)); + } + + signingKeyRing = getSecretKeyRing(signatureKeyId); + signingKey = getSigningKey(signatureKeyId); + if (signingKey == null) { + throw new GeneralException(context.getString(R.string.error_signatureFailed)); + } + + if (signaturePassPhrase == null) { + throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase)); + } + signaturePrivateKey = signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(), + new BouncyCastleProvider()); + if (signaturePrivateKey == null) { + throw new GeneralException(context.getString(R.string.error_couldNotExtractPrivateKey)); + } + if (progress != null) + progress.setProgress(R.string.progress_preparingStreams, 0, 100); + + if (progress != null) + progress.setProgress(R.string.progress_preparingSignature, 30, 100); + + PGPSignatureGenerator signatureGenerator = null; + PGPV3SignatureGenerator signatureV3Generator = null; + + if (forceV3Signature) { + signatureV3Generator = new PGPV3SignatureGenerator(signingKey.getPublicKey() + .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider()); + signatureV3Generator + .initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey); + } else { + signatureGenerator = new PGPSignatureGenerator( + signingKey.getPublicKey().getAlgorithm(), hashAlgorithm, + new BouncyCastleProvider()); + signatureGenerator.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, signaturePrivateKey); + + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + String userId = getMainUserId(getMasterKey(signingKeyRing)); + spGen.setSignerUserID(false, userId); + signatureGenerator.setHashedSubpackets(spGen.generate()); + } + + if (progress != null) + progress.setProgress(R.string.progress_signing, 40, 100); + + armorOut.beginClearText(hashAlgorithm); + + InputStream inStream = data.getInputStream(); + final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream)); + + final byte[] newline = "\r\n".getBytes("UTF-8"); + + if (forceV3Signature) { + processLine(reader.readLine(), armorOut, signatureV3Generator); + } else { + processLine(reader.readLine(), armorOut, signatureGenerator); + } + + while (true) { + final String line = reader.readLine(); + + if (line == null) { + armorOut.write(newline); + break; + } + + armorOut.write(newline); + if (forceV3Signature) { + signatureV3Generator.update(newline); + processLine(line, armorOut, signatureV3Generator); + } else { + signatureGenerator.update(newline); + processLine(line, armorOut, signatureGenerator); + } + } + + armorOut.endClearText(); + + BCPGOutputStream bOut = new BCPGOutputStream(armorOut); + if (forceV3Signature) { + signatureV3Generator.generate().encode(bOut); + } else { + signatureGenerator.generate().encode(bOut); + } + armorOut.close(); + + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + } + + public static void generateSignature(Context context, InputData data, OutputStream outStream, + boolean armored, boolean binary, long signatureKeyId, String signaturePassPhrase, + int hashAlgorithm, boolean forceV3Signature, ProgressDialogUpdater progress) + throws GeneralException, PGPException, IOException, NoSuchAlgorithmException, + SignatureException { + Security.addProvider(new BouncyCastleProvider()); + + ArmoredOutputStream armorOut = null; + OutputStream out = null; + if (armored) { + armorOut = new ArmoredOutputStream(outStream); + armorOut.setHeader("Version", getFullVersion(context)); + out = armorOut; + } else { + out = outStream; + } + + PGPSecretKey signingKey = null; + PGPSecretKeyRing signingKeyRing = null; + PGPPrivateKey signaturePrivateKey = null; + + if (signatureKeyId == 0) { + throw new GeneralException(context.getString(R.string.error_noSignatureKey)); + } + + signingKeyRing = getSecretKeyRing(signatureKeyId); + signingKey = getSigningKey(signatureKeyId); + if (signingKey == null) { + throw new GeneralException(context.getString(R.string.error_signatureFailed)); + } + + if (signaturePassPhrase == null) { + throw new GeneralException(context.getString(R.string.error_noSignaturePassPhrase)); + } + signaturePrivateKey = signingKey.extractPrivateKey(signaturePassPhrase.toCharArray(), + new BouncyCastleProvider()); + if (signaturePrivateKey == null) { + throw new GeneralException(context.getString(R.string.error_couldNotExtractPrivateKey)); + } + if (progress != null) + progress.setProgress(R.string.progress_preparingStreams, 0, 100); + + if (progress != null) + progress.setProgress(R.string.progress_preparingSignature, 30, 100); + + PGPSignatureGenerator signatureGenerator = null; + PGPV3SignatureGenerator signatureV3Generator = null; + + int type = PGPSignature.CANONICAL_TEXT_DOCUMENT; + if (binary) { + type = PGPSignature.BINARY_DOCUMENT; + } + + if (forceV3Signature) { + signatureV3Generator = new PGPV3SignatureGenerator(signingKey.getPublicKey() + .getAlgorithm(), hashAlgorithm, new BouncyCastleProvider()); + signatureV3Generator.initSign(type, signaturePrivateKey); + } else { + signatureGenerator = new PGPSignatureGenerator( + signingKey.getPublicKey().getAlgorithm(), hashAlgorithm, + new BouncyCastleProvider()); + signatureGenerator.initSign(type, signaturePrivateKey); + + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + String userId = getMainUserId(getMasterKey(signingKeyRing)); + spGen.setSignerUserID(false, userId); + signatureGenerator.setHashedSubpackets(spGen.generate()); + } + + if (progress != null) + progress.setProgress(R.string.progress_signing, 40, 100); + + InputStream inStream = data.getInputStream(); + if (binary) { + byte[] buffer = new byte[1 << 16]; + int n = 0; + while ((n = inStream.read(buffer)) > 0) { + if (forceV3Signature) { + signatureV3Generator.update(buffer, 0, n); + } else { + signatureGenerator.update(buffer, 0, n); + } + } + } else { + final BufferedReader reader = new BufferedReader(new InputStreamReader(inStream)); + final byte[] newline = "\r\n".getBytes("UTF-8"); + + while (true) { + final String line = reader.readLine(); + + if (line == null) { + break; + } + + if (forceV3Signature) { + processLine(line, null, signatureV3Generator); + signatureV3Generator.update(newline); + } else { + processLine(line, null, signatureGenerator); + signatureGenerator.update(newline); + } + } + } + + BCPGOutputStream bOut = new BCPGOutputStream(out); + if (forceV3Signature) { + signatureV3Generator.generate().encode(bOut); + } else { + signatureGenerator.generate().encode(bOut); + } + out.close(); + outStream.close(); + + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + } + + public static long getDecryptionKeyId(Context context, InputData data) throws GeneralException, + NoAsymmetricEncryptionException, IOException { + InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); + PGPObjectFactory pgpF = new PGPObjectFactory(in); + PGPEncryptedDataList enc; + Object o = pgpF.nextObject(); + + // the first object might be a PGP marker packet. + if (o instanceof PGPEncryptedDataList) { + enc = (PGPEncryptedDataList) o; + } else { + enc = (PGPEncryptedDataList) pgpF.nextObject(); + } + + if (enc == null) { + throw new GeneralException(context.getString(R.string.error_invalidData)); + } + + // TODO: currently we always only look at the first known key + // find the secret key + PGPSecretKey secretKey = null; + Iterator it = enc.getEncryptedDataObjects(); + boolean gotAsymmetricEncryption = false; + while (it.hasNext()) { + Object obj = it.next(); + if (obj instanceof PGPPublicKeyEncryptedData) { + gotAsymmetricEncryption = true; + PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) obj; + secretKey = getSecretKey(pbe.getKeyID()); + if (secretKey != null) { + break; + } + } + } + + if (!gotAsymmetricEncryption) { + throw new NoAsymmetricEncryptionException(); + } + + if (secretKey == null) { + return Id.key.none; + } + + return secretKey.getKeyID(); + } + + public static boolean hasSymmetricEncryption(Context context, InputData data) + throws GeneralException, IOException { + InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); + PGPObjectFactory pgpF = new PGPObjectFactory(in); + PGPEncryptedDataList enc; + Object o = pgpF.nextObject(); + + // the first object might be a PGP marker packet. + if (o instanceof PGPEncryptedDataList) { + enc = (PGPEncryptedDataList) o; + } else { + enc = (PGPEncryptedDataList) pgpF.nextObject(); + } + + if (enc == null) { + throw new GeneralException(context.getString(R.string.error_invalidData)); + } + + Iterator it = enc.getEncryptedDataObjects(); + while (it.hasNext()) { + Object obj = it.next(); + if (obj instanceof PGPPBEEncryptedData) { + return true; + } + } + + return false; + } + + public static Bundle decrypt(Context context, InputData data, OutputStream outStream, + String passPhrase, ProgressDialogUpdater progress, boolean assumeSymmetric) + throws IOException, GeneralException, PGPException, SignatureException { + if (passPhrase == null) { + passPhrase = ""; + } + Bundle returnData = new Bundle(); + InputStream in = PGPUtil.getDecoderStream(data.getInputStream()); + PGPObjectFactory pgpF = new PGPObjectFactory(in); + PGPEncryptedDataList enc; + Object o = pgpF.nextObject(); + long signatureKeyId = 0; + + int currentProgress = 0; + if (progress != null) + progress.setProgress(R.string.progress_readingData, currentProgress, 100); + + if (o instanceof PGPEncryptedDataList) { + enc = (PGPEncryptedDataList) o; + } else { + enc = (PGPEncryptedDataList) pgpF.nextObject(); + } + + if (enc == null) { + throw new GeneralException(context.getString(R.string.error_invalidData)); + } + + InputStream clear = null; + PGPEncryptedData encryptedData = null; + + currentProgress += 5; + + // TODO: currently we always only look at the first known key or symmetric encryption, + // there might be more... + if (assumeSymmetric) { + PGPPBEEncryptedData pbe = null; + Iterator it = enc.getEncryptedDataObjects(); + // find secret key + while (it.hasNext()) { + Object obj = it.next(); + if (obj instanceof PGPPBEEncryptedData) { + pbe = (PGPPBEEncryptedData) obj; + break; + } + } + + if (pbe == null) { + throw new GeneralException( + context.getString(R.string.error_noSymmetricEncryptionPacket)); + } + + if (progress != null) + progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100); + clear = pbe.getDataStream(passPhrase.toCharArray(), new BouncyCastleProvider()); + encryptedData = pbe; + currentProgress += 5; + } else { + if (progress != null) + progress.setProgress(R.string.progress_findingKey, currentProgress, 100); + PGPPublicKeyEncryptedData pbe = null; + PGPSecretKey secretKey = null; + Iterator it = enc.getEncryptedDataObjects(); + // find secret key + while (it.hasNext()) { + Object obj = it.next(); + if (obj instanceof PGPPublicKeyEncryptedData) { + PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj; + secretKey = getSecretKey(encData.getKeyID()); + if (secretKey != null) { + pbe = encData; + break; + } + } + } + + if (secretKey == null) { + throw new GeneralException(context.getString(R.string.error_noSecretKeyFound)); + } + + currentProgress += 5; + if (progress != null) + progress.setProgress(R.string.progress_extractingKey, currentProgress, 100); + PGPPrivateKey privateKey = null; + try { + privateKey = secretKey.extractPrivateKey(passPhrase.toCharArray(), + new BouncyCastleProvider()); + } catch (PGPException e) { + throw new PGPException(context.getString(R.string.error_wrongPassPhrase)); + } + if (privateKey == null) { + throw new GeneralException( + context.getString(R.string.error_couldNotExtractPrivateKey)); + } + currentProgress += 5; + if (progress != null) + progress.setProgress(R.string.progress_preparingStreams, currentProgress, 100); + clear = pbe.getDataStream(privateKey, new BouncyCastleProvider()); + encryptedData = pbe; + currentProgress += 5; + } + + PGPObjectFactory plainFact = new PGPObjectFactory(clear); + Object dataChunk = plainFact.nextObject(); + PGPOnePassSignature signature = null; + PGPPublicKey signatureKey = null; + int signatureIndex = -1; + + if (dataChunk instanceof PGPCompressedData) { + if (progress != null) + progress.setProgress(R.string.progress_decompressingData, currentProgress, 100); + PGPObjectFactory fact = new PGPObjectFactory( + ((PGPCompressedData) dataChunk).getDataStream()); + dataChunk = fact.nextObject(); + plainFact = fact; + currentProgress += 10; + } + + if (dataChunk instanceof PGPOnePassSignatureList) { + if (progress != null) + progress.setProgress(R.string.progress_processingSignature, currentProgress, 100); + returnData.putBoolean(EXTRA_SIGNATURE, true); + PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk; + for (int i = 0; i < sigList.size(); ++i) { + signature = sigList.get(i); + signatureKey = getPublicKey(signature.getKeyID()); + if (signatureKeyId == 0) { + signatureKeyId = signature.getKeyID(); + } + if (signatureKey == null) { + signature = null; + } else { + signatureIndex = i; + signatureKeyId = signature.getKeyID(); + String userId = null; + PGPPublicKeyRing sigKeyRing = getPublicKeyRing(signatureKeyId); + if (sigKeyRing != null) { + userId = getMainUserId(getMasterKey(sigKeyRing)); + } + returnData.putString(EXTRA_SIGNATURE_USER_ID, userId); + break; + } + } + + returnData.putLong(EXTRA_SIGNATURE_KEY_ID, signatureKeyId); + + if (signature != null) { + signature.initVerify(signatureKey, new BouncyCastleProvider()); + } else { + returnData.putBoolean(EXTRA_SIGNATURE_UNKNOWN, true); + } + + dataChunk = plainFact.nextObject(); + currentProgress += 10; + } + + if (dataChunk instanceof PGPSignatureList) { + dataChunk = plainFact.nextObject(); + } + + if (dataChunk instanceof PGPLiteralData) { + if (progress != null) + progress.setProgress(R.string.progress_decrypting, currentProgress, 100); + PGPLiteralData literalData = (PGPLiteralData) dataChunk; + OutputStream out = outStream; + + byte[] buffer = new byte[1 << 16]; + InputStream dataIn = literalData.getInputStream(); + + int startProgress = currentProgress; + int endProgress = 100; + if (signature != null) { + endProgress = 90; + } else if (encryptedData.isIntegrityProtected()) { + endProgress = 95; + } + int n = 0; + int done = 0; + long startPos = data.getStreamPosition(); + while ((n = dataIn.read(buffer)) > 0) { + out.write(buffer, 0, n); + done += n; + if (signature != null) { + try { + signature.update(buffer, 0, n); + } catch (SignatureException e) { + returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, false); + signature = null; + } + } + // unknown size, but try to at least have a moving, slowing down progress bar + currentProgress = startProgress + (endProgress - startProgress) * done + / (done + 100000); + if (data.getSize() - startPos == 0) { + currentProgress = endProgress; + } else { + currentProgress = (int) (startProgress + (endProgress - startProgress) + * (data.getStreamPosition() - startPos) / (data.getSize() - startPos)); + } + if (progress != null) + progress.setProgress(currentProgress, 100); + } + + if (signature != null) { + if (progress != null) + progress.setProgress(R.string.progress_verifyingSignature, 90, 100); + PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject(); + PGPSignature messageSignature = signatureList.get(signatureIndex); + if (signature.verify(messageSignature)) { + returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, true); + } else { + returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, false); + } + } + } + + // TODO: add integrity somewhere + if (encryptedData.isIntegrityProtected()) { + if (progress != null) + progress.setProgress(R.string.progress_verifyingIntegrity, 95, 100); + if (encryptedData.verify()) { + // passed + } else { + // failed + } + } else { + // no integrity check + } + + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + return returnData; + } + + public static Bundle verifyText(BaseActivity context, InputData data, OutputStream outStream, + ProgressDialogUpdater progress) throws IOException, GeneralException, PGPException, + SignatureException { + Bundle returnData = new Bundle(); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ArmoredInputStream aIn = new ArmoredInputStream(data.getInputStream()); + + if (progress != null) + progress.setProgress(R.string.progress_done, 0, 100); + + // mostly taken from ClearSignedFileProcessor + ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); + int lookAhead = readInputLine(lineOut, aIn); + byte[] lineSep = getLineSeparator(); + + byte[] line = lineOut.toByteArray(); + out.write(line, 0, getLengthWithoutSeparator(line)); + out.write(lineSep); + + while (lookAhead != -1 && aIn.isClearText()) { + lookAhead = readInputLine(lineOut, lookAhead, aIn); + line = lineOut.toByteArray(); + out.write(line, 0, getLengthWithoutSeparator(line)); + out.write(lineSep); + } + + out.close(); + + byte[] clearText = out.toByteArray(); + outStream.write(clearText); + + returnData.putBoolean(EXTRA_SIGNATURE, true); + + if (progress != null) + progress.setProgress(R.string.progress_processingSignature, 60, 100); + PGPObjectFactory pgpFact = new PGPObjectFactory(aIn); + + PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject(); + if (sigList == null) { + throw new GeneralException(context.getString(R.string.error_corruptData)); + } + PGPSignature signature = null; + long signatureKeyId = 0; + PGPPublicKey signatureKey = null; + for (int i = 0; i < sigList.size(); ++i) { + signature = sigList.get(i); + signatureKey = getPublicKey(signature.getKeyID()); + if (signatureKeyId == 0) { + signatureKeyId = signature.getKeyID(); + } + if (signatureKey == null) { + Bundle pauseData = new Bundle(); + pauseData.putInt(Constants.extras.STATUS, Id.message.unknown_signature_key); + pauseData.putLong(Constants.extras.KEY_ID, signatureKeyId); + Message msg = new Message(); + msg.setData(pauseData); + context.sendMessage(msg); + // pause here + context.getRunningThread().pause(); + // see whether the key was found in the meantime + signatureKey = getPublicKey(signature.getKeyID()); + } + + if (signatureKey == null) { + signature = null; + } else { + signatureKeyId = signature.getKeyID(); + String userId = null; + PGPPublicKeyRing sigKeyRing = getPublicKeyRing(signatureKeyId); + if (sigKeyRing != null) { + userId = getMainUserId(getMasterKey(sigKeyRing)); + } + returnData.putString(EXTRA_SIGNATURE_USER_ID, userId); + break; + } + } + + returnData.putLong(EXTRA_SIGNATURE_KEY_ID, signatureKeyId); + + if (signature == null) { + returnData.putBoolean(EXTRA_SIGNATURE_UNKNOWN, true); + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + return returnData; + } + + signature.initVerify(signatureKey, new BouncyCastleProvider()); + + InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText)); + + lookAhead = readInputLine(lineOut, sigIn); + + processLine(signature, lineOut.toByteArray()); + + if (lookAhead != -1) { + do { + lookAhead = readInputLine(lineOut, lookAhead, sigIn); + + signature.update((byte) '\r'); + signature.update((byte) '\n'); + + processLine(signature, lineOut.toByteArray()); + } while (lookAhead != -1); + } + + returnData.putBoolean(EXTRA_SIGNATURE_SUCCESS, signature.verify()); + + if (progress != null) + progress.setProgress(R.string.progress_done, 100, 100); + return returnData; + } + + public static int getStreamContent(Context context, InputStream inStream) throws IOException { + InputStream in = PGPUtil.getDecoderStream(inStream); + PGPObjectFactory pgpF = new PGPObjectFactory(in); + Object object = pgpF.nextObject(); + while (object != null) { + if (object instanceof PGPPublicKeyRing || object instanceof PGPSecretKeyRing) { + return Id.content.keys; + } else if (object instanceof PGPEncryptedDataList) { + return Id.content.encrypted_data; + } + object = pgpF.nextObject(); + } + + return Id.content.unknown; + } + + private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, + final PGPSignatureGenerator pSignatureGenerator) throws IOException, SignatureException { + + if (pLine == null) { + return; + } + + final char[] chars = pLine.toCharArray(); + int len = chars.length; + + while (len > 0) { + if (!Character.isWhitespace(chars[len - 1])) { + break; + } + len--; + } + + final byte[] data = pLine.substring(0, len).getBytes("UTF-8"); + + if (pArmoredOutput != null) { + pArmoredOutput.write(data); + } + pSignatureGenerator.update(data); + } + + private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput, + final PGPV3SignatureGenerator pSignatureGenerator) throws IOException, + SignatureException { + + if (pLine == null) { + return; + } + + final char[] chars = pLine.toCharArray(); + int len = chars.length; + + while (len > 0) { + if (!Character.isWhitespace(chars[len - 1])) { + break; + } + len--; + } + + final byte[] data = pLine.substring(0, len).getBytes("UTF-8"); + + if (pArmoredOutput != null) { + pArmoredOutput.write(data); + } + pSignatureGenerator.update(data); + } + + // taken from ClearSignedFileProcessor in BC + private static void processLine(PGPSignature sig, byte[] line) throws SignatureException, + IOException { + int length = getLengthWithoutWhiteSpace(line); + if (length > 0) { + sig.update(line, 0, length); + } + } + + private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) + throws IOException { + bOut.reset(); + + int lookAhead = -1; + int ch; + + while ((ch = fIn.read()) >= 0) { + bOut.write(ch); + if (ch == '\r' || ch == '\n') { + lookAhead = readPassedEOL(bOut, ch, fIn); + break; + } + } + + return lookAhead; + } + + private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn) + throws IOException { + bOut.reset(); + + int ch = lookAhead; + + do { + bOut.write(ch); + if (ch == '\r' || ch == '\n') { + lookAhead = readPassedEOL(bOut, ch, fIn); + break; + } + } while ((ch = fIn.read()) >= 0); + + if (ch < 0) { + lookAhead = -1; + } + + return lookAhead; + } + + private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) + throws IOException { + int lookAhead = fIn.read(); + + if (lastCh == '\r' && lookAhead == '\n') { + bOut.write(lookAhead); + lookAhead = fIn.read(); + } + + return lookAhead; + } + + private static int getLengthWithoutSeparator(byte[] line) { + int end = line.length - 1; + + while (end >= 0 && isLineEnding(line[end])) { + end--; + } + + return end + 1; + } + + private static boolean isLineEnding(byte b) { + return b == '\r' || b == '\n'; + } + + private static int getLengthWithoutWhiteSpace(byte[] line) { + int end = line.length - 1; + + while (end >= 0 && isWhiteSpace(line[end])) { + end--; + } + + return end + 1; + } + + private static boolean isWhiteSpace(byte b) { + return b == '\r' || b == '\n' || b == '\t' || b == ' '; + } + + private static byte[] getLineSeparator() { + String nl = System.getProperty("line.separator"); + byte[] nlBytes = new byte[nl.length()]; + + for (int i = 0; i != nlBytes.length; i++) { + nlBytes[i] = (byte) nl.charAt(i); + } + + return nlBytes; + } + + public static boolean isReleaseVersion(Context context) { + try { + PackageInfo pi = context.getPackageManager().getPackageInfo(PACKAGE_NAME, 0); + if (pi.versionCode % 100 == 99) { + return true; + } else { + return false; + } + } catch (NameNotFoundException e) { + // impossible! + return false; + } + } + + public static String getVersion(Context context) { + if (VERSION != null) { + return VERSION; + } + try { + PackageInfo pi = context.getPackageManager().getPackageInfo(PACKAGE_NAME, 0); + VERSION = pi.versionName; + return VERSION; + } catch (NameNotFoundException e) { + // impossible! + return "0.0.0"; + } + } + + public static String getFullVersion(Context context) { + return "APG v" + getVersion(context); + } + + public static String generateRandomString(int length) { + SecureRandom random = new SecureRandom(); + /* + * try { random = SecureRandom.getInstance("SHA1PRNG", new BouncyCastleProvider()); } catch + * (NoSuchAlgorithmException e) { // TODO: need to handle this case somehow return null; } + */ + byte bytes[] = new byte[length]; + random.nextBytes(bytes); + String result = ""; + for (int i = 0; i < length; ++i) { + int v = (bytes[i] + 256) % 64; + if (v < 10) { + result += (char) ('0' + v); + } else if (v < 36) { + result += (char) ('A' + v - 10); + } else if (v < 62) { + result += (char) ('a' + v - 36); + } else if (v == 62) { + result += '_'; + } else if (v == 63) { + result += '.'; + } + } + return result; + } + + static long getLengthOfStream(InputStream in) throws IOException { + long size = 0; + long n = 0; + byte dummy[] = new byte[0x10000]; + while ((n = in.read(dummy)) > 0) { + size += n; + } + return size; + } + + public static void deleteFileSecurely(Context context, File file, ProgressDialogUpdater progress) + throws FileNotFoundException, IOException { + long length = file.length(); + SecureRandom random = new SecureRandom(); + RandomAccessFile raf = new RandomAccessFile(file, "rws"); + raf.seek(0); + raf.getFilePointer(); + byte[] data = new byte[1 << 16]; + int pos = 0; + String msg = context.getString(R.string.progress_deletingSecurely, file.getName()); + while (pos < length) { + if (progress != null) + progress.setProgress(msg, (int) (100 * pos / length), 100); + random.nextBytes(data); + raf.write(data); + pos += data.length; + } + raf.close(); + file.delete(); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ApgService.java b/org_apg/src/org/thialfihar/android/apg/ApgService.java new file mode 100644 index 000000000..f154e8132 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ApgService.java @@ -0,0 +1,658 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; + +import org.thialfihar.android.apg.IApgService; +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.provider.KeyRings; +import org.thialfihar.android.apg.provider.Keys; +import org.thialfihar.android.apg.provider.UserIds; + +import android.content.ContentResolver; +import android.content.Intent; +import android.database.Cursor; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.os.Bundle; +import android.os.IBinder; +import android.util.Log; + +public class ApgService extends Service { + private final static String TAG = "ApgService"; + public static final boolean LOCAL_LOGV = true; + public static final boolean LOCAL_LOGD = true; + + @Override + public IBinder onBind(Intent intent) { + if (LOCAL_LOGD) + Log.d(TAG, "bound"); + return mBinder; + } + + /** error status */ + private static enum error { + ARGUMENTS_MISSING, APG_FAILURE, NO_MATCHING_SECRET_KEY, PRIVATE_KEY_PASSPHRASE_WRONG, PRIVATE_KEY_PASSPHRASE_MISSING; + + public int shiftedOrdinal() { + return ordinal() + 100; + } + } + + private static enum call { + encrypt_with_passphrase, encrypt_with_public_key, decrypt, get_keys + } + + /** all arguments that can be passed by calling application */ + public static enum arg { + MESSAGE, // message to encrypt or to decrypt + SYMMETRIC_PASSPHRASE, // key for symmetric en/decryption + PUBLIC_KEYS, // public keys for encryption + ENCRYPTION_ALGORYTHM, // encryption algorithm + HASH_ALGORYTHM, // hash algorithm + ARMORED_OUTPUT, // whether to armor output + FORCE_V3_SIGNATURE, // whether to force v3 signature + COMPRESSION, // what compression to use for encrypted output + SIGNATURE_KEY, // key for signing + PRIVATE_KEY_PASSPHRASE, // passphrase for encrypted private key + KEY_TYPE, // type of key (private or public) + BLOB, // blob passed + } + + /** all things that might be returned */ + private static enum ret { + ERRORS, // string array list with errors + WARNINGS, // string array list with warnings + ERROR, // numeric error + RESULT, // en-/decrypted + FINGERPRINTS, // fingerprints of keys + USER_IDS, // user ids + } + + /** required arguments for each AIDL function */ + private static final HashMap> FUNCTIONS_REQUIRED_ARGS = new HashMap>(); + static { + HashSet args = new HashSet(); + args.add(arg.SYMMETRIC_PASSPHRASE); + FUNCTIONS_REQUIRED_ARGS.put(call.encrypt_with_passphrase.name(), args); + + args = new HashSet(); + args.add(arg.PUBLIC_KEYS); + FUNCTIONS_REQUIRED_ARGS.put(call.encrypt_with_public_key.name(), args); + + args = new HashSet(); + FUNCTIONS_REQUIRED_ARGS.put(call.decrypt.name(), args); + + args = new HashSet(); + args.add(arg.KEY_TYPE); + FUNCTIONS_REQUIRED_ARGS.put(call.get_keys.name(), args); + } + + /** optional arguments for each AIDL function */ + private static final HashMap> FUNCTIONS_OPTIONAL_ARGS = new HashMap>(); + static { + HashSet args = new HashSet(); + args.add(arg.ENCRYPTION_ALGORYTHM); + args.add(arg.HASH_ALGORYTHM); + args.add(arg.ARMORED_OUTPUT); + args.add(arg.FORCE_V3_SIGNATURE); + args.add(arg.COMPRESSION); + args.add(arg.PRIVATE_KEY_PASSPHRASE); + args.add(arg.SIGNATURE_KEY); + args.add(arg.BLOB); + args.add(arg.MESSAGE); + FUNCTIONS_OPTIONAL_ARGS.put(call.encrypt_with_passphrase.name(), args); + FUNCTIONS_OPTIONAL_ARGS.put(call.encrypt_with_public_key.name(), args); + + args = new HashSet(); + args.add(arg.SYMMETRIC_PASSPHRASE); + args.add(arg.PUBLIC_KEYS); + args.add(arg.PRIVATE_KEY_PASSPHRASE); + args.add(arg.MESSAGE); + args.add(arg.BLOB); + FUNCTIONS_OPTIONAL_ARGS.put(call.decrypt.name(), args); + } + + /** a map from ApgService parameters to function calls to get the default */ + private static final HashMap FUNCTIONS_DEFAULTS = new HashMap(); + static { + FUNCTIONS_DEFAULTS.put(arg.ENCRYPTION_ALGORYTHM, "getDefaultEncryptionAlgorithm"); + FUNCTIONS_DEFAULTS.put(arg.HASH_ALGORYTHM, "getDefaultHashAlgorithm"); + FUNCTIONS_DEFAULTS.put(arg.ARMORED_OUTPUT, "getDefaultAsciiArmour"); + FUNCTIONS_DEFAULTS.put(arg.FORCE_V3_SIGNATURE, "getForceV3Signatures"); + FUNCTIONS_DEFAULTS.put(arg.COMPRESSION, "getDefaultMessageCompression"); + } + + /** a map of the default function names to their method */ + private static final HashMap FUNCTIONS_DEFAULTS_METHODS = new HashMap(); + static { + try { + FUNCTIONS_DEFAULTS_METHODS.put("getDefaultEncryptionAlgorithm", + Preferences.class.getMethod("getDefaultEncryptionAlgorithm")); + FUNCTIONS_DEFAULTS_METHODS.put("getDefaultHashAlgorithm", + Preferences.class.getMethod("getDefaultHashAlgorithm")); + FUNCTIONS_DEFAULTS_METHODS.put("getDefaultAsciiArmour", + Preferences.class.getMethod("getDefaultAsciiArmour")); + FUNCTIONS_DEFAULTS_METHODS.put("getForceV3Signatures", + Preferences.class.getMethod("getForceV3Signatures")); + FUNCTIONS_DEFAULTS_METHODS.put("getDefaultMessageCompression", + Preferences.class.getMethod("getDefaultMessageCompression")); + } catch (Exception e) { + Log.e(TAG, "Function method exception: " + e.getMessage()); + } + } + + private static void writeToOutputStream(InputStream is, OutputStream os) throws IOException { + byte[] buffer = new byte[8]; + int len = 0; + while ((len = is.read(buffer)) != -1) { + os.write(buffer, 0, len); + } + } + + private static Cursor getKeyEntries(HashMap pParams) { + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + "(" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "." + + Keys.KEY_RING_ID + " AND " + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + + " = '1'" + ") " + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + "(" + + Keys.TABLE_NAME + "." + Keys._ID + " = " + UserIds.TABLE_NAME + "." + + UserIds.KEY_ID + " AND " + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); + + String orderBy = pParams.containsKey("order_by") ? (String) pParams.get("order_by") + : UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC"; + + String typeVal[] = null; + String typeWhere = null; + if (pParams.containsKey("key_type")) { + typeWhere = KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?"; + typeVal = new String[] { "" + pParams.get("key_type") }; + } + return qb.query(Apg.getDatabase().db(), (String[]) pParams.get("columns"), typeWhere, + typeVal, null, null, orderBy); + } + + /** + * maps a fingerprint or user id of a key to a master key in database + * + * @param search_key + * fingerprint or user id to search for + * @return master key if found, or 0 + */ + private static long getMasterKey(String pSearchKey, Bundle pReturn) { + if (pSearchKey == null || pSearchKey.length() != 8) { + return 0; + } + ArrayList keyList = new ArrayList(); + keyList.add(pSearchKey); + long[] keys = getMasterKey(keyList, pReturn); + if (keys.length > 0) { + return keys[0]; + } else { + return 0; + } + } + + /** + * maps fingerprints or user ids of keys to master keys in database + * + * @param search_keys + * a list of keys (fingerprints or user ids) to look for in database + * @return an array of master keys + */ + private static long[] getMasterKey(ArrayList pSearchKeys, Bundle pReturn) { + + HashMap qParams = new HashMap(); + qParams.put("columns", new String[] { KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 0 + UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 1 + }); + qParams.put("key_type", Id.database.type_public); + + Cursor mCursor = getKeyEntries(qParams); + + if (LOCAL_LOGV) + Log.v(TAG, "going through installed user keys"); + ArrayList masterKeys = new ArrayList(); + while (mCursor.moveToNext()) { + long curMkey = mCursor.getLong(0); + String curUser = mCursor.getString(1); + + String curFprint = Apg.getSmallFingerPrint(curMkey); + if (LOCAL_LOGV) + Log.v(TAG, "current user: " + curUser + " (" + curFprint + ")"); + if (pSearchKeys.contains(curFprint) || pSearchKeys.contains(curUser)) { + if (LOCAL_LOGV) + Log.v(TAG, "master key found for: " + curFprint); + masterKeys.add(curMkey); + pSearchKeys.remove(curFprint); + } else { + if (LOCAL_LOGV) + Log.v(TAG, "Installed key " + curFprint + + " is not in the list of public keys to encrypt with"); + } + } + mCursor.close(); + + long[] masterKeyLongs = new long[masterKeys.size()]; + int i = 0; + for (Long key : masterKeys) { + masterKeyLongs[i++] = key; + } + + if (i == 0) { + Log.w(TAG, "Found not one public key"); + pReturn.getStringArrayList(ret.WARNINGS.name()).add( + "Searched for public key(s) but found not one"); + } + + for (String key : pSearchKeys) { + Log.w(TAG, "Searched for key " + key + " but cannot find it in APG"); + pReturn.getStringArrayList(ret.WARNINGS.name()).add( + "Searched for key " + key + " but cannot find it in APG"); + } + + return masterKeyLongs; + } + + /** + * Add default arguments if missing + * + * @param args + * the bundle to add default parameters to if missing + */ + private void addDefaultArguments(String pCall, Bundle pArgs) { + // check whether there are optional elements defined for that call + if (FUNCTIONS_OPTIONAL_ARGS.containsKey(pCall)) { + Preferences preferences = Preferences.getPreferences(getBaseContext(), true); + + Iterator iter = FUNCTIONS_DEFAULTS.keySet().iterator(); + while (iter.hasNext()) { + arg currentArg = iter.next(); + String currentKey = currentArg.name(); + if (!pArgs.containsKey(currentKey) + && FUNCTIONS_OPTIONAL_ARGS.get(pCall).contains(currentArg)) { + String currentFunctionName = FUNCTIONS_DEFAULTS.get(currentArg); + try { + Class returnType = FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName) + .getReturnType(); + if (returnType == String.class) { + pArgs.putString(currentKey, + (String) FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName) + .invoke(preferences)); + } else if (returnType == boolean.class) { + pArgs.putBoolean(currentKey, + (Boolean) FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName) + .invoke(preferences)); + } else if (returnType == int.class) { + pArgs.putInt(currentKey, + (Integer) FUNCTIONS_DEFAULTS_METHODS.get(currentFunctionName) + .invoke(preferences)); + } else { + Log.e(TAG, "Unknown return type " + returnType.toString() + + " for default option"); + } + } catch (Exception e) { + Log.e(TAG, "Exception in add_default_arguments " + e.getMessage()); + } + } + } + } + } + + /** + * updates a Bundle with default return values + * + * @param pReturn + * the Bundle to update + */ + private void addDefaultReturns(Bundle pReturn) { + ArrayList errors = new ArrayList(); + ArrayList warnings = new ArrayList(); + + pReturn.putStringArrayList(ret.ERRORS.name(), errors); + pReturn.putStringArrayList(ret.WARNINGS.name(), warnings); + } + + /** + * checks for required arguments and adds them to the error if missing + * + * @param function + * the functions required arguments to check for + * @param pArgs + * the Bundle of arguments to check + * @param pReturn + * the bundle to write errors to + */ + private void checkForRequiredArgs(String pFunction, Bundle pArgs, Bundle pReturn) { + if (FUNCTIONS_REQUIRED_ARGS.containsKey(pFunction)) { + Iterator iter = FUNCTIONS_REQUIRED_ARGS.get(pFunction).iterator(); + while (iter.hasNext()) { + String curArg = iter.next().name(); + if (!pArgs.containsKey(curArg)) { + pReturn.getStringArrayList(ret.ERRORS.name()) + .add("Argument missing: " + curArg); + } + } + } + + if (pFunction.equals(call.encrypt_with_passphrase.name()) + || pFunction.equals(call.encrypt_with_public_key.name()) + || pFunction.equals(call.decrypt.name())) { + // check that either MESSAGE or BLOB are there + if (!pArgs.containsKey(arg.MESSAGE.name()) && !pArgs.containsKey(arg.BLOB.name())) { + pReturn.getStringArrayList(ret.ERRORS.name()).add( + "Arguments missing: Neither MESSAGE nor BLOG found"); + } + + } + } + + /** + * checks for unknown arguments and add them to warning if found + * + * @param function + * the functions name to check against + * @param pArgs + * the Bundle of arguments to check + * @param pReturn + * the bundle to write warnings to + */ + private void checkForUnknownArgs(String pFunction, Bundle pArgs, Bundle pReturn) { + + HashSet allArgs = new HashSet(); + if (FUNCTIONS_REQUIRED_ARGS.containsKey(pFunction)) { + allArgs.addAll(FUNCTIONS_REQUIRED_ARGS.get(pFunction)); + } + if (FUNCTIONS_OPTIONAL_ARGS.containsKey(pFunction)) { + allArgs.addAll(FUNCTIONS_OPTIONAL_ARGS.get(pFunction)); + } + + ArrayList unknownArgs = new ArrayList(); + Iterator iter = pArgs.keySet().iterator(); + while (iter.hasNext()) { + String curKey = iter.next(); + try { + arg curArg = arg.valueOf(curKey); + if (!allArgs.contains(curArg)) { + pReturn.getStringArrayList(ret.WARNINGS.name()).add( + "Unknown argument: " + curKey); + unknownArgs.add(curKey); + } + } catch (Exception e) { + pReturn.getStringArrayList(ret.WARNINGS.name()).add("Unknown argument: " + curKey); + unknownArgs.add(curKey); + } + } + + // remove unknown arguments so our bundle has just what we need + for (String arg : unknownArgs) { + pArgs.remove(arg); + } + } + + private boolean prepareArgs(String pCall, Bundle pArgs, Bundle pReturn) { + Apg.initialize(getBaseContext()); + + /* add default return values for all functions */ + addDefaultReturns(pReturn); + + /* add default arguments if missing */ + addDefaultArguments(pCall, pArgs); + if (LOCAL_LOGV) + Log.v(TAG, "add_default_arguments"); + + /* check for required arguments */ + checkForRequiredArgs(pCall, pArgs, pReturn); + if (LOCAL_LOGV) + Log.v(TAG, "check_required_args"); + + /* check for unknown arguments and add to warning if found */ + checkForUnknownArgs(pCall, pArgs, pReturn); + if (LOCAL_LOGV) + Log.v(TAG, "check_unknown_args"); + + /* return if errors happened */ + if (pReturn.getStringArrayList(ret.ERRORS.name()).size() != 0) { + if (LOCAL_LOGV) + Log.v(TAG, "Errors after preparing, not executing " + pCall); + pReturn.putInt(ret.ERROR.name(), error.ARGUMENTS_MISSING.shiftedOrdinal()); + return false; + } + if (LOCAL_LOGV) + Log.v(TAG, "error return"); + + return true; + } + + private boolean encrypt(Bundle pArgs, Bundle pReturn) { + boolean isBlob = pArgs.containsKey(arg.BLOB.name()); + + long pubMasterKeys[] = {}; + if (pArgs.containsKey(arg.PUBLIC_KEYS.name())) { + ArrayList list = pArgs.getStringArrayList(arg.PUBLIC_KEYS.name()); + ArrayList pubKeys = new ArrayList(); + if (LOCAL_LOGV) + Log.v(TAG, "Long size: " + list.size()); + Iterator iter = list.iterator(); + while (iter.hasNext()) { + pubKeys.add(iter.next()); + } + pubMasterKeys = getMasterKey(pubKeys, pReturn); + } + + InputStream inStream = null; + if (isBlob) { + ContentResolver cr = getContentResolver(); + try { + inStream = cr.openInputStream(Uri.parse(pArgs.getString(arg.BLOB.name()))); + } catch (Exception e) { + Log.e(TAG, "... exception on opening blob", e); + } + } else { + inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes()); + } + InputData in = new InputData(inStream, 0); // XXX Size second param? + + OutputStream out = new ByteArrayOutputStream(); + if (LOCAL_LOGV) + Log.v(TAG, "About to encrypt"); + try { + Apg.encrypt(getBaseContext(), // context + in, // input stream + out, // output stream + pArgs.getBoolean(arg.ARMORED_OUTPUT.name()), // ARMORED_OUTPUT + pubMasterKeys, // encryption keys + getMasterKey(pArgs.getString(arg.SIGNATURE_KEY.name()), pReturn), // signature + // key + pArgs.getString(arg.PRIVATE_KEY_PASSPHRASE.name()), // signature passphrase + null, // progress + pArgs.getInt(arg.ENCRYPTION_ALGORYTHM.name()), // encryption + pArgs.getInt(arg.HASH_ALGORYTHM.name()), // hash + pArgs.getInt(arg.COMPRESSION.name()), // compression + pArgs.getBoolean(arg.FORCE_V3_SIGNATURE.name()), // mPreferences.getForceV3Signatures(), + pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) // passPhrase + ); + } catch (Exception e) { + Log.e(TAG, "Exception in encrypt"); + String msg = e.getMessage(); + if (msg.equals(getBaseContext().getString(R.string.error_noSignaturePassPhrase))) { + pReturn.getStringArrayList(ret.ERRORS.name()).add( + "Cannot encrypt (" + arg.PRIVATE_KEY_PASSPHRASE.name() + " missing): " + + msg); + pReturn.putInt(ret.ERROR.name(), + error.PRIVATE_KEY_PASSPHRASE_MISSING.shiftedOrdinal()); + } else if (msg.equals(getBaseContext().getString( + R.string.error_couldNotExtractPrivateKey))) { + pReturn.getStringArrayList(ret.ERRORS.name()).add( + "Cannot encrypt (" + arg.PRIVATE_KEY_PASSPHRASE.name() + + " probably wrong): " + msg); + pReturn.putInt(ret.ERROR.name(), + error.PRIVATE_KEY_PASSPHRASE_WRONG.shiftedOrdinal()); + } else { + pReturn.getStringArrayList(ret.ERRORS.name()).add( + "Internal failure (" + e.getClass() + ") in APG when encrypting: " + + e.getMessage()); + pReturn.putInt(ret.ERROR.name(), error.APG_FAILURE.shiftedOrdinal()); + } + return false; + } + if (LOCAL_LOGV) + Log.v(TAG, "Encrypted"); + if (isBlob) { + ContentResolver cr = getContentResolver(); + try { + OutputStream outStream = cr.openOutputStream(Uri.parse(pArgs.getString(arg.BLOB + .name()))); + writeToOutputStream(new ByteArrayInputStream(out.toString().getBytes()), outStream); + outStream.close(); + } catch (Exception e) { + Log.e(TAG, "... exception on writing blob", e); + } + } else { + pReturn.putString(ret.RESULT.name(), out.toString()); + } + return true; + } + + private final IApgService.Stub mBinder = new IApgService.Stub() { + + public boolean getKeys(Bundle pArgs, Bundle pReturn) { + + prepareArgs("get_keys", pArgs, pReturn); + + HashMap qParams = new HashMap(); + qParams.put("columns", new String[] { + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 0 + UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 1 + }); + + qParams.put("key_type", pArgs.getInt(arg.KEY_TYPE.name())); + + Cursor cursor = getKeyEntries(qParams); + ArrayList fPrints = new ArrayList(); + ArrayList ids = new ArrayList(); + while (cursor.moveToNext()) { + if (LOCAL_LOGV) + Log.v(TAG, "adding key " + Apg.getSmallFingerPrint(cursor.getLong(0))); + fPrints.add(Apg.getSmallFingerPrint(cursor.getLong(0))); + ids.add(cursor.getString(1)); + } + cursor.close(); + + pReturn.putStringArrayList(ret.FINGERPRINTS.name(), fPrints); + pReturn.putStringArrayList(ret.USER_IDS.name(), ids); + return true; + } + + public boolean encryptWithPublicKey(Bundle pArgs, Bundle pReturn) { + if (!prepareArgs("encrypt_with_public_key", pArgs, pReturn)) { + return false; + } + + return encrypt(pArgs, pReturn); + } + + public boolean encryptWithPassphrase(Bundle pArgs, Bundle pReturn) { + if (!prepareArgs("encrypt_with_passphrase", pArgs, pReturn)) { + return false; + } + + return encrypt(pArgs, pReturn); + + } + + public boolean decrypt(Bundle pArgs, Bundle pReturn) { + if (!prepareArgs("decrypt", pArgs, pReturn)) { + return false; + } + + boolean isBlob = pArgs.containsKey(arg.BLOB.name()); + + String passphrase = pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) != null ? pArgs + .getString(arg.SYMMETRIC_PASSPHRASE.name()) : pArgs + .getString(arg.PRIVATE_KEY_PASSPHRASE.name()); + + InputStream inStream = null; + if (isBlob) { + ContentResolver cr = getContentResolver(); + try { + inStream = cr.openInputStream(Uri.parse(pArgs.getString(arg.BLOB.name()))); + } catch (Exception e) { + Log.e(TAG, "... exception on opening blob", e); + } + } else { + inStream = new ByteArrayInputStream(pArgs.getString(arg.MESSAGE.name()).getBytes()); + } + + InputData in = new InputData(inStream, 0); // XXX what size in second parameter? + OutputStream out = new ByteArrayOutputStream(); + if (LOCAL_LOGV) + Log.v(TAG, "About to decrypt"); + try { + Apg.decrypt(getBaseContext(), in, out, passphrase, null, // progress + pArgs.getString(arg.SYMMETRIC_PASSPHRASE.name()) != null // symmetric + ); + } catch (Exception e) { + Log.e(TAG, "Exception in decrypt"); + String msg = e.getMessage(); + if (msg.equals(getBaseContext().getString(R.string.error_noSecretKeyFound))) { + pReturn.getStringArrayList(ret.ERRORS.name()).add("Cannot decrypt: " + msg); + pReturn.putInt(ret.ERROR.name(), error.NO_MATCHING_SECRET_KEY.shiftedOrdinal()); + } else if (msg.equals(getBaseContext().getString(R.string.error_wrongPassPhrase))) { + pReturn.getStringArrayList(ret.ERRORS.name()).add( + "Cannot decrypt (" + arg.PRIVATE_KEY_PASSPHRASE.name() + + " wrong/missing): " + msg); + pReturn.putInt(ret.ERROR.name(), + error.PRIVATE_KEY_PASSPHRASE_WRONG.shiftedOrdinal()); + } else { + pReturn.getStringArrayList(ret.ERRORS.name()).add( + "Internal failure (" + e.getClass() + ") in APG when decrypting: " + + msg); + pReturn.putInt(ret.ERROR.name(), error.APG_FAILURE.shiftedOrdinal()); + } + return false; + } + if (LOCAL_LOGV) + Log.v(TAG, "... decrypted"); + + if (isBlob) { + ContentResolver cr = getContentResolver(); + try { + OutputStream outStream = cr.openOutputStream(Uri.parse(pArgs.getString(arg.BLOB + .name()))); + writeToOutputStream(new ByteArrayInputStream(out.toString().getBytes()), + outStream); + outStream.close(); + } catch (Exception e) { + Log.e(TAG, "... exception on writing blob", e); + } + } else { + pReturn.putString(ret.RESULT.name(), out.toString()); + } + return true; + } + + }; +} diff --git a/org_apg/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java b/org_apg/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java new file mode 100644 index 000000000..35198d7b4 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/AskForSecretKeyPassPhrase.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPSecretKey; +import org.thialfihar.android.apg.R; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +public class AskForSecretKeyPassPhrase { + public static interface PassPhraseCallbackInterface { + void passPhraseCallback(long keyId, String passPhrase); + } + + public static Dialog createDialog(Activity context, long secretKeyId, + PassPhraseCallbackInterface callback) { + AlertDialog.Builder alert = new AlertDialog.Builder(context); + + alert.setTitle(R.string.title_authentication); + + final PGPSecretKey secretKey; + final Activity activity = context; + + if (secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none) { + secretKey = null; + alert.setMessage(context.getString(R.string.passPhraseForSymmetricEncryption)); + } else { + secretKey = Apg.getMasterKey(Apg.getSecretKeyRing(secretKeyId)); + if (secretKey == null) { + alert.setTitle(R.string.title_keyNotFound); + alert.setMessage(context.getString(R.string.keyNotFound, secretKeyId)); + alert.setPositiveButton(android.R.string.ok, new OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + activity.removeDialog(Id.dialog.pass_phrase); + } + }); + alert.setCancelable(false); + return alert.create(); + } + String userId = Apg.getMainUserIdSafe(context, secretKey); + alert.setMessage(context.getString(R.string.passPhraseFor, userId)); + } + + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View view = inflater.inflate(R.layout.passphrase, null); + final EditText input = (EditText) view.findViewById(R.id.passphrase_passphrase); + + final TextView labelNotUsed = (TextView) view + .findViewById(R.id.passphrase_label_passphrase_again); + labelNotUsed.setVisibility(View.GONE); + final EditText inputNotUsed = (EditText) view + .findViewById(R.id.passphrase_passphrase_again); + inputNotUsed.setVisibility(View.GONE); + + alert.setView(view); + + final PassPhraseCallbackInterface cb = callback; + alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + activity.removeDialog(Id.dialog.pass_phrase); + String passPhrase = input.getText().toString(); + long keyId; + if (secretKey != null) { + try { + PGPPrivateKey testKey = secretKey.extractPrivateKey( + passPhrase.toCharArray(), new BouncyCastleProvider()); + if (testKey == null) { + Toast.makeText(activity, R.string.error_couldNotExtractPrivateKey, + Toast.LENGTH_SHORT).show(); + return; + } + } catch (PGPException e) { + Toast.makeText(activity, R.string.wrongPassPhrase, Toast.LENGTH_SHORT) + .show(); + return; + } + keyId = secretKey.getKeyID(); + } else { + keyId = Id.key.symmetric; + } + cb.passPhraseCallback(keyId, passPhrase); + } + }); + + alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + activity.removeDialog(Id.dialog.pass_phrase); + } + }); + + // check if the key has no passphrase + if (secretKey != null) { + try { + Log.d("APG", "check if key has no passphrase..."); + PGPPrivateKey testKey = secretKey.extractPrivateKey("".toCharArray(), + new BouncyCastleProvider()); + if (testKey != null) { + Log.d("APG", "Key has no passphrase!"); + + cb.passPhraseCallback(secretKey.getKeyID(), null); + + return null; + } + } catch (PGPException e) { + + } + } + return alert.create(); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/CachedPassPhrase.java b/org_apg/src/org/thialfihar/android/apg/CachedPassPhrase.java new file mode 100644 index 000000000..85fcdd201 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/CachedPassPhrase.java @@ -0,0 +1,62 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +public class CachedPassPhrase { + public final long timestamp; + public final String passPhrase; + + public CachedPassPhrase(long timestamp, String passPhrase) { + super(); + this.timestamp = timestamp; + this.passPhrase = passPhrase; + } + + @Override + public int hashCode() { + int hc1 = (int) (this.timestamp & 0xffffffff); + int hc2 = (this.passPhrase == null ? 0 : this.passPhrase.hashCode()); + return (hc1 + hc2) * hc2 + hc1; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof CachedPassPhrase)) { + return false; + } + + CachedPassPhrase o = (CachedPassPhrase) other; + if (timestamp != o.timestamp) { + return false; + } + + if (passPhrase != o.passPhrase) { + if (passPhrase == null || o.passPhrase == null) { + return false; + } + + if (!passPhrase.equals(o.passPhrase)) { + return false; + } + } + + return true; + } + + @Override + public String toString() { + return "(" + timestamp + ", *******)"; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/Constants.java b/org_apg/src/org/thialfihar/android/apg/Constants.java new file mode 100644 index 000000000..a0aaf5b5f --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/Constants.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import android.os.Environment; + +public final class Constants { + + public static final String TAG = "APG"; + + public static final class path { + public static final String APP_DIR = Environment.getExternalStorageDirectory() + "/APG"; + } + + public static final class pref { + public static final String HAS_SEEN_HELP = "seenHelp"; + public static final String HAS_SEEN_CHANGE_LOG = "seenChangeLogDialog"; + public static final String DEFAULT_ENCRYPTION_ALGORITHM = "defaultEncryptionAlgorithm"; + public static final String DEFAULT_HASH_ALGORITHM = "defaultHashAlgorithm"; + public static final String DEFAULT_ASCII_ARMOUR = "defaultAsciiArmour"; + public static final String DEFAULT_MESSAGE_COMPRESSION = "defaultMessageCompression"; + public static final String DEFAULT_FILE_COMPRESSION = "defaultFileCompression"; + public static final String PASS_PHRASE_CACHE_TTL = "passPhraseCacheTtl"; + public static final String LANGUAGE = "language"; + public static final String FORCE_V3_SIGNATURES = "forceV3Signatures"; + public static final String KEY_SERVERS = "keyServers"; + } + + public static final class defaults { + public static final String KEY_SERVERS = "pool.sks-keyservers.net, subkeys.pgp.net, pgp.mit.edu"; + } + + public static final class extras { + public static final String PROGRESS = "progress"; + public static final String PROGRESS_MAX = "max"; + public static final String STATUS = "status"; + public static final String MESSAGE = "message"; + public static final String KEY_ID = "keyId"; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/DataDestination.java b/org_apg/src/org/thialfihar/android/apg/DataDestination.java new file mode 100644 index 000000000..2ab530619 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/DataDestination.java @@ -0,0 +1,95 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.Apg.GeneralException; + +import android.content.Context; +import android.os.Environment; + +public class DataDestination { + private String mStreamFilename; + private String mFilename; + private int mMode = Id.mode.undefined; + + public DataDestination() { + + } + + public void setMode(int mode) { + mMode = mode; + } + + public void setFilename(String filename) { + mFilename = filename; + } + + public String getStreamFilename() { + return mStreamFilename; + } + + public OutputStream getOutputStream(Context context) throws Apg.GeneralException, + FileNotFoundException, IOException { + OutputStream out = null; + mStreamFilename = null; + + switch (mMode) { + case Id.mode.stream: { + try { + while (true) { + mStreamFilename = Apg.generateRandomString(32); + if (mStreamFilename == null) { + throw new Apg.GeneralException("couldn't generate random file name"); + } + context.openFileInput(mStreamFilename).close(); + } + } catch (FileNotFoundException e) { + // found a name that isn't used yet + } + out = context.openFileOutput(mStreamFilename, Context.MODE_PRIVATE); + break; + } + + case Id.mode.byte_array: { + out = new ByteArrayOutputStream(); + break; + } + + case Id.mode.file: { + if (mFilename.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) { + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + throw new GeneralException( + context.getString(R.string.error_externalStorageNotReady)); + } + } + out = new FileOutputStream(mFilename); + break; + } + + default: { + break; + } + } + + return out; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/DataSource.java b/org_apg/src/org/thialfihar/android/apg/DataSource.java new file mode 100644 index 000000000..637304012 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/DataSource.java @@ -0,0 +1,118 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.Apg.GeneralException; + +import android.content.Context; +import android.net.Uri; +import android.os.Environment; + +public class DataSource { + private Uri mContentUri = null; + private String mText = null; + private byte[] mData = null; + + public DataSource() { + + } + + public void setUri(Uri uri) { + mContentUri = uri; + mText = null; + mData = null; + } + + public void setUri(String uri) { + if (uri.startsWith("/")) { + setUri(Uri.parse("file://" + uri)); + } else { + setUri(Uri.parse(uri)); + } + } + + public void setText(String text) { + mText = text; + mData = null; + mContentUri = null; + } + + public void setData(byte[] data) { + mData = data; + mText = null; + mContentUri = null; + } + + public boolean isText() { + return mText != null; + } + + public boolean isBinary() { + return mData != null || mContentUri != null; + } + + public InputData getInputData(Context context, boolean withSize) throws GeneralException, + FileNotFoundException, IOException { + InputStream in = null; + long size = 0; + + if (mContentUri != null) { + if (mContentUri.getScheme().equals("file")) { + // get the rest after "file://" + String path = Uri.decode(mContentUri.toString().substring(7)); + if (path.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) { + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + throw new GeneralException( + context.getString(R.string.error_externalStorageNotReady)); + } + } + in = new FileInputStream(path); + File file = new File(path); + if (withSize) { + size = file.length(); + } + } else { + in = context.getContentResolver().openInputStream(mContentUri); + if (withSize) { + InputStream tmp = context.getContentResolver().openInputStream(mContentUri); + size = Apg.getLengthOfStream(tmp); + tmp.close(); + } + } + } else if (mText != null || mData != null) { + byte[] bytes = null; + if (mData != null) { + bytes = mData; + } else { + bytes = mText.getBytes(); + } + in = new ByteArrayInputStream(bytes); + if (withSize) { + size = bytes.length; + } + } + + return new InputData(in, size); + } + +} diff --git a/org_apg/src/org/thialfihar/android/apg/FileDialog.java b/org_apg/src/org/thialfihar/android/apg/FileDialog.java new file mode 100644 index 000000000..1a8cc0846 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/FileDialog.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import org.thialfihar.android.apg.R; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.Toast; + +public class FileDialog { + private static EditText mFilename; + private static ImageButton mBrowse; + private static CheckBox mCheckBox; + private static Activity mActivity; + private static int mRequestCode; + + public static interface OnClickListener { + public void onCancelClick(); + + public void onOkClick(String filename, boolean checkbox); + } + + public static AlertDialog build(Activity activity, String title, String message, + String defaultFile, OnClickListener onClickListener, String fileManagerTitle, + String fileManagerButton, String checkboxText, int requestCode) { + // TODO: fileManagerTitle and fileManagerButton are deprecated, no use for them right now, + // but maybe the Intent now used will someday support them again, so leaving them in + LayoutInflater inflater = (LayoutInflater) activity + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + AlertDialog.Builder alert = new AlertDialog.Builder(activity); + + alert.setTitle(title); + alert.setMessage(message); + + View view = inflater.inflate(R.layout.file_dialog, null); + + mActivity = activity; + mFilename = (EditText) view.findViewById(R.id.input); + mFilename.setText(defaultFile); + mBrowse = (ImageButton) view.findViewById(R.id.btn_browse); + mBrowse.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + openFile(); + } + }); + mRequestCode = requestCode; + mCheckBox = (CheckBox) view.findViewById(R.id.checkbox); + if (checkboxText == null) { + mCheckBox.setEnabled(false); + mCheckBox.setVisibility(View.GONE); + } else { + mCheckBox.setEnabled(true); + mCheckBox.setVisibility(View.VISIBLE); + mCheckBox.setText(checkboxText); + } + + alert.setView(view); + + final OnClickListener clickListener = onClickListener; + + alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + boolean checked = false; + if (mCheckBox.isEnabled()) { + checked = mCheckBox.isChecked(); + } + clickListener.onOkClick(mFilename.getText().toString(), checked); + } + }); + + alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + clickListener.onCancelClick(); + } + }); + return alert.create(); + } + + public static void setFilename(String filename) { + if (mFilename != null) { + mFilename.setText(filename); + } + } + + /** + * Opens the file manager to select a file to open. + */ + private static void openFile() { + String filename = mFilename.getText().toString(); + + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + + intent.setData(Uri.parse("file://" + filename)); + intent.setType("text/plain"); // only .asc or .gpg files + + try { + mActivity.startActivityForResult(intent, mRequestCode); + } catch (ActivityNotFoundException e) { + // No compatible file manager was found. + Toast.makeText(mActivity, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show(); + } + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/HkpKeyServer.java b/org_apg/src/org/thialfihar/android/apg/HkpKeyServer.java new file mode 100644 index 000000000..bfbb30c8c --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/HkpKeyServer.java @@ -0,0 +1,256 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.NameValuePair; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; + +import android.text.Html; + +public class HkpKeyServer extends KeyServer { + private static class HttpError extends Exception { + private static final long serialVersionUID = 1718783705229428893L; + private int mCode; + private String mData; + + public HttpError(int code, String data) { + super("" + code + ": " + data); + mCode = code; + mData = data; + } + + public int getCode() { + return mCode; + } + + public String getData() { + return mData; + } + } + + private String mHost; + private short mPort = 11371; + + // example: + // pub 2048R/9F5C9090 2009-08-17 Jörg Runge + // <joerg@joergrunge.de> + public static Pattern PUB_KEY_LINE = Pattern + .compile( + "pub +([0-9]+)([a-z]+)/.*?0x([0-9a-z]+).*? +([0-9-]+) +(.+)[\n\r]+((?: +.+[\n\r]+)*)", + Pattern.CASE_INSENSITIVE); + public static Pattern USER_ID_LINE = Pattern.compile("^ +(.+)$", Pattern.MULTILINE + | Pattern.CASE_INSENSITIVE); + + public HkpKeyServer(String host) { + mHost = host; + } + + public HkpKeyServer(String host, short port) { + mHost = host; + mPort = port; + } + + static private String readAll(InputStream in, String encoding) throws IOException { + ByteArrayOutputStream raw = new ByteArrayOutputStream(); + + byte buffer[] = new byte[1 << 16]; + int n = 0; + while ((n = in.read(buffer)) != -1) { + raw.write(buffer, 0, n); + } + + if (encoding == null) { + encoding = "utf8"; + } + return raw.toString(encoding); + } + + // TODO: replace this with httpclient + private String query(String request) throws QueryException, HttpError { + InetAddress ips[]; + try { + ips = InetAddress.getAllByName(mHost); + } catch (UnknownHostException e) { + throw new QueryException(e.toString()); + } + for (int i = 0; i < ips.length; ++i) { + try { + String url = "http://" + ips[i].getHostAddress() + ":" + mPort + request; + URL realUrl = new URL(url); + HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection(); + conn.setConnectTimeout(5000); + conn.setReadTimeout(25000); + conn.connect(); + int response = conn.getResponseCode(); + if (response >= 200 && response < 300) { + return readAll(conn.getInputStream(), conn.getContentEncoding()); + } else { + String data = readAll(conn.getErrorStream(), conn.getContentEncoding()); + throw new HttpError(response, data); + } + } catch (MalformedURLException e) { + // nothing to do, try next IP + } catch (IOException e) { + // nothing to do, try next IP + } + } + + throw new QueryException("querying server(s) for '" + mHost + "' failed"); + } + + // TODO: replace this with httpclient + @Override + public List search(String query) throws QueryException, TooManyResponses, + InsufficientQuery { + Vector results = new Vector(); + + if (query.length() < 3) { + throw new InsufficientQuery(); + } + + String encodedQuery; + try { + encodedQuery = URLEncoder.encode(query, "utf8"); + } catch (UnsupportedEncodingException e) { + return null; + } + String request = "/pks/lookup?op=index&search=" + encodedQuery; + + String data = null; + try { + data = query(request); + } catch (HttpError e) { + if (e.getCode() == 404) { + return results; + } else { + if (e.getData().toLowerCase().contains("no keys found")) { + return results; + } else if (e.getData().toLowerCase().contains("too many")) { + throw new TooManyResponses(); + } else if (e.getData().toLowerCase().contains("insufficient")) { + throw new InsufficientQuery(); + } + } + throw new QueryException("querying server(s) for '" + mHost + "' failed"); + } + + Matcher matcher = PUB_KEY_LINE.matcher(data); + while (matcher.find()) { + KeyInfo info = new KeyInfo(); + info.size = Integer.parseInt(matcher.group(1)); + info.algorithm = matcher.group(2); + info.keyId = Apg.keyFromHex(matcher.group(3)); + info.fingerPrint = Apg.getSmallFingerPrint(info.keyId); + String chunks[] = matcher.group(4).split("-"); + info.date = new GregorianCalendar(Integer.parseInt(chunks[0]), + Integer.parseInt(chunks[1]), Integer.parseInt(chunks[2])).getTime(); + info.userIds = new Vector(); + if (matcher.group(5).startsWith("*** KEY")) { + info.revoked = matcher.group(5); + } else { + String tmp = matcher.group(5).replaceAll("<.*?>", ""); + tmp = Html.fromHtml(tmp).toString(); + info.userIds.add(tmp); + } + if (matcher.group(6).length() > 0) { + Matcher matcher2 = USER_ID_LINE.matcher(matcher.group(6)); + while (matcher2.find()) { + String tmp = matcher2.group(1).replaceAll("<.*?>", ""); + tmp = Html.fromHtml(tmp).toString(); + info.userIds.add(tmp); + } + } + results.add(info); + } + + return results; + } + + @Override + public String get(long keyId) throws QueryException { + HttpClient client = new DefaultHttpClient(); + try { + HttpGet get = new HttpGet("http://" + mHost + ":" + mPort + + "/pks/lookup?op=get&search=0x" + Apg.keyToHex(keyId)); + + HttpResponse response = client.execute(get); + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + throw new QueryException("not found"); + } + + HttpEntity entity = response.getEntity(); + InputStream is = entity.getContent(); + String data = readAll(is, EntityUtils.getContentCharSet(entity)); + Matcher matcher = Apg.PGP_PUBLIC_KEY.matcher(data); + if (matcher.find()) { + return matcher.group(1); + } + } catch (IOException e) { + // nothing to do, better luck on the next keyserver + } finally { + client.getConnectionManager().shutdown(); + } + + return null; + } + + @Override + void add(String armouredText) throws AddKeyException { + HttpClient client = new DefaultHttpClient(); + try { + HttpPost post = new HttpPost("http://" + mHost + ":" + mPort + "/pks/add"); + + List nameValuePairs = new ArrayList(2); + nameValuePairs.add(new BasicNameValuePair("keytext", armouredText)); + post.setEntity(new UrlEncodedFormEntity(nameValuePairs)); + + HttpResponse response = client.execute(post); + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + throw new AddKeyException(); + } + } catch (IOException e) { + // nothing to do, better luck on the next keyserver + } finally { + client.getConnectionManager().shutdown(); + } + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/IApgService.aidl b/org_apg/src/org/thialfihar/android/apg/IApgService.aidl new file mode 100644 index 000000000..25780f366 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/IApgService.aidl @@ -0,0 +1,125 @@ +package org.thialfihar.android.apg; + +interface IApgService { + + /* All functions fill the returnVals Bundle with the following keys: + * + * ArrayList "WARNINGS" = Warnings, if any + * ArrayList "ERRORS" = Human readable error descriptions, if any + * int "ERROR" = Numeric representation of error, if any + * starting with 100: + * 100: Required argument missing + * 101: Generic failure of APG + * 102: No matching private key found + * 103: Private key's passphrase wrong + * 104: Private key's passphrase missing + */ + + /* ******************************************************** + * Encryption + * ********************************************************/ + + /* All encryption function's arguments + * + * Bundle params' keys: + * (optional/required) + * TYPE "STRING KEY" = EXPLANATION / VALUES + * + * (required) + * String "MESSAGE" = Message to encrypt + * OR + * String "BLOB" = ContentUri to a file handle + * with binary data to encrypt + * (Attention: file will be overwritten + * with encrypted content!) + * + * (optional) + * int "ENCRYPTION_ALGORYTHM" = Encryption Algorithm + * 7: AES-128, 8: AES-192, 9: AES-256, + * 4: Blowfish, 10: Twofish, 3: CAST5, + * 6: DES, 2: Triple DES, 1: IDEA + * (optional) + * int "HASH_ALGORYTHM" = Hash Algorithm + * 1: MD5, 3: RIPEMD-160, 2: SHA-1, + * 11: SHA-224, 8: SHA-256, 9: SHA-384, + * 10: SHA-512 + * (optional) + * Boolean "ARMORED_OUTPUT" = Armor output + * + * (optional) + * Boolean "FORCE_V3_SIGNATURE" = Force V3 Signatures + * + * (optional) + * int "COMPRESSION" = Compression to use + * 0x21070001: none, 1: Zip, 2: Zlib, + * 3: BZip2 + * (optional) + * String "SIGNATURE_KEY" = Key to sign with + * + * (optional) + * String "PRIVATE_KEY_PASSPHRASE" = Passphrase for signing key + * + * Bundle returnVals (in addition to the ERRORS/WARNINGS above): + * If "MESSAGE" was set: + * String "RESULT" = Encrypted message + */ + + /* Additional argument for function below: + * (required) + * String "SYMMETRIC_PASSPHRASE" = Symmetric passphrase to use + */ + boolean encryptWithPassphrase(in Bundle params, out Bundle returnVals); + + /* Additional argument: + * (required) + * ArrayList "PUBLIC_KEYS" = Public keys (8char fingerprint "123ABC12" OR + * complete id "Alice Meyer ") + */ + boolean encryptWithPublicKey(in Bundle params, out Bundle returnVals); + + /* ******************************************************** + * Decryption + * ********************************************************/ + + /* Bundle params: + * (required) + * String "MESSAGE" = Message to dencrypt + * OR + * String "BLOB" = ContentUri to a file handle + * with binary data to dencrypt + * (Attention: file will be overwritten + * with dencrypted content!) + * + * (optional) + * String "SYMMETRIC_PASSPHRASE" = Symmetric passphrase for decryption + * + * (optional) + * String "PRIVATE_KEY_PASSPHRASE" = Private keys's passphrase on asymmetric encryption + * + * Bundle return_vals: + * If "MESSAGE" was set: + * String "RESULT" = Decrypted message + */ + boolean decrypt(in Bundle params, out Bundle returnVals); + + /* ******************************************************** + * Get key information + * ********************************************************/ + + /* Get info about all available keys + * + * Bundle params: + * (required) + * int "KEY_TYPE" = info about what type of keys to return + * 0: public keys + * 1: private keys + * + * Returns: + * StringArrayList "FINGERPRINTS" = Short fingerprints of keys + * + * StringArrayList "USER_IDS" = User ids of corresponding fingerprints + * (order is the same as in FINGERPRINTS) + */ + boolean getKeys(in Bundle params, out Bundle returnVals); + +} \ No newline at end of file diff --git a/org_apg/src/org/thialfihar/android/apg/Id.java b/org_apg/src/org/thialfihar/android/apg/Id.java new file mode 100644 index 000000000..7308d5d25 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/Id.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import org.spongycastle.bcpg.CompressionAlgorithmTags; + +public final class Id { + + public static final String TAG = "APG"; + + public static final class menu { + public static final int export = 0x21070001; + public static final int delete = 0x21070002; + public static final int edit = 0x21070003; + public static final int update = 0x21070004; + public static final int exportToServer = 0x21070005; + public static final int share = 0x21070006; + public static final int signKey = 0x21070007; + + public static final class option { + public static final int new_pass_phrase = 0x21070001; + public static final int create = 0x21070002; + public static final int about = 0x21070003; + public static final int manage_public_keys = 0x21070004; + public static final int manage_secret_keys = 0x21070005; + public static final int import_keys = 0x21070006; + public static final int export_keys = 0x21070007; + public static final int preferences = 0x21070008; + public static final int search = 0x21070009; + public static final int help = 0x21070010; + public static final int key_server = 0x21070011; + public static final int scanQRCode = 0x21070012; + public static final int encrypt = 0x21070013; + public static final int encrypt_to_clipboard = 0x21070014; + public static final int decrypt = 0x21070015; + public static final int reply = 0x21070016; + public static final int cancel = 0x21070017; + public static final int save = 0x21070018; + public static final int okay = 0x21070019; + + } + } + + public static final class message { + public static final int progress_update = 0x21070001; + public static final int done = 0x21070002; + public static final int import_keys = 0x21070003; + public static final int export_keys = 0x21070004; + public static final int import_done = 0x21070005; + public static final int export_done = 0x21070006; + public static final int create_key = 0x21070007; + public static final int edit_key = 0x21070008; + public static final int delete_done = 0x21070009; + public static final int query_done = 0x21070010; + public static final int unknown_signature_key = 0x21070011; + } + + public static final class request { + public static final int public_keys = 0x21070001; + public static final int secret_keys = 0x21070002; + public static final int filename = 0x21070003; + public static final int output_filename = 0x21070004; + public static final int key_server_preference = 0x21070005; + public static final int look_up_key_id = 0x21070006; + public static final int export_to_server = 0x21070007; + public static final int import_from_qr_code = 0x21070008; + public static final int sign_key = 0x21070009; + } + + public static final class dialog { + public static final int pass_phrase = 0x21070001; + public static final int encrypting = 0x21070002; + public static final int decrypting = 0x21070003; + public static final int new_pass_phrase = 0x21070004; + public static final int pass_phrases_do_not_match = 0x21070005; + public static final int no_pass_phrase = 0x21070006; + public static final int saving = 0x21070007; + public static final int delete_key = 0x21070008; + public static final int import_keys = 0x21070009; + public static final int importing = 0x2107000a; + public static final int export_key = 0x2107000b; + public static final int export_keys = 0x2107000c; + public static final int exporting = 0x2107000d; + public static final int new_account = 0x2107000e; + // public static final int about = 0x2107000f; + public static final int change_log = 0x21070010; + public static final int output_filename = 0x21070011; + public static final int delete_file = 0x21070012; + public static final int deleting = 0x21070013; + public static final int help = 0x21070014; + public static final int querying = 0x21070015; + public static final int lookup_unknown_key = 0x21070016; + public static final int signing = 0x21070017; + } + + public static final class task { + public static final int import_keys = 0x21070001; + public static final int export_keys = 0x21070002; + } + + public static final class database { + public static final int type_public = 0; + public static final int type_secret = 1; + } + + public static final class type { + public static final int public_key = 0x21070001; + public static final int secret_key = 0x21070002; + public static final int user_id = 0x21070003; + public static final int key = 0x21070004; + } + + public static final class choice { + public static final class algorithm { + public static final int dsa = 0x21070001; + public static final int elgamal = 0x21070002; + public static final int rsa = 0x21070003; + } + + public static final class compression { + public static final int none = 0x21070001; + public static final int zlib = CompressionAlgorithmTags.ZLIB; + public static final int bzip2 = CompressionAlgorithmTags.BZIP2; + public static final int zip = CompressionAlgorithmTags.ZIP; + } + + public static final class usage { + public static final int sign_only = 0x21070001; + public static final int encrypt_only = 0x21070002; + public static final int sign_and_encrypt = 0x21070003; + } + + public static final class action { + public static final int encrypt = 0x21070001; + public static final int decrypt = 0x21070002; + public static final int import_public = 0x21070003; + public static final int import_secret = 0x21070004; + } + } + + public static final class return_value { + public static final int ok = 0; + public static final int error = -1; + public static final int no_master_key = -2; + public static final int updated = 1; + public static final int bad = -3; + } + + public static final class target { + public static final int clipboard = 0x21070001; + public static final int email = 0x21070002; + public static final int file = 0x21070003; + public static final int message = 0x21070004; + } + + public static final class mode { + public static final int undefined = 0x21070001; + public static final int byte_array = 0x21070002; + public static final int file = 0x21070003; + public static final int stream = 0x21070004; + } + + public static final class key { + public static final int none = 0; + public static final int symmetric = -1; + } + + public static final class content { + public static final int unknown = 0; + public static final int encrypted_data = 1; + public static final int keys = 2; + } + + public static final class keyserver { + public static final int search = 0x21070001; + public static final int get = 0x21070002; + public static final int add = 0x21070003; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/InputData.java b/org_apg/src/org/thialfihar/android/apg/InputData.java new file mode 100644 index 000000000..ba6caa1c5 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/InputData.java @@ -0,0 +1,39 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import java.io.InputStream; + +public class InputData { + private PositionAwareInputStream mInputStream; + private long mSize; + + public InputData(InputStream inputStream, long size) { + mInputStream = new PositionAwareInputStream(inputStream); + mSize = size; + } + + public InputStream getInputStream() { + return mInputStream; + } + + public long getSize() { + return mSize; + } + + public long getStreamPosition() { + return mInputStream.position(); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/KeyServer.java b/org_apg/src/org/thialfihar/android/apg/KeyServer.java new file mode 100644 index 000000000..bf9335a0c --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/KeyServer.java @@ -0,0 +1,60 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; +import java.util.Vector; + +public abstract class KeyServer { + static public class QueryException extends Exception { + private static final long serialVersionUID = 2703768928624654512L; + + public QueryException(String message) { + super(message); + } + } + + static public class TooManyResponses extends Exception { + private static final long serialVersionUID = 2703768928624654513L; + } + + static public class InsufficientQuery extends Exception { + private static final long serialVersionUID = 2703768928624654514L; + } + + static public class AddKeyException extends Exception { + private static final long serialVersionUID = -507574859137295530L; + } + + static public class KeyInfo implements Serializable { + private static final long serialVersionUID = -7797972113284992662L; + public Vector userIds; + public String revoked; + public Date date; + public String fingerPrint; + public long keyId; + public int size; + public String algorithm; + } + + abstract List search(String query) throws QueryException, TooManyResponses, + InsufficientQuery; + + abstract String get(long keyId) throws QueryException; + + abstract void add(String armouredText) throws AddKeyException; +} diff --git a/org_apg/src/org/thialfihar/android/apg/PausableThread.java b/org_apg/src/org/thialfihar/android/apg/PausableThread.java new file mode 100644 index 000000000..87e7c7ee9 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/PausableThread.java @@ -0,0 +1,49 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +public class PausableThread extends Thread { + private boolean mPaused = false; + + public PausableThread(Runnable runnable) { + super(runnable); + } + + public void pause() { + synchronized (this) { + mPaused = true; + while (mPaused) { + try { + wait(); + } catch (InterruptedException e) { + // ignore + } + } + } + } + + public void unpause() { + synchronized (this) { + mPaused = false; + notify(); + } + } + + public boolean isPaused() { + synchronized (this) { + return mPaused; + } + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/PositionAwareInputStream.java b/org_apg/src/org/thialfihar/android/apg/PositionAwareInputStream.java new file mode 100644 index 000000000..d49713ae6 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/PositionAwareInputStream.java @@ -0,0 +1,81 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import java.io.IOException; +import java.io.InputStream; + +public class PositionAwareInputStream extends InputStream { + private InputStream mStream; + private long mPosition; + + public PositionAwareInputStream(InputStream in) { + mStream = in; + mPosition = 0; + } + + @Override + public int read() throws IOException { + int ch = mStream.read(); + ++mPosition; + return ch; + } + + @Override + public int available() throws IOException { + return mStream.available(); + } + + @Override + public void close() throws IOException { + mStream.close(); + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public int read(byte[] b) throws IOException { + int result = mStream.read(b); + mPosition += result; + return result; + } + + @Override + public int read(byte[] b, int offset, int length) throws IOException { + int result = mStream.read(b, offset, length); + mPosition += result; + return result; + } + + @Override + public synchronized void reset() throws IOException { + mStream.reset(); + mPosition = 0; + } + + @Override + public long skip(long n) throws IOException { + long result = mStream.skip(n); + mPosition += result; + return result; + } + + public long position() { + return mPosition; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/Preferences.java b/org_apg/src/org/thialfihar/android/apg/Preferences.java new file mode 100644 index 000000000..ea6a36368 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/Preferences.java @@ -0,0 +1,184 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.openpgp.PGPEncryptedData; + +import android.content.Context; +import android.content.SharedPreferences; + +import java.util.Vector; + +public class Preferences { + private static Preferences mPreferences; + private SharedPreferences mSharedPreferences; + + public static synchronized Preferences getPreferences(Context context) { + return getPreferences(context, false); + } + + public static synchronized Preferences getPreferences(Context context, boolean force_new) { + if (mPreferences == null || force_new) { + mPreferences = new Preferences(context); + } + return mPreferences; + } + + private Preferences(Context context) { + mSharedPreferences = context.getSharedPreferences("APG.main", Context.MODE_PRIVATE); + } + + public String getLanguage() { + return mSharedPreferences.getString(Constants.pref.LANGUAGE, ""); + } + + public void setLanguage(String value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putString(Constants.pref.LANGUAGE, value); + editor.commit(); + } + + public int getPassPhraseCacheTtl() { + int ttl = mSharedPreferences.getInt(Constants.pref.PASS_PHRASE_CACHE_TTL, 180); + // fix the value if it was set to "never" in previous versions, which currently is not + // supported + if (ttl == 0) { + ttl = 180; + } + return ttl; + } + + public void setPassPhraseCacheTtl(int value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putInt(Constants.pref.PASS_PHRASE_CACHE_TTL, value); + editor.commit(); + } + + public int getDefaultEncryptionAlgorithm() { + return mSharedPreferences.getInt(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM, + PGPEncryptedData.AES_256); + } + + public void setDefaultEncryptionAlgorithm(int value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putInt(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM, value); + editor.commit(); + } + + public int getDefaultHashAlgorithm() { + return mSharedPreferences.getInt(Constants.pref.DEFAULT_HASH_ALGORITHM, + HashAlgorithmTags.SHA256); + } + + public void setDefaultHashAlgorithm(int value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putInt(Constants.pref.DEFAULT_HASH_ALGORITHM, value); + editor.commit(); + } + + public int getDefaultMessageCompression() { + return mSharedPreferences.getInt(Constants.pref.DEFAULT_MESSAGE_COMPRESSION, + Id.choice.compression.zlib); + } + + public void setDefaultMessageCompression(int value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putInt(Constants.pref.DEFAULT_MESSAGE_COMPRESSION, value); + editor.commit(); + } + + public int getDefaultFileCompression() { + return mSharedPreferences.getInt(Constants.pref.DEFAULT_FILE_COMPRESSION, + Id.choice.compression.none); + } + + public void setDefaultFileCompression(int value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putInt(Constants.pref.DEFAULT_FILE_COMPRESSION, value); + editor.commit(); + } + + public boolean getDefaultAsciiArmour() { + return mSharedPreferences.getBoolean(Constants.pref.DEFAULT_ASCII_ARMOUR, false); + } + + public void setDefaultAsciiArmour(boolean value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(Constants.pref.DEFAULT_ASCII_ARMOUR, value); + editor.commit(); + } + + public boolean getForceV3Signatures() { + return mSharedPreferences.getBoolean(Constants.pref.FORCE_V3_SIGNATURES, false); + } + + public void setForceV3Signatures(boolean value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(Constants.pref.FORCE_V3_SIGNATURES, value); + editor.commit(); + } + + public boolean hasSeenChangeLog(String version) { + return mSharedPreferences.getBoolean(Constants.pref.HAS_SEEN_CHANGE_LOG + version, false); + } + + public void setHasSeenChangeLog(String version, boolean value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(Constants.pref.HAS_SEEN_CHANGE_LOG + version, value); + editor.commit(); + } + + public boolean hasSeenHelp() { + return mSharedPreferences.getBoolean(Constants.pref.HAS_SEEN_HELP, false); + } + + public void setHasSeenHelp(boolean value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(Constants.pref.HAS_SEEN_HELP, value); + editor.commit(); + } + + public String[] getKeyServers() { + String rawData = mSharedPreferences.getString(Constants.pref.KEY_SERVERS, + Constants.defaults.KEY_SERVERS); + Vector servers = new Vector(); + String chunks[] = rawData.split(","); + for (int i = 0; i < chunks.length; ++i) { + String tmp = chunks[i].trim(); + if (tmp.length() > 0) { + servers.add(tmp); + } + } + return servers.toArray(chunks); + } + + public void setKeyServers(String[] value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + String rawData = ""; + for (int i = 0; i < value.length; ++i) { + String tmp = value[i].trim(); + if (tmp.length() == 0) { + continue; + } + if (!"".equals(rawData)) { + rawData += ","; + } + rawData += tmp; + } + editor.putString(Constants.pref.KEY_SERVERS, rawData); + editor.commit(); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/Primes.java b/org_apg/src/org/thialfihar/android/apg/Primes.java new file mode 100644 index 000000000..f0f391291 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/Primes.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import java.math.BigInteger; + +public final class Primes { + // taken from http://www.ietf.org/rfc/rfc3526.txt + public static final String P1536 = + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + + "670C354E 4ABC9804 F1746C08 CA237327 FFFFFFFF FFFFFFFF"; + + public static final String P2048 = + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + + "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + + "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + + "15728E5A 8AACAA68 FFFFFFFF FFFFFFFF"; + + public static final String P3072 = + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + + "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + + "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + + "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + + "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + + "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + + "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + + "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + + "43DB5BFC E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF"; + + public static final String P4096 = + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + + "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + + "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + + "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + + "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + + "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + + "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + + "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + + "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" + + "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" + + "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" + + "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" + + "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" + + "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199" + + "FFFFFFFF FFFFFFFF"; + + public static final String P6144 = + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + + "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + + "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + + "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + + "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + + "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + + "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + + "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + + "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" + + "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" + + "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" + + "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" + + "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" + + "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" + + "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD" + + "F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831" + + "179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B" + + "DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF" + + "5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6" + + "D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3" + + "23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" + + "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328" + + "06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C" + + "DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE" + + "12BF2D5B 0B7474D6 E694F91E 6DCC4024 FFFFFFFF FFFFFFFF"; + + public static final String P8192 = + "FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1" + + "29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD" + + "EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245" + + "E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED" + + "EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D" + + "C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F" + + "83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D" + + "670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B" + + "E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9" + + "DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510" + + "15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64" + + "ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7" + + "ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B" + + "F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C" + + "BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31" + + "43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7" + + "88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA" + + "2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6" + + "287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED" + + "1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9" + + "93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492" + + "36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD" + + "F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831" + + "179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B" + + "DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF" + + "5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6" + + "D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3" + + "23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA" + + "CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328" + + "06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C" + + "DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE" + + "12BF2D5B 0B7474D6 E694F91E 6DBE1159 74A3926F 12FEE5E4" + + "38777CB6 A932DF8C D8BEC4D0 73B931BA 3BC832B6 8D9DD300" + + "741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C 5AE4F568" + + "3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9" + + "22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B" + + "4BCBC886 2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A" + + "062B3CF5 B3A278A6 6D2A13F8 3F44F82D DF310EE0 74AB6A36" + + "4597E899 A0255DC1 64F31CC5 0846851D F9AB4819 5DED7EA1" + + "B1D510BD 7EE74D73 FAF36BC3 1ECFA268 359046F4 EB879F92" + + "4009438B 481C6CD7 889A002E D5EE382B C9190DA6 FC026E47" + + "9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71" + + "60C980DD 98EDD3DF FFFFFFFF FFFFFFFF"; + + public static BigInteger getBestPrime(int keySize) { + String primeString; + if (keySize >= (8192 + 6144) / 2) { + primeString = P8192; + } else if (keySize >= (6144 + 4096) / 2) { + primeString = P6144; + } else if (keySize >= (4096 + 3072) / 2) { + primeString = P4096; + } else if (keySize >= (3072 + 2048) / 2) { + primeString = P3072; + } else if (keySize >= (2048 + 1536) / 2) { + primeString = P2048; + } else { + primeString = P1536; + } + + return new BigInteger(primeString.replaceAll(" ", ""), 16); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ProgressDialogUpdater.java b/org_apg/src/org/thialfihar/android/apg/ProgressDialogUpdater.java new file mode 100644 index 000000000..55e177eef --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ProgressDialogUpdater.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +public interface ProgressDialogUpdater { + void setProgress(String message, int current, int total); + + void setProgress(int resourceId, int current, int total); + + void setProgress(int current, int total); +} diff --git a/org_apg/src/org/thialfihar/android/apg/Service.java b/org_apg/src/org/thialfihar/android/apg/Service.java new file mode 100644 index 000000000..a1e298ef6 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/Service.java @@ -0,0 +1,92 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg; + +import android.content.Intent; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; + +public class Service extends android.app.Service { + private final IBinder mBinder = new LocalBinder(); + + public static final String EXTRA_TTL = "ttl"; + + private int mPassPhraseCacheTtl = 15; + private Handler mCacheHandler = new Handler(); + private Runnable mCacheTask = new Runnable() { + public void run() { + // check every ttl/2 seconds, which shouldn't be heavy on the device (even if ttl = 15), + // and makes sure the longest a pass phrase survives in the cache is 1.5 * ttl + int delay = mPassPhraseCacheTtl * 1000 / 2; + // also make sure the delay is not longer than one minute + if (delay > 60000) { + delay = 60000; + } + + delay = Apg.cleanUpCache(mPassPhraseCacheTtl, delay); + // don't check too often, even if we were close + if (delay < 5000) { + delay = 5000; + } + + mCacheHandler.postDelayed(this, delay); + } + }; + + static private boolean mIsRunning = false; + + @Override + public void onCreate() { + super.onCreate(); + + mIsRunning = true; + } + + @Override + public void onDestroy() { + super.onDestroy(); + mIsRunning = false; + } + + @Override + public void onStart(Intent intent, int startId) { + super.onStart(intent, startId); + + if (intent != null) { + mPassPhraseCacheTtl = intent.getIntExtra(EXTRA_TTL, 15); + } + if (mPassPhraseCacheTtl < 15) { + mPassPhraseCacheTtl = 15; + } + mCacheHandler.removeCallbacks(mCacheTask); + mCacheHandler.postDelayed(mCacheTask, 1000); + } + + static public boolean isRunning() { + return mIsRunning; + } + + public class LocalBinder extends Binder { + Service getService() { + return Service.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/provider/ApgServiceBlobDatabase.java b/org_apg/src/org/thialfihar/android/apg/provider/ApgServiceBlobDatabase.java new file mode 100644 index 000000000..1c57e4b7f --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/provider/ApgServiceBlobDatabase.java @@ -0,0 +1,68 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.provider; + +import org.thialfihar.android.apg.ApgService; + +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; +import android.util.Log; + +public class ApgServiceBlobDatabase extends SQLiteOpenHelper { + + private static final String TAG = "ApgServiceBlobDatabase"; + + private static final int VERSION = 1; + private static final String NAME = "apg_service_blob_data"; + private static final String TABLE = "data"; + + public ApgServiceBlobDatabase(Context context) { + super(context, NAME, null, VERSION); + if(ApgService.LOCAL_LOGD) Log.d(TAG, "constructor called"); + } + + @Override + public void onCreate(SQLiteDatabase db) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "onCreate() called"); + db.execSQL("create table " + TABLE + " ( _id integer primary key autoincrement," + + "key text not null)"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "onUpgrade() called"); + // no upgrade necessary yet + } + + public Uri insert(ContentValues vals) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "insert() called"); + SQLiteDatabase db = this.getWritableDatabase(); + long newId = db.insert(TABLE, null, vals); + return ContentUris.withAppendedId(ApgServiceBlobProvider.CONTENT_URI, newId); + } + + public Cursor query(String id, String key) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "query() called"); + SQLiteDatabase db = this.getReadableDatabase(); + return db.query(TABLE, new String[] {"_id"}, + "_id = ? and key = ?", new String[] {id, key}, + null, null, null); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/provider/ApgServiceBlobProvider.java b/org_apg/src/org/thialfihar/android/apg/provider/ApgServiceBlobProvider.java new file mode 100644 index 000000000..c53076c41 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/provider/ApgServiceBlobProvider.java @@ -0,0 +1,152 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.provider; + +import org.thialfihar.android.apg.ApgService; +import org.thialfihar.android.apg.Constants; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +public class ApgServiceBlobProvider extends ContentProvider { + + private static final String TAG = "ApgServiceBlobProvider"; + + public static final Uri CONTENT_URI = Uri.parse("content://org.thialfihar.android.apg.provider.apgserviceblobprovider"); + + private static final String COLUMN_KEY = "key"; + + private static final String STORE_PATH = Constants.path.APP_DIR+"/ApgServiceBlobs"; + + private ApgServiceBlobDatabase mDb = null; + + public ApgServiceBlobProvider() { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "Constructor called"); + File dir = new File(STORE_PATH); + dir.mkdirs(); + if(ApgService.LOCAL_LOGD) Log.d(TAG, "Constructor finished"); + } + + @Override + public int delete(Uri arg0, String arg1, String[] arg2) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "delete() called"); + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getType(Uri arg0) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "getType() called"); + // not needed for now + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues ignored) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "insert() called"); + // ContentValues are actually ignored, because we want to store a blob with no more information + // but have to create an record with the password generated here first + + ContentValues vals = new ContentValues(); + + // Insert a random key in the database. This has to provided by the caller when updating or + // getting the blob + String password = UUID.randomUUID().toString(); + vals.put(COLUMN_KEY, password); + + Uri insertedUri = mDb.insert(vals); + return Uri.withAppendedPath(insertedUri, password); + } + + @Override + public boolean onCreate() { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "onCreate() called"); + mDb = new ApgServiceBlobDatabase(getContext()); + // TODO Auto-generated method stub + return true; + } + + @Override + public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "query() called"); + // TODO Auto-generated method stub + return null; + } + + @Override + public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "update() called"); + // TODO Auto-generated method stub + return 0; + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) throws SecurityException, FileNotFoundException { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "openFile() called"); + if(ApgService.LOCAL_LOGD) Log.d(TAG, "... with uri: "+uri.toString()); + if(ApgService.LOCAL_LOGD) Log.d(TAG, "... with mode: "+mode); + + List segments = uri.getPathSegments(); + if(segments.size() < 2) { + throw new SecurityException("Password not found in URI"); + } + String id = segments.get(0); + String key = segments.get(1); + + if(ApgService.LOCAL_LOGD) Log.d(TAG, "... got id: "+id); + if(ApgService.LOCAL_LOGD) Log.d(TAG, "... and key: "+key); + + // get the data + Cursor result = mDb.query(id, key); + + if(result.getCount() == 0) { + // either the key is wrong or no id exists + throw new FileNotFoundException("No file found with that ID and/or password"); + } + + File targetFile = new File(STORE_PATH, id); + if(mode.equals("w")) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "... will try to open file w"); + if( !targetFile.exists() ) { + try { + targetFile.createNewFile(); + } catch (IOException e) { + Log.e(TAG, "... got IEOException on creating new file", e); + throw new FileNotFoundException("Could not create file to write to"); + } + } + return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE ); + } else if(mode.equals("r")) { + if(ApgService.LOCAL_LOGD) Log.d(TAG, "... will try to open file r"); + if( !targetFile.exists() ) { + throw new FileNotFoundException("Error: Could not find the file requested"); + } + return ParcelFileDescriptor.open(targetFile, ParcelFileDescriptor.MODE_READ_ONLY); + } + + return null; + } + +} diff --git a/org_apg/src/org/thialfihar/android/apg/provider/DataProvider.java b/org_apg/src/org/thialfihar/android/apg/provider/DataProvider.java new file mode 100644 index 000000000..9cf083528 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/provider/DataProvider.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.provider; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.HashMap; + +import org.thialfihar.android.apg.Id; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.text.TextUtils; + +public class DataProvider extends ContentProvider { + public static final String AUTHORITY = "org.thialfihar.android.apg.provider"; + + private static final int PUBLIC_KEY_RING = 101; + private static final int PUBLIC_KEY_RING_ID = 102; + private static final int PUBLIC_KEY_RING_BY_KEY_ID = 103; + private static final int PUBLIC_KEY_RING_BY_EMAILS = 104; + private static final int PUBLIC_KEY_RING_KEY = 111; + private static final int PUBLIC_KEY_RING_KEY_RANK = 112; + private static final int PUBLIC_KEY_RING_USER_ID = 121; + private static final int PUBLIC_KEY_RING_USER_ID_RANK = 122; + + private static final int SECRET_KEY_RING = 201; + private static final int SECRET_KEY_RING_ID = 202; + private static final int SECRET_KEY_RING_BY_KEY_ID = 203; + private static final int SECRET_KEY_RING_BY_EMAILS = 204; + private static final int SECRET_KEY_RING_KEY = 211; + private static final int SECRET_KEY_RING_KEY_RANK = 212; + private static final int SECRET_KEY_RING_USER_ID = 221; + private static final int SECRET_KEY_RING_USER_ID_RANK = 222; + + private static final int DATA_STREAM = 301; + + private static final String PUBLIC_KEY_RING_CONTENT_DIR_TYPE = + "vnd.android.cursor.dir/vnd.thialfihar.apg.public.key_ring"; + private static final String PUBLIC_KEY_RING_CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/vnd.thialfihar.apg.public.key_ring"; + + private static final String PUBLIC_KEY_CONTENT_DIR_TYPE = + "vnd.android.cursor.dir/vnd.thialfihar.apg.public.key"; + private static final String PUBLIC_KEY_CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/vnd.thialfihar.apg.public.key"; + + private static final String SECRET_KEY_RING_CONTENT_DIR_TYPE = + "vnd.android.cursor.dir/vnd.thialfihar.apg.secret.key_ring"; + private static final String SECRET_KEY_RING_CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/vnd.thialfihar.apg.secret.key_ring"; + + private static final String SECRET_KEY_CONTENT_DIR_TYPE = + "vnd.android.cursor.dir/vnd.thialfihar.apg.secret.key"; + private static final String SECRET_KEY_CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/vnd.thialfihar.apg.secret.key"; + + private static final String USER_ID_CONTENT_DIR_TYPE = + "vnd.android.cursor.dir/vnd.thialfihar.apg.user_id"; + private static final String USER_ID_CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/vnd.thialfihar.apg.user_id"; + + public static final String _ID = "_id"; + public static final String MASTER_KEY_ID = "master_key_id"; + public static final String KEY_ID = "key_id"; + public static final String USER_ID = "user_id"; + + private static final UriMatcher mUriMatcher; + + private Database mDb; + + static { + mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + mUriMatcher.addURI(AUTHORITY, "key_rings/public/key_id/*", PUBLIC_KEY_RING_BY_KEY_ID); + mUriMatcher.addURI(AUTHORITY, "key_rings/public/emails/*", PUBLIC_KEY_RING_BY_EMAILS); + + mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/keys", PUBLIC_KEY_RING_KEY); + mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/keys/#", PUBLIC_KEY_RING_KEY_RANK); + + mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/user_ids", PUBLIC_KEY_RING_USER_ID); + mUriMatcher.addURI(AUTHORITY, "key_rings/public/*/user_ids/#", PUBLIC_KEY_RING_USER_ID_RANK); + + mUriMatcher.addURI(AUTHORITY, "key_rings/public", PUBLIC_KEY_RING); + mUriMatcher.addURI(AUTHORITY, "key_rings/public/*", PUBLIC_KEY_RING_ID); + + mUriMatcher.addURI(AUTHORITY, "key_rings/secret/key_id/*", SECRET_KEY_RING_BY_KEY_ID); + mUriMatcher.addURI(AUTHORITY, "key_rings/secret/emails/*", SECRET_KEY_RING_BY_EMAILS); + + mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/keys", SECRET_KEY_RING_KEY); + mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/keys/#", SECRET_KEY_RING_KEY_RANK); + + mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/user_ids", SECRET_KEY_RING_USER_ID); + mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*/user_ids/#", SECRET_KEY_RING_USER_ID_RANK); + + mUriMatcher.addURI(AUTHORITY, "key_rings/secret", SECRET_KEY_RING); + mUriMatcher.addURI(AUTHORITY, "key_rings/secret/*", SECRET_KEY_RING_ID); + + mUriMatcher.addURI(AUTHORITY, "data/*", DATA_STREAM); + } + + @Override + public boolean onCreate() { + mDb = new Database(getContext()); + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + // TODO: implement the others, then use them for the lists + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + HashMap projectionMap = new HashMap(); + + int match = mUriMatcher.match(uri); + int type; + switch (match) { + case PUBLIC_KEY_RING: + case PUBLIC_KEY_RING_ID: + case PUBLIC_KEY_RING_BY_KEY_ID: + case PUBLIC_KEY_RING_BY_EMAILS: + case PUBLIC_KEY_RING_KEY: + case PUBLIC_KEY_RING_KEY_RANK: + case PUBLIC_KEY_RING_USER_ID: + case PUBLIC_KEY_RING_USER_ID_RANK: + type = Id.database.type_public; + break; + + case SECRET_KEY_RING: + case SECRET_KEY_RING_ID: + case SECRET_KEY_RING_BY_KEY_ID: + case SECRET_KEY_RING_BY_EMAILS: + case SECRET_KEY_RING_KEY: + case SECRET_KEY_RING_KEY_RANK: + case SECRET_KEY_RING_USER_ID: + case SECRET_KEY_RING_USER_ID_RANK: + type = Id.database.type_secret; + break; + + default: { + throw new IllegalArgumentException("Unknown URI " + uri); + } + } + + qb.appendWhere(KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = " + type); + + switch (match) { + case PUBLIC_KEY_RING_ID: + case SECRET_KEY_RING_ID: { + qb.appendWhere(" AND " + + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(2)); + + // break omitted intentionally + } + + case PUBLIC_KEY_RING: + case SECRET_KEY_RING: { + qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + + "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + + ") " + + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + + "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + + UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); + + projectionMap.put(_ID, + KeyRings.TABLE_NAME + "." + KeyRings._ID); + projectionMap.put(MASTER_KEY_ID, + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID); + projectionMap.put(USER_ID, + UserIds.TABLE_NAME + "." + UserIds.USER_ID); + + if (TextUtils.isEmpty(sortOrder)) { + sortOrder = UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC"; + } + + break; + } + + case SECRET_KEY_RING_BY_KEY_ID: + case PUBLIC_KEY_RING_BY_KEY_ID: { + qb.setTables(Keys.TABLE_NAME + " AS tmp INNER JOIN " + + KeyRings.TABLE_NAME + " ON (" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + "tmp." + Keys.KEY_RING_ID + ")" + + " INNER JOIN " + Keys.TABLE_NAME + " ON " + + "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + + ") " + + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + + "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + + UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); + + projectionMap.put(_ID, + KeyRings.TABLE_NAME + "." + KeyRings._ID); + projectionMap.put(MASTER_KEY_ID, + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID); + projectionMap.put(USER_ID, + UserIds.TABLE_NAME + "." + UserIds.USER_ID); + + qb.appendWhere(" AND tmp." + Keys.KEY_ID + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(3)); + + break; + } + + case SECRET_KEY_RING_BY_EMAILS: + case PUBLIC_KEY_RING_BY_EMAILS: { + qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + + "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + + ") " + + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + + "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + + UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); + + projectionMap.put(_ID, + KeyRings.TABLE_NAME + "." + KeyRings._ID); + projectionMap.put(MASTER_KEY_ID, + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID); + projectionMap.put(USER_ID, + UserIds.TABLE_NAME + "." + UserIds.USER_ID); + + String emails = uri.getPathSegments().get(3); + String chunks[] = emails.split(" *, *"); + boolean gotCondition = false; + String emailWhere = ""; + for (int i = 0; i < chunks.length; ++i) { + if (chunks[i].length() == 0) { + continue; + } + if (i != 0) { + emailWhere += " OR "; + } + emailWhere += "tmp." + UserIds.USER_ID + " LIKE "; + // match '*', so it has to be at the *end* of the user id + emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">"); + gotCondition = true; + } + + if (gotCondition) { + qb.appendWhere(" AND EXISTS (SELECT tmp." + UserIds._ID + + " FROM " + UserIds.TABLE_NAME + + " AS tmp WHERE tmp." + UserIds.KEY_ID + " = " + + Keys.TABLE_NAME + "." + Keys._ID + + " AND (" + emailWhere + "))"); + } + + break; + } + + default: { + throw new IllegalArgumentException("Unknown URI " + uri); + } + } + + qb.setProjectionMap(projectionMap); + + // If no sort order is specified use the default + String orderBy; + if (TextUtils.isEmpty(sortOrder)) { + orderBy = null; + } else { + orderBy = sortOrder; + } + + //System.out.println(qb.buildQuery(projection, selection, selectionArgs, null, null, sortOrder, null).replace("WHERE", "WHERE\n")); + Cursor c = qb.query(mDb.db(), projection, selection, selectionArgs, null, null, orderBy); + + // Tell the cursor what uri to watch, so it knows when its source data changes + c.setNotificationUri(getContext().getContentResolver(), uri); + return c; + } + + @Override + public String getType(Uri uri) { + switch (mUriMatcher.match(uri)) { + case PUBLIC_KEY_RING: + case PUBLIC_KEY_RING_BY_EMAILS: + return PUBLIC_KEY_RING_CONTENT_DIR_TYPE; + + case PUBLIC_KEY_RING_ID: + return PUBLIC_KEY_RING_CONTENT_ITEM_TYPE; + + case PUBLIC_KEY_RING_BY_KEY_ID: + return PUBLIC_KEY_RING_CONTENT_ITEM_TYPE; + + case PUBLIC_KEY_RING_KEY: + return PUBLIC_KEY_CONTENT_DIR_TYPE; + + case PUBLIC_KEY_RING_KEY_RANK: + return PUBLIC_KEY_CONTENT_ITEM_TYPE; + + case PUBLIC_KEY_RING_USER_ID: + return USER_ID_CONTENT_DIR_TYPE; + + case PUBLIC_KEY_RING_USER_ID_RANK: + return USER_ID_CONTENT_ITEM_TYPE; + + case SECRET_KEY_RING: + case SECRET_KEY_RING_BY_EMAILS: + return SECRET_KEY_RING_CONTENT_DIR_TYPE; + + case SECRET_KEY_RING_ID: + return SECRET_KEY_RING_CONTENT_ITEM_TYPE; + + case SECRET_KEY_RING_BY_KEY_ID: + return SECRET_KEY_RING_CONTENT_ITEM_TYPE; + + case SECRET_KEY_RING_KEY: + return SECRET_KEY_CONTENT_DIR_TYPE; + + case SECRET_KEY_RING_KEY_RANK: + return SECRET_KEY_CONTENT_ITEM_TYPE; + + case SECRET_KEY_RING_USER_ID: + return USER_ID_CONTENT_DIR_TYPE; + + case SECRET_KEY_RING_USER_ID_RANK: + return USER_ID_CONTENT_ITEM_TYPE; + + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } + } + + @Override + public Uri insert(Uri uri, ContentValues initialValues) { + // not supported + return null; + } + + @Override + public int delete(Uri uri, String where, String[] whereArgs) { + // not supported + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { + // not supported + return 0; + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { + int match = mUriMatcher.match(uri); + if (match != DATA_STREAM) { + throw new FileNotFoundException(); + } + String fileName = uri.getPathSegments().get(1); + File file = new File(getContext().getFilesDir().getAbsolutePath(), fileName); + return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/provider/Database.java b/org_apg/src/org/thialfihar/android/apg/provider/Database.java new file mode 100644 index 000000000..8040eb56c --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/provider/Database.java @@ -0,0 +1,616 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.provider; + +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.util.IterableIterator; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.Vector; + +public class Database extends SQLiteOpenHelper { + public static class GeneralException extends Exception { + static final long serialVersionUID = 0xf812773343L; + + public GeneralException(String message) { + super(message); + } + } + + private static final String DATABASE_NAME = "apg"; + private static final int DATABASE_VERSION = 2; + + public static final String AUTHORITY = "org.thialfihar.android.apg.database"; + + public static HashMap sKeyRingsProjection; + public static HashMap sKeysProjection; + public static HashMap sUserIdsProjection; + + private SQLiteDatabase mDb = null; + private int mStatus = 0; + + static { + sKeyRingsProjection = new HashMap(); + sKeyRingsProjection.put(KeyRings._ID, KeyRings._ID); + sKeyRingsProjection.put(KeyRings.MASTER_KEY_ID, KeyRings.MASTER_KEY_ID); + sKeyRingsProjection.put(KeyRings.TYPE, KeyRings.TYPE); + sKeyRingsProjection.put(KeyRings.WHO_ID, KeyRings.WHO_ID); + sKeyRingsProjection.put(KeyRings.KEY_RING_DATA, KeyRings.KEY_RING_DATA); + + sKeysProjection = new HashMap(); + sKeysProjection.put(Keys._ID, Keys._ID); + sKeysProjection.put(Keys.KEY_ID, Keys.KEY_ID); + sKeysProjection.put(Keys.TYPE, Keys.TYPE); + sKeysProjection.put(Keys.IS_MASTER_KEY, Keys.IS_MASTER_KEY); + sKeysProjection.put(Keys.ALGORITHM, Keys.ALGORITHM); + sKeysProjection.put(Keys.KEY_SIZE, Keys.KEY_SIZE); + sKeysProjection.put(Keys.CAN_SIGN, Keys.CAN_SIGN); + sKeysProjection.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT); + sKeysProjection.put(Keys.IS_REVOKED, Keys.IS_REVOKED); + sKeysProjection.put(Keys.CREATION, Keys.CREATION); + sKeysProjection.put(Keys.EXPIRY, Keys.EXPIRY); + sKeysProjection.put(Keys.KEY_DATA, Keys.KEY_DATA); + sKeysProjection.put(Keys.RANK, Keys.RANK); + + sUserIdsProjection = new HashMap(); + sUserIdsProjection.put(UserIds._ID, UserIds._ID); + sUserIdsProjection.put(UserIds.KEY_ID, UserIds.KEY_ID); + sUserIdsProjection.put(UserIds.USER_ID, UserIds.USER_ID); + sUserIdsProjection.put(UserIds.RANK, UserIds.RANK); + } + + public Database(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + // force upgrade to test things + //onUpgrade(getWritableDatabase(), 1, 2); + mDb = getWritableDatabase(); + } + + @Override + protected void finalize() throws Throwable { + mDb.close(); + super.finalize(); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + KeyRings.TABLE_NAME + " (" + + KeyRings._ID + " " + KeyRings._ID_type + "," + + KeyRings.MASTER_KEY_ID + " " + KeyRings.MASTER_KEY_ID_type + ", " + + KeyRings.TYPE + " " + KeyRings.TYPE_type + ", " + + KeyRings.WHO_ID + " " + KeyRings.WHO_ID_type + ", " + + KeyRings.KEY_RING_DATA + " " + KeyRings.KEY_RING_DATA_type + ");"); + + db.execSQL("CREATE TABLE " + Keys.TABLE_NAME + " (" + + Keys._ID + " " + Keys._ID_type + "," + + Keys.KEY_ID + " " + Keys.KEY_ID_type + ", " + + Keys.TYPE + " " + Keys.TYPE_type + ", " + + Keys.IS_MASTER_KEY + " " + Keys.IS_MASTER_KEY_type + ", " + + Keys.ALGORITHM + " " + Keys.ALGORITHM_type + ", " + + Keys.KEY_SIZE + " " + Keys.KEY_SIZE_type + ", " + + Keys.CAN_SIGN + " " + Keys.CAN_SIGN_type + ", " + + Keys.CAN_ENCRYPT + " " + Keys.CAN_ENCRYPT_type + ", " + + Keys.IS_REVOKED + " " + Keys.IS_REVOKED_type + ", " + + Keys.CREATION + " " + Keys.CREATION_type + ", " + + Keys.EXPIRY + " " + Keys.EXPIRY_type + ", " + + Keys.KEY_RING_ID + " " + Keys.KEY_RING_ID_type + ", " + + Keys.KEY_DATA + " " + Keys.KEY_DATA_type + + Keys.RANK + " " + Keys.RANK_type + ");"); + + db.execSQL("CREATE TABLE " + UserIds.TABLE_NAME + " (" + + UserIds._ID + " " + UserIds._ID_type + "," + + UserIds.KEY_ID + " " + UserIds.KEY_ID_type + "," + + UserIds.USER_ID + " " + UserIds.USER_ID_type + "," + + UserIds.RANK + " " + UserIds.RANK_type + ");"); + + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + mDb = db; + for (int version = oldVersion; version < newVersion; ++version) { + switch (version) { + case 1: { // upgrade 1 to 2 + db.execSQL("DROP TABLE IF EXISTS " + KeyRings.TABLE_NAME + ";"); + db.execSQL("DROP TABLE IF EXISTS " + Keys.TABLE_NAME + ";"); + db.execSQL("DROP TABLE IF EXISTS " + UserIds.TABLE_NAME + ";"); + + db.execSQL("CREATE TABLE " + KeyRings.TABLE_NAME + " (" + + KeyRings._ID + " " + KeyRings._ID_type + "," + + KeyRings.MASTER_KEY_ID + " " + KeyRings.MASTER_KEY_ID_type + ", " + + KeyRings.TYPE + " " + KeyRings.TYPE_type + ", " + + KeyRings.WHO_ID + " " + KeyRings.WHO_ID_type + ", " + + KeyRings.KEY_RING_DATA + " " + KeyRings.KEY_RING_DATA_type + ");"); + + db.execSQL("CREATE TABLE " + Keys.TABLE_NAME + " (" + + Keys._ID + " " + Keys._ID_type + "," + + Keys.KEY_ID + " " + Keys.KEY_ID_type + ", " + + Keys.TYPE + " " + Keys.TYPE_type + ", " + + Keys.IS_MASTER_KEY + " " + Keys.IS_MASTER_KEY_type + ", " + + Keys.ALGORITHM + " " + Keys.ALGORITHM_type + ", " + + Keys.KEY_SIZE + " " + Keys.KEY_SIZE_type + ", " + + Keys.CAN_SIGN + " " + Keys.CAN_SIGN_type + ", " + + Keys.CAN_ENCRYPT + " " + Keys.CAN_ENCRYPT_type + ", " + + Keys.IS_REVOKED + " " + Keys.IS_REVOKED_type + ", " + + Keys.CREATION + " " + Keys.CREATION_type + ", " + + Keys.EXPIRY + " " + Keys.EXPIRY_type + ", " + + Keys.KEY_RING_ID + " " + Keys.KEY_RING_ID_type + ", " + + Keys.KEY_DATA + " " + Keys.KEY_DATA_type + + Keys.RANK + " " + Keys.RANK_type + ");"); + + db.execSQL("CREATE TABLE " + UserIds.TABLE_NAME + " (" + + UserIds._ID + " " + UserIds._ID_type + "," + + UserIds.KEY_ID + " " + UserIds.KEY_ID_type + "," + + UserIds.USER_ID + " " + UserIds.USER_ID_type + "," + + UserIds.RANK + " " + UserIds.RANK_type + ");"); + + Cursor cursor = db.query("public_keys", new String[] { "c_key_data" }, + null, null, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + do { + byte[] data = cursor.getBlob(0); + try { + PGPPublicKeyRing keyRing = new PGPPublicKeyRing(data); + saveKeyRing(keyRing); + } catch (IOException e) { + Log.e("apg.db.upgrade", "key import failed: " + e); + } catch (GeneralException e) { + Log.e("apg.db.upgrade", "key import failed: " + e); + } + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + + cursor = db.query("secret_keys", new String[]{ "c_key_data" }, + null, null, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + do { + byte[] data = cursor.getBlob(0); + try { + PGPSecretKeyRing keyRing = new PGPSecretKeyRing(data); + saveKeyRing(keyRing); + } catch (IOException e) { + Log.e("apg.db.upgrade", "key import failed: " + e); + } catch (PGPException e) { + Log.e("apg.db.upgrade", "key import failed: " + e); + } catch (GeneralException e) { + Log.e("apg.db.upgrade", "key import failed: " + e); + } + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + + db.execSQL("DROP TABLE IF EXISTS public_keys;"); + db.execSQL("DROP TABLE IF EXISTS secret_keys;"); + + break; + } + + default: { + break; + } + } + } + mDb = null; + } + + public int saveKeyRing(PGPPublicKeyRing keyRing) throws IOException, GeneralException { + mDb.beginTransaction(); + ContentValues values = new ContentValues(); + PGPPublicKey masterKey = keyRing.getPublicKey(); + long masterKeyId = masterKey.getKeyID(); + + values.put(KeyRings.MASTER_KEY_ID, masterKeyId); + values.put(KeyRings.TYPE, Id.database.type_public); + values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded()); + + long rowId = insertOrUpdateKeyRing(values); + int returnValue = mStatus; + + if (rowId == -1) { + throw new GeneralException("saving public key ring " + masterKeyId + " failed"); + } + + Vector seenIds = new Vector(); + int rank = 0; + for (PGPPublicKey key : new IterableIterator(keyRing.getPublicKeys())) { + seenIds.add(saveKey(rowId, key, rank)); + ++rank; + } + + String seenIdsStr = ""; + for (Integer id : seenIds) { + if (seenIdsStr.length() > 0) { + seenIdsStr += ","; + } + seenIdsStr += id; + } + mDb.delete(Keys.TABLE_NAME, + Keys.KEY_RING_ID + " = ? AND " + + Keys._ID + " NOT IN (" + seenIdsStr + ")", + new String[] { "" + rowId }); + + mDb.setTransactionSuccessful(); + mDb.endTransaction(); + return returnValue; + } + + public int saveKeyRing(PGPSecretKeyRing keyRing) throws IOException, GeneralException { + mDb.beginTransaction(); + ContentValues values = new ContentValues(); + PGPSecretKey masterKey = keyRing.getSecretKey(); + long masterKeyId = masterKey.getKeyID(); + + values.put(KeyRings.MASTER_KEY_ID, masterKeyId); + values.put(KeyRings.TYPE, Id.database.type_secret); + values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded()); + + long rowId = insertOrUpdateKeyRing(values); + int returnValue = mStatus; + + if (rowId == -1) { + throw new GeneralException("saving secret key ring " + masterKeyId + " failed"); + } + + Vector seenIds = new Vector(); + int rank = 0; + for (PGPSecretKey key : new IterableIterator(keyRing.getSecretKeys())) { + seenIds.add(saveKey(rowId, key, rank)); + ++rank; + } + + String seenIdsStr = ""; + for (Integer id : seenIds) { + if (seenIdsStr.length() > 0) { + seenIdsStr += ","; + } + seenIdsStr += id; + } + mDb.delete(Keys.TABLE_NAME, + Keys.KEY_RING_ID + " = ? AND " + + Keys._ID + " NOT IN (" + seenIdsStr + ")", + new String[] { "" + rowId }); + + mDb.setTransactionSuccessful(); + mDb.endTransaction(); + return returnValue; + } + + private int saveKey(long keyRingId, PGPPublicKey key, int rank) + throws IOException, GeneralException { + ContentValues values = new ContentValues(); + + values.put(Keys.KEY_ID, key.getKeyID()); + values.put(Keys.TYPE, Id.database.type_public); + values.put(Keys.IS_MASTER_KEY, key.isMasterKey()); + values.put(Keys.ALGORITHM, key.getAlgorithm()); + values.put(Keys.KEY_SIZE, key.getBitStrength()); + values.put(Keys.CAN_SIGN, Apg.isSigningKey(key)); + values.put(Keys.CAN_ENCRYPT, Apg.isEncryptionKey(key)); + values.put(Keys.IS_REVOKED, key.isRevoked()); + values.put(Keys.CREATION, Apg.getCreationDate(key).getTime() / 1000); + Date expiryDate = Apg.getExpiryDate(key); + if (expiryDate != null) { + values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); + } + values.put(Keys.KEY_RING_ID, keyRingId); + values.put(Keys.KEY_DATA, key.getEncoded()); + values.put(Keys.RANK, rank); + + long rowId = insertOrUpdateKey(values); + + if (rowId == -1) { + throw new GeneralException("saving public key " + key.getKeyID() + " failed"); + } + + Vector seenIds = new Vector(); + int userIdRank = 0; + for (String userId : new IterableIterator(key.getUserIDs())) { + seenIds.add(saveUserId(rowId, userId, userIdRank)); + ++userIdRank; + } + + String seenIdsStr = ""; + for (Integer id : seenIds) { + if (seenIdsStr.length() > 0) { + seenIdsStr += ","; + } + seenIdsStr += id; + } + mDb.delete(UserIds.TABLE_NAME, + UserIds.KEY_ID + " = ? AND " + + UserIds._ID + " NOT IN (" + seenIdsStr + ")", + new String[] { "" + rowId }); + + return (int)rowId; + } + + private int saveKey(long keyRingId, PGPSecretKey key, int rank) + throws IOException, GeneralException { + ContentValues values = new ContentValues(); + + values.put(Keys.KEY_ID, key.getPublicKey().getKeyID()); + values.put(Keys.TYPE, Id.database.type_secret); + values.put(Keys.IS_MASTER_KEY, key.isMasterKey()); + values.put(Keys.ALGORITHM, key.getPublicKey().getAlgorithm()); + values.put(Keys.KEY_SIZE, key.getPublicKey().getBitStrength()); + values.put(Keys.CAN_SIGN, Apg.isSigningKey(key)); + values.put(Keys.CAN_ENCRYPT, Apg.isEncryptionKey(key)); + values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked()); + values.put(Keys.CREATION, Apg.getCreationDate(key).getTime() / 1000); + Date expiryDate = Apg.getExpiryDate(key); + if (expiryDate != null) { + values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); + } + values.put(Keys.KEY_RING_ID, keyRingId); + values.put(Keys.KEY_DATA, key.getEncoded()); + values.put(Keys.RANK, rank); + + long rowId = insertOrUpdateKey(values); + + if (rowId == -1) { + throw new GeneralException("saving secret key " + key.getPublicKey().getKeyID() + " failed"); + } + + Vector seenIds = new Vector(); + int userIdRank = 0; + for (String userId : new IterableIterator(key.getUserIDs())) { + seenIds.add(saveUserId(rowId, userId, userIdRank)); + ++userIdRank; + } + + String seenIdsStr = ""; + for (Integer id : seenIds) { + if (seenIdsStr.length() > 0) { + seenIdsStr += ","; + } + seenIdsStr += id; + } + mDb.delete(UserIds.TABLE_NAME, + UserIds.KEY_ID + " = ? AND " + + UserIds._ID + " NOT IN (" + seenIdsStr + ")", + new String[] { "" + rowId }); + + return (int)rowId; + } + + private int saveUserId(long keyId, String userId, int rank) throws GeneralException { + ContentValues values = new ContentValues(); + + values.put(UserIds.KEY_ID, keyId); + values.put(UserIds.USER_ID, userId); + values.put(UserIds.RANK, rank); + + long rowId = insertOrUpdateUserId(values); + + if (rowId == -1) { + throw new GeneralException("saving user id " + userId + " failed"); + } + + return (int)rowId; + } + + private long insertOrUpdateKeyRing(ContentValues values) { + Cursor c = mDb.query(KeyRings.TABLE_NAME, new String[] { KeyRings._ID }, + KeyRings.MASTER_KEY_ID + " = ? AND " + KeyRings.TYPE + " = ?", + new String[] { + values.getAsString(KeyRings.MASTER_KEY_ID), + values.getAsString(KeyRings.TYPE), + }, + null, null, null); + long rowId = -1; + if (c != null && c.moveToFirst()) { + rowId = c.getLong(0); + mDb.update(KeyRings.TABLE_NAME, values, + KeyRings._ID + " = ?", new String[] { "" + rowId }); + mStatus = Id.return_value.updated; + } else { + rowId = mDb.insert(KeyRings.TABLE_NAME, KeyRings.WHO_ID, values); + mStatus = Id.return_value.ok; + } + + if (c != null) { + c.close(); + } + + return rowId; + } + + private long insertOrUpdateKey(ContentValues values) { + Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID }, + Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", + new String[] { + values.getAsString(Keys.KEY_ID), + values.getAsString(Keys.TYPE), + }, + null, null, null); + long rowId = -1; + if (c != null && c.moveToFirst()) { + rowId = c.getLong(0); + mDb.update(Keys.TABLE_NAME, values, + Keys._ID + " = ?", new String[] { "" + rowId }); + } else { + rowId = mDb.insert(Keys.TABLE_NAME, Keys.KEY_DATA, values); + } + + if (c != null) { + c.close(); + } + + return rowId; + } + + private long insertOrUpdateUserId(ContentValues values) { + Cursor c = mDb.query(UserIds.TABLE_NAME, new String[] { UserIds._ID }, + UserIds.KEY_ID + " = ? AND " + UserIds.USER_ID + " = ?", + new String[] { + values.getAsString(UserIds.KEY_ID), + values.getAsString(UserIds.USER_ID), + }, + null, null, null); + long rowId = -1; + if (c != null && c.moveToFirst()) { + rowId = c.getLong(0); + mDb.update(UserIds.TABLE_NAME, values, + UserIds._ID + " = ?", new String[] { "" + rowId }); + } else { + rowId = mDb.insert(UserIds.TABLE_NAME, UserIds.USER_ID, values); + } + + if (c != null) { + c.close(); + } + + return rowId; + } + + public Object getKeyRing(int keyRingId) { + Cursor c = mDb.query(KeyRings.TABLE_NAME, + new String[] { KeyRings.KEY_RING_DATA, KeyRings.TYPE }, + KeyRings._ID + " = ?", + new String[] { + "" + keyRingId, + }, + null, null, null); + byte[] data = null; + Object keyRing = null; + if (c != null && c.moveToFirst()) { + data = c.getBlob(0); + if (data != null) { + try { + if (c.getInt(1) == Id.database.type_public) { + keyRing = new PGPPublicKeyRing(data); + } else { + keyRing = new PGPSecretKeyRing(data); + } + } catch (IOException e) { + // can't load it, then + } catch (PGPException e) { + // can't load it, then + } + } + } + + if (c != null) { + c.close(); + } + + return keyRing; + } + + public byte[] getKeyRingDataFromKeyId(int type, long keyId) { + Cursor c = mDb.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON (" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + ")", + new String[] { KeyRings.TABLE_NAME + "." + KeyRings.KEY_RING_DATA }, + Keys.TABLE_NAME + "." + Keys.KEY_ID + " = ? AND " + + KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", + new String[] { + "" + keyId, + "" + type, + }, + null, null, null); + + byte[] data = null; + if (c != null && c.moveToFirst()) { + data = c.getBlob(0); + } + + if (c != null) { + c.close(); + } + + return data; + } + + public byte[] getKeyDataFromKeyId(int type, long keyId) { + Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys.KEY_DATA }, + Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", + new String[] { + "" + keyId, + "" + type, + }, + null, null, null); + byte[] data = null; + if (c != null && c.moveToFirst()) { + data = c.getBlob(0); + } + + if (c != null) { + c.close(); + } + + return data; + } + + public void deleteKeyRing(int keyRingId) { + mDb.beginTransaction(); + mDb.delete(KeyRings.TABLE_NAME, + KeyRings._ID + " = ?", new String[] { "" + keyRingId }); + + Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID }, + Keys.KEY_RING_ID + " = ?", + new String[] { + "" + keyRingId, + }, + null, null, null); + if (c != null && c.moveToFirst()) { + do { + int keyId = c.getInt(0); + deleteKey(keyId); + } while (c.moveToNext()); + } + + if (c != null) { + c.close(); + } + + mDb.setTransactionSuccessful(); + mDb.endTransaction(); + } + + private void deleteKey(int keyId) { + mDb.delete(Keys.TABLE_NAME, + Keys._ID + " = ?", new String[] { "" + keyId }); + + mDb.delete(UserIds.TABLE_NAME, + UserIds.KEY_ID + " = ?", new String[] { "" + keyId }); + } + + public SQLiteDatabase db() { + return mDb; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/provider/KeyRings.java b/org_apg/src/org/thialfihar/android/apg/provider/KeyRings.java new file mode 100644 index 000000000..58e95eba6 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/provider/KeyRings.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.provider; + +import android.provider.BaseColumns; + +public class KeyRings implements BaseColumns { + public static final String TABLE_NAME = "key_rings"; + + public static final String _ID_type = "INTEGER PRIMARY KEY"; + public static final String MASTER_KEY_ID = "c_master_key_id"; + public static final String MASTER_KEY_ID_type = "INT64"; + public static final String TYPE = "c_type"; + public static final String TYPE_type = "INTEGER"; + public static final String WHO_ID = "c_who_id"; + public static final String WHO_ID_type = "INTEGER"; + public static final String KEY_RING_DATA = "c_key_ring_data"; + public static final String KEY_RING_DATA_type = "BLOB"; +} diff --git a/org_apg/src/org/thialfihar/android/apg/provider/Keys.java b/org_apg/src/org/thialfihar/android/apg/provider/Keys.java new file mode 100644 index 000000000..618c5e920 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/provider/Keys.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.provider; + +import android.provider.BaseColumns; + +public class Keys implements BaseColumns { + public static final String TABLE_NAME = "keys"; + + public static final String _ID_type = "INTEGER PRIMARY KEY"; + public static final String KEY_ID = "c_key_id"; + public static final String KEY_ID_type = "INT64"; + public static final String TYPE = "c_type"; + public static final String TYPE_type = "INTEGER"; + public static final String IS_MASTER_KEY = "c_is_master_key"; + public static final String IS_MASTER_KEY_type = "INTEGER"; + public static final String ALGORITHM = "c_algorithm"; + public static final String ALGORITHM_type = "INTEGER"; + public static final String KEY_SIZE = "c_key_size"; + public static final String KEY_SIZE_type = "INTEGER"; + public static final String CAN_SIGN = "c_can_sign"; + public static final String CAN_SIGN_type = "INTEGER"; + public static final String CAN_ENCRYPT = "c_can_encrypt"; + public static final String CAN_ENCRYPT_type = "INTEGER"; + public static final String IS_REVOKED = "c_is_revoked"; + public static final String IS_REVOKED_type = "INTEGER"; + public static final String CREATION = "c_creation"; + public static final String CREATION_type = "INTEGER"; + public static final String EXPIRY = "c_expiry"; + public static final String EXPIRY_type = "INTEGER"; + public static final String KEY_RING_ID = "c_key_ring_id"; + public static final String KEY_RING_ID_type = "INTEGER"; + public static final String KEY_DATA = "c_key_data"; + public static final String KEY_DATA_type = "BLOB"; + public static final String RANK = "c_key_data"; + public static final String RANK_type = "INTEGER"; +} diff --git a/org_apg/src/org/thialfihar/android/apg/provider/UserIds.java b/org_apg/src/org/thialfihar/android/apg/provider/UserIds.java new file mode 100644 index 000000000..2050ccf9c --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/provider/UserIds.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.provider; + +import android.provider.BaseColumns; + +public class UserIds implements BaseColumns { + public static final String TABLE_NAME = "user_ids"; + + public static final String _ID_type = "INTEGER PRIMARY KEY"; + public static final String KEY_ID = "c_key_id"; + public static final String KEY_ID_type = "INTEGER"; + public static final String USER_ID = "c_user_id"; + public static final String USER_ID_type = "TEXT"; + public static final String RANK = "c_rank"; + public static final String RANK_type = "INTEGER"; +} diff --git a/org_apg/src/org/thialfihar/android/apg/service/ApgService.java b/org_apg/src/org/thialfihar/android/apg/service/ApgService.java new file mode 100644 index 000000000..bcf336d46 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/service/ApgService.java @@ -0,0 +1,12 @@ + +/** + * TODO: + * - Reimplement all the threads in the activitys as intents in this intentService + * - This IntentService stopps itself after an action is executed + */ + +package org.thialfihar.android.apg.service; + +public class ApgService { + +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/AboutActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/AboutActivity.java new file mode 100644 index 000000000..1988292ae --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/AboutActivity.java @@ -0,0 +1,65 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.Constants; + +import android.app.Activity; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Bundle; +import android.util.Log; +import android.widget.TextView; + +public class AboutActivity extends Activity { + Activity mActivity; + + /** + * Instantiate View for this Activity + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.about_activity); + + mActivity = this; + + TextView versionText = (TextView) findViewById(R.id.about_version); + versionText.setText(getString(R.string.about_version) + " " + getVersion()); + } + + /** + * Get the current package version. + * + * @return The current version. + */ + private String getVersion() { + String result = ""; + try { + PackageManager manager = mActivity.getPackageManager(); + PackageInfo info = manager.getPackageInfo(mActivity.getPackageName(), 0); + + result = String.format("%s (%s)", info.versionName, info.versionCode); + } catch (NameNotFoundException e) { + Log.w(Constants.TAG, "Unable to get application version: " + e.getMessage()); + result = "Unable to get application version."; + } + + return result; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/BaseActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/BaseActivity.java new file mode 100644 index 000000000..022daf420 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/BaseActivity.java @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.AskForSecretKeyPassPhrase; +import org.thialfihar.android.apg.Constants; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.PausableThread; +import org.thialfihar.android.apg.Preferences; +import org.thialfihar.android.apg.ProgressDialogUpdater; +import org.thialfihar.android.apg.Service; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockActivity; +import com.actionbarsherlock.view.MenuItem; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.widget.Toast; + +public class BaseActivity extends SherlockActivity implements Runnable, ProgressDialogUpdater, + AskForSecretKeyPassPhrase.PassPhraseCallbackInterface { + + private ProgressDialog mProgressDialog = null; + private PausableThread mRunningThread = null; + private Thread mDeletingThread = null; + + private long mSecretKeyId = 0; + private String mDeleteFile = null; + + protected Preferences mPreferences; + + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + handlerCallback(msg); + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + + mPreferences = Preferences.getPreferences(this); + + Apg.initialize(this); + + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + File dir = new File(Constants.path.APP_DIR); + if (!dir.exists() && !dir.mkdirs()) { + // ignore this for now, it's not crucial + // that the directory doesn't exist at this point + } + } + + startCacheService(this, mPreferences); + } + + public static void startCacheService(Activity activity, Preferences preferences) { + Intent intent = new Intent(activity, Service.class); + intent.putExtra(Service.EXTRA_TTL, preferences.getPassPhraseCacheTtl()); + activity.startService(intent); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case android.R.id.home: + startActivity(new Intent(this, MainActivity.class)); + return true; + + // TODO: needed?: + case Id.menu.option.search: + startSearch("", false, null, false); + return true; + + default: + break; + + } + return false; + } + + @Override + protected Dialog onCreateDialog(int id) { + // in case it is a progress dialog + mProgressDialog = new ProgressDialog(this); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + mProgressDialog.setCancelable(false); + switch (id) { + case Id.dialog.encrypting: { + mProgressDialog.setMessage(this.getString(R.string.progress_initializing)); + return mProgressDialog; + } + + case Id.dialog.decrypting: { + mProgressDialog.setMessage(this.getString(R.string.progress_initializing)); + return mProgressDialog; + } + + case Id.dialog.saving: { + mProgressDialog.setMessage(this.getString(R.string.progress_saving)); + return mProgressDialog; + } + + case Id.dialog.importing: { + mProgressDialog.setMessage(this.getString(R.string.progress_importing)); + return mProgressDialog; + } + + case Id.dialog.exporting: { + mProgressDialog.setMessage(this.getString(R.string.progress_exporting)); + return mProgressDialog; + } + + case Id.dialog.deleting: { + mProgressDialog.setMessage(this.getString(R.string.progress_initializing)); + return mProgressDialog; + } + + case Id.dialog.querying: { + mProgressDialog.setMessage(this.getString(R.string.progress_querying)); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + mProgressDialog.setCancelable(false); + return mProgressDialog; + } + + case Id.dialog.signing: { + mProgressDialog.setMessage(this.getString(R.string.progress_signing)); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + mProgressDialog.setCancelable(false); + return mProgressDialog; + } + + default: { + break; + } + } + mProgressDialog = null; + + switch (id) { + + case Id.dialog.pass_phrase: { + return AskForSecretKeyPassPhrase.createDialog(this, getSecretKeyId(), this); + } + + case Id.dialog.pass_phrases_do_not_match: { + AlertDialog.Builder alert = new AlertDialog.Builder(this); + + alert.setIcon(android.R.drawable.ic_dialog_alert); + alert.setTitle(R.string.error); + alert.setMessage(R.string.passPhrasesDoNotMatch); + + alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeDialog(Id.dialog.pass_phrases_do_not_match); + } + }); + alert.setCancelable(false); + + return alert.create(); + } + + case Id.dialog.no_pass_phrase: { + AlertDialog.Builder alert = new AlertDialog.Builder(this); + + alert.setIcon(android.R.drawable.ic_dialog_alert); + alert.setTitle(R.string.error); + alert.setMessage(R.string.passPhraseMustNotBeEmpty); + + alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeDialog(Id.dialog.no_pass_phrase); + } + }); + alert.setCancelable(false); + + return alert.create(); + } + + case Id.dialog.delete_file: { + AlertDialog.Builder alert = new AlertDialog.Builder(this); + + alert.setIcon(android.R.drawable.ic_dialog_alert); + alert.setTitle(R.string.warning); + alert.setMessage(this.getString(R.string.fileDeleteConfirmation, getDeleteFile())); + + alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeDialog(Id.dialog.delete_file); + final File file = new File(getDeleteFile()); + showDialog(Id.dialog.deleting); + mDeletingThread = new Thread(new Runnable() { + public void run() { + Bundle data = new Bundle(); + data.putInt(Constants.extras.STATUS, Id.message.delete_done); + try { + Apg.deleteFileSecurely(BaseActivity.this, file, BaseActivity.this); + } catch (FileNotFoundException e) { + data.putString(Apg.EXTRA_ERROR, BaseActivity.this.getString( + R.string.error_fileNotFound, file)); + } catch (IOException e) { + data.putString(Apg.EXTRA_ERROR, BaseActivity.this.getString( + R.string.error_fileDeleteFailed, file)); + } + Message msg = new Message(); + msg.setData(data); + sendMessage(msg); + } + }); + mDeletingThread.start(); + } + }); + alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeDialog(Id.dialog.delete_file); + } + }); + alert.setCancelable(true); + + return alert.create(); + } + + default: { + break; + } + } + + return super.onCreateDialog(id); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case Id.request.secret_keys: { + if (resultCode == RESULT_OK) { + Bundle bundle = data.getExtras(); + setSecretKeyId(bundle.getLong(Apg.EXTRA_KEY_ID)); + } else { + setSecretKeyId(Id.key.none); + } + break; + } + + default: { + break; + } + } + + super.onActivityResult(requestCode, resultCode, data); + } + + public void setProgress(int resourceId, int progress, int max) { + setProgress(getString(resourceId), progress, max); + } + + public void setProgress(int progress, int max) { + Message msg = new Message(); + Bundle data = new Bundle(); + data.putInt(Constants.extras.STATUS, Id.message.progress_update); + data.putInt(Constants.extras.PROGRESS, progress); + data.putInt(Constants.extras.PROGRESS_MAX, max); + msg.setData(data); + mHandler.sendMessage(msg); + } + + public void setProgress(String message, int progress, int max) { + Message msg = new Message(); + Bundle data = new Bundle(); + data.putInt(Constants.extras.STATUS, Id.message.progress_update); + data.putString(Constants.extras.MESSAGE, message); + data.putInt(Constants.extras.PROGRESS, progress); + data.putInt(Constants.extras.PROGRESS_MAX, max); + msg.setData(data); + mHandler.sendMessage(msg); + } + + public void handlerCallback(Message msg) { + Bundle data = msg.getData(); + if (data == null) { + return; + } + + int type = data.getInt(Constants.extras.STATUS); + switch (type) { + case Id.message.progress_update: { + String message = data.getString(Constants.extras.MESSAGE); + if (mProgressDialog != null) { + if (message != null) { + mProgressDialog.setMessage(message); + } + mProgressDialog.setMax(data.getInt(Constants.extras.PROGRESS_MAX)); + mProgressDialog.setProgress(data.getInt(Constants.extras.PROGRESS)); + } + break; + } + + case Id.message.delete_done: { + mProgressDialog = null; + deleteDoneCallback(msg); + break; + } + + case Id.message.import_done: // intentionally no break + case Id.message.export_done: // intentionally no break + case Id.message.query_done: // intentionally no break + case Id.message.done: { + mProgressDialog = null; + doneCallback(msg); + break; + } + + default: { + break; + } + } + } + + public void doneCallback(Message msg) { + + } + + public void deleteDoneCallback(Message msg) { + removeDialog(Id.dialog.deleting); + mDeletingThread = null; + + Bundle data = msg.getData(); + String error = data.getString(Apg.EXTRA_ERROR); + String message; + if (error != null) { + message = getString(R.string.errorMessage, error); + } else { + message = getString(R.string.fileDeleteSuccessful); + } + + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + } + + public void passPhraseCallback(long keyId, String passPhrase) { + Apg.setCachedPassPhrase(keyId, passPhrase); + } + + public void sendMessage(Message msg) { + mHandler.sendMessage(msg); + } + + public PausableThread getRunningThread() { + return mRunningThread; + } + + public void startThread() { + mRunningThread = new PausableThread(this); + mRunningThread.start(); + } + + public void run() { + + } + + public void setSecretKeyId(long id) { + mSecretKeyId = id; + } + + public long getSecretKeyId() { + return mSecretKeyId; + } + + protected void setDeleteFile(String deleteFile) { + mDeleteFile = deleteFile; + } + + protected String getDeleteFile() { + return mDeleteFile; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/DecryptActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/DecryptActivity.java new file mode 100644 index 000000000..ca8d2f018 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/DecryptActivity.java @@ -0,0 +1,864 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Constants; +import org.thialfihar.android.apg.DataDestination; +import org.thialfihar.android.apg.DataSource; +import org.thialfihar.android.apg.FileDialog; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.InputData; +import org.thialfihar.android.apg.PausableThread; +import org.thialfihar.android.apg.provider.DataProvider; +import org.thialfihar.android.apg.util.Compatibility; +import org.thialfihar.android.apg.R; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.ActivityNotFoundException; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Message; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.animation.AnimationUtils; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.ViewFlipper; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.Security; +import java.security.SignatureException; +import java.util.regex.Matcher; + +public class DecryptActivity extends BaseActivity { + private long mSignatureKeyId = 0; + + private Intent mIntent; + + private boolean mReturnResult = false; + private String mReplyTo = null; + private String mSubject = null; + private boolean mSignedOnly = false; + private boolean mAssumeSymmetricEncryption = false; + + private EditText mMessage = null; + private LinearLayout mSignatureLayout = null; + private ImageView mSignatureStatusImage = null; + private TextView mUserId = null; + private TextView mUserIdRest = null; + + private ViewFlipper mSource = null; + private TextView mSourceLabel = null; + private ImageView mSourcePrevious = null; + private ImageView mSourceNext = null; + + // private Button mDecryptButton = null; + // private Button mReplyButton = null; + + private boolean mDecryptEnabled = true; + private String mDecryptString = ""; + private boolean mReplyEnabled = true; + private String mReplyString = ""; + + private int mDecryptTarget; + + private EditText mFilename = null; + private CheckBox mDeleteAfter = null; + private ImageButton mBrowse = null; + + private String mInputFilename = null; + private String mOutputFilename = null; + + private Uri mContentUri = null; + private byte[] mData = null; + private boolean mReturnBinary = false; + + private DataSource mDataSource = null; + private DataDestination mDataDestination = null; + + private long mUnknownSignatureKeyId = 0; + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + + if (mDecryptEnabled) { + menu.add(1, Id.menu.option.decrypt, 0, mDecryptString).setShowAsAction( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + if (mReplyEnabled) { + menu.add(1, Id.menu.option.reply, 1, mReplyString).setShowAsAction( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case Id.menu.option.decrypt: { + decryptClicked(); + + return true; + } + case Id.menu.option.reply: { + replyClicked(); + + return true; + } + + default: { + return super.onOptionsItemSelected(item); + } + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.decrypt); + + mSource = (ViewFlipper) findViewById(R.id.source); + mSourceLabel = (TextView) findViewById(R.id.sourceLabel); + mSourcePrevious = (ImageView) findViewById(R.id.sourcePrevious); + mSourceNext = (ImageView) findViewById(R.id.sourceNext); + + mSourcePrevious.setClickable(true); + mSourcePrevious.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this, + R.anim.push_right_in)); + mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this, + R.anim.push_right_out)); + mSource.showPrevious(); + updateSource(); + } + }); + + mSourceNext.setClickable(true); + OnClickListener nextSourceClickListener = new OnClickListener() { + public void onClick(View v) { + mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this, + R.anim.push_left_in)); + mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this, + R.anim.push_left_out)); + mSource.showNext(); + updateSource(); + } + }; + mSourceNext.setOnClickListener(nextSourceClickListener); + + mSourceLabel.setClickable(true); + mSourceLabel.setOnClickListener(nextSourceClickListener); + + mMessage = (EditText) findViewById(R.id.message); + mSignatureLayout = (LinearLayout) findViewById(R.id.signature); + mSignatureStatusImage = (ImageView) findViewById(R.id.ic_signature_status); + mUserId = (TextView) findViewById(R.id.mainUserId); + mUserIdRest = (TextView) findViewById(R.id.mainUserIdRest); + + // measure the height of the source_file view and set the message view's min height to that, + // so it fills mSource fully... bit of a hack. + View tmp = findViewById(R.id.sourceFile); + tmp.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + int height = tmp.getMeasuredHeight(); + mMessage.setMinimumHeight(height); + + mFilename = (EditText) findViewById(R.id.filename); + mBrowse = (ImageButton) findViewById(R.id.btn_browse); + mBrowse.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + openFile(); + } + }); + + mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterDecryption); + + // default: message source + mSource.setInAnimation(null); + mSource.setOutAnimation(null); + while (mSource.getCurrentView().getId() != R.id.sourceMessage) { + mSource.showNext(); + } + + mIntent = getIntent(); + if (Intent.ACTION_VIEW.equals(mIntent.getAction())) { + Uri uri = mIntent.getData(); + try { + InputStream attachment = getContentResolver().openInputStream(uri); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + byte bytes[] = new byte[1 << 16]; + int length; + while ((length = attachment.read(bytes)) > 0) { + byteOut.write(bytes, 0, length); + } + byteOut.close(); + String data = new String(byteOut.toByteArray()); + mMessage.setText(data); + } catch (FileNotFoundException e) { + // ignore, then + } catch (IOException e) { + // ignore, then + } + } else if (Apg.Intent.DECRYPT.equals(mIntent.getAction())) { + Log.d(Constants.TAG, "Apg Intent DECRYPT startet"); + Bundle extras = mIntent.getExtras(); + if (extras == null) { + Log.d(Constants.TAG, "extra bundle was null"); + extras = new Bundle(); + } else { + Log.d(Constants.TAG, "got extras"); + } + + mData = extras.getByteArray(Apg.EXTRA_DATA); + String textData = null; + if (mData == null) { + Log.d(Constants.TAG, "EXTRA_DATA was null"); + textData = extras.getString(Apg.EXTRA_TEXT); + } else { + Log.d(Constants.TAG, "Got data from EXTRA_DATA"); + } + if (textData != null) { + Log.d(Constants.TAG, "textData null, matching text ..."); + Matcher matcher = Apg.PGP_MESSAGE.matcher(textData); + if (matcher.matches()) { + Log.d(Constants.TAG, "PGP_MESSAGE matched"); + textData = matcher.group(1); + // replace non breakable spaces + textData = textData.replaceAll("\\xa0", " "); + mMessage.setText(textData); + } else { + matcher = Apg.PGP_SIGNED_MESSAGE.matcher(textData); + if (matcher.matches()) { + Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched"); + textData = matcher.group(1); + // replace non breakable spaces + textData = textData.replaceAll("\\xa0", " "); + mMessage.setText(textData); + + mDecryptString = getString(R.string.btn_verify); + // build new action bar + invalidateOptionsMenu(); + } else { + Log.d(Constants.TAG, "Nothing matched!"); + } + } + } + mReplyTo = extras.getString(Apg.EXTRA_REPLY_TO); + mSubject = extras.getString(Apg.EXTRA_SUBJECT); + } else if (Apg.Intent.DECRYPT_FILE.equals(mIntent.getAction())) { + mInputFilename = mIntent.getDataString(); + if ("file".equals(mIntent.getScheme())) { + mInputFilename = Uri.decode(mInputFilename.substring(7)); + } + mFilename.setText(mInputFilename); + guessOutputFilename(); + mSource.setInAnimation(null); + mSource.setOutAnimation(null); + while (mSource.getCurrentView().getId() != R.id.sourceFile) { + mSource.showNext(); + } + } else if (Apg.Intent.DECRYPT_AND_RETURN.equals(mIntent.getAction())) { + mContentUri = mIntent.getData(); + Bundle extras = mIntent.getExtras(); + if (extras == null) { + extras = new Bundle(); + } + + // disable home button on actionbar because this activity is run from another app + final ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(false); + actionBar.setHomeButtonEnabled(false); + + mReturnBinary = extras.getBoolean(Apg.EXTRA_BINARY, false); + + if (mContentUri == null) { + mData = extras.getByteArray(Apg.EXTRA_DATA); + String data = extras.getString(Apg.EXTRA_TEXT); + if (data != null) { + Matcher matcher = Apg.PGP_MESSAGE.matcher(data); + if (matcher.matches()) { + data = matcher.group(1); + // replace non breakable spaces + data = data.replaceAll("\\xa0", " "); + mMessage.setText(data); + } else { + matcher = Apg.PGP_SIGNED_MESSAGE.matcher(data); + if (matcher.matches()) { + data = matcher.group(1); + // replace non breakable spaces + data = data.replaceAll("\\xa0", " "); + mMessage.setText(data); + mDecryptString = getString(R.string.btn_verify); + + // build new action bar + invalidateOptionsMenu(); + } + } + } + } + mReturnResult = true; + } + + if (mSource.getCurrentView().getId() == R.id.sourceMessage + && mMessage.getText().length() == 0) { + + CharSequence clipboardText = Compatibility.getClipboardText(this); + + String data = ""; + if (clipboardText != null) { + Matcher matcher = Apg.PGP_MESSAGE.matcher(clipboardText); + if (!matcher.matches()) { + matcher = Apg.PGP_SIGNED_MESSAGE.matcher(clipboardText); + } + if (matcher.matches()) { + data = matcher.group(1); + mMessage.setText(data); + Toast.makeText(this, R.string.usingClipboardContent, Toast.LENGTH_SHORT).show(); + } + } + } + + mSignatureLayout.setVisibility(View.GONE); + mSignatureLayout.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + if (mSignatureKeyId == 0) { + return; + } + PGPPublicKeyRing key = Apg.getPublicKeyRing(mSignatureKeyId); + if (key != null) { + Intent intent = new Intent(DecryptActivity.this, KeyServerQueryActivity.class); + intent.setAction(Apg.Intent.LOOK_UP_KEY_ID); + intent.putExtra(Apg.EXTRA_KEY_ID, mSignatureKeyId); + startActivity(intent); + } + } + }); + + mReplyEnabled = false; + + // build new actionbar + invalidateOptionsMenu(); + + if (mReturnResult) { + mSourcePrevious.setClickable(false); + mSourcePrevious.setEnabled(false); + mSourcePrevious.setVisibility(View.INVISIBLE); + + mSourceNext.setClickable(false); + mSourceNext.setEnabled(false); + mSourceNext.setVisibility(View.INVISIBLE); + + mSourceLabel.setClickable(false); + mSourceLabel.setEnabled(false); + } + + updateSource(); + + if (mSource.getCurrentView().getId() == R.id.sourceMessage + && (mMessage.getText().length() > 0 || mData != null || mContentUri != null)) { + decryptClicked(); + } + } + + private void openFile() { + String filename = mFilename.getText().toString(); + + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + + intent.setData(Uri.parse("file://" + filename)); + intent.setType("*/*"); + + try { + startActivityForResult(intent, Id.request.filename); + } catch (ActivityNotFoundException e) { + // No compatible file manager was found. + Toast.makeText(this, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show(); + } + } + + private void guessOutputFilename() { + mInputFilename = mFilename.getText().toString(); + File file = new File(mInputFilename); + String filename = file.getName(); + if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) { + filename = filename.substring(0, filename.length() - 4); + } + mOutputFilename = Constants.path.APP_DIR + "/" + filename; + } + + private void updateSource() { + switch (mSource.getCurrentView().getId()) { + case R.id.sourceFile: { + mSourceLabel.setText(R.string.label_file); + mDecryptString = getString(R.string.btn_decrypt); + + // build new action bar + invalidateOptionsMenu(); + break; + } + + case R.id.sourceMessage: { + mSourceLabel.setText(R.string.label_message); + mDecryptString = getString(R.string.btn_decrypt); + + // build new action bar + invalidateOptionsMenu(); + break; + } + + default: { + break; + } + } + } + + private void decryptClicked() { + if (mSource.getCurrentView().getId() == R.id.sourceFile) { + mDecryptTarget = Id.target.file; + } else { + mDecryptTarget = Id.target.message; + } + initiateDecryption(); + } + + private void initiateDecryption() { + if (mDecryptTarget == Id.target.file) { + String currentFilename = mFilename.getText().toString(); + if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { + guessOutputFilename(); + } + + if (mInputFilename.equals("")) { + Toast.makeText(this, R.string.noFileSelected, Toast.LENGTH_SHORT).show(); + return; + } + + if (mInputFilename.startsWith("file")) { + File file = new File(mInputFilename); + if (!file.exists() || !file.isFile()) { + Toast.makeText( + this, + getString(R.string.errorMessage, getString(R.string.error_fileNotFound)), + Toast.LENGTH_SHORT).show(); + return; + } + } + } + + if (mDecryptTarget == Id.target.message) { + String messageData = mMessage.getText().toString(); + Matcher matcher = Apg.PGP_SIGNED_MESSAGE.matcher(messageData); + if (matcher.matches()) { + mSignedOnly = true; + decryptStart(); + return; + } + } + + // else treat it as an decrypted message/file + mSignedOnly = false; + String error = null; + fillDataSource(); + try { + InputData in = mDataSource.getInputData(this, false); + try { + setSecretKeyId(Apg.getDecryptionKeyId(this, in)); + if (getSecretKeyId() == Id.key.none) { + throw new Apg.GeneralException(getString(R.string.error_noSecretKeyFound)); + } + mAssumeSymmetricEncryption = false; + } catch (Apg.NoAsymmetricEncryptionException e) { + setSecretKeyId(Id.key.symmetric); + in = mDataSource.getInputData(this, false); + if (!Apg.hasSymmetricEncryption(this, in)) { + throw new Apg.GeneralException(getString(R.string.error_noKnownEncryptionFound)); + } + mAssumeSymmetricEncryption = true; + } + + if (getSecretKeyId() == Id.key.symmetric + || Apg.getCachedPassPhrase(getSecretKeyId()) == null) { + showDialog(Id.dialog.pass_phrase); + } else { + if (mDecryptTarget == Id.target.file) { + askForOutputFilename(); + } else { + decryptStart(); + } + } + } catch (FileNotFoundException e) { + error = getString(R.string.error_fileNotFound); + } catch (IOException e) { + error = "" + e; + } catch (Apg.GeneralException e) { + error = "" + e; + } + if (error != null) { + Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT) + .show(); + } + } + + private void replyClicked() { + Intent intent = new Intent(this, EncryptActivity.class); + intent.setAction(Apg.Intent.ENCRYPT); + String data = mMessage.getText().toString(); + data = data.replaceAll("(?m)^", "> "); + data = "\n\n" + data; + intent.putExtra(Apg.EXTRA_TEXT, data); + intent.putExtra(Apg.EXTRA_SUBJECT, "Re: " + mSubject); + intent.putExtra(Apg.EXTRA_SEND_TO, mReplyTo); + intent.putExtra(Apg.EXTRA_SIGNATURE_KEY_ID, getSecretKeyId()); + intent.putExtra(Apg.EXTRA_ENCRYPTION_KEY_IDS, new long[] { mSignatureKeyId }); + startActivity(intent); + } + + private void askForOutputFilename() { + showDialog(Id.dialog.output_filename); + } + + @Override + public void passPhraseCallback(long keyId, String passPhrase) { + super.passPhraseCallback(keyId, passPhrase); + if (mDecryptTarget == Id.target.file) { + askForOutputFilename(); + } else { + decryptStart(); + } + } + + private void decryptStart() { + showDialog(Id.dialog.decrypting); + startThread(); + } + + @Override + public void run() { + String error = null; + Security.addProvider(new BouncyCastleProvider()); + + Bundle data = new Bundle(); + Message msg = new Message(); + fillDataSource(); + fillDataDestination(); + try { + InputData in = mDataSource.getInputData(this, true); + OutputStream out = mDataDestination.getOutputStream(this); + + if (mSignedOnly) { + data = Apg.verifyText(this, in, out, this); + } else { + data = Apg.decrypt(this, in, out, Apg.getCachedPassPhrase(getSecretKeyId()), this, + mAssumeSymmetricEncryption); + } + + out.close(); + + if (mDataDestination.getStreamFilename() != null) { + data.putString(Apg.EXTRA_RESULT_URI, "content://" + DataProvider.AUTHORITY + + "/data/" + mDataDestination.getStreamFilename()); + } else if (mDecryptTarget == Id.target.message) { + if (mReturnBinary) { + data.putByteArray(Apg.EXTRA_DECRYPTED_DATA, + ((ByteArrayOutputStream) out).toByteArray()); + } else { + data.putString(Apg.EXTRA_DECRYPTED_MESSAGE, new String( + ((ByteArrayOutputStream) out).toByteArray())); + } + } + } catch (PGPException e) { + error = "" + e; + } catch (IOException e) { + error = "" + e; + } catch (SignatureException e) { + error = "" + e; + } catch (Apg.GeneralException e) { + error = "" + e; + } + + data.putInt(Constants.extras.STATUS, Id.message.done); + + if (error != null) { + data.putString(Apg.EXTRA_ERROR, error); + } + + msg.setData(data); + sendMessage(msg); + } + + @Override + public void handlerCallback(Message msg) { + Bundle data = msg.getData(); + if (data == null) { + return; + } + + if (data.getInt(Constants.extras.STATUS) == Id.message.unknown_signature_key) { + mUnknownSignatureKeyId = data.getLong(Constants.extras.KEY_ID); + showDialog(Id.dialog.lookup_unknown_key); + return; + } + + super.handlerCallback(msg); + } + + @Override + public void doneCallback(Message msg) { + super.doneCallback(msg); + + Bundle data = msg.getData(); + removeDialog(Id.dialog.decrypting); + mSignatureKeyId = 0; + mSignatureLayout.setVisibility(View.GONE); + mReplyEnabled = false; + + // build new action bar + invalidateOptionsMenu(); + + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT) + .show(); + return; + } + + Toast.makeText(this, R.string.decryptionSuccessful, Toast.LENGTH_SHORT).show(); + if (mReturnResult) { + Intent intent = new Intent(); + intent.putExtras(data); + setResult(RESULT_OK, intent); + finish(); + return; + } + + switch (mDecryptTarget) { + case Id.target.message: { + String decryptedMessage = data.getString(Apg.EXTRA_DECRYPTED_MESSAGE); + mMessage.setText(decryptedMessage); + mMessage.setHorizontallyScrolling(false); + mReplyEnabled = false; + + // build new action bar + invalidateOptionsMenu(); + break; + } + + case Id.target.file: { + if (mDeleteAfter.isChecked()) { + setDeleteFile(mInputFilename); + showDialog(Id.dialog.delete_file); + } + break; + } + + default: { + // shouldn't happen + break; + } + } + + if (data.getBoolean(Apg.EXTRA_SIGNATURE)) { + String userId = data.getString(Apg.EXTRA_SIGNATURE_USER_ID); + mSignatureKeyId = data.getLong(Apg.EXTRA_SIGNATURE_KEY_ID); + mUserIdRest.setText("id: " + Apg.getSmallFingerPrint(mSignatureKeyId)); + if (userId == null) { + userId = getResources().getString(R.string.unknownUserId); + } + String chunks[] = userId.split(" <", 2); + userId = chunks[0]; + if (chunks.length > 1) { + mUserIdRest.setText("<" + chunks[1]); + } + mUserId.setText(userId); + + if (data.getBoolean(Apg.EXTRA_SIGNATURE_SUCCESS)) { + mSignatureStatusImage.setImageResource(R.drawable.overlay_ok); + } else if (data.getBoolean(Apg.EXTRA_SIGNATURE_UNKNOWN)) { + mSignatureStatusImage.setImageResource(R.drawable.overlay_error); + Toast.makeText(this, R.string.unknownSignatureKeyTouchToLookUp, Toast.LENGTH_LONG) + .show(); + } else { + mSignatureStatusImage.setImageResource(R.drawable.overlay_error); + } + mSignatureLayout.setVisibility(View.VISIBLE); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case Id.request.filename: { + if (resultCode == RESULT_OK && data != null) { + String filename = data.getDataString(); + if (filename != null) { + // Get rid of URI prefix: + if (filename.startsWith("file://")) { + filename = filename.substring(7); + } + // replace %20 and so on + filename = Uri.decode(filename); + + mFilename.setText(filename); + } + } + return; + } + + case Id.request.output_filename: { + if (resultCode == RESULT_OK && data != null) { + String filename = data.getDataString(); + if (filename != null) { + // Get rid of URI prefix: + if (filename.startsWith("file://")) { + filename = filename.substring(7); + } + // replace %20 and so on + filename = Uri.decode(filename); + + FileDialog.setFilename(filename); + } + } + return; + } + + case Id.request.look_up_key_id: { + PausableThread thread = getRunningThread(); + if (thread != null && thread.isPaused()) { + thread.unpause(); + } + return; + } + + default: { + break; + } + } + + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case Id.dialog.output_filename: { + return FileDialog.build(this, getString(R.string.title_decryptToFile), + getString(R.string.specifyFileToDecryptTo), mOutputFilename, + new FileDialog.OnClickListener() { + public void onOkClick(String filename, boolean checked) { + removeDialog(Id.dialog.output_filename); + mOutputFilename = filename; + decryptStart(); + } + + public void onCancelClick() { + removeDialog(Id.dialog.output_filename); + } + }, getString(R.string.filemanager_titleSave), + getString(R.string.filemanager_btnSave), null, Id.request.output_filename); + } + + case Id.dialog.lookup_unknown_key: { + AlertDialog.Builder alert = new AlertDialog.Builder(this); + + alert.setIcon(android.R.drawable.ic_dialog_alert); + alert.setTitle(R.string.title_unknownSignatureKey); + alert.setMessage(getString(R.string.lookupUnknownKey, + Apg.getSmallFingerPrint(mUnknownSignatureKeyId))); + + alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeDialog(Id.dialog.lookup_unknown_key); + Intent intent = new Intent(DecryptActivity.this, KeyServerQueryActivity.class); + intent.setAction(Apg.Intent.LOOK_UP_KEY_ID); + intent.putExtra(Apg.EXTRA_KEY_ID, mUnknownSignatureKeyId); + startActivityForResult(intent, Id.request.look_up_key_id); + } + }); + alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeDialog(Id.dialog.lookup_unknown_key); + PausableThread thread = getRunningThread(); + if (thread != null && thread.isPaused()) { + thread.unpause(); + } + } + }); + alert.setCancelable(true); + + return alert.create(); + } + + default: { + break; + } + } + + return super.onCreateDialog(id); + } + + protected void fillDataSource() { + mDataSource = new DataSource(); + if (mContentUri != null) { + mDataSource.setUri(mContentUri); + } else if (mDecryptTarget == Id.target.file) { + mDataSource.setUri(mInputFilename); + } else { + if (mData != null) { + mDataSource.setData(mData); + } else { + mDataSource.setText(mMessage.getText().toString()); + } + } + } + + protected void fillDataDestination() { + mDataDestination = new DataDestination(); + if (mContentUri != null) { + mDataDestination.setMode(Id.mode.stream); + } else if (mDecryptTarget == Id.target.file) { + mDataDestination.setFilename(mOutputFilename); + mDataDestination.setMode(Id.mode.file); + } else { + mDataDestination.setMode(Id.mode.byte_array); + } + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/EditKeyActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/EditKeyActivity.java new file mode 100644 index 000000000..e5f4f634d --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/EditKeyActivity.java @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2012 Dominik Schürmann + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Constants; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.provider.Database; +import org.thialfihar.android.apg.ui.widget.KeyEditor; +import org.thialfihar.android.apg.ui.widget.SectionView; +import org.thialfihar.android.apg.util.IterableIterator; +import org.thialfihar.android.apg.R; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Toast; +import android.widget.CompoundButton.OnCheckedChangeListener; + +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SignatureException; +import java.util.Vector; + +public class EditKeyActivity extends BaseActivity { + private Intent mIntent = null; + private ActionBar mActionBar; + + private PGPSecretKeyRing mKeyRing = null; + + private SectionView mUserIds; + private SectionView mKeys; + + private String mCurrentPassPhrase = null; + private String mNewPassPhrase = null; + + private Button mChangePassPhrase; + + private CheckBox mNoPassphrase; + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(1, Id.menu.option.cancel, 0, R.string.btn_doNotSave).setShowAsAction( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + menu.add(1, Id.menu.option.save, 1, R.string.btn_save).setShowAsAction( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case android.R.id.home: + startActivity(new Intent(this, SecretKeyListActivity.class)); + return true; + + case Id.menu.option.save: + saveClicked(); + return true; + + case Id.menu.option.cancel: + finish(); + return true; + + default: + break; + + } + return false; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.edit_key); + + mActionBar = getSupportActionBar(); + + // find views + mChangePassPhrase = (Button) findViewById(R.id.edit_key_btn_change_pass_phrase); + mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase); + + Vector userIds = new Vector(); + Vector keys = new Vector(); + Vector keysUsages = new Vector(); + + // Catch Intents opened from other apps + mIntent = getIntent(); + Bundle extras = mIntent.getExtras(); + if (Apg.Intent.CREATE_KEY.equals(mIntent.getAction())) { + + mActionBar.setTitle(R.string.title_createKey); + + mCurrentPassPhrase = ""; + + if (extras != null) { + + // disable home button on actionbar because this activity is run from another app + mActionBar.setDisplayShowTitleEnabled(true); + mActionBar.setDisplayHomeAsUpEnabled(false); + mActionBar.setHomeButtonEnabled(false); + + // if userId is given, prefill the fields + if (extras.containsKey(Apg.EXTRA_USER_IDS)) { + Log.d(Constants.TAG, "UserIds are given!"); + userIds.add(extras.getString(Apg.EXTRA_USER_IDS)); + } + + // if no passphrase is given + if (extras.containsKey(Apg.EXTRA_NO_PASSPHRASE)) { + boolean noPassphrase = extras.getBoolean(Apg.EXTRA_NO_PASSPHRASE); + if (noPassphrase) { + // check "no passphrase" checkbox and remove button + mNoPassphrase.setChecked(true); + mChangePassPhrase.setVisibility(View.GONE); + } + } + + // generate key + if (extras.containsKey(Apg.EXTRA_GENERATE_DEFAULT_KEYS)) { + boolean generateDefaultKeys = extras + .getBoolean(Apg.EXTRA_GENERATE_DEFAULT_KEYS); + if (generateDefaultKeys) { + + // generate a RSA 2048 key for encryption and signing! + try { + PGPSecretKey masterKey = Apg.createKey(this, Id.choice.algorithm.rsa, + 2048, mCurrentPassPhrase, null); + + // add new masterKey to keys array, which is then added to view + keys.add(masterKey); + keysUsages.add(Id.choice.usage.sign_only); + + PGPSecretKey subKey = Apg.createKey(this, Id.choice.algorithm.rsa, + 2048, mCurrentPassPhrase, masterKey); + + keys.add(subKey); + keysUsages.add(Id.choice.usage.encrypt_only); + } catch (Exception e) { + Log.e(Constants.TAG, "Creating initial key failed: +" + e); + } + } + + } + } + } else if (Apg.Intent.EDIT_KEY.equals(mIntent.getAction())) { + + mActionBar.setTitle(R.string.title_editKey); + + mCurrentPassPhrase = Apg.getEditPassPhrase(); + if (mCurrentPassPhrase == null) { + mCurrentPassPhrase = ""; + } + + if (mCurrentPassPhrase.equals("")) { + // check "no passphrase" checkbox and remove button + mNoPassphrase.setChecked(true); + mChangePassPhrase.setVisibility(View.GONE); + } + + if (extras != null) { + + if (extras.containsKey(Apg.EXTRA_KEY_ID)) { + long keyId = mIntent.getExtras().getLong(Apg.EXTRA_KEY_ID); + + if (keyId != 0) { + PGPSecretKey masterKey = null; + mKeyRing = Apg.getSecretKeyRing(keyId); + if (mKeyRing != null) { + masterKey = Apg.getMasterKey(mKeyRing); + for (PGPSecretKey key : new IterableIterator( + mKeyRing.getSecretKeys())) { + keys.add(key); + keysUsages.add(-1); // get usage when view is created + } + } + if (masterKey != null) { + for (String userId : new IterableIterator( + masterKey.getUserIDs())) { + userIds.add(userId); + } + } + } + } + } + } + + mChangePassPhrase.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + showDialog(Id.dialog.new_pass_phrase); + } + }); + + // disable passphrase when no passphrase checkobox is checked! + mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + // remove passphrase + mNewPassPhrase = null; + + mChangePassPhrase.setVisibility(View.GONE); + } else { + mChangePassPhrase.setVisibility(View.VISIBLE); + } + + } + }); + + // Build layout based on given userIds and keys + LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + LinearLayout container = (LinearLayout) findViewById(R.id.edit_key_container); + mUserIds = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false); + mUserIds.setType(Id.type.user_id); + mUserIds.setUserIds(userIds); + container.addView(mUserIds); + mKeys = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false); + mKeys.setType(Id.type.key); + mKeys.setKeys(keys, keysUsages); + container.addView(mKeys); + + updatePassPhraseButtonText(); + } + + private long getMasterKeyId() { + if (mKeys.getEditors().getChildCount() == 0) { + return 0; + } + return ((KeyEditor) mKeys.getEditors().getChildAt(0)).getValue().getKeyID(); + } + + public boolean isPassphraseSet() { + if (mNoPassphrase.isChecked()) { + return true; + } else if ((!mCurrentPassPhrase.equals("")) + || (mNewPassPhrase != null && !mNewPassPhrase.equals(""))) { + return true; + } else { + return false; + } + } + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case Id.dialog.new_pass_phrase: { + AlertDialog.Builder alert = new AlertDialog.Builder(this); + + if (isPassphraseSet()) { + alert.setTitle(R.string.title_changePassPhrase); + } else { + alert.setTitle(R.string.title_setPassPhrase); + } + alert.setMessage(R.string.enterPassPhraseTwice); + + LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View view = inflater.inflate(R.layout.passphrase, null); + final EditText input1 = (EditText) view.findViewById(R.id.passphrase_passphrase); + final EditText input2 = (EditText) view.findViewById(R.id.passphrase_passphrase_again); + + alert.setView(view); + + alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeDialog(Id.dialog.new_pass_phrase); + + String passPhrase1 = "" + input1.getText(); + String passPhrase2 = "" + input2.getText(); + if (!passPhrase1.equals(passPhrase2)) { + showDialog(Id.dialog.pass_phrases_do_not_match); + return; + } + + if (passPhrase1.equals("")) { + showDialog(Id.dialog.no_pass_phrase); + return; + } + + mNewPassPhrase = passPhrase1; + updatePassPhraseButtonText(); + } + }); + + alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeDialog(Id.dialog.new_pass_phrase); + } + }); + + return alert.create(); + } + + default: { + return super.onCreateDialog(id); + } + } + } + + private void saveClicked() { + if (!isPassphraseSet()) { + Toast.makeText(this, R.string.setAPassPhrase, Toast.LENGTH_SHORT).show(); + return; + } + showDialog(Id.dialog.saving); + startThread(); + } + + @Override + public void run() { + String error = null; + Bundle data = new Bundle(); + Message msg = new Message(); + + try { + String oldPassPhrase = mCurrentPassPhrase; + String newPassPhrase = mNewPassPhrase; + if (newPassPhrase == null) { + newPassPhrase = oldPassPhrase; + } + Apg.buildSecretKey(this, mUserIds, mKeys, oldPassPhrase, newPassPhrase, this); + Apg.setCachedPassPhrase(getMasterKeyId(), newPassPhrase); + } catch (NoSuchProviderException e) { + error = "" + e; + } catch (NoSuchAlgorithmException e) { + error = "" + e; + } catch (PGPException e) { + error = "" + e; + } catch (SignatureException e) { + error = "" + e; + } catch (Apg.GeneralException e) { + error = "" + e; + } catch (Database.GeneralException e) { + error = "" + e; + } catch (IOException e) { + error = "" + e; + } + + data.putInt(Constants.extras.STATUS, Id.message.done); + + if (error != null) { + data.putString(Apg.EXTRA_ERROR, error); + } + + msg.setData(data); + sendMessage(msg); + } + + @Override + public void doneCallback(Message msg) { + super.doneCallback(msg); + + Bundle data = msg.getData(); + removeDialog(Id.dialog.saving); + + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(EditKeyActivity.this, getString(R.string.errorMessage, error), + Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(EditKeyActivity.this, R.string.keySaved, Toast.LENGTH_SHORT).show(); + setResult(RESULT_OK); + finish(); + } + } + + private void updatePassPhraseButtonText() { + mChangePassPhrase.setText(isPassphraseSet() ? R.string.btn_changePassPhrase + : R.string.btn_setPassPhrase); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/EncryptActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/EncryptActivity.java new file mode 100644 index 000000000..a6a4efb18 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/EncryptActivity.java @@ -0,0 +1,1042 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Constants; +import org.thialfihar.android.apg.DataDestination; +import org.thialfihar.android.apg.DataSource; +import org.thialfihar.android.apg.FileDialog; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.InputData; +import org.thialfihar.android.apg.provider.DataProvider; +import org.thialfihar.android.apg.util.Choice; +import org.thialfihar.android.apg.util.Compatibility; +import org.thialfihar.android.apg.R; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; + +import android.app.Dialog; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Message; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.animation.AnimationUtils; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.ViewFlipper; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SignatureException; +import java.util.Vector; + +public class EncryptActivity extends BaseActivity { + private Intent mIntent = null; + private String mSubject = null; + private String mSendTo = null; + + private long mEncryptionKeyIds[] = null; + + private boolean mReturnResult = false; + private EditText mMessage = null; + private Button mSelectKeysButton = null; + + private boolean mEncryptEnabled = false; + private String mEncryptString = ""; + private boolean mEncryptToClipboardEnabled = false; + private String mEncryptToClipboardString = ""; + + private CheckBox mSign = null; + private TextView mMainUserId = null; + private TextView mMainUserIdRest = null; + + private ViewFlipper mSource = null; + private TextView mSourceLabel = null; + private ImageView mSourcePrevious = null; + private ImageView mSourceNext = null; + + private ViewFlipper mMode = null; + private TextView mModeLabel = null; + private ImageView mModePrevious = null; + private ImageView mModeNext = null; + + private int mEncryptTarget; + + private EditText mPassPhrase = null; + private EditText mPassPhraseAgain = null; + private CheckBox mAsciiArmour = null; + private Spinner mFileCompression = null; + + private EditText mFilename = null; + private CheckBox mDeleteAfter = null; + private ImageButton mBrowse = null; + + private String mInputFilename = null; + private String mOutputFilename = null; + + private boolean mAsciiArmourDemand = false; + private boolean mOverrideAsciiArmour = false; + private Uri mContentUri = null; + private byte[] mData = null; + + private DataSource mDataSource = null; + private DataDestination mDataDestination = null; + + private boolean mGenerateSignature = false; + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + + if (mEncryptToClipboardEnabled) { + menu.add(1, Id.menu.option.encrypt_to_clipboard, 0, mEncryptToClipboardString) + .setShowAsAction( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + if (mEncryptEnabled) { + menu.add(1, Id.menu.option.encrypt, 1, mEncryptString).setShowAsAction( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case Id.menu.option.encrypt_to_clipboard: { + encryptToClipboardClicked(); + + return true; + } + case Id.menu.option.encrypt: { + encryptClicked(); + + return true; + } + + default: { + return super.onOptionsItemSelected(item); + } + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.encrypt); + + mGenerateSignature = false; + + mSource = (ViewFlipper) findViewById(R.id.source); + mSourceLabel = (TextView) findViewById(R.id.sourceLabel); + mSourcePrevious = (ImageView) findViewById(R.id.sourcePrevious); + mSourceNext = (ImageView) findViewById(R.id.sourceNext); + + mSourcePrevious.setClickable(true); + mSourcePrevious.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mSource.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, + R.anim.push_right_in)); + mSource.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, + R.anim.push_right_out)); + mSource.showPrevious(); + updateSource(); + } + }); + + mSourceNext.setClickable(true); + OnClickListener nextSourceClickListener = new OnClickListener() { + public void onClick(View v) { + mSource.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, + R.anim.push_left_in)); + mSource.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, + R.anim.push_left_out)); + mSource.showNext(); + updateSource(); + } + }; + mSourceNext.setOnClickListener(nextSourceClickListener); + + mSourceLabel.setClickable(true); + mSourceLabel.setOnClickListener(nextSourceClickListener); + + mMode = (ViewFlipper) findViewById(R.id.mode); + mModeLabel = (TextView) findViewById(R.id.modeLabel); + mModePrevious = (ImageView) findViewById(R.id.modePrevious); + mModeNext = (ImageView) findViewById(R.id.modeNext); + + mModePrevious.setClickable(true); + mModePrevious.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mMode.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, + R.anim.push_right_in)); + mMode.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, + R.anim.push_right_out)); + mMode.showPrevious(); + updateMode(); + } + }); + + OnClickListener nextModeClickListener = new OnClickListener() { + public void onClick(View v) { + mMode.setInAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, + R.anim.push_left_in)); + mMode.setOutAnimation(AnimationUtils.loadAnimation(EncryptActivity.this, + R.anim.push_left_out)); + mMode.showNext(); + updateMode(); + } + }; + mModeNext.setOnClickListener(nextModeClickListener); + + mModeLabel.setClickable(true); + mModeLabel.setOnClickListener(nextModeClickListener); + + mMessage = (EditText) findViewById(R.id.message); + mSelectKeysButton = (Button) findViewById(R.id.btn_selectEncryptKeys); + mSign = (CheckBox) findViewById(R.id.sign); + mMainUserId = (TextView) findViewById(R.id.mainUserId); + mMainUserIdRest = (TextView) findViewById(R.id.mainUserIdRest); + + mPassPhrase = (EditText) findViewById(R.id.passPhrase); + mPassPhraseAgain = (EditText) findViewById(R.id.passPhraseAgain); + + // measure the height of the source_file view and set the message view's min height to that, + // so it fills mSource fully... bit of a hack. + View tmp = findViewById(R.id.sourceFile); + tmp.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + int height = tmp.getMeasuredHeight(); + mMessage.setMinimumHeight(height); + + mFilename = (EditText) findViewById(R.id.filename); + mBrowse = (ImageButton) findViewById(R.id.btn_browse); + mBrowse.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + openFile(); + } + }); + + mFileCompression = (Spinner) findViewById(R.id.fileCompression); + Choice[] choices = new Choice[] { + new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " (" + + getString(R.string.fast) + ")"), + new Choice(Id.choice.compression.zip, "ZIP (" + getString(R.string.fast) + ")"), + new Choice(Id.choice.compression.zlib, "ZLIB (" + getString(R.string.fast) + ")"), + new Choice(Id.choice.compression.bzip2, "BZIP2 (" + getString(R.string.very_slow) + + ")"), }; + ArrayAdapter adapter = new ArrayAdapter(this, + android.R.layout.simple_spinner_item, choices); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mFileCompression.setAdapter(adapter); + + int defaultFileCompression = mPreferences.getDefaultFileCompression(); + for (int i = 0; i < choices.length; ++i) { + if (choices[i].getId() == defaultFileCompression) { + mFileCompression.setSelection(i); + break; + } + } + + mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterEncryption); + + mAsciiArmour = (CheckBox) findViewById(R.id.asciiArmour); + mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour()); + mAsciiArmour.setOnClickListener(new OnClickListener() { + public void onClick(View view) { + guessOutputFilename(); + } + }); + + mSelectKeysButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + selectPublicKeys(); + } + }); + + mSign.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + CheckBox checkBox = (CheckBox) v; + if (checkBox.isChecked()) { + selectSecretKey(); + } else { + setSecretKeyId(Id.key.none); + updateView(); + } + } + }); + + mIntent = getIntent(); + if (Apg.Intent.ENCRYPT.equals(mIntent.getAction()) + || Apg.Intent.ENCRYPT_FILE.equals(mIntent.getAction()) + || Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction()) + || Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) { + mContentUri = mIntent.getData(); + Bundle extras = mIntent.getExtras(); + if (extras == null) { + extras = new Bundle(); + } + + // disable home button on actionbar because this activity is run from another app + if (Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction())) { + final ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(false); + actionBar.setHomeButtonEnabled(false); + } + + if (Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction()) + || Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) { + mReturnResult = true; + } + + if (Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) { + mGenerateSignature = true; + mOverrideAsciiArmour = true; + mAsciiArmourDemand = false; + } + + if (extras.containsKey(Apg.EXTRA_ASCII_ARMOUR)) { + mAsciiArmourDemand = extras.getBoolean(Apg.EXTRA_ASCII_ARMOUR, true); + mOverrideAsciiArmour = true; + mAsciiArmour.setChecked(mAsciiArmourDemand); + } + + mData = extras.getByteArray(Apg.EXTRA_DATA); + String textData = null; + if (mData == null) { + textData = extras.getString(Apg.EXTRA_TEXT); + } + mSendTo = extras.getString(Apg.EXTRA_SEND_TO); + mSubject = extras.getString(Apg.EXTRA_SUBJECT); + long signatureKeyId = extras.getLong(Apg.EXTRA_SIGNATURE_KEY_ID); + long encryptionKeyIds[] = extras.getLongArray(Apg.EXTRA_ENCRYPTION_KEY_IDS); + if (signatureKeyId != 0) { + PGPSecretKeyRing keyRing = Apg.getSecretKeyRing(signatureKeyId); + PGPSecretKey masterKey = null; + if (keyRing != null) { + masterKey = Apg.getMasterKey(keyRing); + if (masterKey != null) { + Vector signKeys = Apg.getUsableSigningKeys(keyRing); + if (signKeys.size() > 0) { + setSecretKeyId(masterKey.getKeyID()); + } + } + } + } + + if (encryptionKeyIds != null) { + Vector goodIds = new Vector(); + for (int i = 0; i < encryptionKeyIds.length; ++i) { + PGPPublicKeyRing keyRing = Apg.getPublicKeyRing(encryptionKeyIds[i]); + PGPPublicKey masterKey = null; + if (keyRing == null) { + continue; + } + masterKey = Apg.getMasterKey(keyRing); + if (masterKey == null) { + continue; + } + Vector encryptKeys = Apg.getUsableEncryptKeys(keyRing); + if (encryptKeys.size() == 0) { + continue; + } + goodIds.add(masterKey.getKeyID()); + } + if (goodIds.size() > 0) { + mEncryptionKeyIds = new long[goodIds.size()]; + for (int i = 0; i < goodIds.size(); ++i) { + mEncryptionKeyIds[i] = goodIds.get(i); + } + } + } + + if (Apg.Intent.ENCRYPT.equals(mIntent.getAction()) + || Apg.Intent.ENCRYPT_AND_RETURN.equals(mIntent.getAction()) + || Apg.Intent.GENERATE_SIGNATURE.equals(mIntent.getAction())) { + if (textData != null) { + mMessage.setText(textData); + } + mSource.setInAnimation(null); + mSource.setOutAnimation(null); + while (mSource.getCurrentView().getId() != R.id.sourceMessage) { + mSource.showNext(); + } + } else if (Apg.Intent.ENCRYPT_FILE.equals(mIntent.getAction())) { + if ("file".equals(mIntent.getScheme())) { + mInputFilename = Uri.decode(mIntent.getDataString().replace("file://", "")); + mFilename.setText(mInputFilename); + guessOutputFilename(); + } + mSource.setInAnimation(null); + mSource.setOutAnimation(null); + while (mSource.getCurrentView().getId() != R.id.sourceFile) { + mSource.showNext(); + } + } + } + + updateView(); + updateSource(); + updateMode(); + + if (mReturnResult) { + mSourcePrevious.setClickable(false); + mSourcePrevious.setEnabled(false); + mSourcePrevious.setVisibility(View.INVISIBLE); + + mSourceNext.setClickable(false); + mSourceNext.setEnabled(false); + mSourceNext.setVisibility(View.INVISIBLE); + + mSourceLabel.setClickable(false); + mSourceLabel.setEnabled(false); + } + + updateButtons(); + + if (mReturnResult + && (mMessage.getText().length() > 0 || mData != null || mContentUri != null) + && ((mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) || getSecretKeyId() != 0)) { + encryptClicked(); + } + } + + private void openFile() { + String filename = mFilename.getText().toString(); + + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + + intent.setData(Uri.parse("file://" + filename)); + intent.setType("*/*"); + + try { + startActivityForResult(intent, Id.request.filename); + } catch (ActivityNotFoundException e) { + // No compatible file manager was found. + Toast.makeText(this, R.string.noFilemanagerInstalled, Toast.LENGTH_SHORT).show(); + } + } + + private void guessOutputFilename() { + mInputFilename = mFilename.getText().toString(); + File file = new File(mInputFilename); + String ending = (mAsciiArmour.isChecked() ? ".asc" : ".gpg"); + mOutputFilename = Constants.path.APP_DIR + "/" + file.getName() + ending; + } + + private void updateSource() { + switch (mSource.getCurrentView().getId()) { + case R.id.sourceFile: { + mSourceLabel.setText(R.string.label_file); + break; + } + + case R.id.sourceMessage: { + mSourceLabel.setText(R.string.label_message); + break; + } + + default: { + break; + } + } + updateButtons(); + } + + private void updateButtons() { + switch (mSource.getCurrentView().getId()) { + case R.id.sourceFile: { + mEncryptEnabled = true; + mEncryptToClipboardEnabled = false; + + break; + } + + case R.id.sourceMessage: { + mSourceLabel.setText(R.string.label_message); + if (mReturnResult) { + mEncryptToClipboardEnabled = false; + } else { + mEncryptToClipboardEnabled = true; + } + if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { + if (mReturnResult) { + mEncryptString = getString(R.string.btn_encrypt); + } else { + mEncryptString = getString(R.string.btn_encryptAndEmail); + } + mEncryptEnabled = true; + mEncryptToClipboardString = getString(R.string.btn_encryptToClipboard); + mEncryptToClipboardEnabled = true; + } else { + if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) { + if (getSecretKeyId() == 0) { + if (mReturnResult) { + mEncryptString = getString(R.string.btn_encrypt); + } else { + mEncryptString = getString(R.string.btn_encryptAndEmail); + } + mEncryptEnabled = false; + mEncryptToClipboardString = getString(R.string.btn_encryptToClipboard); + mEncryptToClipboardEnabled = false; + } else { + if (mReturnResult) { + mEncryptString = getString(R.string.btn_sign); + } else { + mEncryptString = getString(R.string.btn_signAndEmail); + } + + mEncryptEnabled = true; + mEncryptToClipboardString = getString(R.string.btn_signToClipboard); + mEncryptToClipboardEnabled = true; + } + } else { + if (mReturnResult) { + mEncryptString = getString(R.string.btn_encrypt); + } else { + mEncryptString = getString(R.string.btn_encryptAndEmail); + } + + mEncryptEnabled = true; + mEncryptToClipboardString = getString(R.string.btn_encryptToClipboard); + mEncryptToClipboardEnabled = true; + } + } + break; + } + + default: { + break; + } + } + + // build new action bar + invalidateOptionsMenu(); + } + + private void updateMode() { + switch (mMode.getCurrentView().getId()) { + case R.id.modeAsymmetric: { + mModeLabel.setText(R.string.label_asymmetric); + break; + } + + case R.id.modeSymmetric: { + mModeLabel.setText(R.string.label_symmetric); + break; + } + + default: { + break; + } + } + updateButtons(); + } + + private void encryptToClipboardClicked() { + mEncryptTarget = Id.target.clipboard; + initiateEncryption(); + } + + private void encryptClicked() { + if (mSource.getCurrentView().getId() == R.id.sourceFile) { + mEncryptTarget = Id.target.file; + } else { + mEncryptTarget = Id.target.email; + } + initiateEncryption(); + } + + private void initiateEncryption() { + if (mEncryptTarget == Id.target.file) { + String currentFilename = mFilename.getText().toString(); + if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { + guessOutputFilename(); + } + + if (mInputFilename.equals("")) { + Toast.makeText(this, R.string.noFileSelected, Toast.LENGTH_SHORT).show(); + return; + } + + if (!mInputFilename.startsWith("content")) { + File file = new File(mInputFilename); + if (!file.exists() || !file.isFile()) { + Toast.makeText( + this, + getString(R.string.errorMessage, getString(R.string.error_fileNotFound)), + Toast.LENGTH_SHORT).show(); + return; + } + } + } + + // symmetric encryption + if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { + boolean gotPassPhrase = false; + String passPhrase = mPassPhrase.getText().toString(); + String passPhraseAgain = mPassPhraseAgain.getText().toString(); + if (!passPhrase.equals(passPhraseAgain)) { + Toast.makeText(this, R.string.passPhrasesDoNotMatch, Toast.LENGTH_SHORT).show(); + return; + } + + gotPassPhrase = (passPhrase.length() != 0); + if (!gotPassPhrase) { + Toast.makeText(this, R.string.passPhraseMustNotBeEmpty, Toast.LENGTH_SHORT).show(); + return; + } + } else { + boolean encryptIt = (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0); + // for now require at least one form of encryption for files + if (!encryptIt && mEncryptTarget == Id.target.file) { + Toast.makeText(this, R.string.selectEncryptionKey, Toast.LENGTH_SHORT).show(); + return; + } + + if (!encryptIt && getSecretKeyId() == 0) { + Toast.makeText(this, R.string.selectEncryptionOrSignatureKey, Toast.LENGTH_SHORT) + .show(); + return; + } + + if (getSecretKeyId() != 0 && Apg.getCachedPassPhrase(getSecretKeyId()) == null) { + showDialog(Id.dialog.pass_phrase); + return; + } + } + + if (mEncryptTarget == Id.target.file) { + askForOutputFilename(); + } else { + encryptStart(); + } + } + + private void askForOutputFilename() { + showDialog(Id.dialog.output_filename); + } + + @Override + public void passPhraseCallback(long keyId, String passPhrase) { + super.passPhraseCallback(keyId, passPhrase); + if (mEncryptTarget == Id.target.file) { + askForOutputFilename(); + } else { + encryptStart(); + } + } + + private void encryptStart() { + showDialog(Id.dialog.encrypting); + startThread(); + } + + @Override + public void run() { + String error = null; + Bundle data = new Bundle(); + Message msg = new Message(); + + try { + InputData in; + OutputStream out; + boolean useAsciiArmour = true; + long encryptionKeyIds[] = null; + long signatureKeyId = 0; + int compressionId = 0; + boolean signOnly = false; + + String passPhrase = null; + if (mMode.getCurrentView().getId() == R.id.modeSymmetric) { + passPhrase = mPassPhrase.getText().toString(); + if (passPhrase.length() == 0) { + passPhrase = null; + } + } else { + encryptionKeyIds = mEncryptionKeyIds; + signatureKeyId = getSecretKeyId(); + signOnly = (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0); + } + + fillDataSource(signOnly && !mReturnResult); + fillDataDestination(); + + // streams + in = mDataSource.getInputData(this, true); + out = mDataDestination.getOutputStream(this); + + if (mEncryptTarget == Id.target.file) { + useAsciiArmour = mAsciiArmour.isChecked(); + compressionId = ((Choice) mFileCompression.getSelectedItem()).getId(); + } else { + useAsciiArmour = true; + compressionId = mPreferences.getDefaultMessageCompression(); + } + + if (mOverrideAsciiArmour) { + useAsciiArmour = mAsciiArmourDemand; + } + + if (mGenerateSignature) { + Apg.generateSignature(this, in, out, useAsciiArmour, mDataSource.isBinary(), + getSecretKeyId(), Apg.getCachedPassPhrase(getSecretKeyId()), + mPreferences.getDefaultHashAlgorithm(), + mPreferences.getForceV3Signatures(), this); + } else if (signOnly) { + Apg.signText(this, in, out, getSecretKeyId(), + Apg.getCachedPassPhrase(getSecretKeyId()), + mPreferences.getDefaultHashAlgorithm(), + mPreferences.getForceV3Signatures(), this); + } else { + Apg.encrypt(this, in, out, useAsciiArmour, encryptionKeyIds, signatureKeyId, + Apg.getCachedPassPhrase(signatureKeyId), this, + mPreferences.getDefaultEncryptionAlgorithm(), + mPreferences.getDefaultHashAlgorithm(), compressionId, + mPreferences.getForceV3Signatures(), passPhrase); + } + + out.close(); + if (mEncryptTarget != Id.target.file) { + + if (out instanceof ByteArrayOutputStream) { + if (useAsciiArmour) { + String extraData = new String(((ByteArrayOutputStream) out).toByteArray()); + if (mGenerateSignature) { + data.putString(Apg.EXTRA_SIGNATURE_TEXT, extraData); + } else { + data.putString(Apg.EXTRA_ENCRYPTED_MESSAGE, extraData); + } + } else { + byte extraData[] = ((ByteArrayOutputStream) out).toByteArray(); + if (mGenerateSignature) { + data.putByteArray(Apg.EXTRA_SIGNATURE_DATA, extraData); + } else { + data.putByteArray(Apg.EXTRA_ENCRYPTED_DATA, extraData); + } + } + } else if (out instanceof FileOutputStream) { + String fileName = mDataDestination.getStreamFilename(); + String uri = "content://" + DataProvider.AUTHORITY + "/data/" + fileName; + data.putString(Apg.EXTRA_RESULT_URI, uri); + } else { + throw new Apg.GeneralException("No output-data found."); + } + } + } catch (IOException e) { + error = "" + e; + } catch (PGPException e) { + error = "" + e; + } catch (NoSuchProviderException e) { + error = "" + e; + } catch (NoSuchAlgorithmException e) { + error = "" + e; + } catch (SignatureException e) { + error = "" + e; + } catch (Apg.GeneralException e) { + error = "" + e; + } + + data.putInt(Constants.extras.STATUS, Id.message.done); + + if (error != null) { + data.putString(Apg.EXTRA_ERROR, error); + } + + msg.setData(data); + sendMessage(msg); + } + + private void updateView() { + if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) { + mSelectKeysButton.setText(R.string.noKeysSelected); + } else if (mEncryptionKeyIds.length == 1) { + mSelectKeysButton.setText(R.string.oneKeySelected); + } else { + mSelectKeysButton.setText("" + mEncryptionKeyIds.length + " " + + getResources().getString(R.string.nKeysSelected)); + } + + if (getSecretKeyId() == 0) { + mSign.setChecked(false); + mMainUserId.setText(""); + mMainUserIdRest.setText(""); + } else { + String uid = getResources().getString(R.string.unknownUserId); + String uidExtra = ""; + PGPSecretKeyRing keyRing = Apg.getSecretKeyRing(getSecretKeyId()); + if (keyRing != null) { + PGPSecretKey key = Apg.getMasterKey(keyRing); + if (key != null) { + String userId = Apg.getMainUserIdSafe(this, key); + String chunks[] = userId.split(" <", 2); + uid = chunks[0]; + if (chunks.length > 1) { + uidExtra = "<" + chunks[1]; + } + } + } + mMainUserId.setText(uid); + mMainUserIdRest.setText(uidExtra); + mSign.setChecked(true); + } + + updateButtons(); + } + + private void selectPublicKeys() { + Intent intent = new Intent(this, SelectPublicKeyListActivity.class); + Vector keyIds = new Vector(); + if (getSecretKeyId() != 0) { + keyIds.add(getSecretKeyId()); + } + if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) { + for (int i = 0; i < mEncryptionKeyIds.length; ++i) { + keyIds.add(mEncryptionKeyIds[i]); + } + } + long[] initialKeyIds = null; + if (keyIds.size() > 0) { + initialKeyIds = new long[keyIds.size()]; + for (int i = 0; i < keyIds.size(); ++i) { + initialKeyIds[i] = keyIds.get(i); + } + } + intent.putExtra(Apg.EXTRA_SELECTION, initialKeyIds); + startActivityForResult(intent, Id.request.public_keys); + } + + private void selectSecretKey() { + Intent intent = new Intent(this, SelectSecretKeyListActivity.class); + startActivityForResult(intent, Id.request.secret_keys); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case Id.request.filename: { + if (resultCode == RESULT_OK && data != null) { + String filename = data.getDataString(); + if (filename != null) { + // Get rid of URI prefix: + if (filename.startsWith("file://")) { + filename = filename.substring(7); + } + // replace %20 and so on + filename = Uri.decode(filename); + + mFilename.setText(filename); + } + } + return; + } + + case Id.request.output_filename: { + if (resultCode == RESULT_OK && data != null) { + String filename = data.getDataString(); + if (filename != null) { + // Get rid of URI prefix: + if (filename.startsWith("file://")) { + filename = filename.substring(7); + } + // replace %20 and so on + filename = Uri.decode(filename); + + FileDialog.setFilename(filename); + } + } + return; + } + + case Id.request.secret_keys: { + if (resultCode == RESULT_OK) { + super.onActivityResult(requestCode, resultCode, data); + } + updateView(); + break; + } + + case Id.request.public_keys: { + if (resultCode == RESULT_OK) { + Bundle bundle = data.getExtras(); + mEncryptionKeyIds = bundle.getLongArray(Apg.EXTRA_SELECTION); + } + updateView(); + break; + } + + default: { + break; + } + } + + super.onActivityResult(requestCode, resultCode, data); + } + + @Override + public void doneCallback(Message msg) { + super.doneCallback(msg); + + removeDialog(Id.dialog.encrypting); + + Bundle data = msg.getData(); + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT) + .show(); + return; + } + switch (mEncryptTarget) { + case Id.target.clipboard: { + String message = data.getString(Apg.EXTRA_ENCRYPTED_MESSAGE); + Compatibility.copyToClipboard(this, message); + Toast.makeText(this, R.string.encryptionToClipboardSuccessful, Toast.LENGTH_SHORT) + .show(); + break; + } + + case Id.target.email: { + if (mReturnResult) { + Intent intent = new Intent(); + intent.putExtras(data); + setResult(RESULT_OK, intent); + finish(); + return; + } + + String message = data.getString(Apg.EXTRA_ENCRYPTED_MESSAGE); + Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND); + emailIntent.setType("text/plain; charset=utf-8"); + emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, message); + if (mSubject != null) { + emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, mSubject); + } + if (mSendTo != null) { + emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[] { mSendTo }); + } + EncryptActivity.this.startActivity(Intent.createChooser(emailIntent, + getString(R.string.title_sendEmail))); + break; + } + + case Id.target.file: { + Toast.makeText(this, R.string.encryptionSuccessful, Toast.LENGTH_SHORT).show(); + if (mDeleteAfter.isChecked()) { + setDeleteFile(mInputFilename); + showDialog(Id.dialog.delete_file); + } + break; + } + + default: { + // shouldn't happen + break; + } + } + } + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case Id.dialog.output_filename: { + return FileDialog.build(this, getString(R.string.title_encryptToFile), + getString(R.string.specifyFileToEncryptTo), mOutputFilename, + new FileDialog.OnClickListener() { + public void onOkClick(String filename, boolean checked) { + removeDialog(Id.dialog.output_filename); + mOutputFilename = filename; + encryptStart(); + } + + public void onCancelClick() { + removeDialog(Id.dialog.output_filename); + } + }, getString(R.string.filemanager_titleSave), + getString(R.string.filemanager_btnSave), null, Id.request.output_filename); + } + + default: { + break; + } + } + + return super.onCreateDialog(id); + } + + protected void fillDataSource(boolean fixContent) { + mDataSource = new DataSource(); + if (mContentUri != null) { + mDataSource.setUri(mContentUri); + } else if (mEncryptTarget == Id.target.file) { + mDataSource.setUri(mInputFilename); + } else { + if (mData != null) { + mDataSource.setData(mData); + } else { + String message = mMessage.getText().toString(); + if (fixContent) { + // fix the message a bit, trailing spaces and newlines break stuff, + // because GMail sends as HTML and such things fuck up the + // signature, + // TODO: things like "<" and ">" also fuck up the signature + message = message.replaceAll(" +\n", "\n"); + message = message.replaceAll("\n\n+", "\n\n"); + message = message.replaceFirst("^\n+", ""); + // make sure there'll be exactly one newline at the end + message = message.replaceFirst("\n*$", "\n"); + } + mDataSource.setText(message); + } + } + } + + protected void fillDataDestination() { + mDataDestination = new DataDestination(); + if (mContentUri != null) { + mDataDestination.setMode(Id.mode.stream); + } else if (mEncryptTarget == Id.target.file) { + mDataDestination.setFilename(mOutputFilename); + mDataDestination.setMode(Id.mode.file); + } else { + mDataDestination.setMode(Id.mode.byte_array); + } + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/GeneralActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/GeneralActivity.java new file mode 100644 index 000000000..76a5dc92c --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/GeneralActivity.java @@ -0,0 +1,191 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; + +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.util.Choice; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.Toast; + +public class GeneralActivity extends BaseActivity { + private Intent mIntent; + private ArrayAdapter mAdapter; + private ListView mList; + private Button mCancelButton; + private String mDataString; + private Uri mDataUri; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.general); + + mIntent = getIntent(); + + InputStream inStream = null; + { + String data = mIntent.getStringExtra(Intent.EXTRA_TEXT); + if (data != null) { + mDataString = data; + inStream = new ByteArrayInputStream(data.getBytes()); + } + } + + if (inStream == null) { + Uri data = mIntent.getData(); + if (data != null) { + mDataUri = data; + try { + inStream = getContentResolver().openInputStream(data); + } catch (FileNotFoundException e) { + // didn't work + Toast.makeText(this, "failed to open stream", Toast.LENGTH_SHORT).show(); + } + } + } + + if (inStream == null) { + Toast.makeText(this, "no data found", Toast.LENGTH_SHORT).show(); + finish(); + return; + } + + int contentType = Id.content.unknown; + try { + contentType = Apg.getStreamContent(this, inStream); + inStream.close(); + } catch (IOException e) { + // just means that there's no PGP data in there + } + + mList = (ListView) findViewById(R.id.options); + Vector choices = new Vector(); + + if (contentType == Id.content.keys) { + choices.add(new Choice(Id.choice.action.import_public, + getString(R.string.action_importPublic))); + choices.add(new Choice(Id.choice.action.import_secret, + getString(R.string.action_importSecret))); + } + + if (contentType == Id.content.encrypted_data) { + choices.add(new Choice(Id.choice.action.decrypt, getString(R.string.action_decrypt))); + } + + if (contentType == Id.content.unknown) { + choices.add(new Choice(Id.choice.action.encrypt, getString(R.string.action_encrypt))); + } + + mAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, choices); + mList.setAdapter(mAdapter); + + mList.setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) { + clicked(mAdapter.getItem(arg2).getId()); + } + }); + + mCancelButton = (Button) findViewById(R.id.btn_cancel); + mCancelButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + GeneralActivity.this.finish(); + } + }); + + if (choices.size() == 1) { + clicked(choices.get(0).getId()); + } + } + + private void clicked(int id) { + Intent intent = new Intent(); + switch (id) { + case Id.choice.action.encrypt: { + intent.setClass(this, EncryptActivity.class); + if (mDataString != null) { + intent.setAction(Apg.Intent.ENCRYPT); + intent.putExtra(Apg.EXTRA_TEXT, mDataString); + } else if (mDataUri != null) { + intent.setAction(Apg.Intent.ENCRYPT_FILE); + intent.setDataAndType(mDataUri, mIntent.getType()); + } + + break; + } + + case Id.choice.action.decrypt: { + intent.setClass(this, DecryptActivity.class); + if (mDataString != null) { + intent.setAction(Apg.Intent.DECRYPT); + intent.putExtra(Apg.EXTRA_TEXT, mDataString); + } else if (mDataUri != null) { + intent.setAction(Apg.Intent.DECRYPT_FILE); + intent.setDataAndType(mDataUri, mIntent.getType()); + } + + break; + } + + case Id.choice.action.import_public: { + intent.setClass(this, PublicKeyListActivity.class); + intent.setAction(Apg.Intent.IMPORT); + if (mDataString != null) { + intent.putExtra(Apg.EXTRA_TEXT, mDataString); + } else if (mDataUri != null) { + intent.setDataAndType(mDataUri, mIntent.getType()); + } + break; + } + + case Id.choice.action.import_secret: { + intent.setClass(this, SecretKeyListActivity.class); + intent.setAction(Apg.Intent.IMPORT); + if (mDataString != null) { + intent.putExtra(Apg.EXTRA_TEXT, mDataString); + } else if (mDataUri != null) { + intent.setDataAndType(mDataUri, mIntent.getType()); + } + break; + } + + default: { + // shouldn't happen + return; + } + } + + startActivity(intent); + finish(); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/HelpActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/HelpActivity.java new file mode 100644 index 000000000..dda4cbf36 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/HelpActivity.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2012 Dominik Schürmann + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.util.Utils; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockActivity; +import com.actionbarsherlock.view.MenuItem; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.widget.TextView; + +public class HelpActivity extends SherlockActivity { + Activity mActivity; + TextView mHelpText; + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case android.R.id.home: + startActivity(new Intent(this, MainActivity.class)); + return true; + + default: + break; + + } + return false; + } + + /** + * Instantiate View for this Activity + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.help_activity); + + final ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + + mActivity = this; + + mHelpText = (TextView) findViewById(R.id.help_text); + + // load html from html file from /res/raw + String helpText = Utils.readContentFromResource(mActivity, R.raw.help); + + // set text from resources with html markup + mHelpText.setText(Html.fromHtml(helpText)); + // make links work + mHelpText.setMovementMethod(LinkMovementMethod.getInstance()); + + } + +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/ImportFromQRCodeActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/ImportFromQRCodeActivity.java new file mode 100644 index 000000000..6f1043467 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/ImportFromQRCodeActivity.java @@ -0,0 +1,152 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import org.spongycastle.openpgp.PGPKeyRing; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Constants; +import org.thialfihar.android.apg.HkpKeyServer; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.KeyServer.QueryException; +import org.thialfihar.android.apg.R; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; +import android.util.Log; +import android.widget.Toast; + +import com.google.zxing.integration.android.IntentIntegrator; +import com.google.zxing.integration.android.IntentResult; + +public class ImportFromQRCodeActivity extends BaseActivity { + private static final String TAG = "ImportFromQRCodeActivity"; + + private final Bundle status = new Bundle(); + private final Message msg = new Message(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + new IntentIntegrator(this).initiateScan(); + } + + private void importAndSign(final long keyId, final String expectedFingerprint) { + if (expectedFingerprint != null && expectedFingerprint.length() > 0) { + + Thread t = new Thread() { + @Override + public void run() { + try { + // TODO: display some sort of spinner here while the user waits + + HkpKeyServer server = new HkpKeyServer(mPreferences.getKeyServers()[0]); // TODO: there should be only 1 + String encodedKey = server.get(keyId); + + PGPKeyRing keyring = Apg.decodeKeyRing(new ByteArrayInputStream(encodedKey.getBytes())); + if (keyring != null && keyring instanceof PGPPublicKeyRing) { + PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring; + + // make sure the fingerprints match before we cache this thing + String actualFingerprint = Apg.convertToHex(publicKeyRing.getPublicKey().getFingerprint()); + if (expectedFingerprint.equals(actualFingerprint)) { + // store the signed key in our local cache + int retval = Apg.storeKeyRingInCache(publicKeyRing); + if (retval != Id.return_value.ok && retval != Id.return_value.updated) { + status.putString(Apg.EXTRA_ERROR, "Failed to store signed key in local cache"); + } else { + Intent intent = new Intent(ImportFromQRCodeActivity.this, SignKeyActivity.class); + intent.putExtra(Apg.EXTRA_KEY_ID, keyId); + startActivityForResult(intent, Id.request.sign_key); + } + } else { + status.putString(Apg.EXTRA_ERROR, "Scanned fingerprint does NOT match the fingerprint of the received key. You shouldnt trust this key."); + } + } + } catch (QueryException e) { + Log.e(TAG, "Failed to query KeyServer", e); + status.putString(Apg.EXTRA_ERROR, "Failed to query KeyServer"); + status.putInt(Constants.extras.STATUS, Id.message.done); + } catch (IOException e) { + Log.e(TAG, "Failed to query KeyServer", e); + status.putString(Apg.EXTRA_ERROR, "Failed to query KeyServer"); + status.putInt(Constants.extras.STATUS, Id.message.done); + } + } + }; + + t.setName("KeyExchange Download Thread"); + t.setDaemon(true); + t.start(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case IntentIntegrator.REQUEST_CODE: { + boolean debug = true; // TODO: remove this!!! + IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data); + if (debug || (scanResult != null && scanResult.getFormatName() != null)) { + String[] bits = debug ? new String[] { "5993515643896327656", "0816 F68A 6816 68FB 01BF 2CA5 532D 3EB9 1E2F EDE8" } : scanResult.getContents().split(","); + if (bits.length != 2) { + return; // dont know how to handle this. Not a valid code + } + + long keyId = Long.parseLong(bits[0]); + String expectedFingerprint = bits[1]; + + importAndSign(keyId, expectedFingerprint); + + break; + } + } + + case Id.request.sign_key: { + // signals the end of processing. Signature was either applied, or it wasnt + status.putInt(Constants.extras.STATUS, Id.message.done); + + msg.setData(status); + sendMessage(msg); + + break; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + } + } + } + + @Override + public void doneCallback(Message msg) { + super.doneCallback(msg); + + Bundle data = msg.getData(); + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT).show(); + return; + } + + Toast.makeText(this, R.string.keySignSuccess, Toast.LENGTH_SHORT).show(); // TODO + finish(); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/KeyListActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/KeyListActivity.java new file mode 100644 index 000000000..787afd431 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/KeyListActivity.java @@ -0,0 +1,769 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKeyRing; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Constants; +import org.thialfihar.android.apg.FileDialog; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.InputData; +import org.thialfihar.android.apg.provider.KeyRings; +import org.thialfihar.android.apg.provider.Keys; +import org.thialfihar.android.apg.provider.UserIds; +import org.thialfihar.android.apg.R; + +import com.actionbarsherlock.view.MenuItem; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.SearchManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.os.Bundle; +import android.os.Message; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.Button; +import android.widget.ExpandableListView; +import android.widget.ExpandableListView.ExpandableListContextMenuInfo; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Vector; + +public class KeyListActivity extends BaseActivity { + protected ExpandableListView mList; + protected KeyListAdapter mListAdapter; + protected View mFilterLayout; + protected Button mClearFilterButton; + protected TextView mFilterInfo; + + protected int mSelectedItem = -1; + protected int mTask = 0; + + protected String mImportFilename = Constants.path.APP_DIR + "/"; + protected String mExportFilename = Constants.path.APP_DIR + "/"; + + protected String mImportData; + protected boolean mDeleteAfterImport = false; + + protected int mKeyType = Id.type.public_key; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.key_list); + + setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); + + mList = (ExpandableListView) findViewById(R.id.list); + registerForContextMenu(mList); + + mFilterLayout = findViewById(R.id.layout_filter); + mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo); + mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear); + + mClearFilterButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + handleIntent(new Intent()); + } + }); + + handleIntent(getIntent()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + handleIntent(intent); + } + + protected void handleIntent(Intent intent) { + String searchString = null; + if (Intent.ACTION_SEARCH.equals(intent.getAction())) { + searchString = intent.getStringExtra(SearchManager.QUERY); + if (searchString != null && searchString.trim().length() == 0) { + searchString = null; + } + } + + if (searchString == null) { + mFilterLayout.setVisibility(View.GONE); + } else { + mFilterLayout.setVisibility(View.VISIBLE); + mFilterInfo.setText(getString(R.string.filterInfo, searchString)); + } + + if (mListAdapter != null) { + mListAdapter.cleanup(); + } + mListAdapter = new KeyListAdapter(this, searchString); + mList.setAdapter(mListAdapter); + + if (Apg.Intent.IMPORT.equals(intent.getAction())) { + if ("file".equals(intent.getScheme()) && intent.getDataString() != null) { + mImportFilename = Uri.decode(intent.getDataString().replace("file://", "")); + } else { + mImportData = intent.getStringExtra(Apg.EXTRA_TEXT); + } + importKeys(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case Id.menu.option.import_keys: { + showDialog(Id.dialog.import_keys); + return true; + } + + case Id.menu.option.export_keys: { + showDialog(Id.dialog.export_keys); + return true; + } + + default: { + return super.onOptionsItemSelected(item); + } + } + } + + @Override + public boolean onContextItemSelected(android.view.MenuItem menuItem) { + ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo(); + int type = ExpandableListView.getPackedPositionType(info.packedPosition); + int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); + + if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) { + return super.onContextItemSelected(menuItem); + } + + switch (menuItem.getItemId()) { + case Id.menu.export: { + mSelectedItem = groupPosition; + showDialog(Id.dialog.export_key); + return true; + } + + case Id.menu.delete: { + mSelectedItem = groupPosition; + showDialog(Id.dialog.delete_key); + return true; + } + + default: { + return super.onContextItemSelected(menuItem); + } + } + } + + @Override + protected Dialog onCreateDialog(int id) { + boolean singleKeyExport = false; + + switch (id) { + case Id.dialog.delete_key: { + final int keyRingId = mListAdapter.getKeyRingId(mSelectedItem); + mSelectedItem = -1; + // TODO: better way to do this? + String userId = ""; + Object keyRing = Apg.getKeyRing(keyRingId); + if (keyRing != null) { + if (keyRing instanceof PGPPublicKeyRing) { + userId = Apg.getMainUserIdSafe(this, + Apg.getMasterKey((PGPPublicKeyRing) keyRing)); + } else { + userId = Apg.getMainUserIdSafe(this, + Apg.getMasterKey((PGPSecretKeyRing) keyRing)); + } + } + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.warning); + builder.setMessage(getString( + mKeyType == Id.type.public_key ? R.string.keyDeletionConfirmation + : R.string.secretKeyDeletionConfirmation, userId)); + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + deleteKey(keyRingId); + removeDialog(Id.dialog.delete_key); + } + }); + builder.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeDialog(Id.dialog.delete_key); + } + }); + return builder.create(); + } + + case Id.dialog.import_keys: { + return FileDialog.build(this, getString(R.string.title_importKeys), + getString(R.string.specifyFileToImportFrom), mImportFilename, + new FileDialog.OnClickListener() { + public void onOkClick(String filename, boolean checked) { + removeDialog(Id.dialog.import_keys); + mDeleteAfterImport = checked; + mImportFilename = filename; + importKeys(); + } + + public void onCancelClick() { + removeDialog(Id.dialog.import_keys); + } + }, getString(R.string.filemanager_titleOpen), + getString(R.string.filemanager_btnOpen), + getString(R.string.label_deleteAfterImport), Id.request.filename); + } + + case Id.dialog.export_key: { + singleKeyExport = true; + // break intentionally omitted, to use the Id.dialog.export_keys dialog + } + + case Id.dialog.export_keys: { + String title = (singleKeyExport ? getString(R.string.title_exportKey) + : getString(R.string.title_exportKeys)); + + final int thisDialogId = (singleKeyExport ? Id.dialog.export_key + : Id.dialog.export_keys); + + return FileDialog.build(this, title, + getString(mKeyType == Id.type.public_key ? R.string.specifyFileToExportTo + : R.string.specifyFileToExportSecretKeysTo), mExportFilename, + new FileDialog.OnClickListener() { + public void onOkClick(String filename, boolean checked) { + removeDialog(thisDialogId); + mExportFilename = filename; + exportKeys(); + } + + public void onCancelClick() { + removeDialog(thisDialogId); + } + }, getString(R.string.filemanager_titleSave), + getString(R.string.filemanager_btnSave), null, Id.request.filename); + } + + default: { + return super.onCreateDialog(id); + } + } + } + + public void importKeys() { + showDialog(Id.dialog.importing); + mTask = Id.task.import_keys; + startThread(); + } + + public void exportKeys() { + showDialog(Id.dialog.exporting); + mTask = Id.task.export_keys; + startThread(); + } + + @Override + public void run() { + String error = null; + Bundle data = new Bundle(); + Message msg = new Message(); + + try { + InputStream importInputStream = null; + OutputStream exportOutputStream = null; + long size = 0; + if (mTask == Id.task.import_keys) { + if (mImportData != null) { + byte[] bytes = mImportData.getBytes(); + size = bytes.length; + importInputStream = new ByteArrayInputStream(bytes); + } else { + File file = new File(mImportFilename); + size = file.length(); + importInputStream = new FileInputStream(file); + } + } else { + exportOutputStream = new FileOutputStream(mExportFilename); + } + + if (mTask == Id.task.import_keys) { + data = Apg.importKeyRings(this, mKeyType, new InputData(importInputStream, size), + this); + } else { + Vector keyRingIds = new Vector(); + if (mSelectedItem == -1) { + keyRingIds = Apg + .getKeyRingIds(mKeyType == Id.type.public_key ? Id.database.type_public + : Id.database.type_secret); + } else { + int keyRingId = mListAdapter.getKeyRingId(mSelectedItem); + keyRingIds.add(keyRingId); + mSelectedItem = -1; + } + data = Apg.exportKeyRings(this, keyRingIds, exportOutputStream, this); + } + } catch (FileNotFoundException e) { + error = getString(R.string.error_fileNotFound); + } catch (IOException e) { + error = "" + e; + } catch (PGPException e) { + error = "" + e; + } catch (Apg.GeneralException e) { + error = "" + e; + } + + mImportData = null; + + if (mTask == Id.task.import_keys) { + data.putInt(Constants.extras.STATUS, Id.message.import_done); + } else { + data.putInt(Constants.extras.STATUS, Id.message.export_done); + } + + if (error != null) { + data.putString(Apg.EXTRA_ERROR, error); + } + + msg.setData(data); + sendMessage(msg); + } + + protected void deleteKey(int keyRingId) { + Apg.deleteKey(keyRingId); + refreshList(); + } + + protected void refreshList() { + mListAdapter.rebuild(true); + mListAdapter.notifyDataSetChanged(); + } + + @Override + public void doneCallback(Message msg) { + super.doneCallback(msg); + + Bundle data = msg.getData(); + if (data != null) { + int type = data.getInt(Constants.extras.STATUS); + switch (type) { + case Id.message.import_done: { + removeDialog(Id.dialog.importing); + + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(KeyListActivity.this, getString(R.string.errorMessage, error), + Toast.LENGTH_SHORT).show(); + } else { + int added = data.getInt("added"); + int updated = data.getInt("updated"); + int bad = data.getInt("bad"); + String message; + if (added > 0 && updated > 0) { + message = getString(R.string.keysAddedAndUpdated, added, updated); + } else if (added > 0) { + message = getString(R.string.keysAdded, added); + } else if (updated > 0) { + message = getString(R.string.keysUpdated, updated); + } else { + message = getString(R.string.noKeysAddedOrUpdated); + } + Toast.makeText(KeyListActivity.this, message, Toast.LENGTH_SHORT).show(); + if (bad > 0) { + AlertDialog.Builder alert = new AlertDialog.Builder(this); + + alert.setIcon(android.R.drawable.ic_dialog_alert); + alert.setTitle(R.string.warning); + alert.setMessage(this.getString(R.string.badKeysEncountered, bad)); + + alert.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + alert.setCancelable(true); + alert.create().show(); + } else if (mDeleteAfterImport) { + // everything went well, so now delete, if that was turned on + setDeleteFile(mImportFilename); + showDialog(Id.dialog.delete_file); + } + } + refreshList(); + break; + } + + case Id.message.export_done: { + removeDialog(Id.dialog.exporting); + + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(KeyListActivity.this, getString(R.string.errorMessage, error), + Toast.LENGTH_SHORT).show(); + } else { + int exported = data.getInt("exported"); + String message; + if (exported == 1) { + message = getString(R.string.keyExported); + } else if (exported > 0) { + message = getString(R.string.keysExported, exported); + } else { + message = getString(R.string.noKeysExported); + } + Toast.makeText(KeyListActivity.this, message, Toast.LENGTH_SHORT).show(); + } + break; + } + + default: { + break; + } + } + } + } + + protected class KeyListAdapter extends BaseExpandableListAdapter { + private LayoutInflater mInflater; + private Vector> mChildren; + private SQLiteDatabase mDatabase; + private Cursor mCursor; + private String mSearchString; + + private class KeyChild { + public static final int KEY = 0; + public static final int USER_ID = 1; + public static final int FINGER_PRINT = 2; + + public int type; + public String userId; + public long keyId; + public boolean isMasterKey; + public int algorithm; + public int keySize; + public boolean canSign; + public boolean canEncrypt; + public String fingerPrint; + + public KeyChild(long keyId, boolean isMasterKey, int algorithm, int keySize, + boolean canSign, boolean canEncrypt) { + this.type = KEY; + this.keyId = keyId; + this.isMasterKey = isMasterKey; + this.algorithm = algorithm; + this.keySize = keySize; + this.canSign = canSign; + this.canEncrypt = canEncrypt; + } + + public KeyChild(String userId) { + type = USER_ID; + this.userId = userId; + } + + public KeyChild(String fingerPrint, boolean isFingerPrint) { + type = FINGER_PRINT; + this.fingerPrint = fingerPrint; + } + } + + public KeyListAdapter(Context context, String searchString) { + mSearchString = searchString; + + mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mDatabase = Apg.getDatabase().db(); + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + "(" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "." + + Keys.KEY_RING_ID + " AND " + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + + " = '1'" + ") " + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + "(" + + Keys.TABLE_NAME + "." + Keys._ID + " = " + UserIds.TABLE_NAME + "." + + UserIds.KEY_ID + " AND " + UserIds.TABLE_NAME + "." + UserIds.RANK + + " = '0')"); + + if (searchString != null && searchString.trim().length() > 0) { + String[] chunks = searchString.trim().split(" +"); + qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " + UserIds.TABLE_NAME + + " AS tmp WHERE " + "tmp." + UserIds.KEY_ID + " = " + Keys.TABLE_NAME + + "." + Keys._ID); + for (int i = 0; i < chunks.length; ++i) { + qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE "); + qb.appendWhereEscapeString("%" + chunks[i] + "%"); + } + qb.appendWhere(")"); + } + + mCursor = qb.query(mDatabase, new String[] { KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0 + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1 + UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2 + }, KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", new String[] { "" + + (mKeyType == Id.type.public_key ? Id.database.type_public + : Id.database.type_secret) }, null, null, UserIds.TABLE_NAME + "." + + UserIds.USER_ID + " ASC"); + + // content provider way for reference, might have to go back to it sometime: + /* + * Uri contentUri = null; if (mKeyType == Id.type.secret_key) { contentUri = + * Apg.CONTENT_URI_SECRET_KEY_RINGS; } else { contentUri = + * Apg.CONTENT_URI_PUBLIC_KEY_RINGS; } mCursor = getContentResolver().query( contentUri, + * new String[] { DataProvider._ID, // 0 DataProvider.MASTER_KEY_ID, // 1 + * DataProvider.USER_ID, // 2 }, null, null, null); + */ + + startManagingCursor(mCursor); + rebuild(false); + } + + public void cleanup() { + if (mCursor != null) { + stopManagingCursor(mCursor); + mCursor.close(); + } + } + + public void rebuild(boolean requery) { + if (requery) { + mCursor.requery(); + } + mChildren = new Vector>(); + for (int i = 0; i < mCursor.getCount(); ++i) { + mChildren.add(null); + } + } + + protected Vector getChildrenOfGroup(int groupPosition) { + Vector children = mChildren.get(groupPosition); + if (children != null) { + return children; + } + + mCursor.moveToPosition(groupPosition); + children = new Vector(); + Cursor c = mDatabase.query(Keys.TABLE_NAME, new String[] { Keys._ID, // 0 + Keys.KEY_ID, // 1 + Keys.IS_MASTER_KEY, // 2 + Keys.ALGORITHM, // 3 + Keys.KEY_SIZE, // 4 + Keys.CAN_SIGN, // 5 + Keys.CAN_ENCRYPT, // 6 + }, Keys.KEY_RING_ID + " = ?", new String[] { mCursor.getString(0) }, null, null, + Keys.RANK + " ASC"); + + int masterKeyId = -1; + long fingerPrintId = -1; + for (int i = 0; i < c.getCount(); ++i) { + c.moveToPosition(i); + children.add(new KeyChild(c.getLong(1), c.getInt(2) == 1, c.getInt(3), c.getInt(4), + c.getInt(5) == 1, c.getInt(6) == 1)); + if (i == 0) { + masterKeyId = c.getInt(0); + fingerPrintId = c.getLong(1); + } + } + c.close(); + + if (masterKeyId != -1) { + children.insertElementAt(new KeyChild(Apg.getFingerPrint(fingerPrintId), true), 0); + c = mDatabase.query(UserIds.TABLE_NAME, new String[] { UserIds.USER_ID, // 0 + }, UserIds.KEY_ID + " = ? AND " + UserIds.RANK + " > 0", new String[] { "" + + masterKeyId }, null, null, UserIds.RANK + " ASC"); + + for (int i = 0; i < c.getCount(); ++i) { + c.moveToPosition(i); + children.add(new KeyChild(c.getString(0))); + } + c.close(); + } + + mChildren.set(groupPosition, children); + return children; + } + + public boolean hasStableIds() { + return true; + } + + public boolean isChildSelectable(int groupPosition, int childPosition) { + return true; + } + + public int getGroupCount() { + return mCursor.getCount(); + } + + public Object getChild(int groupPosition, int childPosition) { + return null; + } + + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + public int getChildrenCount(int groupPosition) { + return getChildrenOfGroup(groupPosition).size(); + } + + public Object getGroup(int position) { + return position; + } + + public long getGroupId(int position) { + mCursor.moveToPosition(position); + return mCursor.getLong(1); // MASTER_KEY_ID + } + + public int getKeyRingId(int position) { + mCursor.moveToPosition(position); + return mCursor.getInt(0); // _ID + } + + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, + ViewGroup parent) { + mCursor.moveToPosition(groupPosition); + + View view = mInflater.inflate(R.layout.key_list_group_item, null); + view.setBackgroundResource(android.R.drawable.list_selector_background); + + TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); + mainUserId.setText(""); + TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); + mainUserIdRest.setText(""); + + String userId = mCursor.getString(2); // USER_ID + if (userId != null) { + String chunks[] = userId.split(" <", 2); + userId = chunks[0]; + if (chunks.length > 1) { + mainUserIdRest.setText("<" + chunks[1]); + } + mainUserId.setText(userId); + } + + if (mainUserId.getText().length() == 0) { + mainUserId.setText(R.string.unknownUserId); + } + + if (mainUserIdRest.getText().length() == 0) { + mainUserIdRest.setVisibility(View.GONE); + } + return view; + } + + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, + View convertView, ViewGroup parent) { + mCursor.moveToPosition(groupPosition); + + Vector children = getChildrenOfGroup(groupPosition); + + KeyChild child = children.get(childPosition); + View view = null; + switch (child.type) { + case KeyChild.KEY: { + if (child.isMasterKey) { + view = mInflater.inflate(R.layout.key_list_child_item_master_key, null); + } else { + view = mInflater.inflate(R.layout.key_list_child_item_sub_key, null); + } + + TextView keyId = (TextView) view.findViewById(R.id.keyId); + String keyIdStr = Apg.getSmallFingerPrint(child.keyId); + keyId.setText(keyIdStr); + TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails); + String algorithmStr = Apg.getAlgorithmInfo(child.algorithm, child.keySize); + keyDetails.setText("(" + algorithmStr + ")"); + + ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey); + if (!child.canEncrypt) { + encryptIcon.setVisibility(View.GONE); + } + + ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey); + if (!child.canSign) { + signIcon.setVisibility(View.GONE); + } + break; + } + + case KeyChild.USER_ID: { + view = mInflater.inflate(R.layout.key_list_child_item_user_id, null); + TextView userId = (TextView) view.findViewById(R.id.userId); + userId.setText(child.userId); + break; + } + + case KeyChild.FINGER_PRINT: { + view = mInflater.inflate(R.layout.key_list_child_item_user_id, null); + TextView userId = (TextView) view.findViewById(R.id.userId); + userId.setText(getString(R.string.fingerprint) + ":\n" + + child.fingerPrint.replace(" ", "\n")); + break; + } + } + return view; + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case Id.request.filename: { + if (resultCode == RESULT_OK && data != null) { + String filename = data.getDataString(); + if (filename != null) { + // Get rid of URI prefix: + if (filename.startsWith("file://")) { + filename = filename.substring(7); + } + // replace %20 and so on + filename = Uri.decode(filename); + + FileDialog.setFilename(filename); + } + } + return; + } + + default: { + break; + } + } + super.onActivityResult(requestCode, resultCode, data); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/KeyServerExportActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/KeyServerExportActivity.java new file mode 100644 index 000000000..877ae0164 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/KeyServerExportActivity.java @@ -0,0 +1,132 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import org.spongycastle.openpgp.PGPKeyRing; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Constants; +import org.thialfihar.android.apg.HkpKeyServer; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.R; + +import com.actionbarsherlock.view.MenuItem; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.Spinner; +import android.widget.Toast; + +/** + * gpg --send-key activity + * + * Sends the selected public key to a key server + */ +public class KeyServerExportActivity extends BaseActivity { + + private Button export; + private Spinner keyServer; + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case android.R.id.home: + startActivity(new Intent(this, PublicKeyListActivity.class)); + return true; + + default: + break; + + } + return false; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.key_server_export_layout); + + export = (Button) findViewById(R.id.btn_export_to_server); + keyServer = (Spinner) findViewById(R.id.keyServer); + + ArrayAdapter adapter = new ArrayAdapter(this, + android.R.layout.simple_spinner_item, mPreferences.getKeyServers()); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + keyServer.setAdapter(adapter); + if (adapter.getCount() > 0) { + keyServer.setSelection(0); + } else { + export.setEnabled(false); + } + + export.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + startThread(); + } + }); + } + + @Override + public void run() { + String error = null; + Bundle data = new Bundle(); + Message msg = new Message(); + + HkpKeyServer server = new HkpKeyServer((String) keyServer.getSelectedItem()); + + int keyRingId = getIntent().getIntExtra(Apg.EXTRA_KEY_ID, -1); + + PGPKeyRing keyring = Apg.getKeyRing(keyRingId); + if (keyring != null && keyring instanceof PGPPublicKeyRing) { + boolean uploaded = Apg.uploadKeyRingToServer(server, (PGPPublicKeyRing) keyring); + if (!uploaded) { + error = "Unable to export key to selected server"; + } + } + + data.putInt(Constants.extras.STATUS, Id.message.export_done); + + if (error != null) { + data.putString(Apg.EXTRA_ERROR, error); + } + + msg.setData(data); + sendMessage(msg); + } + + @Override + public void doneCallback(Message msg) { + super.doneCallback(msg); + + Bundle data = msg.getData(); + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT) + .show(); + return; + } + + Toast.makeText(this, R.string.keySendSuccess, Toast.LENGTH_SHORT).show(); + finish(); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/KeyServerPreferenceActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/KeyServerPreferenceActivity.java new file mode 100644 index 000000000..e1233661f --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/KeyServerPreferenceActivity.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import java.util.Vector; + +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.ui.widget.Editor; +import org.thialfihar.android.apg.ui.widget.KeyServerEditor; +import org.thialfihar.android.apg.ui.widget.Editor.EditorListener; + +import com.actionbarsherlock.view.Menu; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +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 KeyServerPreferenceActivity extends BaseActivity implements OnClickListener, + EditorListener { + private LayoutInflater mInflater; + private ViewGroup mEditors; + private View mAdd; + private TextView mTitle; + private TextView mSummary; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.key_server_preference); + + mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + mTitle = (TextView) findViewById(R.id.title); + mSummary = (TextView) findViewById(R.id.summary); + + mTitle.setText(R.string.label_keyServers); + + mEditors = (ViewGroup) findViewById(R.id.editors); + mAdd = findViewById(R.id.add); + mAdd.setOnClickListener(this); + + Intent intent = getIntent(); + String servers[] = intent.getStringArrayExtra(Apg.EXTRA_KEY_SERVERS); + if (servers != null) { + for (int i = 0; i < servers.length; ++i) { + KeyServerEditor view = (KeyServerEditor) mInflater.inflate( + R.layout.key_server_editor, mEditors, false); + view.setEditorListener(this); + view.setValue(servers[i]); + mEditors.addView(view); + } + } + + Button okButton = (Button) findViewById(R.id.btn_ok); + okButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + okClicked(); + } + }); + + Button cancelButton = (Button) findViewById(R.id.btn_cancel); + cancelButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + cancelClicked(); + } + }); + } + + public void onDeleted(Editor editor) { + // nothing to do + } + + public void onClick(View v) { + KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor, + mEditors, false); + view.setEditorListener(this); + mEditors.addView(view); + } + + private void cancelClicked() { + setResult(RESULT_CANCELED, null); + finish(); + } + + private void okClicked() { + Intent data = new Intent(); + Vector servers = new Vector(); + for (int i = 0; i < mEditors.getChildCount(); ++i) { + KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i); + String tmp = editor.getValue(); + if (tmp.length() > 0) { + servers.add(tmp); + } + } + String[] dummy = new String[0]; + data.putExtra(Apg.EXTRA_KEY_SERVERS, servers.toArray(dummy)); + setResult(RESULT_OK, data); + finish(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // override this, so no option menu is added (as would be in BaseActivity), since + // we're still in preferences + return true; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/KeyServerQueryActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/KeyServerQueryActivity.java new file mode 100644 index 000000000..2009441ab --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/KeyServerQueryActivity.java @@ -0,0 +1,329 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import java.util.List; +import java.util.Vector; + +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Constants; +import org.thialfihar.android.apg.HkpKeyServer; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.KeyServer.InsufficientQuery; +import org.thialfihar.android.apg.KeyServer.KeyInfo; +import org.thialfihar.android.apg.KeyServer.QueryException; +import org.thialfihar.android.apg.KeyServer.TooManyResponses; + +import com.actionbarsherlock.view.MenuItem; + +import android.app.Activity; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.LinearLayout.LayoutParams; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +public class KeyServerQueryActivity extends BaseActivity { + private ListView mList; + private EditText mQuery; + private Button mSearch; + private KeyInfoListAdapter mAdapter; + private Spinner mKeyServer; + + private int mQueryType; + private String mQueryString; + private long mQueryId; + private volatile List mSearchResult; + private volatile String mKeyData; + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case android.R.id.home: + startActivity(new Intent(this, PublicKeyListActivity.class)); + return true; + + default: + break; + + } + return false; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.key_server_query_layout); + + mQuery = (EditText) findViewById(R.id.query); + mSearch = (Button) findViewById(R.id.btn_search); + mList = (ListView) findViewById(R.id.list); + mAdapter = new KeyInfoListAdapter(this); + mList.setAdapter(mAdapter); + + mKeyServer = (Spinner) findViewById(R.id.keyServer); + ArrayAdapter adapter = new ArrayAdapter(this, + android.R.layout.simple_spinner_item, mPreferences.getKeyServers()); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mKeyServer.setAdapter(adapter); + if (adapter.getCount() > 0) { + mKeyServer.setSelection(0); + } else { + mSearch.setEnabled(false); + } + + mList.setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView adapter, View view, int position, long keyId) { + get(keyId); + } + }); + + mSearch.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + String query = mQuery.getText().toString(); + search(query); + } + }); + + Intent intent = getIntent(); + if (Apg.Intent.LOOK_UP_KEY_ID.equals(intent.getAction()) + || Apg.Intent.LOOK_UP_KEY_ID_AND_RETURN.equals(intent.getAction())) { + long keyId = intent.getLongExtra(Apg.EXTRA_KEY_ID, 0); + if (keyId != 0) { + String query = "0x" + Apg.keyToHex(keyId); + mQuery.setText(query); + search(query); + } + } + } + + private void search(String query) { + showDialog(Id.dialog.querying); + mQueryType = Id.keyserver.search; + mQueryString = query; + mAdapter.setKeys(new Vector()); + startThread(); + } + + private void get(long keyId) { + showDialog(Id.dialog.querying); + mQueryType = Id.keyserver.get; + mQueryId = keyId; + startThread(); + } + + @Override + protected Dialog onCreateDialog(int id) { + ProgressDialog progress = (ProgressDialog) super.onCreateDialog(id); + progress.setMessage(this.getString(R.string.progress_queryingServer, + (String) mKeyServer.getSelectedItem())); + return progress; + } + + @Override + public void run() { + String error = null; + Bundle data = new Bundle(); + Message msg = new Message(); + + try { + HkpKeyServer server = new HkpKeyServer((String) mKeyServer.getSelectedItem()); + if (mQueryType == Id.keyserver.search) { + mSearchResult = server.search(mQueryString); + } else if (mQueryType == Id.keyserver.get) { + mKeyData = server.get(mQueryId); + } + } catch (QueryException e) { + error = "" + e; + } catch (InsufficientQuery e) { + error = "Insufficient query."; + } catch (TooManyResponses e) { + error = "Too many responses."; + } + + data.putInt(Constants.extras.STATUS, Id.message.done); + + if (error != null) { + data.putString(Apg.EXTRA_ERROR, error); + } + + msg.setData(data); + sendMessage(msg); + } + + @Override + public void doneCallback(Message msg) { + super.doneCallback(msg); + + removeDialog(Id.dialog.querying); + + Bundle data = msg.getData(); + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT) + .show(); + return; + } + + if (mQueryType == Id.keyserver.search) { + if (mSearchResult != null) { + Toast.makeText(this, getString(R.string.keysFound, mSearchResult.size()), + Toast.LENGTH_SHORT).show(); + mAdapter.setKeys(mSearchResult); + } + } else if (mQueryType == Id.keyserver.get) { + Intent orgIntent = getIntent(); + if (Apg.Intent.LOOK_UP_KEY_ID_AND_RETURN.equals(orgIntent.getAction())) { + if (mKeyData != null) { + Intent intent = new Intent(); + intent.putExtra(Apg.EXTRA_TEXT, mKeyData); + setResult(RESULT_OK, intent); + } else { + setResult(RESULT_CANCELED); + } + finish(); + } else { + if (mKeyData != null) { + Intent intent = new Intent(this, PublicKeyListActivity.class); + intent.setAction(Apg.Intent.IMPORT); + intent.putExtra(Apg.EXTRA_TEXT, mKeyData); + startActivity(intent); + } + } + } + } + + public class KeyInfoListAdapter extends BaseAdapter { + protected LayoutInflater mInflater; + protected Activity mActivity; + protected List mKeys; + + public KeyInfoListAdapter(Activity activity) { + mActivity = activity; + mInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mKeys = new Vector(); + } + + public void setKeys(List keys) { + mKeys = keys; + notifyDataSetChanged(); + } + + @Override + public boolean hasStableIds() { + return true; + } + + public int getCount() { + return mKeys.size(); + } + + public Object getItem(int position) { + return mKeys.get(position); + } + + public long getItemId(int position) { + return mKeys.get(position).keyId; + } + + public View getView(int position, View convertView, ViewGroup parent) { + KeyInfo keyInfo = mKeys.get(position); + + View view = mInflater.inflate(R.layout.key_server_query_result_item, null); + + TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); + mainUserId.setText(R.string.unknownUserId); + TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); + mainUserIdRest.setText(""); + TextView keyId = (TextView) view.findViewById(R.id.keyId); + keyId.setText(R.string.noKey); + TextView algorithm = (TextView) view.findViewById(R.id.algorithm); + algorithm.setText(""); + TextView status = (TextView) view.findViewById(R.id.status); + status.setText(""); + + String userId = keyInfo.userIds.get(0); + if (userId != null) { + String chunks[] = userId.split(" <", 2); + userId = chunks[0]; + if (chunks.length > 1) { + mainUserIdRest.setText("<" + chunks[1]); + } + mainUserId.setText(userId); + } + + keyId.setText(Apg.getSmallFingerPrint(keyInfo.keyId)); + + if (mainUserIdRest.getText().length() == 0) { + mainUserIdRest.setVisibility(View.GONE); + } + + algorithm.setText("" + keyInfo.size + "/" + keyInfo.algorithm); + + if (keyInfo.revoked != null) { + status.setText("revoked"); + } else { + status.setVisibility(View.GONE); + } + + LinearLayout ll = (LinearLayout) view.findViewById(R.id.list); + if (keyInfo.userIds.size() == 1) { + ll.setVisibility(View.GONE); + } else { + boolean first = true; + boolean second = true; + for (String uid : keyInfo.userIds) { + if (first) { + first = false; + continue; + } + if (!second) { + View sep = new View(mActivity); + sep.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, 1)); + sep.setBackgroundResource(android.R.drawable.divider_horizontal_dark); + ll.addView(sep); + } + TextView uidView = (TextView) mInflater.inflate( + R.layout.key_server_query_result_user_id, null); + uidView.setText(uid); + ll.addView(uidView); + second = false; + } + } + + return view; + } + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/MailListActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/MailListActivity.java new file mode 100644 index 000000000..07e7196d5 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/MailListActivity.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import java.util.Vector; +import java.util.regex.Matcher; + +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Preferences; + +import android.app.ListActivity; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.text.Html; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.TextView; + +public class MailListActivity extends ListActivity { + LayoutInflater mInflater = null; + + public static final String EXTRA_ACCOUNT = "account"; + + private static class Conversation { + public long id; + public String subject; + public Vector messages; + + public Conversation(long id, String subject) { + this.id = id; + this.subject = subject; + } + } + + private static class Message { + public Conversation parent; + public long id; + public String subject; + public String fromAddress; + public String data; + public String replyTo; + public boolean signedOnly; + + public Message(Conversation parent, long id, String subject, + String fromAddress, String replyTo, + String data, boolean signedOnly) { + this.parent = parent; + this.id = id; + this.subject = subject; + this.fromAddress = fromAddress; + this.replyTo = replyTo; + this.data = data; + if (this.replyTo == null || this.replyTo.equals("")) { + this.replyTo = this.fromAddress; + } + this.signedOnly = signedOnly; + } + } + + private Vector mConversations; + private Vector mMessages; + + @Override + protected void onCreate(Bundle savedInstanceState) { + Preferences prefs = Preferences.getPreferences(this); + + super.onCreate(savedInstanceState); + + mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + mConversations = new Vector(); + mMessages = new Vector(); + + String account = getIntent().getExtras().getString(EXTRA_ACCOUNT); + // TODO: what if account is null? + Uri uri = Uri.parse("content://gmail-ls/conversations/" + account); + Cursor cursor = + managedQuery(uri, new String[] { "conversation_id", "subject" }, null, null, null); + for (int i = 0; i < cursor.getCount(); ++i) { + cursor.moveToPosition(i); + + int idIndex = cursor.getColumnIndex("conversation_id"); + int subjectIndex = cursor.getColumnIndex("subject"); + long conversationId = cursor.getLong(idIndex); + Conversation conversation = + new Conversation(conversationId, cursor.getString(subjectIndex)); + Uri messageUri = Uri.withAppendedPath(uri, "" + conversationId + "/messages"); + Cursor messageCursor = + managedQuery(messageUri, new String[] { + "messageId", + "subject", + "fromAddress", + "replyToAddresses", + "body" }, null, null, null); + Vector messages = new Vector(); + for (int j = 0; j < messageCursor.getCount(); ++j) { + messageCursor.moveToPosition(j); + idIndex = messageCursor.getColumnIndex("messageId"); + subjectIndex = messageCursor.getColumnIndex("subject"); + int fromAddressIndex = messageCursor.getColumnIndex("fromAddress"); + int replyToIndex = messageCursor.getColumnIndex("replyToAddresses"); + int bodyIndex = messageCursor.getColumnIndex("body"); + String data = messageCursor.getString(bodyIndex); + data = Html.fromHtml(data).toString(); + boolean signedOnly = false; + Matcher matcher = Apg.PGP_MESSAGE.matcher(data); + if (matcher.matches()) { + data = matcher.group(1); + } else { + matcher = Apg.PGP_SIGNED_MESSAGE.matcher(data); + if (matcher.matches()) { + data = matcher.group(1); + signedOnly = true; + } else { + data = null; + } + } + Message message = + new Message(conversation, + messageCursor.getLong(idIndex), + messageCursor.getString(subjectIndex), + messageCursor.getString(fromAddressIndex), + messageCursor.getString(replyToIndex), + data, signedOnly); + + messages.add(message); + mMessages.add(message); + } + conversation.messages = messages; + mConversations.add(conversation); + } + + setListAdapter(new MailboxAdapter()); + getListView().setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView arg0, View v, int position, long id) { + Intent intent = new Intent(MailListActivity.this, DecryptActivity.class); + intent.setAction(Apg.Intent.DECRYPT); + Message message = (Message) ((MailboxAdapter) getListAdapter()).getItem(position); + intent.putExtra(Apg.EXTRA_TEXT, message.data); + intent.putExtra(Apg.EXTRA_SUBJECT, message.subject); + intent.putExtra(Apg.EXTRA_REPLY_TO, message.replyTo); + startActivity(intent); + } + }); + } + + private class MailboxAdapter extends BaseAdapter implements ListAdapter { + + @Override + public boolean isEnabled(int position) { + Message message = (Message) getItem(position); + return message.data != null; + } + + @Override + public boolean hasStableIds() { + return true; + } + + public int getCount() { + return mMessages.size(); + } + + public Object getItem(int position) { + return mMessages.get(position); + } + + public long getItemId(int position) { + return mMessages.get(position).id; + } + + public View getView(int position, View convertView, ViewGroup parent) { + View view = mInflater.inflate(R.layout.mailbox_message_item, null); + + Message message = (Message) getItem(position); + + TextView subject = (TextView) view.findViewById(R.id.subject); + TextView email = (TextView) view.findViewById(R.id.emailAddress); + ImageView status = (ImageView) view.findViewById(R.id.ic_status); + + subject.setText(message.subject); + email.setText(message.fromAddress); + if (message.data != null) { + if (message.signedOnly) { + status.setImageResource(R.drawable.signed); + } else { + status.setImageResource(R.drawable.encrypted); + } + status.setVisibility(View.VISIBLE); + } else { + status.setVisibility(View.INVISIBLE); + } + + return view; + } + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/MainActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/MainActivity.java new file mode 100644 index 000000000..ff888cd81 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/MainActivity.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import java.security.Security; + +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.R; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; + +import android.app.Dialog; +import android.content.Intent; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.View; +import android.widget.TextView; + +public class MainActivity extends BaseActivity { + static { + Security.addProvider(new BouncyCastleProvider()); + } + + public void manageKeysOnClick(View view) { + startActivity(new Intent(this, PublicKeyListActivity.class)); + } + + public void myKeysOnClick(View view) { + startActivity(new Intent(this, SecretKeyListActivity.class)); + } + + public void encryptOnClick(View view) { + Intent intent = new Intent(MainActivity.this, EncryptActivity.class); + intent.setAction(Apg.Intent.ENCRYPT); + startActivity(intent); + } + + public void decryptOnClick(View view) { + Intent intent = new Intent(MainActivity.this, DecryptActivity.class); + intent.setAction(Apg.Intent.DECRYPT); + startActivity(intent); + } + + public void scanQrcodeOnClick(View view) { + Intent intent = new Intent(this, ImportFromQRCodeActivity.class); + intent.setAction(Apg.Intent.IMPORT_FROM_QR_CODE); + startActivityForResult(intent, Id.request.import_from_qr_code); + } + + public void helpOnClick(View view) { + startActivity(new Intent(this, HelpActivity.class)); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + final ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(false); + actionBar.setHomeButtonEnabled(false); + + // if (!mPreferences.hasSeenHelp()) { + // showDialog(Id.dialog.help); + // } + // + // if (Apg.isReleaseVersion(this) && !mPreferences.hasSeenChangeLog(Apg.getVersion(this))) { + // showDialog(Id.dialog.change_log); + // } + } + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + + // case Id.dialog.change_log: { + // AlertDialog.Builder alert = new AlertDialog.Builder(this); + // + // alert.setTitle("Changes " + Apg.getFullVersion(this)); + // LayoutInflater inflater = (LayoutInflater) this + // .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + // View layout = inflater.inflate(R.layout.info, null); + // TextView message = (TextView) layout.findViewById(R.id.message); + // + // message.setText("Changes:\n" + "* \n" + "\n" + // + "WARNING: be careful editing your existing keys, as they " + // + "WILL be stripped of certificates right now.\n" + "\n" + // + "Also: key cross-certification is NOT supported, so signing " + // + "with those keys will get a warning when the signature is " + "checked.\n" + // + "\n" + "I hope APG continues to be useful to you, please send " + // + "bug reports, feature wishes, feedback."); + // alert.setView(layout); + // + // alert.setCancelable(false); + // alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + // public void onClick(DialogInterface dialog, int id) { + // MainActivity.this.removeDialog(Id.dialog.change_log); + // mPreferences.setHasSeenChangeLog(Apg.getVersion(MainActivity.this), true); + // } + // }); + // + // return alert.create(); + // } + + // case Id.dialog.help: { + // AlertDialog.Builder alert = new AlertDialog.Builder(this); + // + // alert.setTitle(R.string.title_help); + // + // LayoutInflater inflater = (LayoutInflater) this + // .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + // View layout = inflater.inflate(R.layout.info, null); + // TextView message = (TextView) layout.findViewById(R.id.message); + // message.setText(R.string.text_help); + // + // TransformFilter packageNames = new TransformFilter() { + // public final String transformUrl(final Matcher match, String url) { + // String name = match.group(1).toLowerCase(); + // if (name.equals("astro")) { + // return "com.metago.astro"; + // } else if (name.equals("k-9 mail")) { + // return "com.fsck.k9"; + // } else { + // return "org.openintents.filemanager"; + // } + // } + // }; + // + // Pattern pattern = Pattern.compile("(OI File Manager|ASTRO|K-9 Mail)"); + // String scheme = "market://search?q=pname:"; + // message.setAutoLinkMask(0); + // Linkify.addLinks(message, pattern, scheme, null, packageNames); + // + // alert.setView(layout); + // + // alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + // public void onClick(DialogInterface dialog, int id) { + // MainActivity.this.removeDialog(Id.dialog.help); + // mPreferences.setHasSeenHelp(true); + // } + // }); + // + // return alert.create(); + // } + + default: { + return super.onCreateDialog(id); + } + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, Id.menu.option.preferences, 0, R.string.menu_preferences) + .setIcon(R.drawable.ic_menu_settings) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + menu.add(0, Id.menu.option.about, 1, R.string.menu_about).setIcon(R.drawable.ic_menu_about) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case Id.menu.option.about: { + startActivity(new Intent(this, AboutActivity.class)); + return true; + } + + case Id.menu.option.preferences: { + startActivity(new Intent(this, PreferencesActivity.class)); + return true; + } + + default: { + break; + } + } + return false; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + + TextView nameTextView = (TextView) v.findViewById(R.id.accountName); + if (nameTextView != null) { + menu.setHeaderTitle(nameTextView.getText()); + menu.add(0, Id.menu.delete, 0, R.string.menu_deleteAccount); + } + } + +} \ No newline at end of file diff --git a/org_apg/src/org/thialfihar/android/apg/ui/PreferencesActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/PreferencesActivity.java new file mode 100644 index 000000000..8b7ed59a7 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/PreferencesActivity.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.openpgp.PGPEncryptedData; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Constants; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.Preferences; +import org.thialfihar.android.apg.ui.widget.IntegerListPreference; +import org.thialfihar.android.apg.R; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.app.SherlockPreferenceActivity; +import com.actionbarsherlock.view.MenuItem; + +import android.content.Intent; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.PreferenceScreen; + + +public class PreferencesActivity extends SherlockPreferenceActivity { + private IntegerListPreference mPassPhraseCacheTtl = null; + private IntegerListPreference mEncryptionAlgorithm = null; + private IntegerListPreference mHashAlgorithm = null; + private IntegerListPreference mMessageCompression = null; + private IntegerListPreference mFileCompression = null; + private CheckBoxPreference mAsciiArmour = null; + private CheckBoxPreference mForceV3Signatures = null; + private PreferenceScreen mKeyServerPreference = null; + private Preferences mPreferences; + + @Override + protected void onCreate(Bundle savedInstanceState) { + mPreferences = Preferences.getPreferences(this); + super.onCreate(savedInstanceState); + + final ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + + addPreferencesFromResource(R.xml.apg_preferences); + + mPassPhraseCacheTtl = (IntegerListPreference) findPreference(Constants.pref.PASS_PHRASE_CACHE_TTL); + mPassPhraseCacheTtl.setValue("" + mPreferences.getPassPhraseCacheTtl()); + mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry()); + mPassPhraseCacheTtl + .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + mPassPhraseCacheTtl.setValue(newValue.toString()); + mPassPhraseCacheTtl.setSummary(mPassPhraseCacheTtl.getEntry()); + mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString())); + BaseActivity.startCacheService(PreferencesActivity.this, mPreferences); + return false; + } + }); + + mEncryptionAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_ENCRYPTION_ALGORITHM); + int valueIds[] = { PGPEncryptedData.AES_128, PGPEncryptedData.AES_192, + PGPEncryptedData.AES_256, PGPEncryptedData.BLOWFISH, PGPEncryptedData.TWOFISH, + PGPEncryptedData.CAST5, PGPEncryptedData.DES, PGPEncryptedData.TRIPLE_DES, + PGPEncryptedData.IDEA, }; + String entries[] = { "AES-128", "AES-192", "AES-256", "Blowfish", "Twofish", "CAST5", + "DES", "Triple DES", "IDEA", }; + String values[] = new String[valueIds.length]; + for (int i = 0; i < values.length; ++i) { + values[i] = "" + valueIds[i]; + } + mEncryptionAlgorithm.setEntries(entries); + mEncryptionAlgorithm.setEntryValues(values); + mEncryptionAlgorithm.setValue("" + mPreferences.getDefaultEncryptionAlgorithm()); + mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry()); + mEncryptionAlgorithm + .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + mEncryptionAlgorithm.setValue(newValue.toString()); + mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry()); + mPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue + .toString())); + return false; + } + }); + + mHashAlgorithm = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_HASH_ALGORITHM); + valueIds = new int[] { HashAlgorithmTags.MD5, HashAlgorithmTags.RIPEMD160, + HashAlgorithmTags.SHA1, HashAlgorithmTags.SHA224, HashAlgorithmTags.SHA256, + HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA512, }; + entries = new String[] { "MD5", "RIPEMD-160", "SHA-1", "SHA-224", "SHA-256", "SHA-384", + "SHA-512", }; + values = new String[valueIds.length]; + for (int i = 0; i < values.length; ++i) { + values[i] = "" + valueIds[i]; + } + mHashAlgorithm.setEntries(entries); + mHashAlgorithm.setEntryValues(values); + mHashAlgorithm.setValue("" + mPreferences.getDefaultHashAlgorithm()); + mHashAlgorithm.setSummary(mHashAlgorithm.getEntry()); + mHashAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + mHashAlgorithm.setValue(newValue.toString()); + mHashAlgorithm.setSummary(mHashAlgorithm.getEntry()); + mPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString())); + return false; + } + }); + + mMessageCompression = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_MESSAGE_COMPRESSION); + valueIds = new int[] { Id.choice.compression.none, Id.choice.compression.zip, + Id.choice.compression.zlib, Id.choice.compression.bzip2, }; + entries = new String[] { + getString(R.string.choice_none) + " (" + getString(R.string.fast) + ")", + "ZIP (" + getString(R.string.fast) + ")", + "ZLIB (" + getString(R.string.fast) + ")", + "BZIP2 (" + getString(R.string.very_slow) + ")", }; + values = new String[valueIds.length]; + for (int i = 0; i < values.length; ++i) { + values[i] = "" + valueIds[i]; + } + mMessageCompression.setEntries(entries); + mMessageCompression.setEntryValues(values); + mMessageCompression.setValue("" + mPreferences.getDefaultMessageCompression()); + mMessageCompression.setSummary(mMessageCompression.getEntry()); + mMessageCompression + .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + mMessageCompression.setValue(newValue.toString()); + mMessageCompression.setSummary(mMessageCompression.getEntry()); + mPreferences.setDefaultMessageCompression(Integer.parseInt(newValue + .toString())); + return false; + } + }); + + mFileCompression = (IntegerListPreference) findPreference(Constants.pref.DEFAULT_FILE_COMPRESSION); + mFileCompression.setEntries(entries); + mFileCompression.setEntryValues(values); + mFileCompression.setValue("" + mPreferences.getDefaultFileCompression()); + mFileCompression.setSummary(mFileCompression.getEntry()); + mFileCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + mFileCompression.setValue(newValue.toString()); + mFileCompression.setSummary(mFileCompression.getEntry()); + mPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString())); + return false; + } + }); + + mAsciiArmour = (CheckBoxPreference) findPreference(Constants.pref.DEFAULT_ASCII_ARMOUR); + mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour()); + mAsciiArmour.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + mAsciiArmour.setChecked((Boolean) newValue); + mPreferences.setDefaultAsciiArmour((Boolean) newValue); + return false; + } + }); + + mForceV3Signatures = (CheckBoxPreference) findPreference(Constants.pref.FORCE_V3_SIGNATURES); + mForceV3Signatures.setChecked(mPreferences.getForceV3Signatures()); + mForceV3Signatures + .setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + mForceV3Signatures.setChecked((Boolean) newValue); + mPreferences.setForceV3Signatures((Boolean) newValue); + return false; + } + }); + + mKeyServerPreference = (PreferenceScreen) findPreference(Constants.pref.KEY_SERVERS); + String servers[] = mPreferences.getKeyServers(); + mKeyServerPreference.setSummary(getResources().getString(R.string.nKeyServers, + servers.length)); + mKeyServerPreference + .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(PreferencesActivity.this, + KeyServerPreferenceActivity.class); + intent.putExtra(Apg.EXTRA_KEY_SERVERS, mPreferences.getKeyServers()); + startActivityForResult(intent, Id.request.key_server_preference); + return false; + } + }); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case Id.request.key_server_preference: { + if (resultCode == RESULT_CANCELED || data == null) { + return; + } + String servers[] = data.getStringArrayExtra(Apg.EXTRA_KEY_SERVERS); + mPreferences.setKeyServers(servers); + mKeyServerPreference.setSummary(getResources().getString(R.string.nKeyServers, + servers.length)); + break; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + break; + } + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case android.R.id.home: + startActivity(new Intent(this, MainActivity.class)); + return true; + + default: + break; + + } + return false; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/PublicKeyListActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/PublicKeyListActivity.java new file mode 100644 index 000000000..a2551b769 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/PublicKeyListActivity.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Constants; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.R; + +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; + +import android.content.Intent; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.View; +import android.widget.ExpandableListView; +import android.widget.ExpandableListView.ExpandableListContextMenuInfo; + +public class PublicKeyListActivity extends KeyListActivity { + @Override + public void onCreate(Bundle savedInstanceState) { + mExportFilename = Constants.path.APP_DIR + "/pubexport.asc"; + mKeyType = Id.type.public_key; + super.onCreate(savedInstanceState); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(1, Id.menu.option.search, 0, R.string.menu_search) + .setIcon(R.drawable.ic_menu_search).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + menu.add(1, Id.menu.option.scanQRCode, 1, R.string.menu_scanQRCode) + .setIcon(R.drawable.ic_menu_scan_qrcode) + .setShowAsAction( + MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + menu.add(1, Id.menu.option.key_server, 2, R.string.menu_keyServer) + .setIcon(R.drawable.ic_menu_search_list) + .setShowAsAction( + MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + menu.add(0, Id.menu.option.import_keys, 3, R.string.menu_importKeys).setShowAsAction( + MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + menu.add(0, Id.menu.option.export_keys, 4, R.string.menu_exportKeys).setShowAsAction( + MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case Id.menu.option.key_server: { + startActivity(new Intent(this, KeyServerQueryActivity.class)); + + return true; + } + case Id.menu.option.scanQRCode: { + Intent intent = new Intent(this, ImportFromQRCodeActivity.class); + intent.setAction(Apg.Intent.IMPORT_FROM_QR_CODE); + startActivityForResult(intent, Id.request.import_from_qr_code); + + return true; + } + + default: { + return super.onOptionsItemSelected(item); + } + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo; + int type = ExpandableListView.getPackedPositionType(info.packedPosition); + + if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { + // TODO: user id? menu.setHeaderTitle("Key"); + menu.add(0, Id.menu.export, 0, R.string.menu_exportKey); + menu.add(0, Id.menu.delete, 1, R.string.menu_deleteKey); + menu.add(0, Id.menu.update, 1, R.string.menu_updateKey); + menu.add(0, Id.menu.exportToServer, 1, R.string.menu_exportKeyToServer); + menu.add(0, Id.menu.signKey, 1, R.string.menu_signKey); + } + } + + @Override + public boolean onContextItemSelected(android.view.MenuItem menuItem) { + ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo(); + int type = ExpandableListView.getPackedPositionType(info.packedPosition); + int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); + + if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) { + return super.onContextItemSelected(menuItem); + } + + switch (menuItem.getItemId()) { + case Id.menu.update: { + mSelectedItem = groupPosition; + final int keyRingId = mListAdapter.getKeyRingId(groupPosition); + long keyId = 0; + Object keyRing = Apg.getKeyRing(keyRingId); + if (keyRing != null && keyRing instanceof PGPPublicKeyRing) { + keyId = Apg.getMasterKey((PGPPublicKeyRing) keyRing).getKeyID(); + } + if (keyId == 0) { + // this shouldn't happen + return true; + } + + Intent intent = new Intent(this, KeyServerQueryActivity.class); + intent.setAction(Apg.Intent.LOOK_UP_KEY_ID_AND_RETURN); + intent.putExtra(Apg.EXTRA_KEY_ID, keyId); + startActivityForResult(intent, Id.request.look_up_key_id); + + return true; + } + + case Id.menu.exportToServer: { + mSelectedItem = groupPosition; + final int keyRingId = mListAdapter.getKeyRingId(groupPosition); + + Intent intent = new Intent(this, KeyServerExportActivity.class); + intent.setAction(Apg.Intent.EXPORT_KEY_TO_SERVER); + intent.putExtra(Apg.EXTRA_KEY_ID, keyRingId); + startActivityForResult(intent, Id.request.export_to_server); + + return true; + } + + case Id.menu.signKey: { + mSelectedItem = groupPosition; + final int keyRingId = mListAdapter.getKeyRingId(groupPosition); + long keyId = 0; + Object keyRing = Apg.getKeyRing(keyRingId); + if (keyRing != null && keyRing instanceof PGPPublicKeyRing) { + keyId = Apg.getMasterKey((PGPPublicKeyRing) keyRing).getKeyID(); + } + + if (keyId == 0) { + // this shouldn't happen + return true; + } + + Intent intent = new Intent(this, SignKeyActivity.class); + intent.putExtra(Apg.EXTRA_KEY_ID, keyId); + startActivity(intent); + + return true; + } + + default: { + return super.onContextItemSelected(menuItem); + } + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case Id.request.look_up_key_id: { + if (resultCode == RESULT_CANCELED || data == null + || data.getStringExtra(Apg.EXTRA_TEXT) == null) { + return; + } + + Intent intent = new Intent(this, PublicKeyListActivity.class); + intent.setAction(Apg.Intent.IMPORT); + intent.putExtra(Apg.EXTRA_TEXT, data.getStringExtra(Apg.EXTRA_TEXT)); + handleIntent(intent); + break; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + break; + } + } + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/SecretKeyListActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/SecretKeyListActivity.java new file mode 100644 index 000000000..a1b5e753d --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/SecretKeyListActivity.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.AskForSecretKeyPassPhrase; +import org.thialfihar.android.apg.Constants; +import org.thialfihar.android.apg.Id; + +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; + +import android.app.Dialog; +import android.content.Intent; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.View; +import android.widget.ExpandableListView; +import android.widget.ExpandableListView.ExpandableListContextMenuInfo; +import android.widget.ExpandableListView.OnChildClickListener; + +import com.google.zxing.integration.android.IntentIntegrator; + +public class SecretKeyListActivity extends KeyListActivity implements OnChildClickListener { + @Override + public void onCreate(Bundle savedInstanceState) { + mExportFilename = Constants.path.APP_DIR + "/secexport.asc"; + mKeyType = Id.type.secret_key; + super.onCreate(savedInstanceState); + mList.setOnChildClickListener(this); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(3, Id.menu.option.search, 0, R.string.menu_search) + .setIcon(R.drawable.ic_menu_search).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + menu.add(1, Id.menu.option.create, 1, R.string.menu_createKey).setShowAsAction( + MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + menu.add(0, Id.menu.option.import_keys, 2, R.string.menu_importKeys).setShowAsAction( + MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + menu.add(0, Id.menu.option.export_keys, 3, R.string.menu_exportKeys).setShowAsAction( + MenuItem.SHOW_AS_ACTION_NEVER | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case Id.menu.option.create: { + createKey(); + return true; + } + + default: { + return super.onOptionsItemSelected(item); + } + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo; + int type = ExpandableListView.getPackedPositionType(info.packedPosition); + + if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { + // TODO: user id? menu.setHeaderTitle("Key"); + menu.add(0, Id.menu.edit, 0, R.string.menu_editKey); + menu.add(0, Id.menu.export, 1, R.string.menu_exportKey); + menu.add(0, Id.menu.delete, 2, R.string.menu_deleteKey); + menu.add(0, Id.menu.share, 2, R.string.menu_share); + } + } + + @Override + public boolean onContextItemSelected(android.view.MenuItem menuItem) { + ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuItem.getMenuInfo(); + int type = ExpandableListView.getPackedPositionType(info.packedPosition); + int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); + + if (type != ExpandableListView.PACKED_POSITION_TYPE_GROUP) { + return super.onContextItemSelected(menuItem); + } + + switch (menuItem.getItemId()) { + case Id.menu.edit: { + mSelectedItem = groupPosition; + checkPassPhraseAndEdit(); + return true; + } + + case Id.menu.share: { + mSelectedItem = groupPosition; + + long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()) + .getGroupId(mSelectedItem); + String msg = keyId + "," + Apg.getFingerPrint(keyId); + + new IntentIntegrator(this).shareText(msg); + } + + default: { + return super.onContextItemSelected(menuItem); + } + } + } + + public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, + int childPosition, long id) { + mSelectedItem = groupPosition; + checkPassPhraseAndEdit(); + return true; + } + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case Id.dialog.pass_phrase: { + long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()) + .getGroupId(mSelectedItem); + return AskForSecretKeyPassPhrase.createDialog(this, keyId, this); + } + + default: { + return super.onCreateDialog(id); + } + } + } + + public void checkPassPhraseAndEdit() { + long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem); + String passPhrase = Apg.getCachedPassPhrase(keyId); + if (passPhrase == null) { + showDialog(Id.dialog.pass_phrase); + } else { + Apg.setEditPassPhrase(passPhrase); + editKey(); + } + } + + @Override + public void passPhraseCallback(long keyId, String passPhrase) { + super.passPhraseCallback(keyId, passPhrase); + Apg.setEditPassPhrase(passPhrase); + editKey(); + } + + private void createKey() { + Apg.setEditPassPhrase(""); + Intent intent = new Intent(Apg.Intent.CREATE_KEY); + startActivityForResult(intent, Id.message.create_key); + } + + private void editKey() { + long keyId = ((KeyListAdapter) mList.getExpandableListAdapter()).getGroupId(mSelectedItem); + Intent intent = new Intent(Apg.Intent.EDIT_KEY); + intent.putExtra(Apg.EXTRA_KEY_ID, keyId); + startActivityForResult(intent, Id.message.edit_key); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case Id.message.create_key: // intentionally no break + case Id.message.edit_key: { + if (resultCode == RESULT_OK) { + refreshList(); + } + break; + } + + default: { + break; + } + } + + super.onActivityResult(requestCode, resultCode, data); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/SelectPublicKeyListActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/SelectPublicKeyListActivity.java new file mode 100644 index 000000000..07ad60d89 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/SelectPublicKeyListActivity.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import java.util.Vector; + +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.ui.widget.SelectPublicKeyListAdapter; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuItem; + +import android.app.SearchManager; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; + +public class SelectPublicKeyListActivity extends BaseActivity { + protected ListView mList; + protected SelectPublicKeyListAdapter mListAdapter; + protected View mFilterLayout; + protected Button mClearFilterButton; + protected TextView mFilterInfo; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(false); + actionBar.setHomeButtonEnabled(false); + + setContentView(R.layout.select_public_key); + + setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); + + mList = (ListView) findViewById(R.id.list); + // needed in Android 1.5, where the XML attribute gets ignored + mList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + + mFilterLayout = findViewById(R.id.layout_filter); + mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo); + mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear); + + mClearFilterButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + handleIntent(new Intent()); + } + }); + + handleIntent(getIntent()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + handleIntent(intent); + } + + private void handleIntent(Intent intent) { + String searchString = null; + if (Intent.ACTION_SEARCH.equals(intent.getAction())) { + searchString = intent.getStringExtra(SearchManager.QUERY); + if (searchString != null && searchString.trim().length() == 0) { + searchString = null; + } + } + + long selectedKeyIds[] = null; + selectedKeyIds = intent.getLongArrayExtra(Apg.EXTRA_SELECTION); + + if (selectedKeyIds == null) { + Vector vector = new Vector(); + for (int i = 0; i < mList.getCount(); ++i) { + if (mList.isItemChecked(i)) { + vector.add(mList.getItemIdAtPosition(i)); + } + } + selectedKeyIds = new long[vector.size()]; + for (int i = 0; i < vector.size(); ++i) { + selectedKeyIds[i] = vector.get(i); + } + } + + if (searchString == null) { + mFilterLayout.setVisibility(View.GONE); + } else { + mFilterLayout.setVisibility(View.VISIBLE); + mFilterInfo.setText(getString(R.string.filterInfo, searchString)); + } + + if (mListAdapter != null) { + mListAdapter.cleanup(); + } + + mListAdapter = new SelectPublicKeyListAdapter(this, mList, searchString, selectedKeyIds); + mList.setAdapter(mListAdapter); + + if (selectedKeyIds != null) { + for (int i = 0; i < mListAdapter.getCount(); ++i) { + long keyId = mListAdapter.getItemId(i); + for (int j = 0; j < selectedKeyIds.length; ++j) { + if (keyId == selectedKeyIds[j]) { + mList.setItemChecked(i, true); + break; + } + } + } + } + } + + private void cancelClicked() { + setResult(RESULT_CANCELED, null); + finish(); + } + + private void okClicked() { + Intent data = new Intent(); + Vector keys = new Vector(); + Vector userIds = new Vector(); + for (int i = 0; i < mList.getCount(); ++i) { + if (mList.isItemChecked(i)) { + keys.add(mList.getItemIdAtPosition(i)); + userIds.add((String) mList.getItemAtPosition(i)); + } + } + long selectedKeyIds[] = new long[keys.size()]; + for (int i = 0; i < keys.size(); ++i) { + selectedKeyIds[i] = keys.get(i); + } + String userIdArray[] = new String[0]; + data.putExtra(Apg.EXTRA_SELECTION, selectedKeyIds); + data.putExtra(Apg.EXTRA_USER_IDS, userIds.toArray(userIdArray)); + setResult(RESULT_OK, data); + finish(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, Id.menu.option.search, 0, R.string.menu_search).setIcon( + android.R.drawable.ic_menu_search); + menu.add(1, Id.menu.option.cancel, 0, R.string.btn_doNotSave).setShowAsAction( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + menu.add(1, Id.menu.option.okay, 1, R.string.btn_okay).setShowAsAction( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case Id.menu.option.okay: + okClicked(); + return true; + + case Id.menu.option.cancel: + cancelClicked(); + return true; + + default: + break; + + } + return false; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/SelectSecretKeyListActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/SelectSecretKeyListActivity.java new file mode 100644 index 000000000..dec88dd7f --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/SelectSecretKeyListActivity.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.ui.widget.SelectSecretKeyListAdapter; + +import com.actionbarsherlock.app.ActionBar; +import com.actionbarsherlock.view.Menu; + +import android.app.SearchManager; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; + +public class SelectSecretKeyListActivity extends BaseActivity { + protected ListView mList; + protected SelectSecretKeyListAdapter mListAdapter; + protected View mFilterLayout; + protected Button mClearFilterButton; + protected TextView mFilterInfo; + + protected long mSelectedKeyId = 0; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(false); + actionBar.setHomeButtonEnabled(false); + + setContentView(R.layout.select_secret_key); + + setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); + + mList = (ListView) findViewById(R.id.list); + + mList.setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView adapterView, View view, int position, long id) { + Intent data = new Intent(); + data.putExtra(Apg.EXTRA_KEY_ID, id); + data.putExtra(Apg.EXTRA_USER_ID, (String) mList.getItemAtPosition(position)); + setResult(RESULT_OK, data); + finish(); + } + }); + + mFilterLayout = findViewById(R.id.layout_filter); + mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo); + mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear); + + mClearFilterButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + handleIntent(new Intent()); + } + }); + + handleIntent(getIntent()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + handleIntent(intent); + } + + private void handleIntent(Intent intent) { + String searchString = null; + if (Intent.ACTION_SEARCH.equals(intent.getAction())) { + searchString = intent.getStringExtra(SearchManager.QUERY); + if (searchString != null && searchString.trim().length() == 0) { + searchString = null; + } + } + + if (searchString == null) { + mFilterLayout.setVisibility(View.GONE); + } else { + mFilterLayout.setVisibility(View.VISIBLE); + mFilterInfo.setText(getString(R.string.filterInfo, searchString)); + } + + if (mListAdapter != null) { + mListAdapter.cleanup(); + } + + mListAdapter = new SelectSecretKeyListAdapter(this, mList, searchString); + mList.setAdapter(mListAdapter); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, Id.menu.option.search, 0, R.string.menu_search).setIcon( + android.R.drawable.ic_menu_search); + return true; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/SignKeyActivity.java b/org_apg/src/org/thialfihar/android/apg/ui/SignKeyActivity.java new file mode 100644 index 000000000..d853873e1 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/SignKeyActivity.java @@ -0,0 +1,327 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SignatureException; +import java.util.Iterator; + +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPPrivateKey; +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPPublicKeyRing; +import org.spongycastle.openpgp.PGPSecretKey; +import org.spongycastle.openpgp.PGPSignature; +import org.spongycastle.openpgp.PGPSignatureGenerator; +import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.spongycastle.openpgp.PGPSignatureSubpacketVector; +import org.spongycastle.openpgp.PGPUtil; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Constants; +import org.thialfihar.android.apg.HkpKeyServer; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.R; + +import com.actionbarsherlock.view.MenuItem; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.Spinner; +import android.widget.Toast; + +/** + * gpg --sign-key + * + * signs the specified public key with the specified secret master key + */ +public class SignKeyActivity extends BaseActivity { + private static final String TAG = "SignKeyActivity"; + + private long pubKeyId = 0; + private long masterKeyId = 0; + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case android.R.id.home: + startActivity(new Intent(this, PublicKeyListActivity.class)); + return true; + + default: + break; + + } + return false; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // check we havent already signed it + setContentView(R.layout.sign_key_layout); + + final Spinner keyServer = (Spinner) findViewById(R.id.keyServer); + ArrayAdapter adapter = new ArrayAdapter(this, + android.R.layout.simple_spinner_item, mPreferences.getKeyServers()); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + keyServer.setAdapter(adapter); + + final CheckBox sendKey = (CheckBox) findViewById(R.id.sendKey); + if (!sendKey.isChecked()) { + keyServer.setEnabled(false); + } else { + keyServer.setEnabled(true); + } + + sendKey.setOnCheckedChangeListener(new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (!isChecked) { + keyServer.setEnabled(false); + } else { + keyServer.setEnabled(true); + } + } + }); + + Button sign = (Button) findViewById(R.id.sign); + sign.setEnabled(false); // disabled until the user selects a key to sign with + sign.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (pubKeyId != 0) { + initiateSigning(); + } + } + }); + + pubKeyId = getIntent().getLongExtra(Apg.EXTRA_KEY_ID, 0); + if (pubKeyId == 0) { + finish(); // nothing to do if we dont know what key to sign + } else { + // kick off the SecretKey selection activity so the user chooses which key to sign with + // first + Intent intent = new Intent(this, SelectSecretKeyListActivity.class); + startActivityForResult(intent, Id.request.secret_keys); + } + } + + /** + * handles the UI bits of the signing process on the UI thread + */ + private void initiateSigning() { + PGPPublicKeyRing pubring = Apg.getPublicKeyRing(pubKeyId); + if (pubring != null) { + // if we have already signed this key, dont bother doing it again + boolean alreadySigned = false; + + @SuppressWarnings("unchecked") + Iterator itr = pubring.getPublicKey(pubKeyId).getSignatures(); + while (itr.hasNext()) { + PGPSignature sig = itr.next(); + if (sig.getKeyID() == masterKeyId) { + alreadySigned = true; + break; + } + } + + if (!alreadySigned) { + /* + * get the user's passphrase for this key (if required) + */ + String passphrase = Apg.getCachedPassPhrase(masterKeyId); + if (passphrase == null) { + showDialog(Id.dialog.pass_phrase); + return; // bail out; need to wait until the user has entered the passphrase + // before trying again + } else { + startSigning(); + } + } else { + final Bundle status = new Bundle(); + Message msg = new Message(); + + status.putString(Apg.EXTRA_ERROR, "Key has already been signed"); + + status.putInt(Constants.extras.STATUS, Id.message.done); + + msg.setData(status); + sendMessage(msg); + + setResult(Id.return_value.error); + finish(); + } + } + } + + @Override + public long getSecretKeyId() { + return masterKeyId; + } + + @Override + public void passPhraseCallback(long keyId, String passPhrase) { + super.passPhraseCallback(keyId, passPhrase); + startSigning(); + } + + /** + * kicks off the actual signing process on a background thread + */ + private void startSigning() { + showDialog(Id.dialog.signing); + startThread(); + } + + @Override + public void run() { + final Bundle status = new Bundle(); + Message msg = new Message(); + + try { + String passphrase = Apg.getCachedPassPhrase(masterKeyId); + if (passphrase == null || passphrase.length() <= 0) { + status.putString(Apg.EXTRA_ERROR, "Unable to obtain passphrase"); + } else { + PGPPublicKeyRing pubring = Apg.getPublicKeyRing(pubKeyId); + + /* + * sign the incoming key + */ + PGPSecretKey secretKey = Apg.getSecretKey(masterKeyId); + PGPPrivateKey signingKey = secretKey.extractPrivateKey(passphrase.toCharArray(), + BouncyCastleProvider.PROVIDER_NAME); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(secretKey.getPublicKey() + .getAlgorithm(), PGPUtil.SHA256, BouncyCastleProvider.PROVIDER_NAME); + sGen.initSign(PGPSignature.DIRECT_KEY, signingKey); + + PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); + + PGPSignatureSubpacketVector packetVector = spGen.generate(); + sGen.setHashedSubpackets(packetVector); + + PGPPublicKey signedKey = PGPPublicKey.addCertification( + pubring.getPublicKey(pubKeyId), sGen.generate()); + pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey); + + // check if we need to send the key to the server or not + CheckBox sendKey = (CheckBox) findViewById(R.id.sendKey); + if (sendKey.isChecked()) { + Spinner keyServer = (Spinner) findViewById(R.id.keyServer); + HkpKeyServer server = new HkpKeyServer((String) keyServer.getSelectedItem()); + + /* + * upload the newly signed key to the key server + */ + + Apg.uploadKeyRingToServer(server, pubring); + } + + // store the signed key in our local cache + int retval = Apg.storeKeyRingInCache(pubring); + if (retval != Id.return_value.ok && retval != Id.return_value.updated) { + status.putString(Apg.EXTRA_ERROR, "Failed to store signed key in local cache"); + } + } + } catch (PGPException e) { + Log.e(TAG, "Failed to sign key", e); + status.putString(Apg.EXTRA_ERROR, "Failed to sign key"); + status.putInt(Constants.extras.STATUS, Id.message.done); + return; + } catch (NoSuchAlgorithmException e) { + Log.e(TAG, "Failed to sign key", e); + status.putString(Apg.EXTRA_ERROR, "Failed to sign key"); + status.putInt(Constants.extras.STATUS, Id.message.done); + return; + } catch (NoSuchProviderException e) { + Log.e(TAG, "Failed to sign key", e); + status.putString(Apg.EXTRA_ERROR, "Failed to sign key"); + status.putInt(Constants.extras.STATUS, Id.message.done); + return; + } catch (SignatureException e) { + Log.e(TAG, "Failed to sign key", e); + status.putString(Apg.EXTRA_ERROR, "Failed to sign key"); + status.putInt(Constants.extras.STATUS, Id.message.done); + return; + } + + status.putInt(Constants.extras.STATUS, Id.message.done); + + msg.setData(status); + sendMessage(msg); + + if (status.containsKey(Apg.EXTRA_ERROR)) { + setResult(Id.return_value.error); + } else { + setResult(Id.return_value.ok); + } + + finish(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case Id.request.secret_keys: { + if (resultCode == RESULT_OK) { + masterKeyId = data.getLongExtra(Apg.EXTRA_KEY_ID, 0); + + // re-enable the sign button so the user can initiate the sign process + Button sign = (Button) findViewById(R.id.sign); + sign.setEnabled(true); + } + + break; + } + + default: { + super.onActivityResult(requestCode, resultCode, data); + } + } + } + + @Override + public void doneCallback(Message msg) { + super.doneCallback(msg); + + removeDialog(Id.dialog.signing); + + Bundle data = msg.getData(); + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(this, getString(R.string.errorMessage, error), Toast.LENGTH_SHORT) + .show(); + return; + } + + Toast.makeText(this, R.string.keySignSuccess, Toast.LENGTH_SHORT).show(); + finish(); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/widget/DashboardLayout.java b/org_apg/src/org/thialfihar/android/apg/ui/widget/DashboardLayout.java new file mode 100644 index 000000000..8a516ad41 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/widget/DashboardLayout.java @@ -0,0 +1,186 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +/** + * Custom layout that arranges children in a grid-like manner, optimizing for even horizontal and + * vertical whitespace. + */ +public class DashboardLayout extends ViewGroup { + private static final int UNEVEN_GRID_PENALTY_MULTIPLIER = 10; + + private int mMaxChildWidth = 0; + private int mMaxChildHeight = 0; + + public DashboardLayout(Context context) { + super(context, null); + } + + public DashboardLayout(Context context, AttributeSet attrs) { + super(context, attrs, 0); + } + + public DashboardLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mMaxChildWidth = 0; + mMaxChildHeight = 0; + + // Measure once to find the maximum child size. + + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST); + int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.AT_MOST); + + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + + mMaxChildWidth = Math.max(mMaxChildWidth, child.getMeasuredWidth()); + mMaxChildHeight = Math.max(mMaxChildHeight, child.getMeasuredHeight()); + } + + // Measure again for each child to be exactly the same size. + + childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxChildWidth, MeasureSpec.EXACTLY); + childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxChildHeight, MeasureSpec.EXACTLY); + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + setMeasuredDimension(resolveSize(mMaxChildWidth, widthMeasureSpec), + resolveSize(mMaxChildHeight, heightMeasureSpec)); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int width = r - l; + int height = b - t; + + final int count = getChildCount(); + + // Calculate the number of visible children. + int visibleCount = 0; + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } + ++visibleCount; + } + + if (visibleCount == 0) { + return; + } + + // Calculate what number of rows and columns will optimize for even horizontal and + // vertical whitespace between items. Start with a 1 x N grid, then try 2 x N, and so on. + int bestSpaceDifference = Integer.MAX_VALUE; + int spaceDifference; + + // Horizontal and vertical space between items + int hSpace = 0; + int vSpace = 0; + + int cols = 1; + int rows; + + while (true) { + rows = (visibleCount - 1) / cols + 1; + + hSpace = ((width - mMaxChildWidth * cols) / (cols + 1)); + vSpace = ((height - mMaxChildHeight * rows) / (rows + 1)); + + spaceDifference = Math.abs(vSpace - hSpace); + if (rows * cols != visibleCount) { + spaceDifference *= UNEVEN_GRID_PENALTY_MULTIPLIER; + } else if (rows * mMaxChildHeight > height || cols * mMaxChildWidth > width) { + spaceDifference *= UNEVEN_GRID_PENALTY_MULTIPLIER; + } + + if (spaceDifference < bestSpaceDifference) { + // Found a better whitespace squareness/ratio + bestSpaceDifference = spaceDifference; + + // If we found a better whitespace squareness and there's only 1 row, this is + // the best we can do. + if (rows == 1) { + break; + } + } else { + // This is a worse whitespace ratio, use the previous value of cols and exit. + --cols; + rows = (visibleCount - 1) / cols + 1; + hSpace = ((width - mMaxChildWidth * cols) / (cols + 1)); + vSpace = ((height - mMaxChildHeight * rows) / (rows + 1)); + break; + } + + ++cols; + } + + // Lay out children based on calculated best-fit number of rows and cols. + + // If we chose a layout that has negative horizontal or vertical space, force it to zero. + hSpace = Math.max(0, hSpace); + vSpace = Math.max(0, vSpace); + + // Re-use width/height variables to be child width/height. + width = (width - hSpace * (cols + 1)) / cols; + height = (height - vSpace * (rows + 1)) / rows; + + int left, top; + int col, row; + int visibleIndex = 0; + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } + + row = visibleIndex / cols; + col = visibleIndex % cols; + + left = hSpace * (col + 1) + width * col; + top = vSpace * (row + 1) + height * row; + + child.layout(left, top, (hSpace == 0 && col == cols - 1) ? r : (left + width), + (vSpace == 0 && row == rows - 1) ? b : (top + height)); + ++visibleIndex; + } + } +} \ No newline at end of file diff --git a/org_apg/src/org/thialfihar/android/apg/ui/widget/Editor.java b/org_apg/src/org/thialfihar/android/apg/ui/widget/Editor.java new file mode 100644 index 000000000..a7200fad0 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/widget/Editor.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui.widget; + +public interface Editor { + public interface EditorListener { + public void onDeleted(Editor editor); + } + + public void setEditorListener(EditorListener listener); +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/widget/IntegerListPreference.java b/org_apg/src/org/thialfihar/android/apg/ui/widget/IntegerListPreference.java new file mode 100644 index 000000000..1d5f9e3d7 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/widget/IntegerListPreference.java @@ -0,0 +1,95 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.thialfihar.android.apg.ui.widget; + +import android.content.Context; +import android.preference.ListPreference; +import android.util.AttributeSet; + +/** + * A list preference which persists its values as integers instead of strings. + * Code reading the values should use + * {@link android.content.SharedPreferences#getInt}. + * When using XML-declared arrays for entry values, the arrays should be regular + * string arrays containing valid integer values. + * + * @author Rodrigo Damazio + */ +public class IntegerListPreference extends ListPreference { + + public IntegerListPreference(Context context) { + super(context); + + verifyEntryValues(null); + } + + public IntegerListPreference(Context context, AttributeSet attrs) { + super(context, attrs); + + verifyEntryValues(null); + } + + @Override + public void setEntryValues(CharSequence[] entryValues) { + CharSequence[] oldValues = getEntryValues(); + super.setEntryValues(entryValues); + verifyEntryValues(oldValues); + } + + @Override + public void setEntryValues(int entryValuesResId) { + CharSequence[] oldValues = getEntryValues(); + super.setEntryValues(entryValuesResId); + verifyEntryValues(oldValues); + } + + @Override + protected String getPersistedString(String defaultReturnValue) { + // During initial load, there's no known default value + int defaultIntegerValue = Integer.MIN_VALUE; + if (defaultReturnValue != null) { + defaultIntegerValue = Integer.parseInt(defaultReturnValue); + } + + // When the list preference asks us to read a string, instead read an + // integer. + int value = getPersistedInt(defaultIntegerValue); + return Integer.toString(value); + } + + @Override + protected boolean persistString(String value) { + // When asked to save a string, instead save an integer + return persistInt(Integer.parseInt(value)); + } + + private void verifyEntryValues(CharSequence[] oldValues) { + CharSequence[] entryValues = getEntryValues(); + if (entryValues == null) { + return; + } + + for (CharSequence entryValue : entryValues) { + try { + Integer.parseInt(entryValue.toString()); + } catch (NumberFormatException nfe) { + super.setEntryValues(oldValues); + throw nfe; + } + } + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java b/org_apg/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java new file mode 100644 index 000000000..9f7b756eb --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/widget/KeyEditor.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui.widget; + +import org.spongycastle.openpgp.PGPPublicKey; +import org.spongycastle.openpgp.PGPSecretKey; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.util.Choice; +import org.thialfihar.android.apg.R; + +import android.app.DatePickerDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.util.AttributeSet; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.DatePicker; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TextView; + +import java.text.DateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Vector; + +public class KeyEditor extends LinearLayout implements Editor, OnClickListener { + private PGPSecretKey mKey; + + private EditorListener mEditorListener = null; + + private boolean mIsMasterKey; + ImageButton mDeleteButton; + TextView mAlgorithm; + TextView mKeyId; + Spinner mUsage; + TextView mCreationDate; + Button mExpiryDateButton; + GregorianCalendar mExpiryDate; + + private DatePickerDialog.OnDateSetListener mExpiryDateSetListener = new DatePickerDialog.OnDateSetListener() { + public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) { + GregorianCalendar date = new GregorianCalendar(year, monthOfYear, dayOfMonth); + setExpiryDate(date); + } + }; + + public KeyEditor(Context context) { + super(context); + } + + public KeyEditor(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + setDrawingCacheEnabled(true); + setAlwaysDrawnWithCacheEnabled(true); + + mAlgorithm = (TextView) findViewById(R.id.algorithm); + mKeyId = (TextView) findViewById(R.id.keyId); + mCreationDate = (TextView) findViewById(R.id.creation); + mExpiryDateButton = (Button) findViewById(R.id.expiry); + mUsage = (Spinner) findViewById(R.id.usage); + Choice choices[] = { + new Choice(Id.choice.usage.sign_only, getResources().getString( + R.string.choice_signOnly)), + new Choice(Id.choice.usage.encrypt_only, getResources().getString( + R.string.choice_encryptOnly)), + new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString( + R.string.choice_signAndEncrypt)), }; + ArrayAdapter adapter = new ArrayAdapter(getContext(), + android.R.layout.simple_spinner_item, choices); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mUsage.setAdapter(adapter); + + mDeleteButton = (ImageButton) findViewById(R.id.delete); + mDeleteButton.setOnClickListener(this); + + setExpiryDate(null); + + mExpiryDateButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + GregorianCalendar date = mExpiryDate; + if (date == null) { + date = new GregorianCalendar(); + } + + DatePickerDialog dialog = new DatePickerDialog(getContext(), + mExpiryDateSetListener, date.get(Calendar.YEAR), date.get(Calendar.MONTH), + date.get(Calendar.DAY_OF_MONTH)); + dialog.setCancelable(true); + dialog.setButton(Dialog.BUTTON_NEGATIVE, getContext() + .getString(R.string.btn_noDate), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + setExpiryDate(null); + } + }); + dialog.show(); + } + }); + + super.onFinishInflate(); + } + + public void setValue(PGPSecretKey key, boolean isMasterKey, int usage) { + mKey = key; + + mIsMasterKey = isMasterKey; + if (mIsMasterKey) { + mDeleteButton.setVisibility(View.INVISIBLE); + } + + mAlgorithm.setText(Apg.getAlgorithmInfo(key)); + String keyId1Str = Apg.getSmallFingerPrint(key.getKeyID()); + String keyId2Str = Apg.getSmallFingerPrint(key.getKeyID() >> 32); + mKeyId.setText(keyId1Str + " " + keyId2Str); + + Vector choices = new Vector(); + boolean isElGamalKey = (key.getPublicKey().getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT); + if (!isElGamalKey) { + choices.add(new Choice(Id.choice.usage.sign_only, getResources().getString( + R.string.choice_signOnly))); + } + if (!mIsMasterKey) { + choices.add(new Choice(Id.choice.usage.encrypt_only, getResources().getString( + R.string.choice_encryptOnly))); + } + if (!isElGamalKey) { + choices.add(new Choice(Id.choice.usage.sign_and_encrypt, getResources().getString( + R.string.choice_signAndEncrypt))); + } + + ArrayAdapter adapter = new ArrayAdapter(getContext(), + android.R.layout.simple_spinner_item, choices); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mUsage.setAdapter(adapter); + + // Set value in choice dropdown to key + int selectId = 0; + if (Apg.isEncryptionKey(key)) { + if (Apg.isSigningKey(key)) { + selectId = Id.choice.usage.sign_and_encrypt; + } else { + selectId = Id.choice.usage.encrypt_only; + } + } else { + // set usage if it is predefined + if (usage != -1) { + selectId = usage; + } else { + selectId = Id.choice.usage.sign_only; + } + + } + + for (int i = 0; i < choices.size(); ++i) { + if (choices.get(i).getId() == selectId) { + mUsage.setSelection(i); + break; + } + } + + GregorianCalendar cal = new GregorianCalendar(); + cal.setTime(Apg.getCreationDate(key)); + mCreationDate.setText(DateFormat.getDateInstance().format(cal.getTime())); + cal = new GregorianCalendar(); + Date date = Apg.getExpiryDate(key); + if (date == null) { + setExpiryDate(null); + } else { + cal.setTime(Apg.getExpiryDate(key)); + setExpiryDate(cal); + } + + } + + public PGPSecretKey getValue() { + return mKey; + } + + public void onClick(View v) { + final ViewGroup parent = (ViewGroup) getParent(); + if (v == mDeleteButton) { + parent.removeView(this); + if (mEditorListener != null) { + mEditorListener.onDeleted(this); + } + } + } + + public void setEditorListener(EditorListener listener) { + mEditorListener = listener; + } + + private void setExpiryDate(GregorianCalendar date) { + mExpiryDate = date; + if (date == null) { + mExpiryDateButton.setText(R.string.none); + } else { + mExpiryDateButton.setText(DateFormat.getDateInstance().format(date.getTime())); + } + } + + public GregorianCalendar getExpiryDate() { + return mExpiryDate; + } + + public int getUsage() { + return ((Choice) mUsage.getSelectedItem()).getId(); + } + +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/widget/KeyServerEditor.java b/org_apg/src/org/thialfihar/android/apg/ui/widget/KeyServerEditor.java new file mode 100644 index 000000000..cbea7f031 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/widget/KeyServerEditor.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui.widget; + +import org.thialfihar.android.apg.R; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class KeyServerEditor extends LinearLayout implements Editor, OnClickListener { + private EditorListener mEditorListener = null; + + ImageButton mDeleteButton; + TextView mServer; + + public KeyServerEditor(Context context) { + super(context); + } + + public KeyServerEditor(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + setDrawingCacheEnabled(true); + setAlwaysDrawnWithCacheEnabled(true); + + mServer = (TextView) findViewById(R.id.server); + + mDeleteButton = (ImageButton) findViewById(R.id.delete); + mDeleteButton.setOnClickListener(this); + + super.onFinishInflate(); + } + + public void setValue(String value) { + mServer.setText(value); + } + + public String getValue() { + return mServer.getText().toString().trim(); + } + + public void onClick(View v) { + final ViewGroup parent = (ViewGroup)getParent(); + if (v == mDeleteButton) { + parent.removeView(this); + if (mEditorListener != null) { + mEditorListener.onDeleted(this); + } + } + } + + public void setEditorListener(EditorListener listener) { + mEditorListener = listener; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/widget/SectionView.java b/org_apg/src/org/thialfihar/android/apg/ui/widget/SectionView.java new file mode 100644 index 000000000..8f55c9133 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/widget/SectionView.java @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui.widget; + +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.PGPSecretKey; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.ui.widget.Editor.EditorListener; +import org.thialfihar.android.apg.util.Choice; +import org.thialfihar.android.apg.R; + +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TableRow; +import android.widget.TextView; +import android.widget.Toast; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.Vector; + +public class SectionView extends LinearLayout implements OnClickListener, EditorListener, Runnable { + private LayoutInflater mInflater; + private View mAdd; + private ViewGroup mEditors; + private TextView mTitle; + private int mType = 0; + + private Choice mNewKeyAlgorithmChoice; + private int mNewKeySize; + + volatile private PGPSecretKey mNewKey; + private ProgressDialog mProgressDialog; + private Thread mRunningThread = null; + + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + Bundle data = msg.getData(); + if (data != null) { + boolean closeProgressDialog = data.getBoolean("closeProgressDialog"); + if (closeProgressDialog) { + if (mProgressDialog != null) { + mProgressDialog.dismiss(); + mProgressDialog = null; + } + } + + String error = data.getString(Apg.EXTRA_ERROR); + if (error != null) { + Toast.makeText(getContext(), + getContext().getString(R.string.errorMessage, error), + Toast.LENGTH_SHORT).show(); + } + + boolean gotNewKey = data.getBoolean("gotNewKey"); + if (gotNewKey) { + KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, + mEditors, false); + view.setEditorListener(SectionView.this); + boolean isMasterKey = (mEditors.getChildCount() == 0); + view.setValue(mNewKey, isMasterKey, -1); + mEditors.addView(view); + SectionView.this.updateEditorsVisible(); + } + } + } + }; + + public SectionView(Context context) { + super(context); + } + + public SectionView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ViewGroup getEditors() { + return mEditors; + } + + public void setType(int type) { + mType = type; + switch (type) { + case Id.type.user_id: { + mTitle.setText(R.string.section_userIds); + break; + } + + case Id.type.key: { + mTitle.setText(R.string.section_keys); + break; + } + + default: { + break; + } + } + } + + /** {@inheritDoc} */ + @Override + protected void onFinishInflate() { + mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + setDrawingCacheEnabled(true); + setAlwaysDrawnWithCacheEnabled(true); + + mAdd = findViewById(R.id.header); + mAdd.setOnClickListener(this); + + mEditors = (ViewGroup) findViewById(R.id.editors); + mTitle = (TextView) findViewById(R.id.title); + + updateEditorsVisible(); + super.onFinishInflate(); + } + + /** {@inheritDoc} */ + public void onDeleted(Editor editor) { + this.updateEditorsVisible(); + } + + protected void updateEditorsVisible() { + final boolean hasChildren = mEditors.getChildCount() > 0; + mEditors.setVisibility(hasChildren ? View.VISIBLE : View.GONE); + } + + /** {@inheritDoc} */ + public void onClick(View v) { + switch (mType) { + case Id.type.user_id: { + UserIdEditor view = (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item, + mEditors, false); + view.setEditorListener(this); + if (mEditors.getChildCount() == 0) { + view.setIsMainUserId(true); + } + mEditors.addView(view); + break; + } + + case Id.type.key: { + AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); + + View view = mInflater.inflate(R.layout.create_key, null); + dialog.setView(view); + dialog.setTitle(R.string.title_createKey); + + boolean wouldBeMasterKey = (mEditors.getChildCount() == 0); + + final Spinner algorithm = (Spinner) view.findViewById(R.id.create_key_algorithm); + Vector choices = new Vector(); + choices.add(new Choice(Id.choice.algorithm.dsa, getResources().getString(R.string.dsa))); + if (!wouldBeMasterKey) { + choices.add(new Choice(Id.choice.algorithm.elgamal, getResources().getString( + R.string.elgamal))); + } + + choices.add(new Choice(Id.choice.algorithm.rsa, getResources().getString(R.string.rsa))); + + ArrayAdapter adapter = new ArrayAdapter(getContext(), + android.R.layout.simple_spinner_item, choices); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + algorithm.setAdapter(adapter); + // make RSA the default + for (int i = 0; i < choices.size(); ++i) { + if (choices.get(i).getId() == Id.choice.algorithm.rsa) { + algorithm.setSelection(i); + break; + } + } + + final EditText keySize = (EditText) view.findViewById(R.id.create_key_size); + + dialog.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface di, int id) { + di.dismiss(); + try { + mNewKeySize = Integer.parseInt("" + keySize.getText()); + } catch (NumberFormatException e) { + mNewKeySize = 0; + } + + mNewKeyAlgorithmChoice = (Choice) algorithm.getSelectedItem(); + createKey(); + } + }); + + dialog.setCancelable(true); + dialog.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface di, int id) { + di.dismiss(); + } + }); + + dialog.create().show(); + break; + } + + default: { + break; + } + } + this.updateEditorsVisible(); + } + + public void setUserIds(Vector list) { + if (mType != Id.type.user_id) { + return; + } + + mEditors.removeAllViews(); + for (String userId : list) { + UserIdEditor view = (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item, + mEditors, false); + view.setEditorListener(this); + view.setValue(userId); + if (mEditors.getChildCount() == 0) { + view.setIsMainUserId(true); + } + mEditors.addView(view); + } + + this.updateEditorsVisible(); + } + + public void setKeys(Vector list, Vector usages) { + if (mType != Id.type.key) { + return; + } + + mEditors.removeAllViews(); + + // go through all keys and set view based on them + for (int i = 0; i < list.size(); i++) { + KeyEditor view = (KeyEditor) mInflater.inflate(R.layout.edit_key_key_item, mEditors, + false); + view.setEditorListener(this); + boolean isMasterKey = (mEditors.getChildCount() == 0); + view.setValue(list.get(i), isMasterKey, usages.get(i)); + mEditors.addView(view); + } + + this.updateEditorsVisible(); + } + + private void createKey() { + mProgressDialog = new ProgressDialog(getContext()); + mProgressDialog.setMessage(getContext().getString(R.string.progress_generating)); + mProgressDialog.setCancelable(false); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + mProgressDialog.show(); + mRunningThread = new Thread(this); + mRunningThread.start(); + } + + public void run() { + String error = null; + try { + PGPSecretKey masterKey = null; + String passPhrase; + if (mEditors.getChildCount() > 0) { + masterKey = ((KeyEditor) mEditors.getChildAt(0)).getValue(); + passPhrase = Apg.getCachedPassPhrase(masterKey.getKeyID()); + } else { + passPhrase = ""; + } + mNewKey = Apg.createKey(getContext(), mNewKeyAlgorithmChoice.getId(), mNewKeySize, + passPhrase, masterKey); + } catch (NoSuchProviderException e) { + error = "" + e; + } catch (NoSuchAlgorithmException e) { + error = "" + e; + } catch (PGPException e) { + error = "" + e; + } catch (InvalidParameterException e) { + error = "" + e; + } catch (InvalidAlgorithmParameterException e) { + error = "" + e; + } catch (Apg.GeneralException e) { + error = "" + e; + } + + Message message = new Message(); + Bundle data = new Bundle(); + data.putBoolean("closeProgressDialog", true); + if (error != null) { + data.putString(Apg.EXTRA_ERROR, error); + } else { + data.putBoolean("gotNewKey", true); + } + message.setData(data); + mHandler.sendMessage(message); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/widget/SelectPublicKeyListAdapter.java b/org_apg/src/org/thialfihar/android/apg/ui/widget/SelectPublicKeyListAdapter.java new file mode 100644 index 000000000..39e68fb81 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/widget/SelectPublicKeyListAdapter.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui.widget; + +import java.util.Date; + +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.provider.KeyRings; +import org.thialfihar.android.apg.provider.Keys; +import org.thialfihar.android.apg.provider.UserIds; + +import android.app.Activity; +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.CheckBox; +import android.widget.ListView; +import android.widget.TextView; + +public class SelectPublicKeyListAdapter extends BaseAdapter { + protected LayoutInflater mInflater; + protected ListView mParent; + protected SQLiteDatabase mDatabase; + protected Cursor mCursor; + protected String mSearchString; + protected Activity mActivity; + + public SelectPublicKeyListAdapter(Activity activity, ListView parent, String searchString, + long selectedKeyIds[]) { + mSearchString = searchString; + + mActivity = activity; + mParent = parent; + mDatabase = Apg.getDatabase().db(); + mInflater = (LayoutInflater) parent.getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + long now = new Date().getTime() / 1000; + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + "(" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "." + + Keys.KEY_RING_ID + " AND " + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + + " = '1'" + ") " + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + "(" + + Keys.TABLE_NAME + "." + Keys._ID + " = " + UserIds.TABLE_NAME + "." + + UserIds.KEY_ID + " AND " + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); + + String inIdList = null; + + if (selectedKeyIds != null && selectedKeyIds.length > 0) { + inIdList = KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID + " IN ("; + for (int i = 0; i < selectedKeyIds.length; ++i) { + if (i != 0) { + inIdList += ", "; + } + inIdList += DatabaseUtils.sqlEscapeString("" + selectedKeyIds[i]); + } + inIdList += ")"; + } + + if (searchString != null && searchString.trim().length() > 0) { + String[] chunks = searchString.trim().split(" +"); + qb.appendWhere("(EXISTS (SELECT tmp." + UserIds._ID + " FROM " + UserIds.TABLE_NAME + + " AS tmp WHERE " + "tmp." + UserIds.KEY_ID + " = " + Keys.TABLE_NAME + "." + + Keys._ID); + for (int i = 0; i < chunks.length; ++i) { + qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE "); + qb.appendWhereEscapeString("%" + chunks[i] + "%"); + } + qb.appendWhere("))"); + + if (inIdList != null) { + qb.appendWhere(" OR (" + inIdList + ")"); + } + } + + String orderBy = UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC"; + if (inIdList != null) { + orderBy = inIdList + " DESC, " + orderBy; + } + + mCursor = qb.query(mDatabase, new String[] { + KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0 + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1 + UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2 + "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " + + "tmp." + Keys.KEY_RING_ID + " = " + KeyRings.TABLE_NAME + "." + + KeyRings._ID + " AND " + "tmp." + Keys.IS_REVOKED + " = '0' AND " + + "tmp." + Keys.CAN_ENCRYPT + " = '1')", // 3 + "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " + + "tmp." + Keys.KEY_RING_ID + " = " + KeyRings.TABLE_NAME + "." + + KeyRings._ID + " AND " + "tmp." + Keys.IS_REVOKED + " = '0' AND " + + "tmp." + Keys.CAN_ENCRYPT + " = '1' AND " + "tmp." + Keys.CREATION + + " <= '" + now + "' AND " + "(tmp." + Keys.EXPIRY + " IS NULL OR " + + "tmp." + Keys.EXPIRY + " >= '" + now + "'))", // 4 + }, KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", new String[] { "" + + Id.database.type_public }, null, null, orderBy); + + activity.startManagingCursor(mCursor); + } + + public void cleanup() { + if (mCursor != null) { + mActivity.stopManagingCursor(mCursor); + mCursor.close(); + } + } + + @Override + public boolean isEnabled(int position) { + mCursor.moveToPosition(position); + return mCursor.getInt(4) > 0; // valid CAN_ENCRYPT + } + + @Override + public boolean hasStableIds() { + return true; + } + + public int getCount() { + return mCursor.getCount(); + } + + public Object getItem(int position) { + mCursor.moveToPosition(position); + return mCursor.getString(2); // USER_ID + } + + public long getItemId(int position) { + mCursor.moveToPosition(position); + return mCursor.getLong(1); // MASTER_KEY_ID + } + + public View getView(int position, View convertView, ViewGroup parent) { + mCursor.moveToPosition(position); + + View view = mInflater.inflate(R.layout.select_public_key_item, null); + boolean enabled = isEnabled(position); + + TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); + mainUserId.setText(R.string.unknownUserId); + TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); + mainUserIdRest.setText(""); + TextView keyId = (TextView) view.findViewById(R.id.keyId); + keyId.setText(R.string.noKey); + TextView status = (TextView) view.findViewById(R.id.status); + status.setText(R.string.unknownStatus); + + String userId = mCursor.getString(2); // USER_ID + if (userId != null) { + String chunks[] = userId.split(" <", 2); + userId = chunks[0]; + if (chunks.length > 1) { + mainUserIdRest.setText("<" + chunks[1]); + } + mainUserId.setText(userId); + } + + long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID + keyId.setText(Apg.getSmallFingerPrint(masterKeyId)); + + if (mainUserIdRest.getText().length() == 0) { + mainUserIdRest.setVisibility(View.GONE); + } + + if (enabled) { + status.setText(R.string.canEncrypt); + } else { + if (mCursor.getInt(3) > 0) { + // has some CAN_ENCRYPT keys, but col(4) = 0, so must be revoked or expired + status.setText(R.string.expired); + } else { + status.setText(R.string.noKey); + } + } + + status.setText(status.getText() + " "); + + CheckBox selected = (CheckBox) view.findViewById(R.id.selected); + + if (!enabled) { + mParent.setItemChecked(position, false); + } + + selected.setChecked(mParent.isItemChecked(position)); + + view.setEnabled(enabled); + mainUserId.setEnabled(enabled); + mainUserIdRest.setEnabled(enabled); + keyId.setEnabled(enabled); + selected.setEnabled(enabled); + status.setEnabled(enabled); + + return view; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/ui/widget/SelectSecretKeyListAdapter.java b/org_apg/src/org/thialfihar/android/apg/ui/widget/SelectSecretKeyListAdapter.java new file mode 100644 index 000000000..0f4c8e6d1 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/widget/SelectSecretKeyListAdapter.java @@ -0,0 +1,181 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui.widget; + +import java.util.Date; + +import org.thialfihar.android.apg.R; +import org.thialfihar.android.apg.Apg; +import org.thialfihar.android.apg.Id; +import org.thialfihar.android.apg.provider.KeyRings; +import org.thialfihar.android.apg.provider.Keys; +import org.thialfihar.android.apg.provider.UserIds; + +import android.app.Activity; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; + +public class SelectSecretKeyListAdapter extends BaseAdapter { + protected LayoutInflater mInflater; + protected ListView mParent; + protected SQLiteDatabase mDatabase; + protected Cursor mCursor; + protected String mSearchString; + protected Activity mActivity; + + public SelectSecretKeyListAdapter(Activity activity, ListView parent, String searchString) { + mSearchString = searchString; + + mActivity = activity; + mParent = parent; + mDatabase = Apg.getDatabase().db(); + mInflater = (LayoutInflater) parent.getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + long now = new Date().getTime() / 1000; + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + "(" + + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "." + + Keys.KEY_RING_ID + " AND " + Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + + " = '1'" + ") " + " INNER JOIN " + UserIds.TABLE_NAME + " ON " + "(" + + Keys.TABLE_NAME + "." + Keys._ID + " = " + UserIds.TABLE_NAME + "." + + UserIds.KEY_ID + " AND " + UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); + + if (searchString != null && searchString.trim().length() > 0) { + String[] chunks = searchString.trim().split(" +"); + qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " + UserIds.TABLE_NAME + + " AS tmp WHERE " + "tmp." + UserIds.KEY_ID + " = " + Keys.TABLE_NAME + "." + + Keys._ID); + for (int i = 0; i < chunks.length; ++i) { + qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE "); + qb.appendWhereEscapeString("%" + chunks[i] + "%"); + } + qb.appendWhere(")"); + } + + mCursor = qb.query(mDatabase, new String[] { + KeyRings.TABLE_NAME + "." + KeyRings._ID, // 0 + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1 + UserIds.TABLE_NAME + "." + UserIds.USER_ID, // 2 + "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " + + "tmp." + Keys.KEY_RING_ID + " = " + KeyRings.TABLE_NAME + "." + + KeyRings._ID + " AND " + "tmp." + Keys.IS_REVOKED + " = '0' AND " + + "tmp." + Keys.CAN_SIGN + " = '1')", // 3, + "(SELECT COUNT(tmp." + Keys._ID + ") FROM " + Keys.TABLE_NAME + " AS tmp WHERE " + + "tmp." + Keys.KEY_RING_ID + " = " + KeyRings.TABLE_NAME + "." + + KeyRings._ID + " AND " + "tmp." + Keys.IS_REVOKED + " = '0' AND " + + "tmp." + Keys.CAN_SIGN + " = '1' AND " + "tmp." + Keys.CREATION + " <= '" + + now + "' AND " + "(tmp." + Keys.EXPIRY + " IS NULL OR " + "tmp." + + Keys.EXPIRY + " >= '" + now + "'))", // 4 + }, KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", new String[] { "" + + Id.database.type_secret }, null, null, UserIds.TABLE_NAME + "." + UserIds.USER_ID + + " ASC"); + + activity.startManagingCursor(mCursor); + } + + public void cleanup() { + if (mCursor != null) { + mActivity.stopManagingCursor(mCursor); + mCursor.close(); + } + } + + @Override + public boolean isEnabled(int position) { + mCursor.moveToPosition(position); + return mCursor.getInt(4) > 0; // valid CAN_SIGN + } + + @Override + public boolean hasStableIds() { + return true; + } + + public int getCount() { + return mCursor.getCount(); + } + + public Object getItem(int position) { + mCursor.moveToPosition(position); + return mCursor.getString(2); // USER_ID + } + + public long getItemId(int position) { + mCursor.moveToPosition(position); + return mCursor.getLong(1); // MASTER_KEY_ID + } + + public View getView(int position, View convertView, ViewGroup parent) { + mCursor.moveToPosition(position); + + View view = mInflater.inflate(R.layout.select_secret_key_item, null); + boolean enabled = isEnabled(position); + + TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId); + mainUserId.setText(R.string.unknownUserId); + TextView mainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest); + mainUserIdRest.setText(""); + TextView keyId = (TextView) view.findViewById(R.id.keyId); + keyId.setText(R.string.noKey); + TextView status = (TextView) view.findViewById(R.id.status); + status.setText(R.string.unknownStatus); + + String userId = mCursor.getString(2); // USER_ID + if (userId != null) { + String chunks[] = userId.split(" <", 2); + userId = chunks[0]; + if (chunks.length > 1) { + mainUserIdRest.setText("<" + chunks[1]); + } + mainUserId.setText(userId); + } + + long masterKeyId = mCursor.getLong(1); // MASTER_KEY_ID + keyId.setText(Apg.getSmallFingerPrint(masterKeyId)); + + if (mainUserIdRest.getText().length() == 0) { + mainUserIdRest.setVisibility(View.GONE); + } + + if (enabled) { + status.setText(R.string.canSign); + } else { + if (mCursor.getInt(3) > 0) { + // has some CAN_SIGN keys, but col(4) = 0, so must be revoked or expired + status.setText(R.string.expired); + } else { + status.setText(R.string.noKey); + } + } + + status.setText(status.getText() + " "); + + view.setEnabled(enabled); + mainUserId.setEnabled(enabled); + mainUserIdRest.setEnabled(enabled); + keyId.setEnabled(enabled); + status.setEnabled(enabled); + + return view; + } +} \ No newline at end of file diff --git a/org_apg/src/org/thialfihar/android/apg/ui/widget/UserIdEditor.java b/org_apg/src/org/thialfihar/android/apg/ui/widget/UserIdEditor.java new file mode 100644 index 000000000..6b058e31f --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/ui/widget/UserIdEditor.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.ui.widget; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.thialfihar.android.apg.R; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.RadioButton; + +public class UserIdEditor extends LinearLayout implements Editor, OnClickListener { + private EditorListener mEditorListener = null; + + private ImageButton mDeleteButton; + private RadioButton mIsMainUserId; + private EditText mName; + private EditText mEmail; + private EditText mComment; + + // see http://www.regular-expressions.info/email.html + // RFC 2822 if we omit the syntax using double quotes and square brackets + private static final Pattern EMAIL_PATTERN = Pattern + .compile( + "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", + Pattern.CASE_INSENSITIVE); + + public static class NoNameException extends Exception { + static final long serialVersionUID = 0xf812773343L; + + public NoNameException(String message) { + super(message); + } + } + + public static class NoEmailException extends Exception { + static final long serialVersionUID = 0xf812773344L; + + public NoEmailException(String message) { + super(message); + } + } + + public static class InvalidEmailException extends Exception { + static final long serialVersionUID = 0xf812773345L; + + public InvalidEmailException(String message) { + super(message); + } + } + + public UserIdEditor(Context context) { + super(context); + } + + public UserIdEditor(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + setDrawingCacheEnabled(true); + setAlwaysDrawnWithCacheEnabled(true); + + mDeleteButton = (ImageButton) findViewById(R.id.delete); + mDeleteButton.setOnClickListener(this); + mIsMainUserId = (RadioButton) findViewById(R.id.isMainUserId); + mIsMainUserId.setOnClickListener(this); + + mName = (EditText) findViewById(R.id.name); + mEmail = (EditText) findViewById(R.id.email); + mComment = (EditText) findViewById(R.id.comment); + + super.onFinishInflate(); + } + + public void setValue(String userId) { + mName.setText(""); + mComment.setText(""); + mEmail.setText(""); + + Pattern withComment = Pattern.compile("^(.*) [(](.*)[)] <(.*)>$"); + Matcher matcher = withComment.matcher(userId); + if (matcher.matches()) { + mName.setText(matcher.group(1)); + mComment.setText(matcher.group(2)); + mEmail.setText(matcher.group(3)); + return; + } + + Pattern withoutComment = Pattern.compile("^(.*) <(.*)>$"); + matcher = withoutComment.matcher(userId); + if (matcher.matches()) { + mName.setText(matcher.group(1)); + mEmail.setText(matcher.group(2)); + return; + } + } + + public String getValue() throws NoNameException, NoEmailException, InvalidEmailException { + String name = ("" + mName.getText()).trim(); + String email = ("" + mEmail.getText()).trim(); + String comment = ("" + mComment.getText()).trim(); + + if (email.length() > 0) { + Matcher emailMatcher = EMAIL_PATTERN.matcher(email); + if (!emailMatcher.matches()) { + throw new InvalidEmailException(getContext().getString(R.string.error_invalidEmail, + email)); + } + } + + String userId = name; + if (comment.length() > 0) { + userId += " (" + comment + ")"; + } + if (email.length() > 0) { + userId += " <" + email + ">"; + } + + if (userId.equals("")) { + // ok, empty one... + return userId; + } + + // otherwise make sure that name and email exist + if (name.equals("")) { + throw new NoNameException("need a name"); + } + + if (email.equals("")) { + throw new NoEmailException("need an email"); + } + + return userId; + } + + public void onClick(View v) { + final ViewGroup parent = (ViewGroup) getParent(); + if (v == mDeleteButton) { + boolean wasMainUserId = mIsMainUserId.isChecked(); + parent.removeView(this); + if (mEditorListener != null) { + mEditorListener.onDeleted(this); + } + if (wasMainUserId && parent.getChildCount() > 0) { + UserIdEditor editor = (UserIdEditor) parent.getChildAt(0); + editor.setIsMainUserId(true); + } + } else if (v == mIsMainUserId) { + for (int i = 0; i < parent.getChildCount(); ++i) { + UserIdEditor editor = (UserIdEditor) parent.getChildAt(i); + if (editor == this) { + editor.setIsMainUserId(true); + } else { + editor.setIsMainUserId(false); + } + } + } + } + + public void setIsMainUserId(boolean value) { + mIsMainUserId.setChecked(value); + } + + public boolean isMainUserId() { + return mIsMainUserId.isChecked(); + } + + public void setEditorListener(EditorListener listener) { + mEditorListener = listener; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/util/ApgCon.java b/org_apg/src/org/thialfihar/android/apg/util/ApgCon.java new file mode 100644 index 000000000..22a9a7eb5 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/util/ApgCon.java @@ -0,0 +1,836 @@ +/* + * Copyright (C) 2011 Markus Doits + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.util; + +import org.thialfihar.android.apg.IApgService; +import org.thialfihar.android.apg.util.ApgConInterface.OnCallFinishListener; + +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.IBinder; +import android.util.Log; + +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; + +/** + * A APG-AIDL-Wrapper + * + *

+ * This class can be used by other projects to simplify connecting to the + * APG-AIDL-Service. Kind of wrapper of for AIDL. + *

+ * + *

+ * It is not used in this project. + *

+ * + * @author Markus Doits + * @version 1.1rc1 + * + */ +public class ApgCon { + private static final boolean LOCAL_LOGV = true; + private static final boolean LOCAL_LOGD = true; + + private final static String TAG = "ApgCon"; + private final static int API_VERSION = 2; // aidl api-version it expects + private final static String BLOB_URI = "content://org.thialfihar.android.apg.provider.apgserviceblobprovider"; + + /** + * How many seconds to wait for a connection to AGP when connecting. + * Being unsuccessful for this number of seconds, a connection + * is assumed to be failed. + */ + public int secondsToWaitForConnection = 15; + + private class CallAsync extends AsyncTask { + + @Override + protected Void doInBackground(String... arg) { + if( LOCAL_LOGD ) Log.d(TAG, "Async execution starting"); + call(arg[0]); + return null; + } + + protected void onPostExecute(Void res) { + if( LOCAL_LOGD ) Log.d(TAG, "Async execution finished"); + mAsyncRunning = false; + + } + + } + + private final Context mContext; + private final error mConnectionStatus; + private boolean mAsyncRunning = false; + private OnCallFinishListener mOnCallFinishListener; + + private final Bundle mResult = new Bundle(); + private final Bundle mArgs = new Bundle(); + private final ArrayList mErrorList = new ArrayList(); + private final ArrayList mWarningList = new ArrayList(); + + /** Remote service for decrypting and encrypting data */ + private IApgService mApgService = null; + + /** Set apgService accordingly to connection status */ + private ServiceConnection mApgConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + if( LOCAL_LOGD ) Log.d(TAG, "IApgService bound to apgService"); + mApgService = IApgService.Stub.asInterface(service); + } + + public void onServiceDisconnected(ComponentName className) { + if( LOCAL_LOGD ) Log.d(TAG, "IApgService disconnected"); + mApgService = null; + } + }; + + /** + * Different types of local errors + */ + public static enum error { + /** + * no error + */ + NO_ERROR, + /** + * generic error + */ + GENERIC, + /** + * connection to apg service not possible + */ + CANNOT_BIND_TO_APG, + /** + * function to call not provided + */ + CALL_MISSING, + /** + * apg service does not know what to do + */ + CALL_NOT_KNOWN, + /** + * could not find APG being installed + */ + APG_NOT_FOUND, + /** + * found APG but without AIDL interface + */ + APG_AIDL_MISSING, + /** + * found APG but with wrong API + */ + APG_API_MISSMATCH + } + + private static enum ret { + ERROR, // returned from AIDL + RESULT, // returned from AIDL + WARNINGS, // mixed AIDL and LOCAL + ERRORS, // mixed AIDL and LOCAL + } + + /** + * Constructor + * + *

+ * Creates a new ApgCon object and searches for the right APG version on + * initialization. If not found, errors are printed to the error log. + *

+ * + * @param ctx + * the running context + */ + public ApgCon(Context ctx) { + if( LOCAL_LOGV ) Log.v(TAG, "EncryptionService created"); + mContext = ctx; + + error tmpError = null; + try { + if( LOCAL_LOGV ) Log.v(TAG, "Searching for the right APG version"); + ServiceInfo apgServices[] = ctx.getPackageManager().getPackageInfo("org.thialfihar.android.apg", + PackageManager.GET_SERVICES | PackageManager.GET_META_DATA).services; + if (apgServices == null) { + Log.e(TAG, "Could not fetch services"); + tmpError = error.GENERIC; + } else { + boolean apgServiceFound = false; + for (ServiceInfo inf : apgServices) { + if( LOCAL_LOGV ) Log.v(TAG, "Found service of APG: " + inf.name); + if (inf.name.equals("org.thialfihar.android.apg.ApgService")) { + apgServiceFound = true; + if (inf.metaData == null) { + Log.w(TAG, "Could not determine ApgService API"); + Log.w(TAG, "This probably won't work!"); + mWarningList.add("(LOCAL) Could not determine ApgService API"); + tmpError = error.APG_API_MISSMATCH; + } else if (inf.metaData.getInt("api_version") != API_VERSION) { + Log.w(TAG, "Found ApgService API version " + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION); + Log.w(TAG, "This probably won't work!"); + mWarningList.add("(LOCAL) Found ApgService API version " + inf.metaData.getInt("api_version") + " but exspected " + API_VERSION); + tmpError = error.APG_API_MISSMATCH; + } else { + if( LOCAL_LOGV ) Log.v(TAG, "Found api_version " + API_VERSION + ", everything should work"); + tmpError = error.NO_ERROR; + } + } + } + + if (!apgServiceFound) { + Log.e(TAG, "Could not find APG with AIDL interface, this probably won't work"); + mErrorList.add("(LOCAL) Could not find APG with AIDL interface, this probably won't work"); + mResult.putInt(ret.ERROR.name(), error.APG_AIDL_MISSING.ordinal()); + tmpError = error.APG_NOT_FOUND; + } + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Could not find APG, is it installed?", e); + mErrorList.add("(LOCAL) Could not find APG, is it installed?"); + mResult.putInt(ret.ERROR.name(), error.APG_NOT_FOUND.ordinal()); + tmpError = error.APG_NOT_FOUND; + } + + mConnectionStatus = tmpError; + + } + + /** try to connect to the apg service */ + private boolean connect() { + if( LOCAL_LOGV ) Log.v(TAG, "trying to bind the apgService to context"); + + if (mApgService != null) { + if( LOCAL_LOGV ) Log.v(TAG, "allready connected"); + return true; + } + + try { + mContext.bindService(new Intent(IApgService.class.getName()), mApgConnection, Context.BIND_AUTO_CREATE); + } catch (Exception e) { + Log.e(TAG, "could not bind APG service", e); + return false; + } + + int waitCount = 0; + while (mApgService == null && waitCount++ < secondsToWaitForConnection) { + if( LOCAL_LOGV ) Log.v(TAG, "sleeping 1 second to wait for apg"); + android.os.SystemClock.sleep(1000); + } + + if (waitCount >= secondsToWaitForConnection) { + if( LOCAL_LOGV ) Log.v(TAG, "slept waiting for nothing!"); + return false; + } + + return true; + } + + /** + * Disconnects ApgCon from Apg + * + *

+ * This should be called whenever all work with APG is done (e.g. everything + * you wanted to encrypt is encrypted), since connections with AIDL should + * not be upheld indefinitely. + *

+ * + *

+ * Also, if you destroy you end using your ApgCon-instance, this must be + * called or else the connection to APG is leaked + *

+ */ + public void disconnect() { + if( LOCAL_LOGV ) Log.v(TAG, "disconnecting apgService"); + if (mApgService != null) { + mContext.unbindService(mApgConnection); + mApgService = null; + } + } + + private boolean initialize() { + if (mApgService == null) { + if (!connect()) { + if( LOCAL_LOGV ) Log.v(TAG, "connection to apg service failed"); + return false; + } + } + return true; + } + + /** + * Calls a function from APG's AIDL-interface + * + *

+ * After you have set up everything with {@link #setArg(String, String)} + * (and variants), you can call a function of the AIDL-interface. This + * will: + *

    + *
  • start connection to the remote interface (if not already connected)
  • + *
  • call the function passed with all parameters synchronously
  • + *
  • set up everything to retrieve the result and/or warnings/errors
  • + *
  • call the callback if provided + *
+ *

+ * + *

+ * Note your thread will be blocked during execution - if you want to call + * the function asynchronously, see {@link #callAsync(String)}. + *

+ * + * @param function + * a remote function to call + * @return true, if call successful (= no errors), else false + * + * @see #callAsync(String) + * @see #setArg(String, String) + * @see #setOnCallFinishListener(OnCallFinishListener) + */ + public boolean call(String function) { + boolean success = this.call(function, mArgs, mResult); + if (mOnCallFinishListener != null) { + try { + if( LOCAL_LOGD ) Log.d(TAG, "About to execute callback"); + mOnCallFinishListener.onCallFinish(mResult); + if( LOCAL_LOGD ) Log.d(TAG, "Callback executed"); + } catch (Exception e) { + Log.w(TAG, "Exception on callback: (" + e.getClass() + ") " + e.getMessage(), e); + mWarningList.add("(LOCAL) Could not execute callback (" + e.getClass() + "): " + e.getMessage()); + } + } + return success; + } + + /** + * Calls a function of remote interface asynchronously + * + *

+ * This does exactly the same as {@link #call(String)}, but asynchronously. + * While connection to APG and work are done in background, your thread can + * go on executing. + *

+ * + *

+ * To see whether the task is finished, you have two possibilities: + *

    + *
  • In your thread, poll {@link #isRunning()}
  • + *
  • Supply a callback with {@link #setOnCallFinishListener(OnCallFinishListener)}
  • + *
+ *

+ * + * @param function + * a remote function to call + * + * @see #call(String) + * @see #isRunning() + * @see #setOnCallFinishListener(OnCallFinishListener) + */ + public void callAsync(String function) { + mAsyncRunning = true; + new CallAsync().execute(function); + } + + private boolean call(String function, Bundle pArgs, Bundle pReturn) { + + if (!initialize()) { + mErrorList.add("(LOCAL) Cannot bind to ApgService"); + mResult.putInt(ret.ERROR.name(), error.CANNOT_BIND_TO_APG.ordinal()); + return false; + } + + if (function == null || function.length() == 0) { + mErrorList.add("(LOCAL) Function to call missing"); + mResult.putInt(ret.ERROR.name(), error.CALL_MISSING.ordinal()); + return false; + } + + try { + Boolean success = (Boolean) IApgService.class.getMethod(function, Bundle.class, Bundle.class).invoke(mApgService, pArgs, pReturn); + mErrorList.addAll(pReturn.getStringArrayList(ret.ERRORS.name())); + mWarningList.addAll(pReturn.getStringArrayList(ret.WARNINGS.name())); + return success; + } catch (NoSuchMethodException e) { + Log.e(TAG, "Remote call not known (" + function + "): " + e.getMessage(), e); + mErrorList.add("(LOCAL) Remote call not known (" + function + "): " + e.getMessage()); + mResult.putInt(ret.ERROR.name(), error.CALL_NOT_KNOWN.ordinal()); + return false; + } catch (InvocationTargetException e) { + Throwable orig = e.getTargetException(); + Log.w(TAG, "Exception of type '" + orig.getClass() + "' on AIDL call '" + function + "': " + orig.getMessage(), orig); + mErrorList.add("(LOCAL) Exception of type '" + orig.getClass() + "' on AIDL call '" + function + "': " + orig.getMessage()); + return false; + } catch (Exception e) { + Log.e(TAG, "Generic error (" + e.getClass() + "): " + e.getMessage(), e); + mErrorList.add("(LOCAL) Generic error (" + e.getClass() + "): " + e.getMessage()); + mResult.putInt(ret.ERROR.name(), error.GENERIC.ordinal()); + return false; + } + + } + + /** + * Set a string argument for APG + * + *

+ * This defines a string argument for APG's AIDL-interface. + *

+ * + *

+ * To know what key-value-pairs are possible (or required), take a look into + * the IApgService.aidl + *

+ * + *

+ * Note that parameters are not reseted after a call, so you have to + * reset ({@link #clearArgs()}) them manually if you want to. + *

+ * + * + * @param key + * the key + * @param val + * the value + * + * @see #clearArgs() + */ + public void setArg(String key, String val) { + mArgs.putString(key, val); + } + + /** + * Set a string-array argument for APG + * + *

+ * If the AIDL-parameter is an {@literal ArrayList}, you have to use + * this function. + *

+ * + * + *
+     * setArg("a key", new String[]{ "entry 1", "entry 2" });
+     * 
+ *
+ * + * @param key + * the key + * @param vals + * the value + * + * @see #setArg(String, String) + */ + public void setArg(String key, String vals[]) { + ArrayList list = new ArrayList(); + for (String val : vals) { + list.add(val); + } + mArgs.putStringArrayList(key, list); + } + + /** + * Set up a boolean argument for APG + * + * @param key + * the key + * @param vals + * the value + * + * @see #setArg(String, String) + */ + public void setArg(String key, boolean val) { + mArgs.putBoolean(key, val); + } + + /** + * Set up a int argument for APG + * + * @param key + * the key + * @param vals + * the value + * + * @see #setArg(String, String) + */ + public void setArg(String key, int val) { + mArgs.putInt(key, val); + } + + /** + * Set up a int-array argument for APG + *

+ * If the AIDL-parameter is an {@literal ArrayList}, you have to + * use this function. + *

+ * + * @param key + * the key + * @param vals + * the value + * + * @see #setArg(String, String) + */ + public void setArg(String key, int vals[]) { + ArrayList list = new ArrayList(); + for (int val : vals) { + list.add(val); + } + mArgs.putIntegerArrayList(key, list); + } + + /** + * Set up binary data to en/decrypt + * + * @param is + * InputStream to get the data from + */ + public void setBlob(InputStream is) { + if( LOCAL_LOGD ) Log.d(TAG, "setBlob() called"); + // 1. get the new contentUri + ContentResolver cr = mContext.getContentResolver(); + Uri contentUri = cr.insert(Uri.parse(BLOB_URI), new ContentValues()); + + // 2. insert binary data + OutputStream os = null; + try { + os = cr.openOutputStream(contentUri, "w"); + } catch( Exception e ) { + Log.e(TAG, "... exception on setBlob", e); + } + + byte[] buffer = new byte[8]; + int len = 0; + try { + while( (len = is.read(buffer)) != -1) { + os.write(buffer, 0, len); + } + if(LOCAL_LOGD) Log.d(TAG, "... write finished, now closing"); + os.close(); + } catch (Exception e) { + Log.e(TAG, "... error on writing buffer", e); + } + + mArgs.putString("BLOB", contentUri.toString() ); + } + + /** + * Clears all arguments + * + *

+ * Anything the has been set up with the various + * {@link #setArg(String, String)} functions is cleared. + *

+ * + *

+ * Note that any warning, error, callback, result, etc. is NOT cleared with + * this. + *

+ * + * @see #reset() + */ + public void clearArgs() { + mArgs.clear(); + } + + /** + * Return the object associated with the key + * + * @param key + * the object's key you want to return + * @return an object at position key, or null if not set + */ + public Object getArg(String key) { + return mArgs.get(key); + } + + /** + * Iterates through the errors + * + *

+ * With this method you can iterate through all errors. The errors are only + * returned once and deleted immediately afterwards, so you can only return + * each error once. + *

+ * + * @return a human readable description of a error that happened, or null if + * no more errors + * + * @see #hasNextError() + * @see #clearErrors() + */ + public String getNextError() { + if (mErrorList.size() != 0) + return mErrorList.remove(0); + else + return null; + } + + /** + * Check if there are any new errors + * + * @return true, if there are unreturned errors, false otherwise + * + * @see #getNextError() + */ + public boolean hasNextError() { + return mErrorList.size() != 0; + } + + /** + * Get the numeric representation of the last error + * + *

+ * Values <100 mean the error happened locally, values >=100 mean the error + * happened at the remote side (APG). See the IApgService.aidl (or get the + * human readable description with {@link #getNextError()}) for what + * errors >=100 mean. + *

+ * + * @return the id of the error that happened + */ + public int getError() { + if (mResult.containsKey(ret.ERROR.name())) + return mResult.getInt(ret.ERROR.name()); + else + return -1; + } + + /** + * Iterates through the warnings + * + *

+ * With this method you can iterate through all warnings. Warnings are + * only returned once and deleted immediately afterwards, so you can only + * return each warning once. + *

+ * + * @return a human readable description of a warning that happened, or null + * if no more warnings + * + * @see #hasNextWarning() + * @see #clearWarnings() + */ + public String getNextWarning() { + if (mWarningList.size() != 0) + return mWarningList.remove(0); + else + return null; + } + + /** + * Check if there are any new warnings + * + * @return true, if there are unreturned warnings, false otherwise + * + * @see #getNextWarning() + */ + public boolean hasNextWarning() { + return mWarningList.size() != 0; + } + + /** + * Get the result + * + *

+ * This gets your result. After doing an encryption or decryption with APG, + * you get the output with this function. + *

+ * + *

+ * Note when your last remote call is unsuccessful, the result will + * still have the same value like the last successful call (or null, if no + * call was successful). To ensure you do not work with old call's results, + * either be sure to {@link #reset()} (or at least {@link #clearResult()}) + * your instance before each new call or always check that + * {@link #hasNextError()} is false. + *

+ * + *

+ * Note: When handling binary data with {@link #setBlob(InputStream)}, you + * get your result with {@link #getBlobResult()}. + *

+ * + * @return the mResult of the last {@link #call(String)} or + * {@link #callAsync(String)}. + * + * @see #reset() + * @see #clearResult() + * @see #getResultBundle() + * @see #getBlobResult() + */ + public String getResult() { + return mResult.getString(ret.RESULT.name()); + } + + /** + * Get the binary result + * + *

+ * This gets your binary result. It only works if you called {@link #setBlob(InputStream)} before. + * + * If you did not call encrypt nor decrypt, this will be the same data as you inputed. + *

+ * + * @return InputStream of the binary data which was en/decrypted + * + * @see #setBlob(InputStream) + * @see #getResult() + */ + public InputStream getBlobResult() { + if(mArgs.containsKey("BLOB")) { + ContentResolver cr = mContext.getContentResolver(); + InputStream in = null; + try { + in = cr.openInputStream(Uri.parse(mArgs.getString("BLOB"))); + } catch( Exception e ) { + Log.e(TAG, "Could not return blob in result", e); + } + return in; + } else { + return null; + } + } + + /** + * Get the result bundle + * + *

+ * Unlike {@link #getResult()}, which only returns any en-/decrypted + * message, this function returns the complete information that was returned + * by Apg. This also includes the "RESULT", but additionally the warnings, + * errors and any other information. + *

+ *

+ * For warnings and errors it is suggested to use the functions that are + * provided here, namely {@link #getError()}, {@link #getNextError()}, + * {@link #get_next_Warning()} etc.), but if any call returns something non + * standard, you have access to the complete result bundle to extract the + * information. + *

+ * + * @return the complete result bundle of the last call to apg + */ + public Bundle getResultBundle() { + return mResult; + } + + public error getConnectionStatus() { + return mConnectionStatus; + } + + /** + * Clears all unfetched errors + * + * @see #getNextError() + * @see #hasNextError() + */ + public void clearErrors() { + mErrorList.clear(); + mResult.remove(ret.ERROR.name()); + } + + /** + * Clears all unfetched warnings + * + * @see #getNextWarning() + * @see #hasNextWarning() + */ + public void clearWarnings() { + mWarningList.clear(); + } + + /** + * Clears the last mResult + * + * @see #getResult() + */ + public void clearResult() { + mResult.remove(ret.RESULT.name()); + } + + /** + * Set a callback listener when call to AIDL finishes + * + * @param obj + * a object to call back after async execution + * @see ApgConInterface + */ + public void setOnCallFinishListener(OnCallFinishListener lis) { + mOnCallFinishListener = lis; + } + + /** + * Clears any callback object + * + * @see #setOnCallFinishListener(OnCallFinishListener) + */ + public void clearOnCallFinishListener() { + mOnCallFinishListener = null; + } + + /** + * Checks if an async execution is running + * + *

+ * If you started something with {@link #callAsync(String)}, this will + * return true if the task is still running + *

+ * + * @return true, if an async task is still running, false otherwise + * + * @see #callAsync(String) + * + */ + public boolean isRunning() { + return mAsyncRunning; + } + + /** + * Completely resets your instance + * + *

+ * This currently resets everything in this instance. Errors, warnings, + * results, callbacks, ... are removed. Any connection to the remote + * interface is upheld, though. + *

+ * + *

+ * Note when an async execution ({@link #callAsync(String)}) is + * running, it's result, warnings etc. will still be evaluated (which might + * be not what you want). Also mind that any callback you set is also + * reseted, so when finishing the execution any before defined callback will + * NOT BE TRIGGERED. + *

+ */ + public void reset() { + clearErrors(); + clearWarnings(); + clearArgs(); + clearOnCallFinishListener(); + mResult.clear(); + } + +} diff --git a/org_apg/src/org/thialfihar/android/apg/util/ApgConInterface.java b/org_apg/src/org/thialfihar/android/apg/util/ApgConInterface.java new file mode 100644 index 000000000..406427231 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/util/ApgConInterface.java @@ -0,0 +1,21 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.util; + +public interface ApgConInterface { + public static interface OnCallFinishListener { + public abstract void onCallFinish(android.os.Bundle result); + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/util/Choice.java b/org_apg/src/org/thialfihar/android/apg/util/Choice.java new file mode 100644 index 000000000..94cc58f55 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/util/Choice.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.util; + +public class Choice { + private String mName; + private int mId; + + public Choice() { + mId = -1; + mName = ""; + } + + public Choice(int id, String name) { + mId = id; + mName = name; + } + + public int getId() { + return mId; + } + + public String getName() { + return mName; + } + + @Override + public String toString() { + return mName; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/util/Compatibility.java b/org_apg/src/org/thialfihar/android/apg/util/Compatibility.java new file mode 100644 index 000000000..9daa7f73e --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/util/Compatibility.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 Dominik Schürmann + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.util; + +import java.lang.reflect.Method; + +import android.content.Context; +import android.util.Log; + +public class Compatibility { + + private static final String clipboardLabel = "APG"; + + /** + * Wrapper around ClipboardManager based on Android version using Reflection API, from + * http://www.projectsexception.com/blog/?p=87 + * + * @param context + * @param text + */ + public static void copyToClipboard(Context context, String text) { + Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE); + try { + if ("android.text.ClipboardManager".equals(clipboard.getClass().getName())) { + Method method = clipboard.getClass().getMethod("setText", CharSequence.class); + method.invoke(clipboard, text); + } else if ("android.content.ClipboardManager".equals(clipboard.getClass().getName())) { + Class clazz = Class.forName("android.content.ClipData"); + Method method = clazz.getMethod("newPlainText", CharSequence.class, + CharSequence.class); + Object clip = method.invoke(null, clipboardLabel, text); + method = clipboard.getClass().getMethod("setPrimaryClip", clazz); + method.invoke(clipboard, clip); + } + } catch (Exception e) { + Log.e("ProjectsException", "There was and error copying the text to the clipboard: " + + e.getMessage()); + } + } + + /** + * Wrapper around ClipboardManager based on Android version using Reflection API + * + * @param context + * @param text + */ + public static CharSequence getClipboardText(Context context) { + Object clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE); + try { + if ("android.text.ClipboardManager".equals(clipboard.getClass().getName())) { + // CharSequence text = clipboard.getText(); + Method method = clipboard.getClass().getMethod("getText"); + Object text = method.invoke(clipboard); + + return (CharSequence) text; + } else if ("android.content.ClipboardManager".equals(clipboard.getClass().getName())) { + // ClipData clipData = clipboard.getPrimaryClip(); + Method methodGetPrimaryClip = clipboard.getClass().getMethod("getPrimaryClip"); + Object clipData = methodGetPrimaryClip.invoke(clipboard); + + // ClipData.Item clipDataItem = clipData.getItemAt(0); + Method methodGetItemAt = clipData.getClass().getMethod("getItemAt", Integer.TYPE); + Object clipDataItem = methodGetItemAt.invoke(clipData, 0); + + // CharSequence text = clipDataItem.coerceToText(context); + Method methodGetString = clipDataItem.getClass().getMethod("coerceToText", + Context.class); + Object text = methodGetString.invoke(clipDataItem, context); + + return (CharSequence) text; + } else { + return null; + } + } catch (Exception e) { + Log.e("ProjectsException", "There was and error getting the text from the clipboard: " + + e.getMessage()); + + return null; + } + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/util/Constants.java b/org_apg/src/org/thialfihar/android/apg/util/Constants.java new file mode 100644 index 000000000..fac9be649 --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/util/Constants.java @@ -0,0 +1,6 @@ +package org.thialfihar.android.apg.util; + +public class Constants { + public static final String TAG = "APG"; + +} diff --git a/org_apg/src/org/thialfihar/android/apg/util/IterableIterator.java b/org_apg/src/org/thialfihar/android/apg/util/IterableIterator.java new file mode 100644 index 000000000..1071cc81c --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/util/IterableIterator.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010 Thialfihar + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.util; + +import java.util.Iterator; + +public class IterableIterator implements Iterable { + private Iterator mIter; + + public IterableIterator(Iterator iter) { + mIter = iter; + } + + public Iterator iterator() { + return mIter; + } +} diff --git a/org_apg/src/org/thialfihar/android/apg/util/Utils.java b/org_apg/src/org/thialfihar/android/apg/util/Utils.java new file mode 100644 index 000000000..4ea2a48af --- /dev/null +++ b/org_apg/src/org/thialfihar/android/apg/util/Utils.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012 Dominik Schürmann + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.thialfihar.android.apg.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Calendar; +import java.util.GregorianCalendar; + +import android.content.Context; + +public class Utils { + + /** + * Reads html files from /res/raw/example.html to output them as string. See + * http://www.monocube.com/2011/02/08/android-tutorial-html-file-in-webview/ + * + * @param context + * current context + * @param resourceID + * of html file to read + * @return content of html file with formatting + */ + public static String readContentFromResource(Context context, int resourceID) { + InputStream raw = context.getResources().openRawResource(resourceID); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + int i; + try { + i = raw.read(); + while (i != -1) { + stream.write(i); + i = raw.read(); + } + raw.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return stream.toString(); + } + + /** + * Return the number if days between two dates + * + * @param first + * @param second + * @return number of days + */ + public static long getNumDaysBetween(GregorianCalendar first, GregorianCalendar second) { + GregorianCalendar tmp = new GregorianCalendar(); + tmp.setTime(first.getTime()); + long numDays = (second.getTimeInMillis() - first.getTimeInMillis()) / 1000 / 86400; + tmp.add(Calendar.DAY_OF_MONTH, (int) numDays); + while (tmp.before(second)) { + tmp.add(Calendar.DAY_OF_MONTH, 1); + ++numDays; + } + return numDays; + } + +} -- cgit v1.2.3