Compare commits

...

155 Commits

Author SHA1 Message Date
Travis Burtrum 7366f14d32 New all-white status bar icon to fix issue #1356 2015-06-22 22:40:15 -04:00
Vincent Breitmoser 2b26f293b8 update code style 2015-06-18 13:16:43 +02:00
Vincent Breitmoser 7c3518a011 more stable nullable behavior in OperationLog 2015-06-18 13:16:38 +02:00
Vincent Breitmoser 297f53e171 instrument: fix accidentally broken test 2015-06-17 23:11:11 +02:00
Vincent Breitmoser f5aa36ef9f fix rest of resource leaks (#1351) 2015-06-17 21:30:25 +02:00
Vincent Breitmoser 5895385153 fix a couple of resource leaks (#1351) 2015-06-17 20:26:06 +02:00
Dominik Schürmann 300fd8e0f2 Merge pull request #1352 from open-keychain/v/instrument
instrumentation branch
2015-06-17 19:56:13 +02:00
Vincent Breitmoser 04d2b6a507 use regular runner for most cases 2015-06-17 18:53:50 +02:00
Vincent Breitmoser 9a82b33d16 disable instrumentation tests in travis, it's just too slow 2015-06-17 18:53:49 +02:00
Vincent Breitmoser e3ed2c8e72 just force commit ui test coverage 2015-06-17 18:53:49 +02:00
Vincent Breitmoser cc793c2882 another shot at travis 2015-06-17 18:53:49 +02:00
Vincent Breitmoser 5e77117232 fix filesize reporting in decrypt operation 2015-06-17 18:53:46 +02:00
Vincent Breitmoser 8e60ccb650 workaround for coverage bug (for now!)
see android bug report https://code.google.com/p/android/issues/detail?id=170607
2015-06-17 18:53:08 +02:00
Vincent Breitmoser 0d61221c5f instrument: license stuff 2015-06-17 18:53:07 +02:00
Vincent Breitmoser 0c20d40863 instrument: import public keys as public 2015-06-17 18:53:07 +02:00
Vincent Breitmoser 7b416d7d7d instrument: test EncryptKeyCompletionView 2015-06-17 18:53:06 +02:00
Vincent Breitmoser 04e9137b66 instrument: use contrib drawer methods, respect passphrase cache 2015-06-17 18:53:05 +02:00
Vincent Breitmoser d2cdfb34fa work on asymmetric operation instrumentation tests 2015-06-17 18:53:04 +02:00
Vincent Breitmoser d12d469714 some cleanup in instrumentation tests 2015-06-17 18:53:04 +02:00
Vincent Breitmoser e85982a419 nicer preview of decrypt_text_fragment layout 2015-06-17 18:53:03 +02:00
Vincent Breitmoser 3755bce9ce fix nullpointer in ViewKeyFragment 2015-06-17 18:53:02 +02:00
Vincent Breitmoser 7d371d8a39 fix returned text of cleartext verify 2015-06-17 18:53:02 +02:00
Vincent Breitmoser f677446d51 use KeyAdapter in KeySpinner 2015-06-17 18:53:01 +02:00
Vincent Breitmoser b87a7372c9 improve preview of ViewKeyActivity layout 2015-06-17 18:53:00 +02:00
Vincent Breitmoser 84165c092e fix symmetric text decryption 2015-06-17 18:53:00 +02:00
Vincent Breitmoser 2d03965777 instrument: finish symmetric text encryption test 2015-06-17 18:53:00 +02:00
Vincent Breitmoser 312cb38848 preliminary EditKeyTest 2015-06-17 18:52:59 +02:00
Vincent Breitmoser 71ea521198 clean up helper code, add withKeyItemId matcher for KeyListAdapter 2015-06-17 18:52:59 +02:00
Vincent Breitmoser b8305d43dc return actual last log entry, including from sublogentryparcels 2015-06-17 18:52:59 +02:00
Vincent Breitmoser b7834b4326 fix recursive log in modifySecretKeyRing 2015-06-17 18:52:58 +02:00
Vincent Breitmoser ae199313ee instrument: change handling in PassphraseDialogActivity to work with espresso 2015-06-17 18:52:21 +02:00
Vincent Breitmoser db0266c0ae instrument: work on instrumentation tests 2015-06-17 18:52:20 +02:00
Vincent Breitmoser 908545e521 instrument: add test for symmetric text encrypt/decrypt 2015-06-17 18:52:20 +02:00
Vincent Breitmoser fe4659f8d6 instrument: add helper for snackbar check (2) 2015-06-17 18:52:20 +02:00
Vincent Breitmoser 34c7252048 add extra to disregard first-time dialog to main activity 2015-06-17 18:52:19 +02:00
Vincent Breitmoser 7998b2a262 instrument: add helper method for snackbar checking 2015-06-17 18:52:19 +02:00
Vincent Breitmoser 6f47c78981 stash away stuff 2015-06-17 18:52:19 +02:00
Vincent Breitmoser 442aed8a2d update instrumentation test to JUnit4 2015-06-17 18:52:19 +02:00
Vincent Breitmoser 46e75a085e travis: disable instrumentation for now 2015-06-17 18:52:06 +02:00
Vincent Breitmoser fb87f2ed2a travis: random testing 2015-06-17 18:52:05 +02:00
Vincent Breitmoser d599825046 add instrumentation tests 2015-06-17 18:52:05 +02:00
Vincent Breitmoser d445b4d2e0 try travis with android lang type 2015-06-17 18:52:05 +02:00
Vincent Breitmoser 2c47035e02 (minor) layout changes 2015-06-17 18:52:03 +02:00
Vincent Breitmoser 5e4842ab64 fix instrumentation test(s) 2015-06-17 18:51:50 +02:00
Vincent Breitmoser 66e7876abd add ToolableViewAnimator 2015-06-17 18:51:45 +02:00
Dominik Schürmann 0b3317600b Cleanup in build.gradle 2015-06-16 00:08:17 +02:00
Vincent 655cdfbbee Merge pull request #1338 from josecastillo/development
Improved smart card error handling
2015-06-12 15:00:16 +02:00
Vincent d57324aa4b Merge pull request #1331 from open-keychain/v/eventbus
replace messenger hack with eventbus
2015-06-12 14:59:26 +02:00
Dominik Schürmann 4f55b1f5d1 Use svg for build status 2015-06-11 22:41:17 +02:00
Joey Castillo cda8b63bb4 Replace AssertionErrors with snackbar notifications, fix style issues. 2015-06-11 11:52:09 -04:00
Vincent Breitmoser 5d652e4c41 some bugfixes for new CryptoOperationFragment 2015-06-11 17:10:40 +02:00
Vincent Breitmoser 244f92ed57 Merge remote-tracking branch 'origin/master' into v/eventbus 2015-06-11 15:34:55 +02:00
Dominik Schürmann 98ba424576 Fix coveralls 2015-06-11 13:12:45 +02:00
Dominik Schürmann 7f67658de9 Try coveralls support 2015-06-11 12:58:21 +02:00
Dominik Schürmann b856d82ae2 Jacoco support for local unit tests 2015-06-11 12:54:15 +02:00
Joey Castillo 14226461c1 Improved smart card error handling 2015-06-10 20:11:55 -04:00
Dominik Schürmann 7a5121894e Remove unused deps, fix travis 2015-06-11 00:36:05 +02:00
Dominik Schürmann 7c32098211 Add missing WorkaroundBuildConfig 2015-06-11 00:32:18 +02:00
Dominik Schürmann 6749b03d9a Fix debug/release build separation 2015-06-11 00:31:41 +02:00
Dominik Schürmann d16b09b2a6 Use new officially supported way for local unit tests, many dependencies upgraded for this, temporary disabled separate debug builds 2015-06-11 00:05:13 +02:00
Dominik Schürmann 05fcbcae7b Temporary fix for testDebug 2015-06-10 22:35:06 +02:00
Dominik Schürmann 260364e267 Use authority res strings 2015-06-10 20:00:29 +02:00
Dominik Schürmann aa31abd93f Allow debug build besides release build 2015-06-10 19:47:29 +02:00
Vincent Breitmoser 8da88f33bc fix unit tests 2015-06-10 17:09:55 +02:00
Vincent Breitmoser 61dce088c2 backport CryptoOperationFragment changes without eventbus 2015-06-10 15:45:10 +02:00
Vincent Breitmoser 9b6416943b Merge remote-tracking branch 'origin/master' into v/eventbus 2015-06-10 14:49:02 +02:00
Dominik Schürmann e6ea98fabc Improve FAQ entry for file manager 2015-06-08 22:46:08 +02:00
Dominik Schürmann 47f98493e2 Improve FAQ entry for file manager 2015-06-08 22:44:26 +02:00
Dominik Schürmann 28cb70d7dc Merge pull request #1333 from VPhantom/patch-1
Explicit mention of dependency on 3rd party tool
2015-06-08 22:41:37 +02:00
Stéphane Lavergne 1045deb0e3 Explicit mention of dependency on 3rd party tool
I had the darnest time remembering my old APG days where something was said in passing about, unlike any other application I know, the need here for OI File Manager (or others, later on) to be able to select local files.  I'm sure I'm not the only person who could benefit from this reminder. :)
2015-06-08 16:31:48 -04:00
Vincent Breitmoser 0505af7520 select correct item in drawer on backstack change in main activity 2015-06-08 16:18:16 +02:00
Dominik Schürmann 69c8ecd553 Merge pull request #1327 from adithyaphilip/fix-null
Prevent NullPointerException due to Activity death
2015-06-08 09:55:13 +02:00
Vincent Breitmoser 074b6633b0 eventbus: initial attempt, replace messenger hack with eventbus communication 2015-06-06 23:17:42 +02:00
Adithya Abraham Philip 82f3d70224 prevent null fragments on activity death 2015-06-03 19:01:01 +05:30
Vincent Breitmoser 7cfc0d80d0 Merge branch 'v/sticky-prefs' 2015-06-03 01:23:51 +02:00
Vincent Breitmoser ecfbc743f3 keep prefernces individual per dialog (affects only compression) 2015-06-03 01:21:06 +02:00
Vincent Breitmoser bd5a5c0138 Partially revert "switch to native DialogFragment, fix some nullpointers", but keep nullpointer fixes
This reverts commit 403f74f558.

Conflicts:
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java
2015-06-03 01:16:44 +02:00
Vincent Breitmoser b3ebb64666 hide invoke nfc button in android < 5 2015-06-03 01:08:32 +02:00
Dominik Schürmann e2988c2a68 Fix comments in service 2015-06-01 15:52:07 +02:00
Dominik Schürmann 7d29901f64 Merge pull request #1320 from adithyaphilip/merge-services-new
Merged CloudImportService and KeychainIntentService into KeychainService
2015-06-01 15:50:09 +02:00
Adithya Abraham Philip ebba24cbd8 corrected indentation 2015-06-01 17:44:44 +05:30
Adithya Abraham Philip 6bc40d12ad renamed KeychainIntentService to KeychainService 2015-06-01 17:43:00 +05:30
Adithya Abraham Philip 14a08361e5 merged services 2015-06-01 17:41:44 +05:30
Dominik Schürmann b356df900f Better use HEAD instead of master to link to files 2015-06-01 10:26:30 +02:00
Dominik Schürmann af5fc66229 Merge pull request #1317 from adithyaphilip/master
Merge key from Keyserver with that from Keybase instead of replacing
2015-06-01 10:15:46 +02:00
Dominik Schürmann 784dbc087e Fix links to branch 2015-06-01 09:43:56 +02:00
Vincent Breitmoser 403f74f558 switch to native DialogFragment, fix some nullpointers 2015-05-31 19:45:25 +02:00
Vincent Breitmoser 204893a025 fix small non-deterministic failure case in unit test 2015-05-31 05:41:18 +02:00
Vincent Breitmoser 1bc14ab6ae revert some accidentally committed cruft 2015-05-31 05:22:14 +02:00
Vincent Breitmoser cf5fadae76 implement sticky preferences 2015-05-31 05:18:58 +02:00
Vincent Breitmoser 3976eadf06 handle empty passphrases in PassphraseDialogActivity 2015-05-31 05:16:41 +02:00
Adithya Abraham Philip 93f3a98eae prevent keybase key replacing keyserver key 2015-05-31 02:49:11 +05:30
Vincent 5d87872245 Merge pull request #1318 from adithyaphilip/remove-nan
Prevent NaN Progress Dialog on import with no selected keys
2015-05-30 23:16:07 +02:00
Adithya Abraham Philip cfeffef80d prevent NaN dialog on import with no selected keys 2015-05-31 02:36:45 +05:30
Vincent Breitmoser 3be44898db only promote subkeys which are actually present 2015-05-30 14:08:49 +02:00
Vincent Breitmoser ef209450c6 some decrypt/verify bug fixes 2015-05-30 13:52:56 +02:00
Vincent Breitmoser 99fe806ea3 adapt unit tests to PgpDecryptVerifyInputParcel 2015-05-30 13:52:35 +02:00
Vincent Breitmoser 0d8370be1d rewrite PgpDecryptVerify input, introduce PgpDecryptVerifyInputParcel 2015-05-30 13:25:47 +02:00
Vincent Breitmoser 36ecd60c1b better error handling for bad encrypted data checksum 2015-05-30 13:09:09 +02:00
Vincent Breitmoser bde58c6ff1 delete correct cache entries (important for yubikey pins!) 2015-05-30 02:47:14 +02:00
Vincent Breitmoser 61a6346f89 show status indicators in EncryptFragment 2015-05-30 02:24:45 +02:00
Vincent Breitmoser 313b4ac7d3 rewrite data flow in EncryptFileFragment preserve state correctly 2015-05-30 00:20:11 +02:00
Vincent Breitmoser b9563ff2ef externalize caching functionality from CertifyKeyFragment 2015-05-30 00:17:00 +02:00
Vincent Breitmoser 1406eec2dc make KeyItem serializble, for state in TokenTextCompleteView 2015-05-30 00:16:27 +02:00
Vincent Breitmoser 8be6450a36 preserve state in DecryptFilesActivity/-Fragment 2015-05-29 21:38:20 +02:00
Vincent Breitmoser c9f9af6603 preserve state in CertifyKeyFragment 2015-05-29 21:07:56 +02:00
Vincent Breitmoser dd94c70fbe fix RequiredInputParcel handling in CertifyOperation 2015-05-29 20:26:06 +02:00
Dominik Schürmann 213798dde1 branch info 2015-05-29 12:19:30 +02:00
Vincent Breitmoser e174b8af3b Merge remote-tracking branch 'origin/master' into development 2015-05-29 11:41:02 +02:00
Vincent Breitmoser 25d0325c5f Merge remote-tracking branch 'origin/development' into development 2015-05-29 11:31:48 +02:00
Dominik Schürmann b794719020 Version 3.2.3 2015-05-29 02:33:04 +02:00
Vincent Breitmoser 38d8f4be52 Merge remote-tracking branch 'origin/master' into development
Conflicts:
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java
	OpenKeychain/src/main/res/values/strings.xml
2015-05-28 23:05:41 +02:00
Vincent Breitmoser 40703fe961 Merge branch 'master' into development
Conflicts:
	OpenKeychain/src/main/res/values/strings.xml
2015-05-28 16:37:15 +02:00
Vincent Breitmoser 4ecd4389b3 don't keep an activity reference in CreateKeyFinalFragment 2015-05-28 15:02:50 +02:00
Vincent Breitmoser 62e65a8240 create keys with fixed timestamp 2015-05-28 14:41:26 +02:00
Vincent Breitmoser 724726a4fd warn on signature earlier than key creation, err on significantly earlier 2015-05-28 11:40:35 +02:00
Vincent Breitmoser a8e95f676e don't show allowed key list if no key exists, and some minor PgpDecryptVerify changes 2015-05-28 02:27:44 +02:00
Vincent Breitmoser 426d17bd0a correctly preserve state in EncryptTextFragment 2015-05-27 23:03:04 +02:00
Vincent Breitmoser 56a75774d0 correctly preserve state in EncryptFilesFragment 2015-05-27 22:55:36 +02:00
Vincent Breitmoser 8dc9773c1e move synchronous encryption into activity, and preserve checkbox state 2015-05-27 22:33:14 +02:00
Vincent Breitmoser fed0e7db8d preserve state in KeySpinner, and some lint fixes 2015-05-27 22:07:34 +02:00
Vincent Breitmoser 6c17734e73 rewrite EncryptActivity data flow 2015-05-27 21:15:36 +02:00
Vincent Breitmoser 08e0357471 fix nullpointer in encrypttextactivity. fixes #1267 2015-05-27 18:18:34 +02:00
Vincent c4b774f7b8 Merge pull request #1293 from Yoshi64Bit/development
highlight currently selected item in navigation drawer
2015-05-23 11:14:20 +02:00
Yoshi64Bit 8a15d28ed9 highlight currently selected item in navigation drawer 2015-05-23 07:49:45 +02:00
Dominik Schürmann 1651f9fb61 AssertionError instead of RuntimeException 2015-05-21 13:32:22 +02:00
Dominik Schürmann 0456e04c1a Change convertFingerprintToHex to use RuntimeException 2015-05-21 11:00:52 +02:00
Dominik Schürmann 43d9e2ba76 Version 3.2.2 2015-05-21 10:34:39 +02:00
Dominik Schürmann ab63fa8091 Fix language based on feedback from transifex 2015-05-21 10:31:46 +02:00
Dominik Schürmann 2cdaa75b01 Fix fingerprint length check in QR Code import 2015-05-19 19:07:58 +02:00
Dominik Schürmann c8266203f8 Use Mode.ALPHANUMERIC for QR codes to save space 2015-05-19 18:27:04 +02:00
Dominik Schürmann 0f520975e4 Improve strings 2015-05-19 16:12:04 +02:00
Vincent Breitmoser 4885361cd2 check fingerprint length after scanning qr code
Fixes #1281
2015-05-17 10:37:03 +02:00
Vincent Breitmoser b430ba51eb do nfc dispatching in MainActivity 2015-05-17 02:09:58 +02:00
Vincent Breitmoser bd8e45b556 open ViewKeyActivity by subkey in NfcBaseActivity 2015-05-17 01:57:26 +02:00
Vincent Breitmoser cd0d84d10d simplify MainActivity and fix backstack issues 2015-05-17 01:46:00 +02:00
Vincent Breitmoser 064c9d461f re-parcel log in LogDisplayFragment 2015-05-17 01:17:01 +02:00
Vincent Breitmoser 7e5e0df0bc fingerprints are 20 bytes, not 40. duh. 2015-05-17 00:59:50 +02:00
Vincent Breitmoser 48f6e20f6c load yubikey fragment after mMasterKeyId is available 2015-05-17 00:54:14 +02:00
Vincent Breitmoser c1e7fcf024 apply promote operation to specific subkeys present on yubikey only 2015-05-17 00:35:10 +02:00
Vincent Breitmoser f554cc9c93 pass masterKeyId to yubikey fragment 2015-05-16 23:59:04 +02:00
Vincent Breitmoser 4b2c8a1309 allow state loss when yubikey fragment is loaded 2015-05-16 23:30:32 +02:00
Vincent Breitmoser a81474b7a5 yubikey: don't assume signing key is masterKeyId in ViewKeyActivity
Conflicts:
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
2015-05-16 23:30:32 +02:00
Vincent Breitmoser 022fde29ae fix fragment handling in ViewKeyActivity
Conflicts:
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
2015-05-16 23:30:32 +02:00
Dominik Schürmann 8c97bee14c zxing lib is now on jcenter, see https://github.com/journeyapps/zxing-android-embedded/issues/39 2015-05-15 13:31:01 +02:00
Vincent 5f6421e82b Merge pull request #1278 from josecastillo/nfckeytocard
Support for moving RSA keys to Yubikey or NFC smart card
2015-05-15 12:42:42 +02:00
Joey Castillo 2d3f745c36 Removing unused SubkeyChange constructor. 2015-05-13 17:56:18 -04:00
Joey Castillo aa75534e5b Moving blank smart card messages to strings.xml 2015-05-13 17:56:17 -04:00
Joey Castillo 0504033c6b Adding unit tests for PgpKeyOperation keytocard functionality. 2015-05-13 17:56:17 -04:00
Joey Castillo bc48ce4210 Add check for exporting two keys to same smart card slot. 2015-05-13 17:56:17 -04:00
Joey Castillo de2006a61f Bugfixes and changes based on feedback 2015-05-13 17:56:12 -04:00
Joey Castillo 28b9068ae0 Adding keytocard flag to SubkeyChange: UI sets this flag to initiate keytocard; operation unsets it and fills in dummyDivert to finish it. 2015-05-13 14:36:34 -04:00
Joey Castillo d21fb77336 Moving keytocard process into PgpKeyOperation. 2015-05-13 14:36:30 -04:00
Joey Castillo a0107afd3e Moved checks from fragment to operation, impoved logging. 2015-05-12 17:31:14 -04:00
Joey Castillo 76241e90ad Adding NFC Key to Card operation, accessible from Edit Key activity. 2015-05-12 14:44:26 -04:00
275 changed files with 6477 additions and 3288 deletions

View File

@ -1,19 +1,36 @@
language: java
language: android
jdk: openjdk7
before_install:
# Install base Android SDK
- sudo apt-get update -qq
- if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm lib32z1 lib32stdc++6; fi
- wget http://dl.google.com/android/android-sdk_r24.1.2-linux.tgz
- tar xzf android-sdk_r24.1.2-linux.tgz
- export ANDROID_HOME=$PWD/android-sdk-linux
- export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
# container based build, we don't need root anyways
sudo: false
# env:
# global:
# - ANDROID_API_LEVEL=21
# - ANDROID_ABI=armeabi-v7a
# - ADB_INSTALL_TIMEOUT=8 # minutes (2 minutes by default)
android:
components:
- build-tools-22.0.1
- build-tools-21.1.2
- build-tools-21.1.1
- build-tools-19.1.0
- android-22
- android-21
- android-19
- platform-tools
- extra-android-support
- extra-android-m2repository
licenses:
- 'android-sdk-preview-license-52d11cd2'
- 'android-sdk-license-.+'
- 'google-gdk-license-.+'
# doesn't work, travis is just too slow
# before_script:
# - echo no | android create avd --force -n test -t android-$ANDROID_API_LEVEL --abi $ANDROID_ABI
# - emulator -avd test -no-skin -no-audio -no-window &
# - ./tools/android-wait-for-emulator
# - adb shell input keyevent 82 &
# Install required Android components.
#- echo "y" | android update sdk -a --filter build-tools-19.1.0,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force
- ( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) | android update sdk --no-ui --all --force --filter build-tools-22.0.1,build-tools-21.1.2,build-tools-21.1.1,build-tools-19.1.0,android-22,android-21,android-19,platform-tools,extra-android-support,extra-android-m2repository
install: echo "Installation done"
script:
- ./gradlew assemble -S -q
- ./gradlew --info OpenKeychain-Test:testDebug
# - ./gradlew connectedAndroidTest
- ./gradlew testDebug jacocoTestReport coveralls

View File

@ -1 +1 @@
Please go to https://github.com/open-keychain/open-keychain/blob/development/OpenKeychain/src/main/res/raw/help_changelog.md
Please go to https://github.com/open-keychain/open-keychain/blob/HEAD/OpenKeychain/src/main/res/raw/help_changelog.md

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 95 95"
enable-background="new 0 0 100 100"
xml:space="preserve"
id="svg2"
inkscape:version="0.48.4 r9939"
width="100%"
height="100%"
sodipodi:docname="ic_stat_notify.svg"><metadata
id="metadata14"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs12" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1145"
id="namedview10"
showgrid="false"
inkscape:zoom="3.776"
inkscape:cx="56.6318"
inkscape:cy="73.41883"
inkscape:window-x="1917"
inkscape:window-y="-3"
inkscape:window-maximized="1"
inkscape:current-layer="svg2"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" /><path
d="m 36.917194,57.417227 5.333672,5.267283 9.512321,1.139867 1.441261,9.395693 9.80158,1.42359 0.863749,9.681407 9.944699,1.281231 L 74.966476,95 93.848,93.718769 95,74.642665 57.527222,37.629328 c 0,0 5.836602,-15.873525 -7.855375,-29.398624 C 35.97987,-5.2943953 17.169906,0.04556 8.5233497,8.587099 0.30816341,16.700566 -5.1686273,36.063379 7.5144667,48.589975 22.215327,63.112582 36.917194,57.417227 36.917194,57.417227 z m 53.904158,19.076105 -0.28926,5.411632 -38.338543,-37.869481 2.88353,-2.848176 35.744273,35.306025 z M 13.884235,13.882256 c 5.835594,-5.76305 13.617394,-7.4205864 17.383821,-3.700338 3.76542,3.719253 2.088317,11.406638 -3.747277,17.170684 -5.835595,5.762055 -13.618402,7.418596 -17.383822,3.699343 C 6.3715371,27.332692 8.0486407,19.645306 13.884235,13.882256 z"
id="path4"
inkscape:connector-curvature="0"
style="fill:#ffffff" /></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,121 +0,0 @@
buildscript {
repositories {
jcenter()
}
dependencies {
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information
classpath 'com.novoda:gradle-android-test-plugin:0.10.4'
}
}
apply plugin: 'java'
apply plugin: 'android-test'
apply plugin: 'jacoco'
dependencies {
testCompile 'junit:junit:4.11'
testCompile 'com.google.android:android:4.1.1.4'
testCompile('com.squareup:fest-android:1.0.8') { exclude module: 'support-v4' }
testCompile 'org.apache.maven:maven-ant-tasks:2.1.3'
testCompile ('org.robolectric:robolectric:2.4') {
exclude module: 'classworlds'
exclude module: 'maven-artifact'
exclude module: 'maven-artifact-manager'
exclude module: 'maven-error-diagnostics'
exclude module: 'maven-model'
exclude module: 'maven-plugin-registry'
exclude module: 'maven-profile'
exclude module: 'maven-project'
exclude module: 'maven-settings'
exclude module: 'nekohtml'
exclude module: 'plexus-container-default'
exclude module: 'plexus-interpolation'
exclude module: 'plexus-utils'
exclude module: 'support-v4' // crazy but my android studio don't like this dependency and to fix it remove .idea and re import project
exclude module: 'wagon-file'
exclude module: 'wagon-http-lightweight'
exclude module: 'wagon-http-shared'
exclude module: 'wagon-provider-api'
}
}
test {
exclude '**/*$*'
}
android {
projectUnderTest ':OpenKeychain'
}
jacoco {
toolVersion = "0.7.2.201409121644"
}
def coverageSourceDirs = [
'../OpenKeychain/src/main/java',
'../OpenKeychain/src/gen',
'../OpenKeychain/build/source/apt/debug',
'../OpenKeychain/build/source/generated/buildConfig/debug',
'../OpenKeychain/build/source/generated/r/debug'
]
jacocoTestReport {
reports {
xml.enabled = true
html.destination "${buildDir}/jacocoHtml"
}
// class R is used, but usage will not be covered, so ignore this class from report
classDirectories = fileTree(dir: '../OpenKeychain/build/intermediates/classes/debug/org/sufficientlysecure/keychain', exclude: [ 'R*.class' ])
additionalSourceDirs = files(coverageSourceDirs)
executionData = files('build/jacoco/testDebug.exec')
}
// new workaround to force add custom output dirs for android studio
task addTest {
def file = file(project.name + ".iml")
doLast {
try {
def parsedXml = (new XmlParser()).parse(file)
def node = parsedXml.component[1]
def outputNode = parsedXml.component[1].output[0]
def outputTestNode = parsedXml.component[1].'output-test'[0]
def rewrite = false
new Node(node, 'sourceFolder', ['url': 'file://$MODULE_DIR$/' + "${it}", 'isTestSource': "true"])
if(outputNode == null) {
new Node(node, 'output', ['url': 'file://$MODULE_DIR$/build/resources/testDebug'])
} else {
if(outputNode.attributes['url'] != 'file://$MODULE_DIR$/build/resources/testDebug') {
outputNode.attributes = ['url': 'file://$MODULE_DIR$/build/resources/testDebug']
rewrite = true
}
}
if(outputTestNode == null) {
new Node(node, 'output-test', ['url': 'file://$MODULE_DIR$/build/test-classes/debug'])
} else {
if(outputTestNode.attributes['url'] != 'file://$MODULE_DIR$/build/test-classes/debug') {
outputTestNode.attributes = ['url': 'file://$MODULE_DIR$/build/test-classes/debug']
rewrite = true
}
}
if(rewrite) {
def writer = new StringWriter()
new XmlNodePrinter(new PrintWriter(writer)).print(parsedXml)
file.text = writer.toString()
}
} catch (FileNotFoundException e) {
// iml not found, common on command line only builds
}
}
}
// always do the addtest on prebuild
gradle.projectsEvaluated {
testDebugClasses.dependsOn(addTest)
}

View File

@ -1,5 +1,7 @@
apply plugin: 'com.android.application'
apply plugin: 'witness'
apply plugin: 'jacoco'
apply plugin: 'com.github.kt3k.coveralls'
dependencies {
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information
@ -10,12 +12,23 @@ dependencies {
compile 'com.android.support:appcompat-v7:22.1.1'
compile 'com.android.support:recyclerview-v7:22.1.0'
compile 'com.android.support:cardview-v7:22.1.0'
// Unit tests in the local JVM with Robolectric
// https://developer.android.com/training/testing/unit-testing/local-unit-tests.html
// https://github.com/nenick/AndroidStudioAndRobolectric
// http://www.vogella.com/tutorials/Robolectric/article.html
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:3.0-rc3'
// UI testing libs
androidTestCompile 'com.android.support.test:runner:0.2'
androidTestCompile 'com.android.support.test:rules:0.2'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.1'
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.1'
// UI testing with Espresso
androidTestCompile 'com.android.support.test:runner:0.3'
androidTestCompile 'com.android.support.test:rules:0.3'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2') {
exclude group: 'com.android.support', module: 'appcompat'
exclude group: 'com.android.support', module: 'support-v4'
exclude module: 'recyclerview-v7'
}
// Temporary workaround for bug: https://code.google.com/p/android-test-kit/issues/detail?id=136
// from https://github.com/googlesamples/android-testing/blob/master/build.gradle#L21
@ -31,7 +44,7 @@ dependencies {
compile 'com.jpardogo.materialtabstrip:library:1.0.9'
compile 'com.getbase:floatingactionbutton:1.9.0'
compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0'
compile "com.splitwise:tokenautocomplete:1.3.3@aar"
compile 'com.splitwise:tokenautocomplete:1.3.3@aar'
compile 'se.emilsjolander:stickylistheaders:2.6.0'
compile 'org.sufficientlysecure:html-textview:1.1'
compile 'com.mikepenz.materialdrawer:library:2.8.2@aar'
@ -98,8 +111,16 @@ android {
defaultConfig {
minSdkVersion 15
targetSdkVersion 22
versionCode 32300
versionName "3.2.3"
applicationId "org.sufficientlysecure.keychain"
// the androidjunitrunner is broken regarding coverage, see here:
// https://code.google.com/p/android/issues/detail?id=170607
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// this workaround runner fixes the coverage problem, BUT doesn't work
// with android studio single test execution. use it to generate coverage
// data, but keep the other one otherwis
// testInstrumentationRunner "org.sufficientlysecure.keychain.JacocoWorkaroundJUnitRunner"
}
compileOptions {
@ -111,6 +132,25 @@ android {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// Reference them in the java files with e.g. BuildConfig.ACCOUNT_TYPE.
buildConfigField "String", "ACCOUNT_TYPE", "\"org.sufficientlysecure.keychain.account\""
// Reference them in .xml files.
resValue "string", "account_type", "org.sufficientlysecure.keychain.account"
}
debug {
applicationIdSuffix ".debug"
// Reference them in the java files with e.g. BuildConfig.ACCOUNT_TYPE.
buildConfigField "String", "ACCOUNT_TYPE", "\"org.sufficientlysecure.keychain.debug.account\""
// Reference them in .xml files.
resValue "string", "account_type", "org.sufficientlysecure.keychain.debug.account"
// Enable code coverage (Jacoco)
testCoverageEnabled true
}
}
@ -163,6 +203,42 @@ android {
}
}
// apply plugin: 'spoon'
task jacocoTestReport(type:JacocoReport) {
group = "Reporting"
description = "Generate Jacoco coverage reports"
classDirectories = fileTree(
dir: "${buildDir}/intermediates/classes/debug",
excludes: ['**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/BuildConfig.*',
'**/Manifest*.*']
)
sourceDirectories = files("${buildDir.parent}/src/main/java")
additionalSourceDirs = files([
"${buildDir}/generated/source/buildConfig/debug",
"${buildDir}/generated/source/r/debug"
])
executionData = files([
"${buildDir}/jacoco/testDebug.exec",
"${buildDir}/outputs/code-coverage/connected/coverage.ec"
])
reports {
xml.enabled = true
html.enabled = true
}
}
// Fix for: No report file available: [/home/travis/build/open-keychain/open-keychain/OpenKeychain/build/reports/cobertura/coverage.xml, /home/travis/build/open-keychain/open-keychain/OpenKeychain/build/reports/jacoco/test/jacocoTestReport.xml]
coveralls {
jacocoReportPath 'build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml'
}
// NOTE: This disables Lint!
tasks.whenTaskAdded { task ->
if (task.name.contains('lint')) {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,156 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFTxJOIBEACpXpXjz5ivyR/uWJexJXZrLHrvBRcivQSxzvy5Owdun8MGOxzo
YTPAJzskZH4Gg9MIQj9puUke8A59o9KtNMAd3Szp6aQs3lv/eGOw0DUe6g8a6kJ+
oQsHMYYLnyuYrOvQrBewKhh4FynkryvUZCL25dgrMHeTeMAeslCscdT6JYd2gxgi
o0+Bn8R6BonS7hTZStfAOYB0SO7X+7R0CpPsMiHtNZASzLRkk53dB9AO6m81CurP
qh1oPjc7j1QIiwCszSD8IxuR3fH6nyUlMtqx/mRmKNlbIYnr3sN5VAMp7THq/Qjt
9XDs1XHBE+0H+yg7K4fPamSJsxy9zPq/rA9vy4pQIMzxugxeWEn4OPWtguRSYtI1
WA+Ki93QJ+DVRlpMvz/MA71ZkvWUHpFnIssU52NdHZrIZ21KCo7PiPRY9tQkVhz5
S39a8npSeCIwZ9bH3UGKrRyPkhal59rjPqhZky1X+O2iTfbbFrPE+I4b46pWX9XU
UgXDnUh8Qeymhh0f1DTcbquW2nwU/R1quKjw/TCZQxKnkAzCXsVFk2QoIG1qDVrU
YlKXTXoYaRfzhMSYSvh7JHqdldB+MyKAbXpLtZsYIlLkuqe/KI0IPfO69ajDxLOr
+wTtuA9T1SICGmQkjYkQiqVy4y7vDW4Zw143sPXreZhiBbLBoWAIJ/JcbQARAQAB
tAVYIDx4PokCNwQTAQoAIQULCQgHAwYVCgkLCAMEFgIDAQIZAQWCVPEk4wKeAQKb
AQAKCRCdYE0vMQcWo/TGD/9SEfE7LxWw4pPNTpVTECDztpLGrj7kCr0anO6XnCqL
3OYoZz5vZvKBmunoPT7VqerQIZCnhB0b0ZOI+l51J03Xwvfy1+MIQCnZz++JY94B
h5dv3QAj8WUJJ6KfKrAxoXGU67mNhW5oe3mXQd3/a0JjmjV2yzFFOS4edyWPQal9
lY6MpOKfEIiD6uRqh8A1A9jAstJ9c5XHVtuBv265DEK5tMyAU3cmtm0pEzgmvPoG
kxo1vDAhP9GKb+DcBrB6XuZ7Kahl+kDpEhmuI6drxJBhez169aE43dK7G3X1W78f
OyO0C4jWAy1kVj4aYT4qgJj8TwRoAwlzX5RqmK4RW17sX3nOlff/FQclepawOrU+
LO5ZgbQ4qG2yDJSJ+tcS2fIO1MoI5vPa7DIVEpMM7DbrPYVy0Ix/xv80MwKQhnWs
P5tLRGuJ7JbKPzqvBtG7xWow5isOMkqeBkU0yUr59tAtHwM6c3Vi2at9YBiraqBY
3mgEukIuNpSuhFSBhniVUovVGgj8LCLDL+mpuc9+HUzYWJKGks+eGY0Od+dJjgh+
wXQk0rTTkY80kKpIjREDOVuPRhsw5OYb63fbiaYwormPx4pXv2mitOYNAXy3YNpR
Xl5MvObYLQugpqtyjpijyyANbsHKWwClkL/vxnbcfRXF307NQGSwhs3gKpSuovVE
edEkI2QBI0VncGdwaWQ6QGRuczp0ZXN0Lm11Z2VuZ3VpbGQuY29tiQIcBBMBCgAG
BYJU9KuzAAoJEJ1gTS8xBxajnPEP+gL3MasL9GcXt0c93QkQsRay8IMCspM+Qt/X
rfoUSJb41V4nqeyumX/kRmY7/eMZJnxIJgn8aWmRjiPhasUMBfeXmm4RAmwHEkFP
6nw288dHIlKgRaAMwfs44thKxPKTkscLiSqbmrRhLULW840pIZkgtOLmhH2tiaa7
w/hgDCMgHfwZaSlQkEDfvAef7i61itNdBlL0e0CelzNL0Uc5P6b/Nvn5uWlrLIHZ
evOx8dswhmwz25D7KZQPh91JpAqoLk5hj3/DAbVxeUFK/Zt9o/bu+ij57qSfRif3
HlLydH98FFcetqLbGDt1/Eiea3tpqjK5Xgf+HOppQo2MpOCP64xNejGWnKx6M7yF
A/yiI4Ahe4uP9+SXar0FPLGXAfyc/pSrRN94dY+iXpWPCSlNRNAOyr1mxPCvw9MM
C5wYnfLLSWUMPYv9rQyr1pvYyxe2r0Fla6JAkRy5nMnR1RdorwafeMcyQi3nllBS
S0wzmagus1W2qqRbbOR8J5CXkkjbw9vV0vXFnCHq93ms9Ebnhr5rNzVndKnxVCmK
hftW7gFz8Qz3tG66eSWA/6VrRvfUNfR5cUBpVOARqxqYz7OPrGg0Lkfw+JPQX8v/
WMIfM6Jsj/koJmOb2y/h0rJ+iTBofPCoGONKIzlOK5c8bhSmU/27KX1xBacTgN9Q
hlulIbZs0SsqZAAS1odwZ3BpZCtjb29raWU6QGRuczp0ZXN0Lm11Z2VuZ3VpbGQu
Y29tiQIcBBMBCgAGBYJU901MAAoJEJ1gTS8xBxajHKAP/3WDeUfBkuq50v3E8SBx
qDHXomPncNOccRPS1/DYwxVp5JYE8NsFW2rlmeJiy4Rhm4MnptUFUTPMBfYOcHvT
oWr/3Gz0y5ik5oiPC5hAXWCPrNkdtgBugBByL+0+w2PWKQqwBIc9ef+7aIh28dKn
6zr71Au+DXJKqYz/jOqY3IJGzBkydxOPSy83WNPm9OR/zfhCOoCTDAy+0TGxYgjW
Idi4k4yDRWhAeF+GqJRuDf1RKZxPL1MjwhX0Lv4ndR+V3sECRVh6HRIBGYYti8kU
0Q9jdbYjU+guestyyvsWQBOaGRP+gGQY+fYdHb2YUK6jMcndPxbRonjaRY5Bcc0J
NAYAqFv/WyR7hd4SWXI8Zg3u+GV7x8ZbnUOwfvXBVvZohbycpoT7WVOPRhQ2CQ5P
uS2E5QaoA6fUfH2qUAaxlvnfMRPL9E1svIEFXMtf1GqO6hzzJWV6Qw59HODTy568
StloM6IVnlolJfxf7+aEQx+KrmxJqVVWlbZ5u5gzQa+Mak0N5F6m8w4VftyHT0W6
RinNh3IGbk64+uXYzAUhJ6badfXrrxTP0wkjPPPjNC/BKfPCzYhZnyGLCuFwlOqE
o1Nq6qMzSHOlxR08ycX0MLNEfWsqu2CV8YGLYThejOr8JSjyXM3ANHNB+AZ5/eeY
AGUc5XgxTbq/MpnytiBzaZfw0Tw7ZAAS1odwZ3BpZCtjb29raWU6Z2VuZXJpY0Bo
dHRwczovL211Z2VuZ3VpbGQuY29tL3BncGtleS50eHSJAhwEEwEIAAYFglT4KOIA
CgkQnWBNLzEHFqOZJQ//UL+/ZfikmVA8DDIbbd8beIgJ0uRZgQ4aVuxRNiWhxQbl
5KPQaiztrwr3ESgyf3HjjUyGhg5/UP+ObvloCqcwCphSrBNxHiEp31jLin7QfWLp
vzPOjtMJNEeq3yeXq+YpHw2VZ+nod+XD785YtLBqq1SipNSZEifRYnsLhqQOj6cB
t0F2RLBm5afLbd4KjelT7W7229lYlGJkoQEwrmVYASiMfeBDBnHJ1Xgp1P0dm1gS
ERVSzV2qW6w0/psOHq4cnq4XWSn/IRW3UYfHIDDZJG125keYoaJAbBnzC+OO4eV2
KheY5dkmaeT0RmgzpEyJq5PRDNpnoXvSYuC9KGiNTbCiQ3liJnUJslbzDwvTyTfa
wxUba4dXhpcSE8mjXa/9/vMJER5yW0zzkSa+fjIIZcL0PQqlTeDBzFMSA9Ut9Y+m
6vdGwelIeyb38dWvsDC1NUUR2pydTsRtFM1o7jUtGoovQlLh5/9x/YDh8vqpzip3
L5SSvN4RdXN4P+EZor4xfi0PVbHoDZusxyPfKEFlCnX6k9qEaWplacLbl8M8mtpv
++3vYVW6hA3rK/6XdftFDJpbmj+zTXvrATE/lvPcJHiswiuMLx9BPoEiv1MGXTyE
6TxB5H7L1eCQWfbZYnkEWiuXlfDyofvDfufiGTsBAQzN3ePuhYUNznUB+1syD4E=
=Sxs8
-----END PGP PUBLIC KEY BLOCK-----
-----BEGIN PGP PRIVATE KEY BLOCK-----
lQdGBFTxJOIBEACpXpXjz5ivyR/uWJexJXZrLHrvBRcivQSxzvy5Owdun8MGOxzo
YTPAJzskZH4Gg9MIQj9puUke8A59o9KtNMAd3Szp6aQs3lv/eGOw0DUe6g8a6kJ+
oQsHMYYLnyuYrOvQrBewKhh4FynkryvUZCL25dgrMHeTeMAeslCscdT6JYd2gxgi
o0+Bn8R6BonS7hTZStfAOYB0SO7X+7R0CpPsMiHtNZASzLRkk53dB9AO6m81CurP
qh1oPjc7j1QIiwCszSD8IxuR3fH6nyUlMtqx/mRmKNlbIYnr3sN5VAMp7THq/Qjt
9XDs1XHBE+0H+yg7K4fPamSJsxy9zPq/rA9vy4pQIMzxugxeWEn4OPWtguRSYtI1
WA+Ki93QJ+DVRlpMvz/MA71ZkvWUHpFnIssU52NdHZrIZ21KCo7PiPRY9tQkVhz5
S39a8npSeCIwZ9bH3UGKrRyPkhal59rjPqhZky1X+O2iTfbbFrPE+I4b46pWX9XU
UgXDnUh8Qeymhh0f1DTcbquW2nwU/R1quKjw/TCZQxKnkAzCXsVFk2QoIG1qDVrU
YlKXTXoYaRfzhMSYSvh7JHqdldB+MyKAbXpLtZsYIlLkuqe/KI0IPfO69ajDxLOr
+wTtuA9T1SICGmQkjYkQiqVy4y7vDW4Zw143sPXreZhiBbLBoWAIJ/JcbQARAQAB
/gkDCMorb5SPPoHekALY3vvpC5rmKLs5ULOzs9BaI0qq/D5kQjSw4WnQIJwLTgI8
iwRYL+pUn7t8txDAEWKCSI1kVUaNocWYeUpiEYo9WWWHwGpf2ZZXwS/cQYi1APD5
U8uN/fKwFyRzLEXi++b6Lp4GA3l6f3sEwpSu1Wz/jRKDAweBENrlrCPEbn+VNkF8
X4aIhFNr95WFmTmjNp1AP+Ons4uny4sBVJqkbzoosLNSBHN05+mWsrWepTVGI0VP
SPmm/PY2st+zwwRPjGbWvEcFHUCnZMGrF1bhl7nQ+tZh5KTgCe/gcPVlf3ZQtEqG
RdzlWA1jsDF4h1/vpynOyONpZ5Hx0QWe2z1C7VeR4SsgASdbtX4EWj4Squpya0rf
X4k/EoY7vYX/OoOfi5aExkM79vHzN2EM89KI9ecoCfHy5x6Ovlhep7mL/p48zJZU
Q5pDfT5aLL6XV6T2gOWRFnowdETEBHWt6qYRhW47mvkdAiEBBpPi3sQnYM7M1hvD
2/yjogrgCUgAfCvh4vJbu2LPiGHkqFoS2iEEDexAcQ8REw+ePi4cg818R23zAJDV
xdLqpKig71YDTDxxkCuNoJeHOdFa2I9J9hjlTgYEBU6oJ+9TlsIueEhDnEGiScmn
SrhsFOcNyi+TJQg9KMx1AcX3RsCpOsRMxlqY0+zOh6YCh5MGXk/QQGGLnEFEax/V
ia2nCj91xA8BwPwjAOmb1Z9rdAkdLuPybLEAhlfu3ZFTDy8uQnL0LvC4grKUlYcm
soeWSaOihxZ6Q6ZxxWOysQX/kYBz5TCn/liSKr2qJZVxur487RYGIrWDKSJtPotc
Ke1gdUGG+EZJji6ik8blTfwtbuwCRX1WuiZiJidG5RtD0v84fm0U9uolY5HX4GFO
y8LCJVASyOvD0Af1gf6MIwAQTt+X3UIQr7zyN/QN5aBpVnI5wN5FmGoojz+MBDE2
/35kLYMQiQ3IspZR5apVpWJ2OPD/i0OunQU6t39zKR+0N5YPGLq2ZcTgRPGkSffz
DX0jxta8Gl55ysY9aKp3LvbZqvAyHBttnOuNzkw3KgTl1jAsOj3pjeVF+kPWwvAE
fjsSZjkXTFRZC7VG7bawzJK80Ux7EoQEwi7ixup5UK3DFwlp4eK0KGFJKaIKYj7V
8CHQE0muDx2s5KmSysYs+r2888r4MEAlEz1WyXdDoyQA0dIGfpvnts5AJCZqceal
akI5rQXwyD2K4X0N5IA54E1iJnYRwebXGsn8ldD2w+WEfWq3g87e3enmHSe48DuD
k3I0mf7V7AMEmrwuB+xVN035/QwScQeWE+tV6AdHleu4Ceo9b9VlmE/PYE3P+xKM
8ZZIe/wAeHhsocSlFL8c8hwXUNQ4TXOrsgfDF+3DYAXvQiwM1WxdPV8zSPU2Mu/2
4Q0Ba0ZepcKQNU+CPV88XRbzfb8NZ/mTmnHYX/P7vIWyg8i0w+z2Pm+/GFr1I7Gb
ily11beRweFUs97xnz21g+bJ2NrJkAP6ewFLO3FTWJoK4l/wFNRp9PcwvqGsqSxR
OdxrYN9RCmhiemACiJbYxoQN7wTCmmaHdtXcfqkYJjNsKydUwPV/Kaaf+PlWBOVp
yTOVh7MMPN8fjBPFYcFZ5xs/z/gpI7dFu6sXjEw8F6Pe+hPgp+IeWmQsUTgoQ8bD
HoK/MjjsuD88rWdDXdbfc/PpIC/cGqPpu89sOS8hSQJVgKn48TKbkIA+FrWogeuw
Ofr/IXYT2qlcwAmlrdRjLswyTsLM5eQU3VH5/IlVilQQjFZBbiqBjOW0BVggPHg+
iQI3BBMBCgAhBQsJCAcDBhUKCQsIAwQWAgMBAhkBBYJU8STjAp4BApsBAAoJEJ1g
TS8xBxaj9MYP/1IR8TsvFbDik81OlVMQIPO2ksauPuQKvRqc7pecKovc5ihnPm9m
8oGa6eg9PtWp6tAhkKeEHRvRk4j6XnUnTdfC9/LX4whAKdnP74lj3gGHl2/dACPx
ZQknop8qsDGhcZTruY2Fbmh7eZdB3f9rQmOaNXbLMUU5Lh53JY9BqX2Vjoyk4p8Q
iIPq5GqHwDUD2MCy0n1zlcdW24G/brkMQrm0zIBTdya2bSkTOCa8+gaTGjW8MCE/
0Ypv4NwGsHpe5nspqGX6QOkSGa4jp2vEkGF7PXr1oTjd0rsbdfVbvx87I7QLiNYD
LWRWPhphPiqAmPxPBGgDCXNflGqYrhFbXuxfec6V9/8VByV6lrA6tT4s7lmBtDio
bbIMlIn61xLZ8g7Uygjm89rsMhUSkwzsNus9hXLQjH/G/zQzApCGdaw/m0tEa4ns
lso/Oq8G0bvFajDmKw4ySp4GRTTJSvn20C0fAzpzdWLZq31gGKtqoFjeaAS6Qi42
lK6EVIGGeJVSi9UaCPwsIsMv6am5z34dTNhYkoaSz54ZjQ5350mOCH7BdCTStNOR
jzSQqkiNEQM5W49GGzDk5hvrd9uJpjCiuY/Hile/aaK05g0BfLdg2lFeXky85tgt
C6Cmq3KOmKPLIA1uwcpbAKWQv+/Gdtx9FcXfTs1AZLCGzeAqlK6i9UR50SQjZAEj
RWdwZ3BpZDpAZG5zOnRlc3QubXVnZW5ndWlsZC5jb22JAhwEEwEKAAYFglT0q7MA
CgkQnWBNLzEHFqOc8Q/6Avcxqwv0Zxe3Rz3dCRCxFrLwgwKykz5C39et+hRIlvjV
Xiep7K6Zf+RGZjv94xkmfEgmCfxpaZGOI+FqxQwF95eabhECbAcSQU/qfDbzx0ci
UqBFoAzB+zji2ErE8pOSxwuJKpuatGEtQtbzjSkhmSC04uaEfa2JprvD+GAMIyAd
/BlpKVCQQN+8B5/uLrWK010GUvR7QJ6XM0vRRzk/pv82+fm5aWssgdl687Hx2zCG
bDPbkPsplA+H3UmkCqguTmGPf8MBtXF5QUr9m32j9u76KPnupJ9GJ/ceUvJ0f3wU
Vx62otsYO3X8SJ5re2mqMrleB/4c6mlCjYyk4I/rjE16MZacrHozvIUD/KIjgCF7
i4/35JdqvQU8sZcB/Jz+lKtE33h1j6JelY8JKU1E0A7KvWbE8K/D0wwLnBid8stJ
ZQw9i/2tDKvWm9jLF7avQWVrokCRHLmcydHVF2ivBp94xzJCLeeWUFJLTDOZqC6z
VbaqpFts5HwnkJeSSNvD29XS9cWcIer3eaz0RueGvms3NWd0qfFUKYqF+1buAXPx
DPe0brp5JYD/pWtG99Q19HlxQGlU4BGrGpjPs4+saDQuR/D4k9Bfy/9Ywh8zomyP
+SgmY5vbL+HSsn6JMGh88KgY40ojOU4rlzxuFKZT/bspfXEFpxOA31CGW6UhtmzR
KypkABLWh3BncGlkK2Nvb2tpZTpAZG5zOnRlc3QubXVnZW5ndWlsZC5jb22JAhwE
EwEKAAYFglT3TUwACgkQnWBNLzEHFqMcoA//dYN5R8GS6rnS/cTxIHGoMdeiY+dw
05xxE9LX8NjDFWnklgTw2wVbauWZ4mLLhGGbgyem1QVRM8wF9g5we9Ohav/cbPTL
mKTmiI8LmEBdYI+s2R22AG6AEHIv7T7DY9YpCrAEhz15/7toiHbx0qfrOvvUC74N
ckqpjP+M6pjcgkbMGTJ3E49LLzdY0+b05H/N+EI6gJMMDL7RMbFiCNYh2LiTjINF
aEB4X4aolG4N/VEpnE8vUyPCFfQu/id1H5XewQJFWHodEgEZhi2LyRTRD2N1tiNT
6C56y3LK+xZAE5oZE/6AZBj59h0dvZhQrqMxyd0/FtGieNpFjkFxzQk0BgCoW/9b
JHuF3hJZcjxmDe74ZXvHxludQ7B+9cFW9miFvJymhPtZU49GFDYJDk+5LYTlBqgD
p9R8fapQBrGW+d8xE8v0TWy8gQVcy1/Uao7qHPMlZXpDDn0c4NPLnrxK2WgzohWe
WiUl/F/v5oRDH4qubEmpVVaVtnm7mDNBr4xqTQ3kXqbzDhV+3IdPRbpGKc2HcgZu
Trj65djMBSEnptp19euvFM/TCSM88+M0L8Ep88LNiFmfIYsK4XCU6oSjU2rqozNI
c6XFHTzJxfQws0R9ayq7YJXxgYthOF6M6vwlKPJczcA0c0H4Bnn955gAZRzleDFN
ur8ymfK2IHNpl/DRPDtkABLWh3BncGlkK2Nvb2tpZTpnZW5lcmljQGh0dHBzOi8v
bXVnZW5ndWlsZC5jb20vcGdwa2V5LnR4dIkCHAQTAQgABgWCVPgo4gAKCRCdYE0v
MQcWo5klD/9Qv79l+KSZUDwMMhtt3xt4iAnS5FmBDhpW7FE2JaHFBuXko9BqLO2v
CvcRKDJ/ceONTIaGDn9Q/45u+WgKpzAKmFKsE3EeISnfWMuKftB9Yum/M86O0wk0
R6rfJ5er5ikfDZVn6eh35cPvzli0sGqrVKKk1JkSJ9FiewuGpA6PpwG3QXZEsGbl
p8tt3gqN6VPtbvbb2ViUYmShATCuZVgBKIx94EMGccnVeCnU/R2bWBIRFVLNXapb
rDT+mw4erhyerhdZKf8hFbdRh8cgMNkkbXbmR5ihokBsGfML447h5XYqF5jl2SZp
5PRGaDOkTImrk9EM2mehe9Ji4L0oaI1NsKJDeWImdQmyVvMPC9PJN9rDFRtrh1eG
lxITyaNdr/3+8wkRHnJbTPORJr5+MghlwvQ9CqVN4MHMUxID1S31j6bq90bB6Uh7
Jvfx1a+wMLU1RRHanJ1OxG0UzWjuNS0aii9CUuHn/3H9gOHy+qnOKncvlJK83hF1
c3g/4RmivjF+LQ9VsegNm6zHI98oQWUKdfqT2oRpamVpwtuXwzya2m/77e9hVbqE
Desr/pd1+0UMmluaP7NNe+sBMT+W89wkeKzCK4wvH0E+gSK/UwZdPITpPEHkfsvV
4JBZ9tlieQRaK5eV8PKh+8N+5+IZOwEBDM3d4+6FhQ3OdQH7WzIPgQ==
=AYKY
-----END PGP PRIVATE KEY BLOCK-----

View File

@ -0,0 +1,262 @@
/*
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain;
import android.app.Activity;
import android.content.Intent;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import android.widget.AdapterView;
import org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.MainActivity;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.contrib.DrawerActions.openDrawer;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.not;
import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar;
import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource;
import static org.sufficientlysecure.keychain.TestHelpers.randomString;
import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId;
import static org.sufficientlysecure.keychain.matcher.DrawableMatcher.withDrawable;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class AsymmetricOperationTests {
@Rule
public final ActivityTestRule<MainActivity> mActivity
= new ActivityTestRule<MainActivity>(MainActivity.class) {
@Override
protected Intent getActivityIntent() {
Intent intent = super.getActivityIntent();
intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true);
return intent;
}
};
@Before
public void setUp() throws Exception {
Activity activity = mActivity.getActivity();
// import these two, make sure they're there
importKeysFromResource(activity, "x.sec.asc");
// make sure no passphrases are cached
PassphraseCacheService.clearCachedPassphrases(activity);
}
@Test
public void testTextEncryptDecryptFromToken() throws Exception {
// navigate to 'encrypt text'
openDrawer(R.id.drawer_layout);
onView(withText(R.string.nav_encrypt_decrypt)).perform(click());
onView(withId(R.id.encrypt_text)).perform(click());
String cleartext = randomString(10, 30);
{ // encrypt
// the EncryptKeyCompletionView is tested individually
onView(withId(R.id.recipient_list)).perform(tokenEncryptViewAddToken(0x9D604D2F310716A3L));
onView(withId(R.id.encrypt_text_text)).perform(typeText(cleartext));
onView(withId(R.id.encrypt_copy)).perform(click());
}
// go to decrypt from clipboard view
pressBack();
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
{ // decrypt
onView(withId(R.id.passphrase_passphrase)).perform(typeText("x"));
onView(withText(R.string.btn_unlock)).perform(click());
onView(withId(R.id.decrypt_text_plaintext)).check(matches(
withText(cleartext)));
onView(withId(R.id.result_encryption_text)).check(matches(
withText(R.string.decrypt_result_encrypted)));
onView(withId(R.id.result_signature_text)).check(matches(
withText(R.string.decrypt_result_no_signature)));
onView(withId(R.id.result_signature_layout)).check(matches(
not(isDisplayed())));
onView(withId(R.id.result_encryption_icon)).check(matches(
withDrawable(R.drawable.status_lock_closed_24dp)));
onView(withId(R.id.result_signature_icon)).check(matches(
withDrawable(R.drawable.status_signature_unknown_cutout_24dp)));
}
}
@Test
public void testTextEncryptDecryptFromKeyView() throws Exception {
String cleartext = randomString(10, 30);
{ // encrypt
// navigate to edit key dialog
onData(withKeyItemId(0x9D604D2F310716A3L))
.inAdapterView(allOf(isAssignableFrom(AdapterView.class),
isDescendantOfA(withId(R.id.key_list_list))))
.perform(click());
onView(withId(R.id.view_key_action_encrypt_text)).perform(click());
onView(withId(R.id.encrypt_text_text)).perform(typeText(cleartext));
onView(withId(R.id.encrypt_copy)).perform(click());
}
// go to decrypt from clipboard view
pressBack();
pressBack();
openDrawer(R.id.drawer_layout);
onView(withText(R.string.nav_encrypt_decrypt)).perform(click());
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
{ // decrypt
onView(withId(R.id.passphrase_passphrase)).perform(typeText("x"));
onView(withText(R.string.btn_unlock)).perform(click());
onView(withId(R.id.decrypt_text_plaintext)).check(matches(
withText(cleartext)));
onView(withId(R.id.result_encryption_text)).check(matches(
withText(R.string.decrypt_result_encrypted)));
onView(withId(R.id.result_signature_text)).check(matches(
withText(R.string.decrypt_result_no_signature)));
onView(withId(R.id.result_signature_layout)).check(matches(
not(isDisplayed())));
onView(withId(R.id.result_encryption_icon)).check(matches(
withDrawable(R.drawable.status_lock_closed_24dp)));
onView(withId(R.id.result_signature_icon)).check(matches(
withDrawable(R.drawable.status_signature_unknown_cutout_24dp)));
}
pressBack();
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
{ // decrypt again, passphrase should be cached
onView(withId(R.id.decrypt_text_plaintext)).check(matches(
withText(cleartext)));
onView(withId(R.id.result_encryption_text)).check(matches(
withText(R.string.decrypt_result_encrypted)));
onView(withId(R.id.result_signature_text)).check(matches(
withText(R.string.decrypt_result_no_signature)));
onView(withId(R.id.result_signature_layout)).check(matches(
not(isDisplayed())));
onView(withId(R.id.result_encryption_icon)).check(matches(
withDrawable(R.drawable.status_lock_closed_24dp)));
onView(withId(R.id.result_signature_icon)).check(matches(
withDrawable(R.drawable.status_signature_unknown_cutout_24dp)));
}
}
@Test
public void testSignVerify() throws Exception {
String cleartext = randomString(10, 30);
// navigate to 'encrypt text'
openDrawer(R.id.drawer_layout);
onView(withText(R.string.nav_encrypt_decrypt)).perform(click());
onView(withId(R.id.encrypt_text)).perform(click());
{ // sign
onView(withId(R.id.encrypt_copy)).perform(click());
checkSnackbar(Style.ERROR, R.string.error_empty_text);
// navigate to edit key dialog
onView(withId(R.id.sign)).perform(click());
onData(withKeyItemId(0x9D604D2F310716A3L))
.inAdapterView(isAssignableFrom(AdapterView.class))
.perform(click());
onView(withId(R.id.encrypt_text_text)).perform(typeText(cleartext));
onView(withId(R.id.encrypt_copy)).perform(click());
onView(withId(R.id.passphrase_passphrase)).perform(typeText("x"));
onView(withText(R.string.btn_unlock)).perform(click());
checkSnackbar(Style.OK, R.string.msg_se_success);
}
// go to decrypt from clipboard view
pressBack();
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
{ // decrypt
onView(withId(R.id.decrypt_text_plaintext)).check(matches(
// startsWith because there may be extra newlines
withText(CoreMatchers.startsWith(cleartext))));
onView(withId(R.id.result_encryption_text)).check(matches(
withText(R.string.decrypt_result_not_encrypted)));
onView(withId(R.id.result_signature_text)).check(matches(
withText(R.string.decrypt_result_signature_secret)));
onView(withId(R.id.result_signature_layout)).check(matches(
isDisplayed()));
onView(withId(R.id.result_encryption_icon)).check(matches(
withDrawable(R.drawable.status_lock_open_24dp)));
onView(withId(R.id.result_signature_icon)).check(matches(
withDrawable(R.drawable.status_signature_verified_cutout_24dp)));
}
}
}

View File

@ -17,15 +17,18 @@
package org.sufficientlysecure.keychain;
import android.content.Intent;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import android.text.method.HideReturnsTransformationMethod;
import android.text.method.PasswordTransformationMethod;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
import org.sufficientlysecure.keychain.ui.MainActivity;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
@ -44,6 +47,7 @@ import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withError
import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withTransformationMethod;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class CreateKeyActivityTest {
public static final String SAMPLE_NAME = "Sample Name";
@ -52,10 +56,21 @@ public class CreateKeyActivityTest {
public static final String SAMPLE_PASSWORD = "sample_password";
@Rule
public ActivityTestRule<CreateKeyActivity> mActivityRule = new ActivityTestRule<>(CreateKeyActivity.class);
public final ActivityTestRule<MainActivity> mActivity
= new ActivityTestRule<MainActivity>(MainActivity.class) {
@Override
protected Intent getActivityIntent() {
Intent intent = super.getActivityIntent();
intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true);
return intent;
}
};
@Test
public void testCreateMyKey() {
mActivity.getActivity();
// Clicks create my key
onView(withId(R.id.create_key_create_key_button))
.perform(click());

View File

@ -0,0 +1,89 @@
/*
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain;
import android.app.Activity;
import android.content.Intent;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import android.widget.AdapterView;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.ui.MainActivity;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.CoreMatchers.allOf;
import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar;
import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RunWith(AndroidJUnit4.class)
@LargeTest
public class EditKeyTest {
@Rule
public final ActivityTestRule<MainActivity> mActivity
= new ActivityTestRule<MainActivity>(MainActivity.class) {
@Override
protected Intent getActivityIntent() {
Intent intent = super.getActivityIntent();
intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true);
return intent;
}
};
@Test
public void test01Edit() throws Exception {
Activity activity = mActivity.getActivity();
new KeychainDatabase(activity).clearDatabase();
// import key for testing, get a stable initial state
importKeysFromResource(activity, "x.sec.asc");
// navigate to edit key dialog
onData(withKeyItemId(0x9D604D2F310716A3L))
.inAdapterView(allOf(isAssignableFrom(AdapterView.class),
isDescendantOfA(withId(R.id.key_list_list))))
.perform(click());
onView(withId(R.id.menu_key_view_edit)).perform(click());
// no-op should yield snackbar
onView(withText(R.string.btn_save)).perform(click());
checkSnackbar(Style.ERROR, R.string.msg_mf_error_noop);
}
}

View File

@ -0,0 +1,132 @@
/*
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain;
import android.content.Intent;
import android.support.test.espresso.matcher.ViewMatchers;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.sufficientlysecure.keychain.ui.MainActivity;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import static android.support.test.InstrumentationRegistry.getInstrumentation;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.contrib.DrawerActions.openDrawer;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.CoreMatchers.not;
import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar;
import static org.sufficientlysecure.keychain.TestHelpers.randomString;
import static org.sufficientlysecure.keychain.matcher.DrawableMatcher.withDrawable;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RunWith(AndroidJUnit4.class)
@LargeTest
public class EncryptDecryptSymmetricTests {
public static final String PASSPHRASE = randomString(5, 20);
@Rule
public final ActivityTestRule<MainActivity> mActivity
= new ActivityTestRule<MainActivity>(MainActivity.class) {
@Override
protected Intent getActivityIntent() {
Intent intent = super.getActivityIntent();
intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true);
return intent;
}
};
@Test
public void testSymmetricTextEncryptDecrypt() throws Exception {
MainActivity activity = mActivity.getActivity();
String text = randomString(10, 30);
// navigate to encrypt/decrypt
openDrawer(R.id.drawer_layout);
onView(ViewMatchers.withText(R.string.nav_encrypt_decrypt)).perform(click());
onView(withId(R.id.encrypt_text)).perform(click());
{
onView(withId(R.id.encrypt_text_text)).perform(typeText(text));
openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext());
onView(withText(R.string.label_symmetric)).perform(click());
onView(withId(R.id.passphrase)).perform(typeText(PASSPHRASE));
onView(withId(R.id.encrypt_copy)).perform(click());
checkSnackbar(Style.ERROR, R.string.passphrases_do_not_match);
onView(withId(R.id.passphraseAgain)).perform(typeText(PASSPHRASE));
onView(withId(R.id.encrypt_text_text)).check(matches(withText(text)));
onView(withId(R.id.encrypt_copy)).perform(click());
checkSnackbar(Style.OK, R.string.msg_se_success);
}
// go to decrypt from clipboard view
pressBack();
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
{
onView(withId(R.id.passphrase_passphrase)).perform(typeText(PASSPHRASE));
onView(withText(R.string.btn_unlock)).perform(click());
onView(withId(R.id.decrypt_text_plaintext)).check(matches(
withText(text)));
// TODO write generic status verifier
onView(withId(R.id.result_encryption_text)).check(matches(
withText(R.string.decrypt_result_encrypted)));
onView(withId(R.id.result_signature_text)).check(matches(
withText(R.string.decrypt_result_no_signature)));
onView(withId(R.id.result_signature_layout)).check(matches(
not(isDisplayed())));
onView(withId(R.id.result_encryption_icon)).check(matches(
withDrawable(R.drawable.status_lock_closed_24dp)));
onView(withId(R.id.result_signature_icon)).check(matches(
withDrawable(R.drawable.status_signature_unknown_cutout_24dp)));
}
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain;
import android.app.Activity;
import android.content.Intent;
import android.support.test.espresso.action.ViewActions;
import android.support.test.espresso.matcher.RootMatchers;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import android.view.KeyEvent;
import android.widget.AdapterView;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.sufficientlysecure.keychain.ui.EncryptTextActivity;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.CoreMatchers.allOf;
import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource;
import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyToken;
@RunWith(AndroidJUnit4.class)
@LargeTest
public class EncryptKeyCompletionViewTest {
@Rule
public final ActivityTestRule<EncryptTextActivity> mActivity
= new ActivityTestRule<>(EncryptTextActivity.class);
@Test
public void testTextEncryptDecryptFromToken() throws Exception {
Intent intent = new Intent();
intent.putExtra(EncryptTextActivity.EXTRA_ENCRYPTION_KEY_IDS, new long[] { 0x9D604D2F310716A3L });
Activity activity = mActivity.launchActivity(intent);
// import these two, make sure they're there
importKeysFromResource(activity, "x.sec.asc");
// check if the element passed in from intent
onView(withId(R.id.recipient_list)).check(matches(withKeyToken(0x9D604D2F310716A3L)));
onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL));
// type X, select from list, check if it's there
onView(withId(R.id.recipient_list)).perform(typeText("x"));
onData(withKeyItemId(0x9D604D2F310716A3L)).inRoot(RootMatchers.isPlatformPopup())
.inAdapterView(allOf(isAssignableFrom(AdapterView.class),
hasDescendant(withId(R.id.key_list_item_name)))).perform(click());
onView(withId(R.id.recipient_list)).check(matches(withKeyToken(0x9D604D2F310716A3L)));
onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL));
onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL));
// add directly, check if it's there
onView(withId(R.id.recipient_list)).perform(tokenEncryptViewAddToken(0x9D604D2F310716A3L));
onView(withId(R.id.recipient_list)).check(matches(withKeyToken(0x9D604D2F310716A3L)));
onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL));
}
}

View File

@ -0,0 +1,29 @@
package org.sufficientlysecure.keychain;
import java.lang.reflect.Method;
import android.os.Bundle;
import android.support.test.runner.AndroidJUnitRunner;
public class JacocoWorkaroundJUnitRunner extends AndroidJUnitRunner {
static {
System.setProperty("jacoco-agent.destfile", "/data/data/"
+ BuildConfig.APPLICATION_ID + "/coverage.ec");
}
@Override
public void finish(int resultCode, Bundle results) {
try {
Class rt = Class.forName("org.jacoco.agent.rt.RT");
Method getAgent = rt.getMethod("getAgent");
Method dump = getAgent.getReturnType().getMethod("dump", boolean.class);
Object agent = getAgent.invoke(null);
dump.invoke(agent, false);
} catch (Exception e) {
e.printStackTrace();
}
super.finish(resultCode, results);
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain;
import java.util.Random;
import android.content.Context;
import android.support.annotation.StringRes;
import org.hamcrest.CoreMatchers;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import static android.support.test.InstrumentationRegistry.getInstrumentation;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSnackbarLineColor;
public class TestHelpers {
public static void checkSnackbar(Style style, @StringRes Integer text) {
onView(withClassName(CoreMatchers.endsWith("Snackbar")))
.check(matches(withSnackbarLineColor(style.mLineColor)));
if (text != null) {
onView(withClassName(CoreMatchers.endsWith("Snackbar")))
.check(matches(hasDescendant(withText(text))));
}
}
static void importKeysFromResource(Context context, String name) throws Exception {
IteratorWithIOThrow<UncachedKeyRing> stream = UncachedKeyRing.fromStream(
getInstrumentation().getContext().getAssets().open(name));
ProviderHelper helper = new ProviderHelper(context);
while(stream.hasNext()) {
UncachedKeyRing ring = stream.next();
if (ring.isSecret()) {
helper.saveSecretKeyRing(ring, new ProgressScaler());
} else {
helper.savePublicKeyRing(ring, new ProgressScaler());
}
}
}
public static String randomString(int min, int max) {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789!@#$%^&*()-_=";
Random r = new Random();
StringBuilder passbuilder = new StringBuilder();
// 5% chance for an empty string
for(int i = 0, j = r.nextInt(max)+min; i < j; i++) {
passbuilder.append(chars.charAt(r.nextInt(chars.length())));
}
return passbuilder.toString();
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.sufficientlysecure.keychain.actions;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.matcher.ViewMatchers;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.view.View;
import com.tokenautocomplete.TokenCompleteTextView;
import org.hamcrest.Matcher;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import static android.support.test.InstrumentationRegistry.getTargetContext;
public abstract class CustomActions {
public static ViewAction tokenEncryptViewAddToken(long keyId) throws Exception {
CanonicalizedPublicKeyRing ring =
new ProviderHelper(getTargetContext()).getCanonicalizedPublicKeyRing(keyId);
final Object item = new KeyAdapter.KeyItem(ring);
return new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return ViewMatchers.isAssignableFrom(TokenCompleteTextView.class);
}
@Override
public String getDescription() {
return "add completion token";
}
@Override
public void perform(UiController uiController, View view) {
((TokenCompleteTextView) view).addObject(item);
}
};
}
public static ViewAction tokenViewAddToken(final Object item) {
return new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return ViewMatchers.isAssignableFrom(TokenCompleteTextView.class);
}
@Override
public String getDescription() {
return "add completion token";
}
@Override
public void perform(UiController uiController, View view) {
((TokenCompleteTextView) view).addObject(item);
}
};
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.sufficientlysecure.keychain.matcher;
import android.support.annotation.ColorRes;
import android.support.test.espresso.matcher.BoundedMatcher;
import android.view.View;
import com.nispok.snackbar.Snackbar;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.sufficientlysecure.keychain.EncryptKeyCompletionViewTest;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView;
import static android.support.test.internal.util.Checks.checkNotNull;
public abstract class CustomMatchers {
public static Matcher<View> withSnackbarLineColor(@ColorRes final int colorRes) {
return new BoundedMatcher<View, Snackbar>(Snackbar.class) {
public void describeTo(Description description) {
description.appendText("with color resource id: " + colorRes);
}
@Override
public boolean matchesSafely(Snackbar snackbar) {
return snackbar.getResources().getColor(colorRes) == snackbar.getLineColor();
}
};
}
public static Matcher<Object> withKeyItemId(final long keyId) {
return new BoundedMatcher<Object, KeyItem>(KeyItem.class) {
@Override
public boolean matchesSafely(KeyItem item) {
return item.mKeyId == keyId;
}
@Override
public void describeTo(Description description) {
description.appendText("with key id: " + keyId);
}
};
}
public static Matcher<View> withKeyToken(@ColorRes final long keyId) {
return new BoundedMatcher<View, EncryptKeyCompletionView>(EncryptKeyCompletionView.class) {
public void describeTo(Description description) {
description.appendText("with key id token: " + keyId);
}
@Override
public boolean matchesSafely(EncryptKeyCompletionView tokenView) {
for (Object object : tokenView.getObjects()) {
if (object instanceof KeyItem && ((KeyItem) object).mKeyId == keyId) {
return true;
}
}
return false;
}
};
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright (C) 2015 Xavi Rigau <xrigau@gmail.com>
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* From the droidcon anroid espresso repository.
* https://github.com/xrigau/droidcon-android-espresso/
*
*/
package org.sufficientlysecure.keychain.matcher;
import android.content.res.Resources;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
public class DrawableMatcher extends TypeSafeMatcher<View> {
private final int mResourceId;
private final boolean mIgnoreFilters;
public DrawableMatcher(int resourceId, boolean ignoreFilters) {
super(View.class);
mResourceId = resourceId;
mIgnoreFilters = ignoreFilters;
}
private String resourceName = null;
private Drawable expectedDrawable = null;
@Override
public boolean matchesSafely(View target) {
if (expectedDrawable == null) {
loadDrawableFromResources(target.getResources());
}
if (invalidExpectedDrawable()) {
return false;
}
if (target instanceof ImageView) {
return hasImage((ImageView) target) || hasBackground(target);
}
if (target instanceof TextView) {
return hasCompoundDrawable((TextView) target) || hasBackground(target);
}
return hasBackground(target);
}
private void loadDrawableFromResources(Resources resources) {
try {
expectedDrawable = resources.getDrawable(mResourceId);
resourceName = resources.getResourceEntryName(mResourceId);
} catch (Resources.NotFoundException ignored) {
// view could be from a context unaware of the resource id.
}
}
private boolean invalidExpectedDrawable() {
return expectedDrawable == null;
}
private boolean hasImage(ImageView target) {
return isSameDrawable(target.getDrawable());
}
private boolean hasCompoundDrawable(TextView target) {
for (Drawable drawable : target.getCompoundDrawables()) {
if (isSameDrawable(drawable)) {
return true;
}
}
return false;
}
private boolean hasBackground(View target) {
return isSameDrawable(target.getBackground());
}
private boolean isSameDrawable(Drawable drawable) {
if (drawable == null) {
return false;
}
// if those are both bitmap drawables, compare their bitmaps (ignores color filters, which is what we want!)
if (mIgnoreFilters && drawable instanceof BitmapDrawable && expectedDrawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap().equals(((BitmapDrawable) expectedDrawable).getBitmap());
}
return expectedDrawable.getConstantState().equals(drawable.getConstantState());
}
@Override
public void describeTo(Description description) {
description.appendText("with drawable from resource id: ");
description.appendValue(mResourceId);
if (resourceName != null) {
description.appendText("[");
description.appendText(resourceName);
description.appendText("]");
}
}
public static DrawableMatcher withDrawable(int resourceId, boolean ignoreFilters) {
return new DrawableMatcher(resourceId, ignoreFilters);
}
public static DrawableMatcher withDrawable(int resourceId) {
return new DrawableMatcher(resourceId, true);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -2,9 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.sufficientlysecure.keychain"
android:installLocation="auto"
android:versionCode="32300"
android:versionName="3.2.3">
android:installLocation="auto">
<!--
General remarks
@ -50,9 +48,9 @@
android:name="android.hardware.screen.portrait"
android:required="false" />
<permission android:name="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE" />
<permission android:name="${applicationId}.WRITE_TEMPORARY_STORAGE" />
<uses-permission android:name="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE" />
<uses-permission android:name="${applicationId}.WRITE_TEMPORARY_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@ -719,15 +717,15 @@
android:exported="false"
android:process=":remote_api" />
<service
android:name=".service.KeychainIntentService"
android:name=".service.KeychainNewService"
android:exported="false" />
<service
android:name=".service.CloudImportService"
android:name=".service.KeychainService"
android:exported="false" />
<provider
android:name=".provider.KeychainProvider"
android:authorities="org.sufficientlysecure.keychain.provider"
android:authorities="${applicationId}.provider"
android:exported="false" />
<!-- Internal classes of the remote APIs (not exported) -->
@ -805,9 +803,9 @@
<!-- Storage Provider for temporary decrypted files -->
<provider
android:name=".provider.TemporaryStorageProvider"
android:authorities="org.sufficientlysecure.keychain.tempstorage"
android:authorities="${applicationId}.tempstorage"
android:exported="true"
android:writePermission="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE" />
android:writePermission="${applicationId}.WRITE_TEMPORARY_STORAGE" />
</application>

View File

@ -31,14 +31,17 @@ public final class Constants {
public static final boolean DEBUG_LOG_DB_QUERIES = false;
public static final boolean DEBUG_SYNC_REMOVE_CONTACTS = false;
public static final String TAG = "Keychain";
public static final String TAG = DEBUG ? "Keychain D" : "Keychain";
public static final String PACKAGE_NAME = "org.sufficientlysecure.keychain";
public static final String ACCOUNT_NAME = "OpenKeychain";
public static final String ACCOUNT_TYPE = PACKAGE_NAME + ".account";
public static final String ACCOUNT_NAME = DEBUG ? "OpenKeychain D" : "OpenKeychain";
public static final String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE;
public static final String CUSTOM_CONTACT_DATA_MIME_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key";
public static final String PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";
public static final String TEMPSTORAGE_AUTHORITY = BuildConfig.APPLICATION_ID + ".tempstorage";
// as defined in http://tools.ietf.org/html/rfc3156, section 7
public static final String NFC_MIME = "application/pgp-keys";
@ -84,6 +87,10 @@ public final class Constants {
public static final String SEARCH_KEYBASE = "search_keybase_pref";
public static final String USE_DEFAULT_YUBIKEY_PIN = "useDefaultYubikeyPin";
public static final String USE_NUMKEYPAD_FOR_YUBIKEY_PIN = "useNumKeypadForYubikeyPin";
public static final String ENCRYPT_FILENAMES = "encryptFilenames";
public static final String FILE_USE_COMPRESSION = "useFileCompression";
public static final String TEXT_USE_COMPRESSION = "useTextCompression";
public static final String USE_ARMOR = "useArmor";
}
public static final class Defaults {

View File

@ -128,8 +128,6 @@ public class KeychainApplication extends Application {
/**
* Add OpenKeychain account to Android to link contacts with keys
*
* @param context
*/
public static void setupAccountAsNeeded(Context context) {
try {
@ -165,7 +163,7 @@ public class KeychainApplication extends Application {
int edgeDrawableId = context.getResources().getIdentifier("overscroll_edge", "drawable", "android");
Drawable androidEdge = context.getResources().getDrawable(edgeDrawableId);
androidEdge.setColorFilter(brandColor, PorterDuff.Mode.SRC_IN);
} catch (Resources.NotFoundException e) {
} catch (Exception ignored) {
}
}
}

View File

@ -74,6 +74,10 @@ public class ClipboardReflection {
Method methodGetPrimaryClip = clipboard.getClass().getMethod("getPrimaryClip");
Object clipData = methodGetPrimaryClip.invoke(clipboard);
if (clipData == null) {
return null;
}
// ClipData.Item clipDataItem = clipData.getItemAt(0);
Method methodGetItemAt = clipData.getClass().getMethod("getItemAt", int.class);
Object clipDataItem = methodGetItemAt.invoke(clipData, 0);

View File

@ -18,17 +18,20 @@
package org.sufficientlysecure.keychain.operations;
import android.content.Context;
import android.os.Parcelable;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.Passphrase;
import java.util.concurrent.atomic.AtomicBoolean;
public abstract class BaseOperation implements PassphraseCacheInterface {
public abstract class BaseOperation <T extends Parcelable> implements PassphraseCacheInterface {
final public Context mContext;
final public Progressable mProgressable;
@ -40,7 +43,7 @@ public abstract class BaseOperation implements PassphraseCacheInterface {
* of common methods for progress, cancellation and passphrase cache handling.
*
* An "operation" in this sense is a high level operation which is called
* by the KeychainIntentService or OpenPgpService services. Concrete
* by the KeychainService or OpenPgpService services. Concrete
* subclasses of this class should implement either a single or a group of
* related operations. An operation must rely solely on its input
* parameters for operation specifics. It should also write a log of its
@ -49,7 +52,7 @@ public abstract class BaseOperation implements PassphraseCacheInterface {
*
* An operation must *not* throw exceptions of any kind, errors should be
* handled as part of the OperationResult! Consequently, all handling of
* errors in KeychainIntentService and OpenPgpService should consist of
* errors in KeychainService and OpenPgpService should consist of
* informational rather than operational means.
*
* Note that subclasses of this class should be either Android- or
@ -73,6 +76,10 @@ public abstract class BaseOperation implements PassphraseCacheInterface {
mCancelled = cancelled;
}
public OperationResult execute(T input, CryptoInputParcel cryptoInput) {
return null;
}
public void updateProgress(int message, int current, int total) {
if (mProgressable != null) {
mProgressable.setProgress(message, current, total);

View File

@ -64,7 +64,7 @@ public class CertifyOperation extends BaseOperation {
super(context, providerHelper, progressable, cancelled);
}
public CertifyResult certify(CertifyActionsParcel parcel, CryptoInputParcel cryptoInput, String keyServerUri) {
public CertifyResult execute(CertifyActionsParcel parcel, CryptoInputParcel cryptoInput) {
OperationLog log = new OperationLog();
log.add(LogType.MSG_CRT, 0);
@ -79,13 +79,32 @@ public class CertifyOperation extends BaseOperation {
log.add(LogType.MSG_CRT_UNLOCK, 1);
certificationKey = secretKeyRing.getSecretKey();
if (!cryptoInput.hasPassphrase()) {
return new CertifyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
certificationKey.getKeyId(), certificationKey.getKeyId(), null));
}
Passphrase passphrase;
// certification is always with the master key id, so use that one
Passphrase passphrase = cryptoInput.getPassphrase();
switch (certificationKey.getSecretKeyType()) {
case PIN:
case PATTERN:
case PASSPHRASE:
if (!cryptoInput.hasPassphrase()) {
return new CertifyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
certificationKey.getKeyId(), certificationKey.getKeyId(), null));
}
// certification is always with the master key id, so use that one
passphrase = cryptoInput.getPassphrase();
break;
case PASSPHRASE_EMPTY:
passphrase = new Passphrase("");
break;
case DIVERT_TO_CARD:
passphrase = null;
break;
default:
log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2);
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
}
if (!certificationKey.unlock(passphrase)) {
log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2);
@ -167,8 +186,8 @@ public class CertifyOperation extends BaseOperation {
HkpKeyserver keyServer = null;
ImportExportOperation importExportOperation = null;
if (keyServerUri != null) {
keyServer = new HkpKeyserver(keyServerUri);
if (parcel.keyServerUri != null) {
keyServer = new HkpKeyserver(parcel.keyServerUri);
importExportOperation = new ImportExportOperation(mContext, mProviderHelper, mProgressable);
}

View File

@ -37,7 +37,6 @@ import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.util.concurrent.atomic.AtomicBoolean;
@ -51,7 +50,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* @see SaveKeyringParcel
*
*/
public class EditKeyOperation extends BaseOperation {
public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
public EditKeyOperation(Context context, ProviderHelper providerHelper,
Progressable progressable, AtomicBoolean cancelled) {

View File

@ -38,7 +38,6 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
@ -157,6 +156,15 @@ public class ImportExportOperation extends BaseOperation {
}
/**
* Since the introduction of multithreaded import, we expect calling functions to handle the key sync i,e
* ContactSyncAdapterService.requestSync()
*
* @param entries keys to import
* @param num number of keys to import
* @param keyServerUri contains uri of keyserver to import from, if it is an import from cloud
* @return
*/
public ImportKeyResult importKeyRings(Iterator<ParcelableKeyRing> entries, int num, String keyServerUri) {
updateProgress(R.string.progress_importing, 0, 100);
@ -244,25 +252,25 @@ public class ImportExportOperation extends BaseOperation {
try {
log.add(LogType.MSG_IMPORT_FETCH_KEYBASE, 2, entry.mKeybaseName);
byte[] data = keybaseServer.get(entry.mKeybaseName).getBytes();
key = UncachedKeyRing.decodeFromData(data);
UncachedKeyRing keybaseKey = UncachedKeyRing.decodeFromData(data);
// If there already is a key (of keybase origin), merge the two
if (key != null) {
// If there already is a key, merge the two
if (key != null && keybaseKey != null) {
log.add(LogType.MSG_IMPORT_MERGE, 3);
UncachedKeyRing merged = UncachedKeyRing.decodeFromData(data);
merged = key.merge(merged, log, 4);
keybaseKey = key.merge(keybaseKey, log, 4);
// If the merge didn't fail, use the new merged key
if (merged != null) {
key = merged;
if (keybaseKey != null) {
key = keybaseKey;
} else {
log.add(LogType.MSG_IMPORT_MERGE_ERROR, 4);
}
} else {
log.add(LogType.MSG_IMPORT_FETCH_ERROR_DECODE, 3);
key = UncachedKeyRing.decodeFromData(data);
} else if (keybaseKey != null) {
key = keybaseKey;
}
} catch (Keyserver.QueryFailedException e) {
// download failed, too bad. just proceed
Log.e(Constants.TAG, "query failed", e);
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3);
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3, e.getMessage());
}
}
}
@ -331,8 +339,8 @@ public class ImportExportOperation extends BaseOperation {
// Special: make sure new data is synced into contacts
// disabling sync right now since it reduces speed while multi-threading
// so, we expect calling functions to take care of it. KeychainIntentService handles this
//ContactSyncAdapterService.requestSync();
// so, we expect calling functions to take care of it. KeychainService handles this
// ContactSyncAdapterService.requestSync();
// convert to long array
long[] importedMasterKeyIdsArray = new long[importedMasterKeyIds.size()];
@ -376,8 +384,6 @@ public class ImportExportOperation extends BaseOperation {
log.add(LogType.MSG_IMPORT_ERROR, 1);
}
ContactSyncAdapterService.requestSync();
return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret,
importedMasterKeyIdsArray);
}
@ -405,13 +411,17 @@ public class ImportExportOperation extends BaseOperation {
try {
OutputStream outStream = new FileOutputStream(outputFile);
ExportResult result = exportKeyRings(log, masterKeyIds, exportSecret, outStream);
if (result.cancelled()) {
//noinspection ResultOfMethodCallIgnored
new File(outputFile).delete();
try {
ExportResult result = exportKeyRings(log, masterKeyIds, exportSecret, outStream);
if (result.cancelled()) {
//noinspection ResultOfMethodCallIgnored
new File(outputFile).delete();
}
return result;
} finally {
outStream.close();
}
return result;
} catch (FileNotFoundException e) {
} catch (IOException e) {
log.add(LogType.MSG_EXPORT_ERROR_FOPEN, 1);
return new ExportResult(ExportResult.RESULT_ERROR, log);
}

View File

@ -55,7 +55,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* a pending result, it will terminate.
*
*/
public class SignEncryptOperation extends BaseOperation {
public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
public SignEncryptOperation(Context context, ProviderHelper providerHelper,
Progressable progressable, AtomicBoolean cancelled) {

View File

@ -36,6 +36,23 @@ public class DecryptVerifyResult extends InputPendingResult {
// https://tools.ietf.org/html/rfc4880#page56
String mCharset;
byte[] mOutputBytes;
public DecryptVerifyResult(int result, OperationLog log) {
super(result, log);
}
public DecryptVerifyResult(OperationLog log, RequiredInputParcel requiredInput) {
super(log, requiredInput);
}
public DecryptVerifyResult(Parcel source) {
super(source);
mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
mDecryptMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader());
}
public boolean isKeysDisallowed () {
return (mResult & RESULT_KEY_DISALLOWED) == RESULT_KEY_DISALLOWED;
}
@ -64,18 +81,12 @@ public class DecryptVerifyResult extends InputPendingResult {
mCharset = charset;
}
public DecryptVerifyResult(int result, OperationLog log) {
super(result, log);
public void setOutputBytes(byte[] outputBytes) {
mOutputBytes = outputBytes;
}
public DecryptVerifyResult(OperationLog log, RequiredInputParcel requiredInput) {
super(log, requiredInput);
}
public DecryptVerifyResult(Parcel source) {
super(source);
mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
mDecryptMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader());
public byte[] getOutputBytes() {
return mOutputBytes;
}
public int describeContents() {

View File

@ -22,6 +22,7 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
@ -101,9 +102,9 @@ public abstract class OperationResult implements Parcelable {
}
public OperationLog getLog() {
// If there is only a single entry, and it's a compound one, return that log
if (mLog.isSingleCompound()) {
return ((SubLogEntryParcel) mLog.getFirst()).getSubResult().getLog();
SubLogEntryParcel singleSubLog = mLog.getSubResultIfSingle();
if (singleSubLog != null) {
return singleSubLog.getSubResult().getLog();
}
// Otherwse, return our regular log
return mLog;
@ -169,9 +170,9 @@ public abstract class OperationResult implements Parcelable {
public static class SubLogEntryParcel extends LogEntryParcel {
OperationResult mSubResult;
@NonNull OperationResult mSubResult;
public SubLogEntryParcel(OperationResult subResult, LogType type, int indent, Object... parameters) {
public SubLogEntryParcel(@NonNull OperationResult subResult, LogType type, int indent, Object... parameters) {
super(type, indent, parameters);
mSubResult = subResult;
@ -209,6 +210,10 @@ public abstract class OperationResult implements Parcelable {
String logText;
LogEntryParcel entryParcel = mLog.getLast();
if (entryParcel == null) {
Log.e(Constants.TAG, "Tried to show empty log!");
return Notify.create(activity, R.string.error_empty_log, Style.ERROR);
}
// special case: first parameter may be a quantity
if (entryParcel.mParameters != null && entryParcel.mParameters.length > 0
&& entryParcel.mParameters[0] instanceof Integer) {
@ -269,7 +274,7 @@ public abstract class OperationResult implements Parcelable {
* mark.
*
*/
public static enum LogType {
public enum LogType {
MSG_INTERNAL_ERROR (LogLevel.ERROR, R.string.msg_internal_error),
MSG_OPERATION_CANCELLED (LogLevel.CANCELLED, R.string.msg_cancelled),
@ -495,6 +500,12 @@ public abstract class OperationResult implements Parcelable {
MSG_MF_ERROR_REVOKED_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_revoked_primary),
MSG_MF_ERROR_SIG (LogLevel.ERROR, R.string.msg_mf_error_sig),
MSG_MF_ERROR_SUBKEY_MISSING(LogLevel.ERROR, R.string.msg_mf_error_subkey_missing),
MSG_MF_ERROR_CONFLICTING_NFC_COMMANDS(LogLevel.ERROR, R.string.msg_mf_error_conflicting_nfc_commands),
MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT(LogLevel.ERROR, R.string.msg_mf_error_duplicate_keytocard_for_slot),
MSG_MF_ERROR_INVALID_FLAGS_FOR_KEYTOCARD(LogLevel.ERROR, R.string.msg_mf_error_invalid_flags_for_keytocard),
MSG_MF_ERROR_BAD_NFC_ALGO(LogLevel.ERROR, R.string.edit_key_error_bad_nfc_algo),
MSG_MF_ERROR_BAD_NFC_SIZE(LogLevel.ERROR, R.string.edit_key_error_bad_nfc_size),
MSG_MF_ERROR_BAD_NFC_STRIPPED(LogLevel.ERROR, R.string.edit_key_error_bad_nfc_stripped),
MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master),
MSG_MF_NOTATION_PIN (LogLevel.DEBUG, R.string.msg_mf_notation_pin),
MSG_MF_NOTATION_EMPTY (LogLevel.DEBUG, R.string.msg_mf_notation_empty),
@ -512,6 +523,8 @@ public abstract class OperationResult implements Parcelable {
MSG_MF_SUBKEY_NEW (LogLevel.INFO, R.string.msg_mf_subkey_new),
MSG_MF_SUBKEY_REVOKE (LogLevel.INFO, R.string.msg_mf_subkey_revoke),
MSG_MF_SUBKEY_STRIP (LogLevel.INFO, R.string.msg_mf_subkey_strip),
MSG_MF_KEYTOCARD_START (LogLevel.INFO, R.string.msg_mf_keytocard_start),
MSG_MF_KEYTOCARD_FINISH (LogLevel.OK, R.string.msg_mf_keytocard_finish),
MSG_MF_SUCCESS (LogLevel.OK, R.string.msg_mf_success),
MSG_MF_UID_ADD (LogLevel.INFO, R.string.msg_mf_uid_add),
MSG_MF_UID_PRIMARY (LogLevel.INFO, R.string.msg_mf_uid_primary),
@ -588,6 +601,7 @@ public abstract class OperationResult implements Parcelable {
MSG_DC_CLEAR_SIGNATURE_OK (LogLevel.OK, R.string.msg_dc_clear_signature_ok),
MSG_DC_CLEAR_SIGNATURE (LogLevel.DEBUG, R.string.msg_dc_clear_signature),
MSG_DC_ERROR_BAD_PASSPHRASE (LogLevel.ERROR, R.string.msg_dc_error_bad_passphrase),
MSG_DC_ERROR_CORRUPT_DATA (LogLevel.ERROR, R.string.msg_dc_error_corrupt_data),
MSG_DC_ERROR_EXTRACT_KEY (LogLevel.ERROR, R.string.msg_dc_error_extract_key),
MSG_DC_ERROR_INTEGRITY_CHECK (LogLevel.ERROR, R.string.msg_dc_error_integrity_check),
MSG_DC_ERROR_INTEGRITY_MISSING (LogLevel.ERROR, R.string.msg_dc_error_integrity_missing),
@ -687,6 +701,7 @@ public abstract class OperationResult implements Parcelable {
MSG_IMPORT_FETCH_KEYBASE (LogLevel.INFO, R.string.msg_import_fetch_keybase),
MSG_IMPORT_KEYSERVER (LogLevel.DEBUG, R.string.msg_import_keyserver),
MSG_IMPORT_MERGE (LogLevel.DEBUG, R.string.msg_import_merge),
MSG_IMPORT_MERGE_ERROR (LogLevel.ERROR, R.string.msg_import_merge_error),
MSG_IMPORT_FINGERPRINT_ERROR (LogLevel.ERROR, R.string.msg_import_fingerprint_error),
MSG_IMPORT_FINGERPRINT_OK (LogLevel.DEBUG, R.string.msg_import_fingerprint_ok),
MSG_IMPORT_ERROR (LogLevel.ERROR, R.string.msg_import_error),
@ -755,7 +770,7 @@ public abstract class OperationResult implements Parcelable {
}
/** Enumeration of possible log levels. */
public static enum LogLevel {
public enum LogLevel {
DEBUG,
INFO,
WARN,
@ -795,8 +810,15 @@ public abstract class OperationResult implements Parcelable {
mParcels.add(new SubLogEntryParcel(subResult, subLog.getFirst().mType, indent, subLog.getFirst().mParameters));
}
boolean isSingleCompound() {
return mParcels.size() == 1 && getFirst() instanceof SubLogEntryParcel;
public SubLogEntryParcel getSubResultIfSingle() {
if (mParcels.size() != 1) {
return null;
}
LogEntryParcel first = getFirst();
if (first instanceof SubLogEntryParcel) {
return (SubLogEntryParcel) first;
}
return null;
}
public void clear() {
@ -844,7 +866,11 @@ public abstract class OperationResult implements Parcelable {
if (mParcels.isEmpty()) {
return null;
}
return mParcels.get(mParcels.size() -1);
LogEntryParcel last = mParcels.get(mParcels.size() -1);
if (last instanceof SubLogEntryParcel) {
return ((SubLogEntryParcel) last).getSubResult().getLog().getLast();
}
return last;
}
@Override

View File

@ -18,10 +18,10 @@
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.S2K;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.util.IterableIterator;
@ -96,12 +96,6 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
});
}
/** Create a dummy secret ring from this key */
public UncachedKeyRing createDummySecretRing () {
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), null);
return new UncachedKeyRing(secRing);
}
/** Create a dummy secret ring from this key */
public UncachedKeyRing createDivertSecretRing (byte[] cardAid, long[] subKeyIds) {
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), cardAid);
@ -114,7 +108,10 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
// stripped dummy, then move divert-to-card keys over
PGPSecretKeyRing newRing = PGPSecretKeyRing.constructDummyFromPublic(getRing());
for (long subKeyId : subKeyIds) {
newRing = PGPSecretKeyRing.insertSecretKey(newRing, secRing.getSecretKey(subKeyId));
PGPSecretKey key = secRing.getSecretKey(subKeyId);
if (key != null) {
newRing = PGPSecretKeyRing.insertSecretKey(newRing, key);
}
}
return new UncachedKeyRing(newRing);

View File

@ -33,6 +33,7 @@ import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
@ -45,6 +46,8 @@ import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import java.nio.ByteBuffer;
import java.security.PrivateKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@ -283,6 +286,27 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
}
}
// For use only in card export; returns the secret key in Chinese Remainder Theorem format.
public RSAPrivateCrtKey getCrtSecretKey() throws PgpGeneralException {
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PgpGeneralException("Cannot get secret key attributes while key is locked.");
}
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
throw new PgpGeneralException("Cannot get secret key attributes of divert-to-card key.");
}
JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
PrivateKey retVal;
try {
retVal = keyConverter.getPrivateKey(mPrivateKey);
} catch (PGPException e) {
throw new PgpGeneralException("Error converting private key!", e);
}
return (RSAPrivateCrtKey)retVal;
}
public byte[] getIv() {
return mSecretKey.getIV();
}

View File

@ -22,6 +22,7 @@ import android.text.TextUtils;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import java.io.Serializable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -91,7 +92,7 @@ public abstract class KeyRing {
return userIdString;
}
public static class UserId {
public static class UserId implements Serializable {
public final String name;
public final String email;
public final String comment;

View File

@ -28,6 +28,7 @@ import org.spongycastle.openpgp.PGPCompressedData;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.spongycastle.openpgp.PGPEncryptedDataList;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPKeyValidationException;
import org.spongycastle.openpgp.PGPLiteralData;
import org.spongycastle.openpgp.PGPOnePassSignature;
import org.spongycastle.openpgp.PGPOnePassSignatureList;
@ -57,6 +58,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
@ -65,6 +67,7 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -72,147 +75,87 @@ import java.net.URLConnection;
import java.security.SignatureException;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
/**
* This class uses a Builder pattern!
*/
public class PgpDecryptVerify extends BaseOperation {
public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> {
private InputData mData;
private OutputStream mOutStream;
private boolean mAllowSymmetricDecryption;
private Set<Long> mAllowedKeyIds;
private boolean mDecryptMetadataOnly;
private byte[] mDetachedSignature;
private String mRequiredSignerFingerprint;
private boolean mSignedLiteralData;
protected PgpDecryptVerify(Builder builder) {
super(builder.mContext, builder.mProviderHelper, builder.mProgressable);
// private Constructor can only be called from Builder
this.mData = builder.mData;
this.mOutStream = builder.mOutStream;
this.mAllowSymmetricDecryption = builder.mAllowSymmetricDecryption;
this.mAllowedKeyIds = builder.mAllowedKeyIds;
this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly;
this.mDetachedSignature = builder.mDetachedSignature;
this.mSignedLiteralData = builder.mSignedLiteralData;
this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint;
}
public static class Builder {
// mandatory parameter
private Context mContext;
private ProviderHelper mProviderHelper;
private InputData mData;
// optional
private OutputStream mOutStream = null;
private Progressable mProgressable = null;
private boolean mAllowSymmetricDecryption = true;
private Set<Long> mAllowedKeyIds = null;
private boolean mDecryptMetadataOnly = false;
private byte[] mDetachedSignature = null;
private String mRequiredSignerFingerprint = null;
private boolean mSignedLiteralData = false;
public Builder(Context context, ProviderHelper providerHelper,
Progressable progressable,
InputData data, OutputStream outStream) {
mContext = context;
mProviderHelper = providerHelper;
mProgressable = progressable;
mData = data;
mOutStream = outStream;
}
/**
* This is used when verifying signed literals to check that they are signed with
* the required key
*/
public Builder setRequiredSignerFingerprint(String fingerprint) {
mRequiredSignerFingerprint = fingerprint;
return this;
}
/**
* This is to force a mode where the message is just the signature key id and
* then a literal data packet; used in Keybase.io proofs
*/
public Builder setSignedLiteralData(boolean signedLiteralData) {
mSignedLiteralData = signedLiteralData;
return this;
}
public Builder setAllowSymmetricDecryption(boolean allowSymmetricDecryption) {
mAllowSymmetricDecryption = allowSymmetricDecryption;
return this;
}
/**
* Allow these key ids alone for decryption.
* This means only ciphertexts encrypted for one of these private key can be decrypted.
*/
public Builder setAllowedKeyIds(Set<Long> allowedKeyIds) {
mAllowedKeyIds = allowedKeyIds;
return this;
}
/**
* If enabled, the actual decryption/verification of the content will not be executed.
* The metadata only will be decrypted and returned.
*/
public Builder setDecryptMetadataOnly(boolean decryptMetadataOnly) {
mDecryptMetadataOnly = decryptMetadataOnly;
return this;
}
/**
* If detachedSignature != null, it will be used exclusively to verify the signature
*/
public Builder setDetachedSignature(byte[] detachedSignature) {
mDetachedSignature = detachedSignature;
return this;
}
public PgpDecryptVerify build() {
return new PgpDecryptVerify(this);
}
public PgpDecryptVerify(Context context, ProviderHelper providerHelper, Progressable progressable) {
super(context, providerHelper, progressable);
}
/**
* Decrypts and/or verifies data based on parameters of class
*/
public DecryptVerifyResult execute(CryptoInputParcel cryptoInput) {
public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput) {
InputData inputData;
OutputStream outputStream;
if (input.getInputBytes() != null) {
byte[] inputBytes = input.getInputBytes();
inputData = new InputData(new ByteArrayInputStream(inputBytes), inputBytes.length);
} else {
try {
InputStream inputStream = mContext.getContentResolver().openInputStream(input.getInputUri());
long inputSize = FileHelper.getFileSize(mContext, input.getInputUri(), 0);
inputData = new InputData(inputStream, inputSize);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
}
if (input.getOutputUri() == null) {
outputStream = new ByteArrayOutputStream();
} else {
try {
outputStream = mContext.getContentResolver().openOutputStream(input.getOutputUri());
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
}
DecryptVerifyResult result = executeInternal(input, cryptoInput, inputData, outputStream);
if (outputStream instanceof ByteArrayOutputStream) {
byte[] outputData = ((ByteArrayOutputStream) outputStream).toByteArray();
result.setOutputBytes(outputData);
}
return result;
}
public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
InputData inputData, OutputStream outputStream) {
return executeInternal(input, cryptoInput, inputData, outputStream);
}
private DecryptVerifyResult executeInternal(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
InputData inputData, OutputStream outputStream) {
try {
if (mDetachedSignature != null) {
if (input.getDetachedSignature() != null) {
Log.d(Constants.TAG, "Detached signature present, verifying with this signature only");
return verifyDetachedSignature(mData.getInputStream(), 0);
return verifyDetachedSignature(input, inputData, outputStream, 0);
} else {
// automatically works with PGP ascii armor and PGP binary
InputStream in = PGPUtil.getDecoderStream(mData.getInputStream());
InputStream in = PGPUtil.getDecoderStream(inputData.getInputStream());
if (in instanceof ArmoredInputStream) {
ArmoredInputStream aIn = (ArmoredInputStream) in;
// it is ascii armored
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
if (mSignedLiteralData) {
return verifySignedLiteralData(aIn, 0);
if (input.isSignedLiteralData()) {
return verifySignedLiteralData(input, aIn, outputStream, 0);
} else if (aIn.isClearText()) {
// a cleartext signature, verify it with the other method
return verifyCleartextSignature(aIn, 0);
return verifyCleartextSignature(aIn, outputStream, 0);
} else {
// else: ascii armored encryption! go on...
return decryptVerify(cryptoInput, in, 0);
return decryptVerify(input, cryptoInput, in, outputStream, 0);
}
} else {
return decryptVerify(cryptoInput, in, 0);
return decryptVerify(input, cryptoInput, in, outputStream, 0);
}
}
} catch (PGPException e) {
@ -231,7 +174,8 @@ public class PgpDecryptVerify extends BaseOperation {
/**
* Verify Keybase.io style signed literal data
*/
private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent)
private DecryptVerifyResult verifySignedLiteralData(
PgpDecryptVerifyInputParcel input, InputStream in, OutputStream out, int indent)
throws IOException, PGPException {
OperationLog log = new OperationLog();
log.add(LogType.MSG_VL, indent);
@ -282,9 +226,9 @@ public class PgpDecryptVerify extends BaseOperation {
}
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(signingRing.getFingerprint());
if (!(mRequiredSignerFingerprint.equals(fingerprint))) {
if (!(input.getRequiredSignerFingerprint().equals(fingerprint))) {
log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + mRequiredSignerFingerprint +
Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + input.getRequiredSignerFingerprint() +
" got " + fingerprint + "!");
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
@ -316,7 +260,7 @@ public class PgpDecryptVerify extends BaseOperation {
int length;
byte[] buffer = new byte[1 << 16];
while ((length = dataIn.read(buffer)) > 0) {
mOutStream.write(buffer, 0, length);
out.write(buffer, 0, length);
signature.update(buffer, 0, length);
}
@ -362,8 +306,9 @@ public class PgpDecryptVerify extends BaseOperation {
/**
* Decrypt and/or verifies binary or ascii armored pgp
*/
private DecryptVerifyResult decryptVerify(CryptoInputParcel cryptoInput,
InputStream in, int indent) throws IOException, PGPException {
private DecryptVerifyResult decryptVerify(
PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
InputStream in, OutputStream out, int indent) throws IOException, PGPException {
OperationLog log = new OperationLog();
@ -454,13 +399,13 @@ public class PgpDecryptVerify extends BaseOperation {
}
// allow only specific keys for decryption?
if (mAllowedKeyIds != null) {
if (input.getAllowedKeyIds() != null) {
long masterKeyId = secretKeyRing.getMasterKeyId();
Log.d(Constants.TAG, "encData.getKeyID(): " + subKeyId);
Log.d(Constants.TAG, "mAllowedKeyIds: " + mAllowedKeyIds);
Log.d(Constants.TAG, "mAllowedKeyIds: " + input.getAllowedKeyIds());
Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
if (!mAllowedKeyIds.contains(masterKeyId)) {
if (!input.getAllowedKeyIds().contains(masterKeyId)) {
// this key is in our db, but NOT allowed!
// continue with the next packet in the while loop
skippedDisallowedKey = true;
@ -514,7 +459,7 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_SYM, indent);
if (!mAllowSymmetricDecryption) {
if (!input.isAllowSymmetricDecryption()) {
log.add(LogType.MSG_DC_SYM_SKIP, indent + 1);
continue;
}
@ -596,7 +541,12 @@ public class PgpDecryptVerify extends BaseOperation {
try {
PublicKeyDataDecryptorFactory decryptorFactory
= secretEncryptionKey.getDecryptorFactory(cryptoInput);
clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
try {
clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
} catch (PGPKeyValidationException | ArrayIndexOutOfBoundsException e) {
log.add(LogType.MSG_DC_ERROR_CORRUPT_DATA, indent + 1);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
} catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) {
@ -758,7 +708,7 @@ public class PgpDecryptVerify extends BaseOperation {
}
// return here if we want to decrypt the metadata only
if (mDecryptMetadataOnly) {
if (input.isDecryptMetadataOnly()) {
log.add(LogType.MSG_DC_OK_META_ONLY, indent);
DecryptVerifyResult result =
new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
@ -781,13 +731,13 @@ public class PgpDecryptVerify extends BaseOperation {
InputStream dataIn = literalData.getInputStream();
long alreadyWritten = 0;
long wholeSize = mData.getSize() - mData.getStreamPosition();
long wholeSize = 0; // TODO inputData.getSize() - inputData.getStreamPosition();
int length;
byte[] buffer = new byte[1 << 16];
while ((length = dataIn.read(buffer)) > 0) {
Log.d(Constants.TAG, "read bytes: " + length);
if (mOutStream != null) {
mOutStream.write(buffer, 0, length);
// Log.d(Constants.TAG, "read bytes: " + length);
if (out != null) {
out.write(buffer, 0, length);
}
// update signature buffer if signature is also present
@ -807,6 +757,12 @@ public class PgpDecryptVerify extends BaseOperation {
// TODO: slow annealing to fake a progress?
}
metadata = new OpenPgpMetadata(
originalFilename,
mimeType,
literalData.getModificationTime().getTime(),
alreadyWritten);
if (signature != null) {
updateProgress(R.string.progress_verifying_signature, 90, 100);
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
@ -883,7 +839,7 @@ public class PgpDecryptVerify extends BaseOperation {
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
*/
private DecryptVerifyResult verifyCleartextSignature(
ArmoredInputStream aIn, int indent) throws IOException, PGPException {
ArmoredInputStream aIn, OutputStream outputStream, int indent) throws IOException, PGPException {
OperationLog log = new OperationLog();
@ -913,8 +869,9 @@ public class PgpDecryptVerify extends BaseOperation {
out.close();
byte[] clearText = out.toByteArray();
if (mOutStream != null) {
mOutStream.write(clearText);
if (outputStream != null) {
outputStream.write(clearText);
outputStream.close();
}
updateProgress(R.string.progress_processing_signature, 60, 100);
@ -981,7 +938,8 @@ public class PgpDecryptVerify extends BaseOperation {
return result;
}
private DecryptVerifyResult verifyDetachedSignature(InputStream in, int indent)
private DecryptVerifyResult verifyDetachedSignature(
PgpDecryptVerifyInputParcel input, InputData inputData, OutputStream out, int indent)
throws IOException, PGPException {
OperationLog log = new OperationLog();
@ -991,7 +949,7 @@ public class PgpDecryptVerify extends BaseOperation {
signatureResultBuilder.setSignatureOnly(true);
updateProgress(R.string.progress_processing_signature, 0, 100);
InputStream detachedSigIn = new ByteArrayInputStream(mDetachedSignature);
InputStream detachedSigIn = new ByteArrayInputStream(input.getDetachedSignature());
detachedSigIn = PGPUtil.getDecoderStream(detachedSigIn);
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(detachedSigIn);
@ -1016,12 +974,13 @@ public class PgpDecryptVerify extends BaseOperation {
ProgressScaler progressScaler = new ProgressScaler(mProgressable, 60, 90, 100);
long alreadyWritten = 0;
long wholeSize = mData.getSize() - mData.getStreamPosition();
long wholeSize = inputData.getSize() - inputData.getStreamPosition();
int length;
byte[] buffer = new byte[1 << 16];
InputStream in = inputData.getInputStream();
while ((length = in.read(buffer)) > 0) {
if (mOutStream != null) {
mOutStream.write(buffer, 0, length);
if (out != null) {
out.write(buffer, 0, length);
}
// update signature buffer if signature is also present

View File

@ -0,0 +1,162 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.pgp;
import java.util.HashSet;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
public class PgpDecryptVerifyInputParcel implements Parcelable {
private Uri mInputUri;
private Uri mOutputUri;
private byte[] mInputBytes;
private boolean mAllowSymmetricDecryption;
private HashSet<Long> mAllowedKeyIds;
private boolean mDecryptMetadataOnly;
private byte[] mDetachedSignature;
private String mRequiredSignerFingerprint;
private boolean mSignedLiteralData;
public PgpDecryptVerifyInputParcel() {
}
public PgpDecryptVerifyInputParcel(Uri inputUri, Uri outputUri) {
mInputUri = inputUri;
mOutputUri = outputUri;
}
public PgpDecryptVerifyInputParcel(byte[] inputBytes) {
mInputBytes = inputBytes;
}
PgpDecryptVerifyInputParcel(Parcel source) {
// we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
mInputUri = source.readParcelable(getClass().getClassLoader());
mOutputUri = source.readParcelable(getClass().getClassLoader());
mInputBytes = source.createByteArray();
mAllowSymmetricDecryption = source.readInt() != 0;
mAllowedKeyIds = (HashSet<Long>) source.readSerializable();
mDecryptMetadataOnly = source.readInt() != 0;
mDetachedSignature = source.createByteArray();
mRequiredSignerFingerprint = source.readString();
mSignedLiteralData = source.readInt() != 0;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(mInputUri, 0);
dest.writeParcelable(mOutputUri, 0);
dest.writeByteArray(mInputBytes);
dest.writeInt(mAllowSymmetricDecryption ? 1 : 0);
dest.writeSerializable(mAllowedKeyIds);
dest.writeInt(mDecryptMetadataOnly ? 1 : 0);
dest.writeByteArray(mDetachedSignature);
dest.writeString(mRequiredSignerFingerprint);
dest.writeInt(mSignedLiteralData ? 1 : 0);
}
byte[] getInputBytes() {
return mInputBytes;
}
Uri getInputUri() {
return mInputUri;
}
Uri getOutputUri() {
return mOutputUri;
}
boolean isAllowSymmetricDecryption() {
return mAllowSymmetricDecryption;
}
public PgpDecryptVerifyInputParcel setAllowSymmetricDecryption(boolean allowSymmetricDecryption) {
mAllowSymmetricDecryption = allowSymmetricDecryption;
return this;
}
HashSet<Long> getAllowedKeyIds() {
return mAllowedKeyIds;
}
public PgpDecryptVerifyInputParcel setAllowedKeyIds(HashSet<Long> allowedKeyIds) {
mAllowedKeyIds = allowedKeyIds;
return this;
}
boolean isDecryptMetadataOnly() {
return mDecryptMetadataOnly;
}
public PgpDecryptVerifyInputParcel setDecryptMetadataOnly(boolean decryptMetadataOnly) {
mDecryptMetadataOnly = decryptMetadataOnly;
return this;
}
byte[] getDetachedSignature() {
return mDetachedSignature;
}
public PgpDecryptVerifyInputParcel setDetachedSignature(byte[] detachedSignature) {
mDetachedSignature = detachedSignature;
return this;
}
String getRequiredSignerFingerprint() {
return mRequiredSignerFingerprint;
}
public PgpDecryptVerifyInputParcel setRequiredSignerFingerprint(String requiredSignerFingerprint) {
mRequiredSignerFingerprint = requiredSignerFingerprint;
return this;
}
boolean isSignedLiteralData() {
return mSignedLiteralData;
}
public PgpDecryptVerifyInputParcel setSignedLiteralData(boolean signedLiteralData) {
mSignedLiteralData = signedLiteralData;
return this;
}
public static final Creator<PgpDecryptVerifyInputParcel> CREATOR = new Creator<PgpDecryptVerifyInputParcel>() {
public PgpDecryptVerifyInputParcel createFromParcel(final Parcel source) {
return new PgpDecryptVerifyInputParcel(source);
}
public PgpDecryptVerifyInputParcel[] newArray(final int size) {
return new PgpDecryptVerifyInputParcel[size];
}
};
}

View File

@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.S2K;
import org.spongycastle.bcpg.sig.Features;
import org.spongycastle.bcpg.sig.KeyFlags;
@ -45,8 +46,10 @@ import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
@ -59,6 +62,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcKeyToCardOperationsBuilder;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
@ -68,6 +72,7 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
@ -358,8 +363,8 @@ public class PgpKeyOperation {
*
*/
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR,
CryptoInputParcel cryptoInput,
SaveKeyringParcel saveParcel) {
CryptoInputParcel cryptoInput,
SaveKeyringParcel saveParcel) {
OperationLog log = new OperationLog();
int indent = 0;
@ -402,9 +407,61 @@ public class PgpKeyOperation {
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
// Ensure we don't have multiple keys for the same slot.
boolean hasSign = false;
boolean hasEncrypt = false;
boolean hasAuth = false;
for(SaveKeyringParcel.SubkeyChange change : saveParcel.mChangeSubKeys) {
if (change.mMoveKeyToCard) {
// If this is a keytocard operation, see if it was completed: look for a hash
// matching the given subkey ID in cryptoData.
byte[] subKeyId = new byte[8];
ByteBuffer buf = ByteBuffer.wrap(subKeyId);
buf.putLong(change.mKeyId).rewind();
byte[] serialNumber = cryptoInput.getCryptoData().get(buf);
if (serialNumber != null) {
change.mMoveKeyToCard = false;
change.mDummyDivert = serialNumber;
}
}
if (change.mMoveKeyToCard) {
// Pending keytocard operation. Need to make sure that we don't have multiple
// subkeys pending for the same slot.
CanonicalizedSecretKey wsK = wsKR.getSecretKey(change.mKeyId);
if ((wsK.canSign() || wsK.canCertify())) {
if (hasSign) {
log.add(LogType.MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT, indent + 1);
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
} else {
hasSign = true;
}
} else if ((wsK.canEncrypt())) {
if (hasEncrypt) {
log.add(LogType.MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT, indent + 1);
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
} else {
hasEncrypt = true;
}
} else if ((wsK.canAuthenticate())) {
if (hasAuth) {
log.add(LogType.MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT, indent + 1);
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
} else {
hasAuth = true;
}
} else {
log.add(LogType.MSG_MF_ERROR_INVALID_FLAGS_FOR_KEYTOCARD, indent + 1);
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
}
}
if (isDummy(masterSecretKey) || saveParcel.isRestrictedOnly()) {
log.add(LogType.MSG_MF_RESTRICTED_MODE, indent);
return internalRestricted(sKR, saveParcel, log);
return internalRestricted(sKR, saveParcel, log, indent + 1);
}
// Do we require a passphrase? If so, pass it along
@ -430,11 +487,14 @@ public class PgpKeyOperation {
int masterKeyFlags, long masterKeyExpiry,
CryptoInputParcel cryptoInput,
SaveKeyringParcel saveParcel,
OperationLog log, int indent) {
OperationLog log,
int indent) {
NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder(
cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(),
masterSecretKey.getKeyID());
NfcKeyToCardOperationsBuilder nfcKeyToCardOps = new NfcKeyToCardOperationsBuilder(
masterSecretKey.getKeyID());
progress(R.string.progress_modify, 0);
@ -744,22 +804,36 @@ public class PgpKeyOperation {
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
if (change.mDummyStrip || change.mDummyDivert != null) {
if (change.mDummyStrip) {
// IT'S DANGEROUS~
// no really, it is. this operation irrevocably removes the private key data from the key
if (change.mDummyStrip) {
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
} else if (change.mMoveKeyToCard) {
if (checkSmartCardCompatibility(sKey, log, indent + 1)) {
log.add(LogType.MSG_MF_KEYTOCARD_START, indent + 1,
KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
nfcKeyToCardOps.addSubkey(change.mKeyId);
} else {
// the serial number must be 16 bytes in length
if (change.mDummyDivert.length != 16) {
log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
// Appropriate log message already set by checkSmartCardCompatibility
return new PgpEditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
}
} else if (change.mDummyDivert != null) {
// NOTE: Does this code get executed? Or always handled in internalRestricted?
if (change.mDummyDivert.length != 16) {
log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
log.add(LogType.MSG_MF_KEYTOCARD_FINISH, indent + 1,
KeyFormattingUtils.convertKeyIdToHex(change.mKeyId),
Hex.toHexString(change.mDummyDivert, 8, 6));
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
}
// This doesn't concern us any further
if (!change.mRecertify && (change.mExpiry == null && change.mFlags == null)) {
continue;
@ -981,11 +1055,21 @@ public class PgpKeyOperation {
progress(R.string.progress_done, 100);
if (!nfcSignOps.isEmpty() && !nfcKeyToCardOps.isEmpty()) {
log.add(LogType.MSG_MF_ERROR_CONFLICTING_NFC_COMMANDS, indent+1);
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
if (!nfcSignOps.isEmpty()) {
log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent);
return new PgpEditKeyResult(log, nfcSignOps.build());
}
if (!nfcKeyToCardOps.isEmpty()) {
log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent);
return new PgpEditKeyResult(log, nfcKeyToCardOps.build());
}
log.add(LogType.MSG_MF_SUCCESS, indent);
return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR));
@ -996,9 +1080,7 @@ public class PgpKeyOperation {
* otherwise.
*/
private PgpEditKeyResult internalRestricted(PGPSecretKeyRing sKR, SaveKeyringParcel saveParcel,
OperationLog log) {
int indent = 1;
OperationLog log, int indent) {
progress(R.string.progress_modify, 0);
@ -1043,6 +1125,9 @@ public class PgpKeyOperation {
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
log.add(LogType.MSG_MF_KEYTOCARD_FINISH, indent + 1,
KeyFormattingUtils.convertKeyIdToHex(change.mKeyId),
Hex.toHexString(change.mDummyDivert, 8, 6));
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert);
}
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
@ -1488,4 +1573,29 @@ public class PgpKeyOperation {
&& s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD;
}
private static boolean checkSmartCardCompatibility(PGPSecretKey key, OperationLog log, int indent) {
PGPPublicKey publicKey = key.getPublicKey();
int algorithm = publicKey.getAlgorithm();
if (algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT &&
algorithm != PublicKeyAlgorithmTags.RSA_SIGN &&
algorithm != PublicKeyAlgorithmTags.RSA_GENERAL) {
log.add(LogType.MSG_MF_ERROR_BAD_NFC_ALGO, indent + 1);
return false;
}
// Key size must be 2048
int keySize = publicKey.getBitStrength();
if (keySize != 2048) {
log.add(LogType.MSG_MF_ERROR_BAD_NFC_SIZE, indent + 1);
return false;
}
// Secret key parts must be available
if (isDivertToCard(key) || isDummy(key)) {
log.add(LogType.MSG_MF_ERROR_BAD_NFC_STRIPPED, indent + 1);
return false;
}
return true;
}
}

View File

@ -20,13 +20,8 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.Passphrase;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.Map;
import android.os.Parcel;
import android.os.Parcelable;

View File

@ -57,6 +57,10 @@ public class SignEncryptParcel extends PgpSignEncryptInputParcel {
}
public boolean isIncomplete() {
return mInputUris.size() > mOutputUris.size();
}
public byte[] getBytes() {
return mBytes;
}

View File

@ -90,7 +90,7 @@ public class KeychainContract {
String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name
}
public static final String CONTENT_AUTHORITY = Constants.PACKAGE_NAME + ".provider";
public static final String CONTENT_AUTHORITY = Constants.PROVIDER_AUTHORITY;
private static final Uri BASE_CONTENT_URI_INTERNAL = Uri
.parse("content://" + CONTENT_AUTHORITY);

View File

@ -179,7 +179,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
+ ")";
KeychainDatabase(Context context) {
public KeychainDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
@ -391,10 +391,15 @@ public class KeychainDatabase extends SQLiteOpenHelper {
private static void copy(File in, File out) throws IOException {
FileInputStream is = new FileInputStream(in);
FileOutputStream os = new FileOutputStream(out);
byte[] buf = new byte[512];
while (is.available() > 0) {
int count = is.read(buf, 0, 512);
os.write(buf, 0, count);
try {
byte[] buf = new byte[512];
while (is.available() > 0) {
int count = is.read(buf, 0, 512);
os.write(buf, 0, count);
}
} finally {
is.close();
os.close();
}
}

View File

@ -1514,8 +1514,8 @@ public class ProviderHelper {
return keyIds;
}
public Set<Long> getAllowedKeyIdsForApp(Uri uri) {
Set<Long> keyIds = new HashSet<>();
public HashSet<Long> getAllowedKeyIdsForApp(Uri uri) {
HashSet<Long> keyIds = new HashSet<>();
Cursor cursor = mContentResolver.query(uri, null, null, null, null);
try {

View File

@ -45,7 +45,8 @@ public class TemporaryStorageProvider extends ContentProvider {
private static final String COLUMN_ID = "id";
private static final String COLUMN_NAME = "name";
private static final String COLUMN_TIME = "time";
private static final Uri BASE_URI = Uri.parse("content://org.sufficientlysecure.keychain.tempstorage/");
public static final String CONTENT_AUTHORITY = Constants.TEMPSTORAGE_AUTHORITY;
private static final Uri BASE_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
private static final int DB_VERSION = 2;
private static File cacheDir;

View File

@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEnt
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.pgp.PgpConstants;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
@ -63,7 +64,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
public class OpenPgpService extends RemoteService {
@ -166,6 +167,7 @@ public class OpenPgpService extends RemoteService {
Intent data, RequiredInputParcel requiredInput) {
switch (requiredInput.mType) {
case NFC_KEYTOCARD:
case NFC_DECRYPT:
case NFC_SIGN: {
// build PendingIntent for YubiKey NFC operations
@ -487,23 +489,23 @@ public class OpenPgpService extends RemoteService {
}
}
private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor input,
private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor inputDescriptor,
ParcelFileDescriptor output, boolean decryptMetadataOnly) {
InputStream is = null;
OutputStream os = null;
InputStream inputStream = null;
OutputStream outputStream = null;
try {
// Get Input- and OutputStream from ParcelFileDescriptor
is = new ParcelFileDescriptor.AutoCloseInputStream(input);
inputStream = new ParcelFileDescriptor.AutoCloseInputStream(inputDescriptor);
// output is optional, e.g., for verifying detached signatures
if (decryptMetadataOnly || output == null) {
os = null;
outputStream = null;
} else {
os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(output);
}
String currentPkg = getCurrentCallingPackage();
Set<Long> allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp(
HashSet<Long> allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp(
KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) {
@ -511,33 +513,32 @@ public class OpenPgpService extends RemoteService {
ApiAccounts.buildBaseUri(currentPkg)));
}
long inputLength = is.available();
InputData inputData = new InputData(is, inputLength);
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
this, new ProviderHelper(getContext()), null, inputData, os
);
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
if (inputParcel == null) {
inputParcel = new CryptoInputParcel();
CryptoInputParcel cryptoInput = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
if (cryptoInput == null) {
cryptoInput = new CryptoInputParcel();
}
// override passphrase in input parcel if given by API call
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(),
cryptoInput = new CryptoInputParcel(cryptoInput.getSignatureTime(),
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
}
byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
PgpDecryptVerify op = new PgpDecryptVerify(this, mProviderHelper, null);
long inputLength = inputStream.available();
InputData inputData = new InputData(inputStream, inputLength);
// allow only private keys associated with accounts of this app
// no support for symmetric encryption
builder.setAllowSymmetricDecryption(false)
.setAllowedKeyIds(allowedKeyIds)
.setDecryptMetadataOnly(decryptMetadataOnly)
.setDetachedSignature(detachedSignature);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel()
.setAllowSymmetricDecryption(false)
.setAllowedKeyIds(allowedKeyIds)
.setDecryptMetadataOnly(decryptMetadataOnly)
.setDetachedSignature(detachedSignature);
DecryptVerifyResult pgpResult = builder.build().execute(inputParcel);
DecryptVerifyResult pgpResult = op.execute(input, cryptoInput, inputData, outputStream);
if (pgpResult.isPending()) {
// prepare and return PendingIntent to be executed by client
@ -623,16 +624,16 @@ public class OpenPgpService extends RemoteService {
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
} finally {
if (is != null) {
if (inputStream != null) {
try {
is.close();
inputStream.close();
} catch (IOException e) {
Log.e(Constants.TAG, "IOException when closing InputStream", e);
}
}
if (os != null) {
if (outputStream != null) {
try {
os.close();
outputStream.close();
} catch (IOException e) {
Log.e(Constants.TAG, "IOException when closing OutputStream", e);
}

View File

@ -217,13 +217,15 @@ public class AppSettingsActivity extends BaseActivity {
// show accounts only if available (deprecated API)
Cursor cursor = getContentResolver().query(accountsUri, null, null, null, null);
if (cursor.moveToFirst()) {
if (cursor != null && cursor.moveToFirst()) try {
mAccountsLabel.setVisibility(View.VISIBLE);
mAccountsListFragment = AccountsListFragment.newInstance(accountsUri);
// Create an instance of the fragments
getSupportFragmentManager().beginTransaction()
.replace(R.id.api_accounts_list_fragment, mAccountsListFragment)
.commitAllowingStateLoss();
} finally {
cursor.close();
}
// Create an instance of the fragments

View File

@ -43,6 +43,8 @@ public class CertifyActionsParcel implements Parcelable {
public ArrayList<CertifyAction> mCertifyActions = new ArrayList<>();
public String keyServerUri;
public CertifyActionsParcel(long masterKeyId) {
mMasterKeyId = masterKeyId;
mLevel = CertifyLevel.DEFAULT;

View File

@ -1,384 +0,0 @@
/*
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.ImportExportOperation;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* When this service is started it will initiate a multi-threaded key import and when done it will
* shut itself down.
*/
public class CloudImportService extends Service implements Progressable {
// required as extras from intent
public static final String EXTRA_MESSENGER = "messenger";
public static final String EXTRA_DATA = "data";
// required by data bundle
public static final String IMPORT_KEY_LIST = "import_key_list";
public static final String IMPORT_KEY_SERVER = "import_key_server";
// indicates a request to cancel the import
public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL";
// tells the spawned threads whether the user has requested a cancel
private static AtomicBoolean mActionCancelled = new AtomicBoolean(false);
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* Used to accumulate the results of individual key imports
*/
private class KeyImportAccumulator {
private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog();
private int mTotalKeys;
private int mImportedKeys = 0;
private Progressable mImportProgressable;
ArrayList<Long> mImportedMasterKeyIds = new ArrayList<Long>();
private int mBadKeys = 0;
private int mNewKeys = 0;
private int mUpdatedKeys = 0;
private int mSecret = 0;
private int mResultType = 0;
public KeyImportAccumulator(int totalKeys) {
mTotalKeys = totalKeys;
// ignore updates from ImportExportOperation for now
mImportProgressable = new Progressable() {
@Override
public void setProgress(String message, int current, int total) {
}
@Override
public void setProgress(int resourceId, int current, int total) {
}
@Override
public void setProgress(int current, int total) {
}
@Override
public void setPreventCancel() {
}
};
}
public Progressable getImportProgressable() {
return mImportProgressable;
}
public int getTotalKeys() {
return mTotalKeys;
}
public int getImportedKeys() {
return mImportedKeys;
}
public synchronized void accumulateKeyImport(ImportKeyResult result) {
mImportedKeys++;
mImportLog.addAll(result.getLog().toList());//accumulates log
mBadKeys += result.mBadKeys;
mNewKeys += result.mNewKeys;
mUpdatedKeys += result.mUpdatedKeys;
mSecret += result.mSecret;
long[] masterKeyIds = result.getImportedMasterKeyIds();
for (long masterKeyId : masterKeyIds) {
mImportedMasterKeyIds.add(masterKeyId);
}
// if any key import has been cancelled, set result type to cancelled
// resultType is added to in getConsolidatedKayImport to account for remaining factors
mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED;
}
/**
* returns accumulated result of all imports so far
*/
public ImportKeyResult getConsolidatedImportKeyResult() {
// adding required information to mResultType
// special case,no keys requested for import
if (mBadKeys == 0 && mNewKeys == 0 && mUpdatedKeys == 0) {
mResultType = ImportKeyResult.RESULT_FAIL_NOTHING;
} else {
if (mNewKeys > 0) {
mResultType |= ImportKeyResult.RESULT_OK_NEWKEYS;
}
if (mUpdatedKeys > 0) {
mResultType |= ImportKeyResult.RESULT_OK_UPDATED;
}
if (mBadKeys > 0) {
mResultType |= ImportKeyResult.RESULT_WITH_ERRORS;
if (mNewKeys == 0 && mUpdatedKeys == 0) {
mResultType |= ImportKeyResult.RESULT_ERROR;
}
}
if (mImportLog.containsWarnings()) {
mResultType |= ImportKeyResult.RESULT_WARNINGS;
}
}
long masterKeyIds[] = new long[mImportedMasterKeyIds.size()];
for (int i = 0; i < masterKeyIds.length; i++) {
masterKeyIds[i] = mImportedMasterKeyIds.get(i);
}
return new ImportKeyResult(mResultType, mImportLog, mNewKeys, mUpdatedKeys, mBadKeys,
mSecret, masterKeyIds);
}
public boolean isImportFinished() {
return mTotalKeys == mImportedKeys;
}
}
private KeyImportAccumulator mKeyImportAccumulator;
Messenger mMessenger;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (ACTION_CANCEL.equals(intent.getAction())) {
mActionCancelled.set(true);
return Service.START_NOT_STICKY;
}
mActionCancelled.set(false);//we haven't been cancelled, yet
Bundle extras = intent.getExtras();
mMessenger = (Messenger) extras.get(EXTRA_MESSENGER);
Bundle data = extras.getBundle(EXTRA_DATA);
final String keyServer = data.getString(IMPORT_KEY_SERVER);
// keyList being null (in case key list to be reaad from cache) is checked by importKeys
final ArrayList<ParcelableKeyRing> keyList = data.getParcelableArrayList(IMPORT_KEY_LIST);
// Adding keys to the ThreadPoolExecutor takes time, we don't want to block the main thread
Thread baseImportThread = new Thread(new Runnable() {
@Override
public void run() {
importKeys(keyList, keyServer);
}
});
baseImportThread.start();
return Service.START_NOT_STICKY;
}
public void importKeys(ArrayList<ParcelableKeyRing> keyList, final String keyServer) {
ParcelableFileCache<ParcelableKeyRing> cache =
new ParcelableFileCache<>(this, "key_import.pcl");
int totKeys = 0;
Iterator<ParcelableKeyRing> keyListIterator = null;
// either keyList or cache must be null, no guarantees otherwise
if (keyList == null) {//export from cache, copied from ImportExportOperation.importKeyRings
try {
ParcelableFileCache.IteratorWithSize<ParcelableKeyRing> it = cache.readCache();
keyListIterator = it;
totKeys = it.getSize();
} catch (IOException e) {
// Special treatment here, we need a lot
OperationResult.OperationLog log = new OperationResult.OperationLog();
log.add(OperationResult.LogType.MSG_IMPORT, 0, 0);
log.add(OperationResult.LogType.MSG_IMPORT_ERROR_IO, 0, 0);
keyImportFailed(new ImportKeyResult(ImportKeyResult.RESULT_ERROR, log));
}
} else {
keyListIterator = keyList.iterator();
totKeys = keyList.size();
}
if (keyListIterator != null) {
mKeyImportAccumulator = new KeyImportAccumulator(totKeys);
setProgress(0, totKeys);
final int maxThreads = 200;
ExecutorService importExecutor = new ThreadPoolExecutor(0, maxThreads,
30L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
while (keyListIterator.hasNext()) {
final ParcelableKeyRing pkRing = keyListIterator.next();
Runnable importOperationRunnable = new Runnable() {
@Override
public void run() {
ImportKeyResult result = null;
try {
ImportExportOperation importExportOperation = new ImportExportOperation(
CloudImportService.this,
new ProviderHelper(CloudImportService.this),
mKeyImportAccumulator.getImportProgressable(),
mActionCancelled);
ArrayList<ParcelableKeyRing> list = new ArrayList<>();
list.add(pkRing);
result = importExportOperation.importKeyRings(list,
keyServer);
} finally {
// in the off-chance that importKeyRings does something to crash the
// thread before it can call singleKeyRingImportCompleted, our imported
// key count will go wrong. This will cause the service to never die,
// and the progress dialog to stay displayed. The finally block was
// originally meant to ensure singleKeyRingImportCompleted was called,
// and checks for null were to be introduced, but in such a scenario,
// knowing an uncaught error exists in importKeyRings is more important.
// if a null gets passed, something wrong is happening. We want a crash.
singleKeyRingImportCompleted(result);
}
}
};
importExecutor.execute(importOperationRunnable);
}
}
}
private synchronized void singleKeyRingImportCompleted(ImportKeyResult result) {
// increase imported key count and accumulate log and bad, new etc. key counts from result
mKeyImportAccumulator.accumulateKeyImport(result);
setProgress(mKeyImportAccumulator.getImportedKeys(), mKeyImportAccumulator.getTotalKeys());
if (mKeyImportAccumulator.isImportFinished()) {
ContactSyncAdapterService.requestSync();
sendMessageToHandler(ServiceProgressHandler.MessageStatus.OKAY,
mKeyImportAccumulator.getConsolidatedImportKeyResult());
stopSelf();//we're done here
}
}
private void keyImportFailed(ImportKeyResult result) {
sendMessageToHandler(ServiceProgressHandler.MessageStatus.OKAY, result);
}
private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status, Integer arg2, Bundle data) {
Message msg = Message.obtain();
assert msg != null;
msg.arg1 = status.ordinal();
if (arg2 != null) {
msg.arg2 = arg2;
}
if (data != null) {
msg.setData(data);
}
try {
mMessenger.send(msg);
} catch (RemoteException e) {
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
} catch (NullPointerException e) {
Log.w(Constants.TAG, "Messenger is null!", e);
}
}
private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status, OperationResult data) {
Bundle bundle = new Bundle();
bundle.putParcelable(OperationResult.EXTRA_RESULT, data);
sendMessageToHandler(status, null, bundle);
}
private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status, Bundle data) {
sendMessageToHandler(status, null, data);
}
private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status) {
sendMessageToHandler(status, null, null);
}
/**
* Set progress of ProgressDialog by sending message to handler on UI thread
*/
@Override
public synchronized void setProgress(String message, int progress, int max) {
Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
+ max);
Bundle data = new Bundle();
if (message != null) {
data.putString(ServiceProgressHandler.DATA_MESSAGE, message);
}
data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress);
data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max);
sendMessageToHandler(ServiceProgressHandler.MessageStatus.UPDATE_PROGRESS, null, data);
}
@Override
public synchronized void setProgress(int resourceId, int progress, int max) {
setProgress(getString(resourceId), progress, max);
}
@Override
public synchronized void setProgress(int progress, int max) {
setProgress(null, progress, max);
}
@Override
public synchronized void setPreventCancel() {
sendMessageToHandler(ServiceProgressHandler.MessageStatus.PREVENT_CANCEL);
}
}

View File

@ -1,746 +0,0 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service;
import android.app.IntentService;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import com.textuality.keybase.lib.Proof;
import com.textuality.keybase.lib.prover.Prover;
import org.json.JSONObject;
import org.spongycastle.openpgp.PGPUtil;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.keyimport.Keyserver;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.CertifyOperation;
import org.sufficientlysecure.keychain.operations.DeleteOperation;
import org.sufficientlysecure.keychain.operations.EditKeyOperation;
import org.sufficientlysecure.keychain.operations.ImportExportOperation;
import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import de.measite.minidns.Client;
import de.measite.minidns.DNSMessage;
import de.measite.minidns.Question;
import de.measite.minidns.Record;
import de.measite.minidns.record.Data;
import de.measite.minidns.record.TXT;
/**
* This Service contains all important long lasting operations for OpenKeychain. It receives Intents with
* data from the activities or other apps, queues these intents, executes them, and stops itself
* after doing them.
*/
public class KeychainIntentService extends IntentService implements Progressable {
/* extras that can be given by intent */
public static final String EXTRA_MESSENGER = "messenger";
public static final String EXTRA_DATA = "data";
/* possible actions */
public static final String ACTION_SIGN_ENCRYPT = Constants.INTENT_PREFIX + "SIGN_ENCRYPT";
public static final String ACTION_DECRYPT_VERIFY = Constants.INTENT_PREFIX + "DECRYPT_VERIFY";
public static final String ACTION_VERIFY_KEYBASE_PROOF = Constants.INTENT_PREFIX + "VERIFY_KEYBASE_PROOF";
public static final String ACTION_DECRYPT_METADATA = Constants.INTENT_PREFIX + "DECRYPT_METADATA";
public static final String ACTION_EDIT_KEYRING = Constants.INTENT_PREFIX + "EDIT_KEYRING";
public static final String ACTION_PROMOTE_KEYRING = Constants.INTENT_PREFIX + "PROMOTE_KEYRING";
public static final String ACTION_IMPORT_KEYRING = Constants.INTENT_PREFIX + "IMPORT_KEYRING";
public static final String ACTION_EXPORT_KEYRING = Constants.INTENT_PREFIX + "EXPORT_KEYRING";
public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING";
public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING";
public static final String ACTION_DELETE = Constants.INTENT_PREFIX + "DELETE";
public static final String ACTION_CONSOLIDATE = Constants.INTENT_PREFIX + "CONSOLIDATE";
public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL";
/* keys for data bundle */
// encrypt, decrypt, import export
public static final String TARGET = "target";
public static final String SOURCE = "source";
// possible targets:
public static enum IOType {
UNKNOWN,
BYTES,
URI;
private static final IOType[] values = values();
public static IOType fromInt(int n) {
if (n < 0 || n >= values.length) {
return UNKNOWN;
} else {
return values[n];
}
}
}
// encrypt
public static final String ENCRYPT_DECRYPT_INPUT_URI = "input_uri";
public static final String ENCRYPT_DECRYPT_OUTPUT_URI = "output_uri";
public static final String SIGN_ENCRYPT_PARCEL = "sign_encrypt_parcel";
// decrypt/verify
public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes";
// keybase proof
public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint";
public static final String KEYBASE_PROOF = "keybase_proof";
// save keyring
public static final String EDIT_KEYRING_PARCEL = "save_parcel";
public static final String EDIT_KEYRING_PASSPHRASE = "passphrase";
public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
// delete keyring(s)
public static final String DELETE_KEY_LIST = "delete_list";
public static final String DELETE_IS_SECRET = "delete_is_secret";
// import key
public static final String IMPORT_KEY_LIST = "import_key_list";
public static final String IMPORT_KEY_SERVER = "import_key_server";
// export key
public static final String EXPORT_FILENAME = "export_filename";
public static final String EXPORT_URI = "export_uri";
public static final String EXPORT_SECRET = "export_secret";
public static final String EXPORT_ALL = "export_all";
public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id";
// upload key
public static final String UPLOAD_KEY_SERVER = "upload_key_server";
// certify key
public static final String CERTIFY_PARCEL = "certify_parcel";
// promote key
public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id";
public static final String PROMOTE_CARD_AID = "promote_card_aid";
public static final String PROMOTE_SUBKEY_IDS = "promote_fingerprints";
// consolidate
public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery";
/*
* possible data keys as result send over messenger
*/
// decrypt/verify
public static final String RESULT_DECRYPTED_BYTES = "decrypted_data";
Messenger mMessenger;
// this attribute can possibly merged with the one above? not sure...
private AtomicBoolean mActionCanceled = new AtomicBoolean(false);
public KeychainIntentService() {
super("KeychainIntentService");
}
/**
* The IntentService calls this method from the default worker thread with the intent that
* started the service. When this method returns, IntentService stops the service, as
* appropriate.
*/
@Override
protected void onHandleIntent(Intent intent) {
// We have not been cancelled! (yet)
mActionCanceled.set(false);
Bundle extras = intent.getExtras();
if (extras == null) {
Log.e(Constants.TAG, "Extras bundle is null!");
return;
}
if (!(extras.containsKey(EXTRA_MESSENGER) || extras.containsKey(EXTRA_DATA) || (intent
.getAction() == null))) {
Log.e(Constants.TAG,
"Extra bundle must contain a messenger, a data bundle, and an action!");
return;
}
Uri dataUri = intent.getData();
mMessenger = (Messenger) extras.get(EXTRA_MESSENGER);
Bundle data = extras.getBundle(EXTRA_DATA);
if (data == null) {
Log.e(Constants.TAG, "data extra is null!");
return;
}
Log.logDebugBundle(data, "EXTRA_DATA");
ProviderHelper providerHelper = new ProviderHelper(this);
String action = intent.getAction();
// executeServiceMethod action from extra bundle
switch (action) {
case ACTION_CERTIFY_KEYRING: {
// Input
CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL);
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
String keyServerUri = data.getString(UPLOAD_KEY_SERVER);
// Operation
CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled);
CertifyResult result = op.certify(parcel, cryptoInput, keyServerUri);
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
break;
}
case ACTION_CONSOLIDATE: {
// Operation
ConsolidateResult result;
if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) {
result = new ProviderHelper(this).consolidateDatabaseStep2(this);
} else {
result = new ProviderHelper(this).consolidateDatabaseStep1(this);
}
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
break;
}
case ACTION_DECRYPT_METADATA: {
try {
/* Input */
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
InputData inputData = createDecryptInputData(data);
// verifyText and decrypt returning additional resultData values for the
// verification of signatures
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
this, new ProviderHelper(this), this, inputData, null
);
builder.setAllowSymmetricDecryption(true)
.setDecryptMetadataOnly(true);
DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput);
sendMessageToHandler(MessageStatus.OKAY, decryptVerifyResult);
} catch (Exception e) {
sendErrorToHandler(e);
}
break;
}
case ACTION_VERIFY_KEYBASE_PROOF: {
try {
Proof proof = new Proof(new JSONObject(data.getString(KEYBASE_PROOF)));
setProgress(R.string.keybase_message_fetching_data, 0, 100);
Prover prover = Prover.findProverFor(proof);
if (prover == null) {
sendProofError(getString(R.string.keybase_no_prover_found) + ": " + proof.getPrettyName());
return;
}
if (!prover.fetchProofData()) {
sendProofError(prover.getLog(), getString(R.string.keybase_problem_fetching_evidence));
return;
}
String requiredFingerprint = data.getString(KEYBASE_REQUIRED_FINGERPRINT);
if (!prover.checkFingerprint(requiredFingerprint)) {
sendProofError(getString(R.string.keybase_key_mismatch));
return;
}
String domain = prover.dnsTxtCheckRequired();
if (domain != null) {
DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT));
if (dnsQuery == null) {
sendProofError(prover.getLog(), getString(R.string.keybase_dns_query_failure));
return;
}
Record[] records = dnsQuery.getAnswers();
List<List<byte[]>> extents = new ArrayList<List<byte[]>>();
for (Record r : records) {
Data d = r.getPayload();
if (d instanceof TXT) {
extents.add(((TXT) d).getExtents());
}
}
if (!prover.checkDnsTxt(extents)) {
sendProofError(prover.getLog(), null);
return;
}
}
byte[] messageBytes = prover.getPgpMessage().getBytes();
if (prover.rawMessageCheckRequired()) {
InputStream messageByteStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(messageBytes));
if (!prover.checkRawMessageBytes(messageByteStream)) {
sendProofError(prover.getLog(), null);
return;
}
}
// kind of awkward, but this whole class wants to pull bytes out of data
data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal());
data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, messageBytes);
InputData inputData = createDecryptInputData(data);
OutputStream outStream = createCryptOutputStream(data);
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
this, new ProviderHelper(this), this,
inputData, outStream
);
builder.setSignedLiteralData(true).setRequiredSignerFingerprint(requiredFingerprint);
DecryptVerifyResult decryptVerifyResult = builder.build().execute(
new CryptoInputParcel());
outStream.close();
if (!decryptVerifyResult.success()) {
OperationLog log = decryptVerifyResult.getLog();
OperationResult.LogEntryParcel lastEntry = null;
for (OperationResult.LogEntryParcel entry : log) {
lastEntry = entry;
}
sendProofError(getString(lastEntry.mType.getMsgId()));
return;
}
if (!prover.validate(outStream.toString())) {
sendProofError(getString(R.string.keybase_message_payload_mismatch));
return;
}
Bundle resultData = new Bundle();
resultData.putString(ServiceProgressHandler.DATA_MESSAGE, "OK");
// these help the handler construct a useful human-readable message
resultData.putString(ServiceProgressHandler.KEYBASE_PROOF_URL, prover.getProofUrl());
resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_URL, prover.getPresenceUrl());
resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_LABEL, prover.getPresenceLabel());
sendMessageToHandler(MessageStatus.OKAY, resultData);
} catch (Exception e) {
sendErrorToHandler(e);
}
break;
}
case ACTION_DECRYPT_VERIFY: {
try {
/* Input */
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
InputData inputData = createDecryptInputData(data);
OutputStream outStream = createCryptOutputStream(data);
/* Operation */
Bundle resultData = new Bundle();
// verifyText and decrypt returning additional resultData values for the
// verification of signatures
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
this, new ProviderHelper(this), this,
inputData, outStream
);
builder.setAllowSymmetricDecryption(true);
DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput);
outStream.close();
resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult);
/* Output */
finalizeDecryptOutputStream(data, resultData, outStream);
Log.logDebugBundle(resultData, "resultData");
sendMessageToHandler(MessageStatus.OKAY, resultData);
} catch (IOException | PgpGeneralException e) {
// TODO get rid of this!
sendErrorToHandler(e);
}
break;
}
case ACTION_DELETE: {
// Input
long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST);
boolean isSecret = data.getBoolean(DELETE_IS_SECRET);
// Operation
DeleteOperation op = new DeleteOperation(this, new ProviderHelper(this), this);
DeleteResult result = op.execute(masterKeyIds, isSecret);
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
break;
}
case ACTION_EDIT_KEYRING: {
// Input
SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL);
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
// Operation
EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled);
OperationResult result = op.execute(saveParcel, cryptoInput);
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
break;
}
case ACTION_PROMOTE_KEYRING: {
// Input
long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID);
byte[] cardAid = data.getByteArray(PROMOTE_CARD_AID);
long[] subKeyIds = data.getLongArray(PROMOTE_SUBKEY_IDS);
// Operation
PromoteKeyOperation op = new PromoteKeyOperation(
this, providerHelper, this, mActionCanceled);
PromoteKeyResult result = op.execute(keyRingId, cardAid, subKeyIds);
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
break;
}
case ACTION_EXPORT_KEYRING: {
// Input
boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
String outputFile = data.getString(EXPORT_FILENAME);
Uri outputUri = data.getParcelable(EXPORT_URI);
boolean exportAll = data.getBoolean(EXPORT_ALL);
long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
// Operation
ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this);
ExportResult result;
if (outputFile != null) {
result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile);
} else {
result = importExportOperation.exportToUri(masterKeyIds, exportSecret, outputUri);
}
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
break;
}
case ACTION_IMPORT_KEYRING: {
// Input
String keyServer = data.getString(IMPORT_KEY_SERVER);
ArrayList<ParcelableKeyRing> list = data.getParcelableArrayList(IMPORT_KEY_LIST);
ParcelableFileCache<ParcelableKeyRing> cache =
new ParcelableFileCache<>(this, "key_import.pcl");
// Operation
ImportExportOperation importExportOperation = new ImportExportOperation(
this, providerHelper, this, mActionCanceled);
// Either list or cache must be null, no guarantees otherwise.
ImportKeyResult result = list != null
? importExportOperation.importKeyRings(list, keyServer)
: importExportOperation.importKeyRings(cache, keyServer);
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
break;
}
case ACTION_SIGN_ENCRYPT: {
// Input
SignEncryptParcel inputParcel = data.getParcelable(SIGN_ENCRYPT_PARCEL);
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
// Operation
SignEncryptOperation op = new SignEncryptOperation(
this, new ProviderHelper(this), this, mActionCanceled);
SignEncryptResult result = op.execute(inputParcel, cryptoInput);
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
break;
}
case ACTION_UPLOAD_KEYRING: {
try {
/* Input */
String keyServer = data.getString(UPLOAD_KEY_SERVER);
// and dataUri!
/* Operation */
HkpKeyserver server = new HkpKeyserver(keyServer);
CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this);
try {
importExportOperation.uploadKeyRingToServer(server, keyring);
} catch (Keyserver.AddKeyException e) {
throw new PgpGeneralException("Unable to export key to selected server");
}
sendMessageToHandler(MessageStatus.OKAY);
} catch (Exception e) {
sendErrorToHandler(e);
}
break;
}
}
}
private void sendProofError(List<String> log, String label) {
String msg = null;
label = (label == null) ? "" : label + ": ";
for (String m : log) {
Log.e(Constants.TAG, label + m);
msg = m;
}
sendProofError(label + msg);
}
private void sendProofError(String msg) {
Bundle bundle = new Bundle();
bundle.putString(ServiceProgressHandler.DATA_ERROR, msg);
sendMessageToHandler(MessageStatus.OKAY, bundle);
}
private void sendErrorToHandler(Exception e) {
// TODO: Implement a better exception handling here
// contextualize the exception, if necessary
String message;
if (e instanceof PgpGeneralMsgIdException) {
e = ((PgpGeneralMsgIdException) e).getContextualized(this);
message = e.getMessage();
} else {
message = e.getMessage();
}
Log.d(Constants.TAG, "KeychainIntentService Exception: ", e);
Bundle data = new Bundle();
data.putString(ServiceProgressHandler.DATA_ERROR, message);
sendMessageToHandler(MessageStatus.EXCEPTION, null, data);
}
private void sendMessageToHandler(MessageStatus status, Integer arg2, Bundle data) {
Message msg = Message.obtain();
assert msg != null;
msg.arg1 = status.ordinal();
if (arg2 != null) {
msg.arg2 = arg2;
}
if (data != null) {
msg.setData(data);
}
try {
mMessenger.send(msg);
} catch (RemoteException e) {
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
} catch (NullPointerException e) {
Log.w(Constants.TAG, "Messenger is null!", e);
}
}
private void sendMessageToHandler(MessageStatus status, OperationResult data) {
Bundle bundle = new Bundle();
bundle.putParcelable(OperationResult.EXTRA_RESULT, data);
sendMessageToHandler(status, null, bundle);
}
private void sendMessageToHandler(MessageStatus status, Bundle data) {
sendMessageToHandler(status, null, data);
}
private void sendMessageToHandler(MessageStatus status) {
sendMessageToHandler(status, null, null);
}
/**
* Set progress of ProgressDialog by sending message to handler on UI thread
*/
public void setProgress(String message, int progress, int max) {
Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
+ max);
Bundle data = new Bundle();
if (message != null) {
data.putString(ServiceProgressHandler.DATA_MESSAGE, message);
}
data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress);
data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max);
sendMessageToHandler(MessageStatus.UPDATE_PROGRESS, null, data);
}
public void setProgress(int resourceId, int progress, int max) {
setProgress(getString(resourceId), progress, max);
}
public void setProgress(int progress, int max) {
setProgress(null, progress, max);
}
@Override
public void setPreventCancel() {
sendMessageToHandler(MessageStatus.PREVENT_CANCEL);
}
private InputData createDecryptInputData(Bundle data) throws IOException, PgpGeneralException {
return createCryptInputData(data, DECRYPT_CIPHERTEXT_BYTES);
}
private InputData createCryptInputData(Bundle data, String bytesName) throws PgpGeneralException, IOException {
int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET);
IOType type = IOType.fromInt(source);
switch (type) {
case BYTES: /* encrypting bytes directly */
byte[] bytes = data.getByteArray(bytesName);
return new InputData(new ByteArrayInputStream(bytes), bytes.length);
case URI: /* encrypting content uri */
Uri providerUri = data.getParcelable(ENCRYPT_DECRYPT_INPUT_URI);
// InputStream
return new InputData(getContentResolver().openInputStream(providerUri), FileHelper.getFileSize(this, providerUri, 0));
default:
throw new PgpGeneralException("No target chosen!");
}
}
private OutputStream createCryptOutputStream(Bundle data) throws PgpGeneralException, FileNotFoundException {
int target = data.getInt(TARGET);
IOType type = IOType.fromInt(target);
switch (type) {
case BYTES:
return new ByteArrayOutputStream();
case URI:
Uri providerUri = data.getParcelable(ENCRYPT_DECRYPT_OUTPUT_URI);
return getContentResolver().openOutputStream(providerUri);
default:
throw new PgpGeneralException("No target chosen!");
}
}
private void finalizeDecryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream) {
finalizeCryptOutputStream(data, resultData, outStream, RESULT_DECRYPTED_BYTES);
}
private void finalizeCryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream, String bytesName) {
int target = data.getInt(TARGET);
IOType type = IOType.fromInt(target);
switch (type) {
case BYTES:
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
resultData.putByteArray(bytesName, output);
break;
case URI:
// nothing, output was written, just send okay and verification bundle
break;
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (ACTION_CANCEL.equals(intent.getAction())) {
mActionCanceled.set(true);
return START_NOT_STICKY;
}
return super.onStartCommand(intent, flags, startId);
}
}

View File

@ -0,0 +1,188 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service;
import java.util.concurrent.atomic.AtomicBoolean;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcelable;
import android.os.RemoteException;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.BaseOperation;
import org.sufficientlysecure.keychain.operations.CertifyOperation;
import org.sufficientlysecure.keychain.operations.EditKeyOperation;
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.Log;
/**
* This Service contains all important long lasting operations for OpenKeychain. It receives Intents with
* data from the activities or other apps, executes them, and stops itself after doing them.
*/
public class KeychainNewService extends Service implements Progressable {
// messenger for communication (hack)
public static final String EXTRA_MESSENGER = "messenger";
// extras for operation
public static final String EXTRA_OPERATION_INPUT = "op_input";
public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
// this attribute can possibly merged with the one above? not sure...
private AtomicBoolean mActionCanceled = new AtomicBoolean(false);
ThreadLocal<Messenger> mMessenger = new ThreadLocal<>();
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* This is run on the main thread, we need to spawn a runnable which runs on another thread for the actual operation
*/
@Override
public int onStartCommand(final Intent intent, int flags, int startId) {
Runnable actionRunnable = new Runnable() {
@Override
public void run() {
// We have not been cancelled! (yet)
mActionCanceled.set(false);
Bundle extras = intent.getExtras();
// Set messenger for communication (for this particular thread)
mMessenger.set(extras.<Messenger>getParcelable(EXTRA_MESSENGER));
// Input
Parcelable inputParcel = extras.getParcelable(EXTRA_OPERATION_INPUT);
CryptoInputParcel cryptoInput = extras.getParcelable(EXTRA_CRYPTO_INPUT);
// Operation
BaseOperation op;
// just for brevity
KeychainNewService outerThis = KeychainNewService.this;
if (inputParcel instanceof SignEncryptParcel) {
op = new SignEncryptOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof PgpDecryptVerifyInputParcel) {
op = new PgpDecryptVerify(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof SaveKeyringParcel) {
op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof CertifyAction) {
op = new CertifyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else {
return;
}
@SuppressWarnings("unchecked") // this is unchecked, we make sure it's the correct op above!
OperationResult result = op.execute(inputParcel, cryptoInput);
sendMessageToHandler(MessageStatus.OKAY, result);
}
};
Thread actionThread = new Thread(actionRunnable);
actionThread.start();
return START_NOT_STICKY;
}
private void sendMessageToHandler(MessageStatus status, Integer arg2, Bundle data) {
Message msg = Message.obtain();
assert msg != null;
msg.arg1 = status.ordinal();
if (arg2 != null) {
msg.arg2 = arg2;
}
if (data != null) {
msg.setData(data);
}
try {
mMessenger.get().send(msg);
} catch (RemoteException e) {
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
} catch (NullPointerException e) {
Log.w(Constants.TAG, "Messenger is null!", e);
}
}
private void sendMessageToHandler(MessageStatus status, OperationResult data) {
Bundle bundle = new Bundle();
bundle.putParcelable(OperationResult.EXTRA_RESULT, data);
sendMessageToHandler(status, null, bundle);
}
private void sendMessageToHandler(MessageStatus status) {
sendMessageToHandler(status, null, null);
}
/**
* Set progress of ProgressDialog by sending message to handler on UI thread
*/
@Override
public void setProgress(String message, int progress, int max) {
Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
+ max);
Bundle data = new Bundle();
if (message != null) {
data.putString(ServiceProgressHandler.DATA_MESSAGE, message);
}
data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress);
data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max);
sendMessageToHandler(MessageStatus.UPDATE_PROGRESS, null, data);
}
@Override
public void setProgress(int resourceId, int progress, int max) {
setProgress(getString(resourceId), progress, max);
}
@Override
public void setProgress(int progress, int max) {
setProgress(null, progress, max);
}
@Override
public void setPreventCancel() {
sendMessageToHandler(MessageStatus.PREVENT_CANCEL);
}
}

View File

@ -0,0 +1,777 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import com.textuality.keybase.lib.Proof;
import com.textuality.keybase.lib.prover.Prover;
import org.json.JSONObject;
import org.spongycastle.openpgp.PGPUtil;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.keyimport.Keyserver;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.CertifyOperation;
import org.sufficientlysecure.keychain.operations.DeleteOperation;
import org.sufficientlysecure.keychain.operations.EditKeyOperation;
import org.sufficientlysecure.keychain.operations.ImportExportOperation;
import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import de.measite.minidns.Client;
import de.measite.minidns.DNSMessage;
import de.measite.minidns.Question;
import de.measite.minidns.Record;
import de.measite.minidns.record.Data;
import de.measite.minidns.record.TXT;
/**
* This Service contains all important long lasting operations for OpenKeychain. It receives Intents with
* data from the activities or other apps, executes them, and stops itself after doing them.
*/
public class KeychainService extends Service implements Progressable {
/* extras that can be given by intent */
public static final String EXTRA_MESSENGER = "messenger";
public static final String EXTRA_DATA = "data";
/* possible actions */
public static final String ACTION_VERIFY_KEYBASE_PROOF = Constants.INTENT_PREFIX + "VERIFY_KEYBASE_PROOF";
public static final String ACTION_EDIT_KEYRING = Constants.INTENT_PREFIX + "EDIT_KEYRING";
public static final String ACTION_PROMOTE_KEYRING = Constants.INTENT_PREFIX + "PROMOTE_KEYRING";
public static final String ACTION_IMPORT_KEYRING = Constants.INTENT_PREFIX + "IMPORT_KEYRING";
public static final String ACTION_EXPORT_KEYRING = Constants.INTENT_PREFIX + "EXPORT_KEYRING";
public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING";
public static final String ACTION_DELETE = Constants.INTENT_PREFIX + "DELETE";
public static final String ACTION_CONSOLIDATE = Constants.INTENT_PREFIX + "CONSOLIDATE";
public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL";
/* keys for data bundle */
// keybase proof
public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint";
public static final String KEYBASE_PROOF = "keybase_proof";
// save keyring
public static final String EDIT_KEYRING_PARCEL = "save_parcel";
public static final String EDIT_KEYRING_PASSPHRASE = "passphrase";
public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
// delete keyring(s)
public static final String DELETE_KEY_LIST = "delete_list";
public static final String DELETE_IS_SECRET = "delete_is_secret";
// import key
public static final String IMPORT_KEY_LIST = "import_key_list";
public static final String IMPORT_KEY_SERVER = "import_key_server";
// export key
public static final String EXPORT_FILENAME = "export_filename";
public static final String EXPORT_URI = "export_uri";
public static final String EXPORT_SECRET = "export_secret";
public static final String EXPORT_ALL = "export_all";
public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id";
// upload key
public static final String UPLOAD_KEY_SERVER = "upload_key_server";
// promote key
public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id";
public static final String PROMOTE_CARD_AID = "promote_card_aid";
public static final String PROMOTE_SUBKEY_IDS = "promote_fingerprints";
// consolidate
public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery";
Messenger mMessenger;
// this attribute can possibly merged with the one above? not sure...
private AtomicBoolean mActionCanceled = new AtomicBoolean(false);
private KeyImportAccumulator mKeyImportAccumulator;
private KeychainService mKeychainService;
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* This is run on the main thread, we need to spawn a runnable which runs on another thread for the actual operation
*/
@Override
public int onStartCommand(final Intent intent, int flags, int startId) {
mKeychainService = this;
if (ACTION_CANCEL.equals(intent.getAction())) {
mActionCanceled.set(true);
return START_NOT_STICKY;
}
Runnable actionRunnable = new Runnable() {
@Override
public void run() {
// We have not been cancelled! (yet)
mActionCanceled.set(false);
Bundle extras = intent.getExtras();
if (extras == null) {
Log.e(Constants.TAG, "Extras bundle is null!");
return;
}
if (!(extras.containsKey(EXTRA_MESSENGER) || extras.containsKey(EXTRA_DATA) || (intent
.getAction() == null))) {
Log.e(Constants.TAG,
"Extra bundle must contain a messenger, a data bundle, and an action!");
return;
}
Uri dataUri = intent.getData();
mMessenger = (Messenger) extras.get(EXTRA_MESSENGER);
Bundle data = extras.getBundle(EXTRA_DATA);
if (data == null) {
Log.e(Constants.TAG, "data extra is null!");
return;
}
Log.logDebugBundle(data, "EXTRA_DATA");
ProviderHelper providerHelper = new ProviderHelper(mKeychainService);
String action = intent.getAction();
// executeServiceMethod action from extra bundle
switch (action) {
case ACTION_CONSOLIDATE: {
// Operation
ConsolidateResult result;
if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) {
result = providerHelper.consolidateDatabaseStep2(mKeychainService);
} else {
result = providerHelper.consolidateDatabaseStep1(mKeychainService);
}
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
break;
}
case ACTION_VERIFY_KEYBASE_PROOF: {
try {
Proof proof = new Proof(new JSONObject(data.getString(KEYBASE_PROOF)));
setProgress(R.string.keybase_message_fetching_data, 0, 100);
Prover prover = Prover.findProverFor(proof);
if (prover == null) {
sendProofError(getString(R.string.keybase_no_prover_found) + ": " + proof
.getPrettyName());
return;
}
if (!prover.fetchProofData()) {
sendProofError(prover.getLog(), getString(R.string.keybase_problem_fetching_evidence));
return;
}
String requiredFingerprint = data.getString(KEYBASE_REQUIRED_FINGERPRINT);
if (!prover.checkFingerprint(requiredFingerprint)) {
sendProofError(getString(R.string.keybase_key_mismatch));
return;
}
String domain = prover.dnsTxtCheckRequired();
if (domain != null) {
DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT));
if (dnsQuery == null) {
sendProofError(prover.getLog(), getString(R.string.keybase_dns_query_failure));
return;
}
Record[] records = dnsQuery.getAnswers();
List<List<byte[]>> extents = new ArrayList<List<byte[]>>();
for (Record r : records) {
Data d = r.getPayload();
if (d instanceof TXT) {
extents.add(((TXT) d).getExtents());
}
}
if (!prover.checkDnsTxt(extents)) {
sendProofError(prover.getLog(), null);
return;
}
}
byte[] messageBytes = prover.getPgpMessage().getBytes();
if (prover.rawMessageCheckRequired()) {
InputStream messageByteStream = PGPUtil.getDecoderStream(new ByteArrayInputStream
(messageBytes));
if (!prover.checkRawMessageBytes(messageByteStream)) {
sendProofError(prover.getLog(), null);
return;
}
}
PgpDecryptVerify op = new PgpDecryptVerify(mKeychainService, providerHelper,
mKeychainService);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(messageBytes)
.setSignedLiteralData(true)
.setRequiredSignerFingerprint(requiredFingerprint);
DecryptVerifyResult decryptVerifyResult = op.execute(input, new CryptoInputParcel());
if (!decryptVerifyResult.success()) {
OperationLog log = decryptVerifyResult.getLog();
OperationResult.LogEntryParcel lastEntry = null;
for (OperationResult.LogEntryParcel entry : log) {
lastEntry = entry;
}
sendProofError(getString(lastEntry.mType.getMsgId()));
return;
}
if (!prover.validate(new String(decryptVerifyResult.getOutputBytes()))) {
sendProofError(getString(R.string.keybase_message_payload_mismatch));
return;
}
Bundle resultData = new Bundle();
resultData.putString(ServiceProgressHandler.DATA_MESSAGE, "OK");
// these help the handler construct a useful human-readable message
resultData.putString(ServiceProgressHandler.KEYBASE_PROOF_URL, prover.getProofUrl());
resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_URL, prover.getPresenceUrl());
resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_LABEL, prover
.getPresenceLabel());
sendMessageToHandler(MessageStatus.OKAY, resultData);
} catch (Exception e) {
sendErrorToHandler(e);
}
break;
}
case ACTION_DELETE: {
// Input
long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST);
boolean isSecret = data.getBoolean(DELETE_IS_SECRET);
// Operation
DeleteOperation op = new DeleteOperation(mKeychainService, providerHelper, mKeychainService);
DeleteResult result = op.execute(masterKeyIds, isSecret);
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
break;
}
case ACTION_EDIT_KEYRING: {
// Input
SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL);
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
// Operation
EditKeyOperation op = new EditKeyOperation(mKeychainService, providerHelper,
mKeychainService, mActionCanceled);
OperationResult result = op.execute(saveParcel, cryptoInput);
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
break;
}
case ACTION_PROMOTE_KEYRING: {
// Input
long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID);
byte[] cardAid = data.getByteArray(PROMOTE_CARD_AID);
long[] subKeyIds = data.getLongArray(PROMOTE_SUBKEY_IDS);
// Operation
PromoteKeyOperation op = new PromoteKeyOperation(
mKeychainService, providerHelper, mKeychainService,
mActionCanceled);
PromoteKeyResult result = op.execute(keyRingId, cardAid, subKeyIds);
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
break;
}
case ACTION_EXPORT_KEYRING: {
// Input
boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
String outputFile = data.getString(EXPORT_FILENAME);
Uri outputUri = data.getParcelable(EXPORT_URI);
boolean exportAll = data.getBoolean(EXPORT_ALL);
long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
// Operation
ImportExportOperation importExportOperation = new ImportExportOperation(
mKeychainService, providerHelper, mKeychainService);
ExportResult result;
if (outputFile != null) {
result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile);
} else {
result = importExportOperation.exportToUri(masterKeyIds, exportSecret, outputUri);
}
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
break;
}
case ACTION_IMPORT_KEYRING: {
// Input
String keyServer = data.getString(IMPORT_KEY_SERVER);
ArrayList<ParcelableKeyRing> keyList = data.getParcelableArrayList(IMPORT_KEY_LIST);
// either keyList or cache must be null, no guarantees otherwise
if (keyList == null) {// import from file, do serially
serialKeyImport(null, keyServer, providerHelper);
} else {
// if there is more than one key with the same fingerprint, we do a serial import to prevent
// https://github.com/open-keychain/open-keychain/issues/1221
HashSet<String> keyFingerprintSet = new HashSet<>();
for (int i = 0; i < keyList.size(); i++) {
keyFingerprintSet.add(keyList.get(i).mExpectedFingerprint);
}
if (keyFingerprintSet.size() == keyList.size()) {
// all keys have unique fingerprints
multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer);
} else {
serialKeyImport(keyList, keyServer, providerHelper);
}
}
break;
}
case ACTION_UPLOAD_KEYRING: {
try {
// Input
String keyServer = data.getString(UPLOAD_KEY_SERVER);
// and dataUri!
// Operation
HkpKeyserver server = new HkpKeyserver(keyServer);
CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
ImportExportOperation importExportOperation = new ImportExportOperation(mKeychainService,
providerHelper, mKeychainService);
try {
importExportOperation.uploadKeyRingToServer(server, keyring);
} catch (Keyserver.AddKeyException e) {
throw new PgpGeneralException("Unable to export key to selected server");
}
sendMessageToHandler(MessageStatus.OKAY);
} catch (Exception e) {
sendErrorToHandler(e);
}
break;
}
}
if (!intent.getAction().equals(ACTION_IMPORT_KEYRING)) {
// import keyring handles stopping service on its own
stopSelf();
}
}
};
Thread actionThread = new Thread(actionRunnable);
actionThread.start();
return START_NOT_STICKY;
}
private void sendProofError(List<String> log, String label) {
String msg = null;
label = (label == null) ? "" : label + ": ";
for (String m : log) {
Log.e(Constants.TAG, label + m);
msg = m;
}
sendProofError(label + msg);
}
private void sendProofError(String msg) {
Bundle bundle = new Bundle();
bundle.putString(ServiceProgressHandler.DATA_ERROR, msg);
sendMessageToHandler(MessageStatus.OKAY, bundle);
}
private void sendErrorToHandler(Exception e) {
// TODO: Implement a better exception handling here
// contextualize the exception, if necessary
String message;
if (e instanceof PgpGeneralMsgIdException) {
e = ((PgpGeneralMsgIdException) e).getContextualized(mKeychainService);
message = e.getMessage();
} else {
message = e.getMessage();
}
Log.d(Constants.TAG, "KeychainService Exception: ", e);
Bundle data = new Bundle();
data.putString(ServiceProgressHandler.DATA_ERROR, message);
sendMessageToHandler(MessageStatus.EXCEPTION, null, data);
}
private void sendMessageToHandler(MessageStatus status, Integer arg2, Bundle data) {
Message msg = Message.obtain();
assert msg != null;
msg.arg1 = status.ordinal();
if (arg2 != null) {
msg.arg2 = arg2;
}
if (data != null) {
msg.setData(data);
}
try {
mMessenger.send(msg);
} catch (RemoteException e) {
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
} catch (NullPointerException e) {
Log.w(Constants.TAG, "Messenger is null!", e);
}
}
private void sendMessageToHandler(MessageStatus status, OperationResult data) {
Bundle bundle = new Bundle();
bundle.putParcelable(OperationResult.EXTRA_RESULT, data);
sendMessageToHandler(status, null, bundle);
}
private void sendMessageToHandler(MessageStatus status, Bundle data) {
sendMessageToHandler(status, null, data);
}
private void sendMessageToHandler(MessageStatus status) {
sendMessageToHandler(status, null, null);
}
/**
* Set progress of ProgressDialog by sending message to handler on UI thread
*/
@Override
public void setProgress(String message, int progress, int max) {
Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
+ max);
Bundle data = new Bundle();
if (message != null) {
data.putString(ServiceProgressHandler.DATA_MESSAGE, message);
}
data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress);
data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max);
sendMessageToHandler(MessageStatus.UPDATE_PROGRESS, null, data);
}
@Override
public void setProgress(int resourceId, int progress, int max) {
setProgress(getString(resourceId), progress, max);
}
@Override
public void setProgress(int progress, int max) {
setProgress(null, progress, max);
}
@Override
public void setPreventCancel() {
sendMessageToHandler(MessageStatus.PREVENT_CANCEL);
}
public void serialKeyImport(ArrayList<ParcelableKeyRing> keyList, final String keyServer,
ProviderHelper providerHelper) {
Log.d(Constants.TAG, "serial key import starting");
ParcelableFileCache<ParcelableKeyRing> cache =
new ParcelableFileCache<>(mKeychainService, "key_import.pcl");
// Operation
ImportExportOperation importExportOperation = new ImportExportOperation(
mKeychainService, providerHelper, mKeychainService,
mActionCanceled);
// Either list or cache must be null, no guarantees otherwise.
ImportKeyResult result = keyList != null
? importExportOperation.importKeyRings(keyList, keyServer)
: importExportOperation.importKeyRings(cache, keyServer);
ContactSyncAdapterService.requestSync();
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
stopSelf();
}
public void multiThreadedKeyImport(Iterator<ParcelableKeyRing> keyListIterator, int totKeys, final String
keyServer) {
Log.d(Constants.TAG, "Multi-threaded key import starting");
if (keyListIterator != null) {
mKeyImportAccumulator = new KeyImportAccumulator(totKeys, mKeychainService);
setProgress(0, totKeys);
final int maxThreads = 200;
ExecutorService importExecutor = new ThreadPoolExecutor(0, maxThreads,
30L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
while (keyListIterator.hasNext()) {
final ParcelableKeyRing pkRing = keyListIterator.next();
Runnable importOperationRunnable = new Runnable() {
@Override
public void run() {
ImportKeyResult result = null;
try {
ImportExportOperation importExportOperation = new ImportExportOperation(
mKeychainService,
new ProviderHelper(mKeychainService),
mKeyImportAccumulator.getImportProgressable(),
mActionCanceled);
ArrayList<ParcelableKeyRing> list = new ArrayList<>();
list.add(pkRing);
result = importExportOperation.importKeyRings(list,
keyServer);
} finally {
// in the off-chance that importKeyRings does something to crash the
// thread before it can call singleKeyRingImportCompleted, our imported
// key count will go wrong. This will cause the service to never die,
// and the progress dialog to stay displayed. The finally block was
// originally meant to ensure singleKeyRingImportCompleted was called,
// and checks for null were to be introduced, but in such a scenario,
// knowing an uncaught error exists in importKeyRings is more important.
// if a null gets passed, something wrong is happening. We want a crash.
mKeyImportAccumulator.singleKeyRingImportCompleted(result);
}
}
};
importExecutor.execute(importOperationRunnable);
}
}
}
/**
* Used to accumulate the results of individual key imports
*/
private class KeyImportAccumulator {
private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog();
private int mTotalKeys;
private int mImportedKeys = 0;
private Progressable mInternalProgressable;
ArrayList<Long> mImportedMasterKeyIds = new ArrayList<Long>();
private int mBadKeys = 0;
private int mNewKeys = 0;
private int mUpdatedKeys = 0;
private int mSecret = 0;
private int mResultType = 0;
/**
* meant to be used with a service due to stopSelf() in singleKeyRingImportCompleted. Remove this if
* generalising.
*
* @param totalKeys total number of keys to be imported
* @param externalProgressable the external progressable to be updated every time a key is imported
*/
public KeyImportAccumulator(int totalKeys, Progressable externalProgressable) {
mTotalKeys = totalKeys;
// ignore updates from ImportExportOperation for now
mInternalProgressable = new Progressable() {
@Override
public void setProgress(String message, int current, int total) {
}
@Override
public void setProgress(int resourceId, int current, int total) {
}
@Override
public void setProgress(int current, int total) {
}
@Override
public void setPreventCancel() {
}
};
}
private synchronized void singleKeyRingImportCompleted(ImportKeyResult result) {
// increase imported key count and accumulate log and bad, new etc. key counts from result
mKeyImportAccumulator.accumulateKeyImport(result);
setProgress(mKeyImportAccumulator.getImportedKeys(), mKeyImportAccumulator.getTotalKeys());
if (mKeyImportAccumulator.isImportFinished()) {
ContactSyncAdapterService.requestSync();
sendMessageToHandler(ServiceProgressHandler.MessageStatus.OKAY,
mKeyImportAccumulator.getConsolidatedImportKeyResult());
stopSelf();//we're done here
}
}
public Progressable getImportProgressable() {
return mInternalProgressable;
}
public int getTotalKeys() {
return mTotalKeys;
}
public int getImportedKeys() {
return mImportedKeys;
}
public synchronized void accumulateKeyImport(ImportKeyResult result) {
mImportedKeys++;
mImportLog.addAll(result.getLog().toList());//accumulates log
mBadKeys += result.mBadKeys;
mNewKeys += result.mNewKeys;
mUpdatedKeys += result.mUpdatedKeys;
mSecret += result.mSecret;
long[] masterKeyIds = result.getImportedMasterKeyIds();
for (long masterKeyId : masterKeyIds) {
mImportedMasterKeyIds.add(masterKeyId);
}
// if any key import has been cancelled, set result type to cancelled
// resultType is added to in getConsolidatedKayImport to account for remaining factors
mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED;
}
/**
* returns accumulated result of all imports so far
*/
public ImportKeyResult getConsolidatedImportKeyResult() {
// adding required information to mResultType
// special case,no keys requested for import
if (mBadKeys == 0 && mNewKeys == 0 && mUpdatedKeys == 0) {
mResultType = ImportKeyResult.RESULT_FAIL_NOTHING;
} else {
if (mNewKeys > 0) {
mResultType |= ImportKeyResult.RESULT_OK_NEWKEYS;
}
if (mUpdatedKeys > 0) {
mResultType |= ImportKeyResult.RESULT_OK_UPDATED;
}
if (mBadKeys > 0) {
mResultType |= ImportKeyResult.RESULT_WITH_ERRORS;
if (mNewKeys == 0 && mUpdatedKeys == 0) {
mResultType |= ImportKeyResult.RESULT_ERROR;
}
}
if (mImportLog.containsWarnings()) {
mResultType |= ImportKeyResult.RESULT_WARNINGS;
}
}
long masterKeyIds[] = new long[mImportedMasterKeyIds.size()];
for (int i = 0; i < masterKeyIds.length; i++) {
masterKeyIds[i] = mImportedMasterKeyIds.get(i);
}
return new ImportKeyResult(mResultType, mImportLog, mNewKeys, mUpdatedKeys, mBadKeys,
mSecret, masterKeyIds);
}
public boolean isImportFinished() {
return mTotalKeys == mImportedKeys;
}
}
}

View File

@ -36,6 +36,9 @@ import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.app.NotificationCompat;
import android.support.v4.util.LongSparseArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
@ -149,6 +152,14 @@ public class PassphraseCacheService extends Service {
context.startService(intent);
}
public static void clearCachedPassphrases(Context context) {
Log.d(Constants.TAG, "PassphraseCacheService.clearCachedPassphrase()");
Intent intent = new Intent(context, PassphraseCacheService.class);
intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
context.startService(intent);
}
/**
* Gets a cached passphrase from memory by sending an intent to the service. This method is
@ -411,9 +422,9 @@ public class PassphraseCacheService extends Service {
long referenceKeyId;
if (Preferences.getPreferences(mContext).getPassphraseCacheSubs()) {
referenceKeyId = intent.getLongExtra(EXTRA_KEY_ID, 0L);
} else {
referenceKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, 0L);
} else {
referenceKeyId = intent.getLongExtra(EXTRA_KEY_ID, 0L);
}
// Stop specific ttl alarm and
am.cancel(buildIntent(this, referenceKeyId));
@ -466,11 +477,26 @@ public class PassphraseCacheService extends Service {
}
}
// from de.azapps.mirakel.helper.Helpers from https://github.com/MirakelX/mirakel-android
private static Bitmap getBitmap(int resId, Context ctx) {
final int mLargeIconWidth = (int) ctx.getResources().getDimension(
android.R.dimen.notification_large_icon_width);
final int mLargeIconHeight = (int) ctx.getResources().getDimension(
android.R.dimen.notification_large_icon_height);
final Drawable d = ctx.getResources().getDrawable(resId);
final Bitmap b = Bitmap.createBitmap(mLargeIconWidth, mLargeIconHeight, Bitmap.Config.ARGB_8888);
final Canvas c = new Canvas(b);
d.setBounds(0, 0, mLargeIconWidth, mLargeIconHeight);
d.draw(c);
return b;
}
private Notification getNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
builder.setSmallIcon(R.drawable.ic_launcher)
builder.setSmallIcon(R.drawable.ic_stat_notify)
.setLargeIcon(getBitmap(R.drawable.ic_launcher, getBaseContext()))
.setContentTitle(getString(R.string.app_name))
.setContentText(String.format(getString(R.string.passp_cache_notif_n_keys),
mPassphraseCache.size()));
@ -502,7 +528,8 @@ public class PassphraseCacheService extends Service {
);
} else {
// Fallback, since expandable notifications weren't available back then
builder.setSmallIcon(R.drawable.ic_launcher)
builder.setSmallIcon(R.drawable.ic_stat_notify)
.setLargeIcon(getBitmap(R.drawable.ic_launcher, getBaseContext()))
.setContentTitle(String.format(getString(R.string.passp_cache_notif_n_keys),
mPassphraseCache.size()))
.setContentText(getString(R.string.passp_cache_notif_click_to_clear));

View File

@ -95,7 +95,8 @@ public class SaveKeyringParcel implements Parcelable {
}
for (SubkeyChange change : mChangeSubKeys) {
if (change.mRecertify || change.mFlags != null || change.mExpiry != null) {
if (change.mRecertify || change.mFlags != null || change.mExpiry != null
|| change.mMoveKeyToCard) {
return false;
}
}
@ -142,6 +143,8 @@ public class SaveKeyringParcel implements Parcelable {
public boolean mRecertify;
// if this flag is true, the subkey should be changed to a stripped key
public boolean mDummyStrip;
// if this flag is true, the subkey should be moved to a card
public boolean mMoveKeyToCard;
// if this is non-null, the subkey will be changed to a divert-to-card
// key for the given serial number
public byte[] mDummyDivert;
@ -161,16 +164,16 @@ public class SaveKeyringParcel implements Parcelable {
mExpiry = expiry;
}
public SubkeyChange(long keyId, boolean dummyStrip, byte[] dummyDivert) {
public SubkeyChange(long keyId, boolean dummyStrip, boolean moveKeyToCard) {
this(keyId, null, null);
// these flags are mutually exclusive!
if (dummyStrip && dummyDivert != null) {
if (dummyStrip && moveKeyToCard) {
throw new AssertionError(
"cannot set strip and divert flags at the same time - this is a bug!");
"cannot set strip and keytocard flags at the same time - this is a bug!");
}
mDummyStrip = dummyStrip;
mDummyDivert = dummyDivert;
mMoveKeyToCard = moveKeyToCard;
}
@Override
@ -179,6 +182,7 @@ public class SaveKeyringParcel implements Parcelable {
out += "mFlags: " + mFlags + ", ";
out += "mExpiry: " + mExpiry + ", ";
out += "mDummyStrip: " + mDummyStrip + ", ";
out += "mMoveKeyToCard: " + mMoveKeyToCard + ", ";
out += "mDummyDivert: [" + (mDummyDivert == null ? 0 : mDummyDivert.length) + " bytes]";
return out;

View File

@ -17,8 +17,7 @@
package org.sufficientlysecure.keychain.service;
import android.app.Activity;
import android.content.Intent;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@ -27,7 +26,6 @@ import android.support.v4.app.FragmentManager;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
@ -35,7 +33,7 @@ import org.sufficientlysecure.keychain.util.Log;
public class ServiceProgressHandler extends Handler {
// possible messages sent from this service to handler on ui
public static enum MessageStatus{
public enum MessageStatus {
UNKNOWN,
OKAY,
EXCEPTION,
@ -44,9 +42,8 @@ public class ServiceProgressHandler extends Handler {
private static final MessageStatus[] values = values();
public static MessageStatus fromInt(int n)
{
if(n < 0 || n >= values.length) {
public static MessageStatus fromInt(int n) {
if (n < 0 || n >= values.length) {
return UNKNOWN;
} else {
return values[n];
@ -66,74 +63,58 @@ public class ServiceProgressHandler extends Handler {
public static final String KEYBASE_PRESENCE_URL = "keybase_presence_url";
public static final String KEYBASE_PRESENCE_LABEL = "keybase_presence_label";
Activity mActivity;
ProgressDialogFragment mProgressDialogFragment;
FragmentActivity mActivity;
public ServiceProgressHandler(Activity activity) {
this.mActivity = activity;
public ServiceProgressHandler(FragmentActivity activity) {
mActivity = activity;
}
public ServiceProgressHandler(Activity activity,
ProgressDialogFragment progressDialogFragment) {
this.mActivity = activity;
this.mProgressDialogFragment = progressDialogFragment;
public void showProgressDialog() {
showProgressDialog("", ProgressDialog.STYLE_SPINNER, false);
}
public ServiceProgressHandler(Activity activity,
String progressDialogMessage,
int progressDialogStyle,
ProgressDialogFragment.ServiceType serviceType) {
this(activity, progressDialogMessage, progressDialogStyle, false, serviceType);
}
public void showProgressDialog(
String progressDialogMessage, int progressDialogStyle, boolean cancelable) {
public ServiceProgressHandler(Activity activity,
String progressDialogMessage,
int progressDialogStyle,
boolean cancelable,
ProgressDialogFragment.ServiceType serviceType) {
this.mActivity = activity;
this.mProgressDialogFragment = ProgressDialogFragment.newInstance(
final ProgressDialogFragment frag = ProgressDialogFragment.newInstance(
progressDialogMessage,
progressDialogStyle,
cancelable,
serviceType);
}
public void showProgressDialog(FragmentActivity activity) {
if (mProgressDialogFragment == null) {
return;
}
cancelable);
// TODO: This is a hack!, see
// http://stackoverflow.com/questions/10114324/show-dialogfragment-from-onactivityresult
final FragmentManager manager = activity.getSupportFragmentManager();
final FragmentManager manager = mActivity.getSupportFragmentManager();
Handler handler = new Handler();
handler.post(new Runnable() {
public void run() {
mProgressDialogFragment.show(manager, "progressDialog");
frag.show(manager, "progressDialog");
}
});
}
@Override
public void handleMessage(Message message) {
Bundle data = message.getData();
if (mProgressDialogFragment == null) {
// Log.e(Constants.TAG,
// "Progress has not been updated because mProgressDialogFragment was null!");
ProgressDialogFragment progressDialogFragment =
(ProgressDialogFragment) mActivity.getSupportFragmentManager()
.findFragmentByTag("progressDialog");
if (progressDialogFragment == null) {
Log.e(Constants.TAG, "Progress has not been updated because mProgressDialogFragment was null!");
return;
}
MessageStatus status = MessageStatus.fromInt(message.arg1);
switch (status) {
case OKAY:
mProgressDialogFragment.dismissAllowingStateLoss();
progressDialogFragment.dismissAllowingStateLoss();
break;
case EXCEPTION:
mProgressDialogFragment.dismissAllowingStateLoss();
progressDialogFragment.dismissAllowingStateLoss();
// show error from service
if (data.containsKey(DATA_ERROR)) {
@ -149,13 +130,13 @@ public class ServiceProgressHandler extends Handler {
// update progress from service
if (data.containsKey(DATA_MESSAGE)) {
mProgressDialogFragment.setProgress(data.getString(DATA_MESSAGE),
progressDialogFragment.setProgress(data.getString(DATA_MESSAGE),
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
} else if (data.containsKey(DATA_MESSAGE_ID)) {
mProgressDialogFragment.setProgress(data.getInt(DATA_MESSAGE_ID),
progressDialogFragment.setProgress(data.getInt(DATA_MESSAGE_ID),
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
} else {
mProgressDialogFragment.setProgress(data.getInt(DATA_PROGRESS),
progressDialogFragment.setProgress(data.getInt(DATA_PROGRESS),
data.getInt(DATA_PROGRESS_MAX));
}
}
@ -163,7 +144,7 @@ public class ServiceProgressHandler extends Handler {
break;
case PREVENT_CANCEL:
mProgressDialogFragment.setPreventCancel(true);
progressDialogFragment.setPreventCancel(true);
break;
default:

View File

@ -1,5 +1,6 @@
package org.sufficientlysecure.keychain.service.input;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@ -11,7 +12,7 @@ import android.os.Parcelable;
public class RequiredInputParcel implements Parcelable {
public enum RequiredInputType {
PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT
PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_KEYTOCARD
}
public Date mSignatureTime;
@ -211,4 +212,46 @@ public class RequiredInputParcel implements Parcelable {
}
public static class NfcKeyToCardOperationsBuilder {
ArrayList<byte[]> mSubkeysToExport = new ArrayList<>();
Long mMasterKeyId;
public NfcKeyToCardOperationsBuilder(Long masterKeyId) {
mMasterKeyId = masterKeyId;
}
public RequiredInputParcel build() {
byte[][] inputHashes = new byte[mSubkeysToExport.size()][];
mSubkeysToExport.toArray(inputHashes);
ByteBuffer buf = ByteBuffer.wrap(mSubkeysToExport.get(0));
// We need to pass in a subkey here...
return new RequiredInputParcel(RequiredInputType.NFC_KEYTOCARD,
inputHashes, null, null, mMasterKeyId, buf.getLong());
}
public void addSubkey(long subkeyId) {
byte[] subKeyId = new byte[8];
ByteBuffer buf = ByteBuffer.wrap(subKeyId);
buf.putLong(subkeyId).rewind();
mSubkeysToExport.add(subKeyId);
}
public void addAll(RequiredInputParcel input) {
if (!mMasterKeyId.equals(input.mMasterKeyId)) {
throw new AssertionError("Master keys must match, this is a programming error!");
}
if (input.mType != RequiredInputType.NFC_KEYTOCARD) {
throw new AssertionError("Operation types must match, this is a programming error!");
}
Collections.addAll(mSubkeysToExport, input.mInputHashes);
}
public boolean isEmpty() {
return mSubkeysToExport.isEmpty();
}
}
}

View File

@ -52,12 +52,11 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
import org.sufficientlysecure.keychain.util.Log;
@ -65,10 +64,12 @@ import org.sufficientlysecure.keychain.util.Preferences;
import java.util.ArrayList;
public class CertifyKeyFragment extends CryptoOperationFragment
public class CertifyKeyFragment
extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult>
implements LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_CHECK_STATES = "check_states";
private CheckBox mUploadKeyCheckbox;
ListView mUserIds;
@ -89,7 +90,6 @@ public class CertifyKeyFragment extends CryptoOperationFragment
private static final int INDEX_IS_REVOKED = 4;
private MultiUserIdsAdapter mUserIdsAdapter;
private Messenger mPassthroughMessenger;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
@ -102,24 +102,30 @@ public class CertifyKeyFragment extends CryptoOperationFragment
return;
}
mPassthroughMessenger = getActivity().getIntent().getParcelableExtra(
KeychainIntentService.EXTRA_MESSENGER);
mPassthroughMessenger = null; // TODO remove, development hack
ArrayList<Boolean> checkedStates;
if (savedInstanceState != null) {
checkedStates = (ArrayList<Boolean>) savedInstanceState.getSerializable(ARG_CHECK_STATES);
// key spinner and the checkbox keep their own state
} else {
checkedStates = null;
// preselect certify key id if given
long certifyKeyId = getActivity().getIntent().getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none);
if (certifyKeyId != Constants.key.none) {
try {
CachedPublicKeyRing key = (new ProviderHelper(getActivity())).getCachedPublicKeyRing(certifyKeyId);
if (key.canCertify()) {
mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId);
// preselect certify key id if given
long certifyKeyId = getActivity().getIntent()
.getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none);
if (certifyKeyId != Constants.key.none) {
try {
CachedPublicKeyRing key = (new ProviderHelper(getActivity())).getCachedPublicKeyRing(certifyKeyId);
if (key.canCertify()) {
mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId);
}
} catch (PgpKeyNotFoundException e) {
Log.e(Constants.TAG, "certify certify check failed", e);
}
} catch (PgpKeyNotFoundException e) {
Log.e(Constants.TAG, "certify certify check failed", e);
}
}
mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0);
mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates);
mUserIds.setAdapter(mUserIdsAdapter);
mUserIds.setDividerHeight(0);
@ -132,6 +138,15 @@ public class CertifyKeyFragment extends CryptoOperationFragment
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
ArrayList<Boolean> states = mUserIdsAdapter.getCheckStates();
// no proper parceling method available :(
outState.putSerializable(ARG_CHECK_STATES, states);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.certify_key_fragment, null);
@ -156,7 +171,7 @@ public class CertifyKeyFragment extends CryptoOperationFragment
Notify.create(getActivity(), getString(R.string.select_key_to_certify),
Notify.Style.ERROR).show();
} else {
cryptoOperation(new CryptoInputParcel());
cryptoOperation();
}
}
});
@ -287,85 +302,39 @@ public class CertifyKeyFragment extends CryptoOperationFragment
}
@Override
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
protected CertifyActionsParcel createOperationInput() {
// Bail out if there is not at least one user id selected
ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions();
if (certifyActions.isEmpty()) {
Notify.create(getActivity(), "No identities selected!",
Notify.Style.ERROR).show();
return;
return null;
}
Bundle data = new Bundle();
{
long selectedKeyId = mCertifyKeySpinner.getSelectedKeyId();
long selectedKeyId = mCertifyKeySpinner.getSelectedKeyId();
// fill values for this action
CertifyActionsParcel parcel = new CertifyActionsParcel(selectedKeyId);
parcel.mCertifyActions.addAll(certifyActions);
// fill values for this action
CertifyActionsParcel actionsParcel = new CertifyActionsParcel(selectedKeyId);
actionsParcel.mCertifyActions.addAll(certifyActions);
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel);
if (mUploadKeyCheckbox.isChecked()) {
String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver);
}
}
// cached for next cryptoOperation loop
cacheActionsParcel(actionsParcel);
// Send all information needed to service to sign key in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_CERTIFY_KEYRING);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
return actionsParcel;
}
if (mPassthroughMessenger != null) {
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, mPassthroughMessenger);
} else {
// Message is received after signing is done in KeychainIntentService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
getActivity(),
getString(R.string.progress_certifying),
ProgressDialog.STYLE_SPINNER,
true,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
public void handleMessage(Message message) {
// handle messages by KeychainIntentCryptoServiceHandler first
super.handleMessage(message);
// handle pending messages
if (handlePendingMessage(message)) {
return;
}
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
Bundle data = message.getData();
CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT);
Intent intent = new Intent();
intent.putExtra(CertifyResult.EXTRA_RESULT, result);
getActivity().setResult(Activity.RESULT_OK, intent);
getActivity().finish();
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
// show progress dialog
saveHandler.showProgressDialog(getActivity());
}
// start service with intent
getActivity().startService(intent);
if (mPassthroughMessenger != null) {
getActivity().setResult(Activity.RESULT_OK);
getActivity().finish();
}
@Override
protected void onCryptoOperationSuccess(CertifyResult result) {
Intent intent = new Intent();
intent.putExtra(CertifyResult.EXTRA_RESULT, result);
getActivity().setResult(Activity.RESULT_OK, intent);
getActivity().finish();
}
@Override
protected void onCryptoOperationCancelled() {
super.onCryptoOperationCancelled();
}
}

View File

@ -25,9 +25,8 @@ import android.os.Messenger;
import android.support.v4.app.FragmentActivity;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
/**
* We can not directly create a dialog on the application context.
@ -49,12 +48,9 @@ public class ConsolidateDialogActivity extends FragmentActivity {
}
private void consolidateRecovery(boolean recovery) {
// Message is received after importing is done in KeychainIntentService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
this,
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
// Message is received after importing is done in KeychainService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(this) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
@ -68,7 +64,7 @@ public class ConsolidateDialogActivity extends FragmentActivity {
return;
}
final ConsolidateResult result =
returnData.getParcelable(KeychainIntentService.RESULT_CONSOLIDATE);
returnData.getParcelable(KeychainService.RESULT_CONSOLIDATE);
if (result == null) {
return;
}
@ -81,20 +77,23 @@ public class ConsolidateDialogActivity extends FragmentActivity {
};
// Send all information needed to service to import key in other thread
Intent intent = new Intent(this, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_CONSOLIDATE);
Intent intent = new Intent(this, KeychainService.class);
intent.setAction(KeychainService.ACTION_CONSOLIDATE);
// fill values for this action
Bundle data = new Bundle();
data.putBoolean(KeychainIntentService.CONSOLIDATE_RECOVERY, recovery);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
data.putBoolean(KeychainService.CONSOLIDATE_RECOVERY, recovery);
intent.putExtra(KeychainService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
// show progress dialog
saveHandler.showProgressDialog(this);
saveHandler.showProgressDialog(
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL, false
);
// start service with intent
startService(intent);

View File

@ -17,6 +17,8 @@
package org.sufficientlysecure.keychain.ui;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@ -41,6 +43,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
public static final String EXTRA_FIRST_TIME = "first_time";
public static final String EXTRA_ADDITIONAL_EMAILS = "additional_emails";
public static final String EXTRA_PASSPHRASE = "passphrase";
public static final String EXTRA_USE_SMART_CARD_SETTINGS = "use_smart_card_settings";
public static final String EXTRA_NFC_USER_ID = "nfc_user_id";
public static final String EXTRA_NFC_AID = "nfc_aid";
@ -53,6 +56,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
ArrayList<String> mAdditionalEmails;
Passphrase mPassphrase;
boolean mFirstTime;
boolean mUseSmartCardSettings;
Fragment mCurrentFragment;
@ -68,6 +72,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
mAdditionalEmails = savedInstanceState.getStringArrayList(EXTRA_ADDITIONAL_EMAILS);
mPassphrase = savedInstanceState.getParcelable(EXTRA_PASSPHRASE);
mFirstTime = savedInstanceState.getBoolean(EXTRA_FIRST_TIME);
mUseSmartCardSettings = savedInstanceState.getBoolean(EXTRA_USE_SMART_CARD_SETTINGS);
mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
} else {
@ -77,6 +82,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
mName = intent.getStringExtra(EXTRA_NAME);
mEmail = intent.getStringExtra(EXTRA_EMAIL);
mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false);
mUseSmartCardSettings = intent.getBooleanExtra(EXTRA_USE_SMART_CARD_SETTINGS, false);
if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) {
byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS);
@ -116,23 +122,45 @@ public class CreateKeyActivity extends BaseNfcActivity {
byte[] nfcAid = nfcGetAid();
String userId = nfcGetUserId();
try {
long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints);
CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId);
ring.getMasterKeyId();
// If all fingerprint bytes are 0, the card contains no keys.
boolean cardContainsKeys = false;
for (byte b : scannedFingerprints) {
if (b != 0) {
cardContainsKeys = true;
break;
}
}
Intent intent = new Intent(this, ViewKeyActivity.class);
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, userId);
intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, scannedFingerprints);
startActivity(intent);
finish();
if (cardContainsKeys) {
try {
long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints);
CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId);
ring.getMasterKeyId();
} catch (PgpKeyNotFoundException e) {
Fragment frag = CreateKeyYubiKeyImportFragment.createInstance(
scannedFingerprints, nfcAid, userId);
loadFragment(frag, FragAction.TO_RIGHT);
Intent intent = new Intent(this, ViewKeyActivity.class);
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, userId);
intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, scannedFingerprints);
startActivity(intent);
finish();
} catch (PgpKeyNotFoundException e) {
Fragment frag = CreateKeyYubiKeyImportFragment.createInstance(
scannedFingerprints, nfcAid, userId);
loadFragment(frag, FragAction.TO_RIGHT);
}
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.first_time_blank_smartcard_title)
.setMessage(R.string.first_time_blank_smartcard_message)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int button) {
CreateKeyActivity.this.mUseSmartCardSettings = true;
}
})
.setNegativeButton(android.R.string.no, null).show();
}
}
@ -146,6 +174,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
outState.putStringArrayList(EXTRA_ADDITIONAL_EMAILS, mAdditionalEmails);
outState.putParcelable(EXTRA_PASSPHRASE, mPassphrase);
outState.putBoolean(EXTRA_FIRST_TIME, mFirstTime);
outState.putBoolean(EXTRA_USE_SMART_CARD_SETTINGS, mUseSmartCardSettings);
}
@Override

View File

@ -38,13 +38,12 @@ import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
@ -157,12 +156,22 @@ public class CreateKeyFinalFragment extends Fragment {
if (mSaveKeyringParcel == null) {
mSaveKeyringParcel = new SaveKeyringParcel();
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 4096, null, KeyFlags.CERTIFY_OTHER, 0L));
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 4096, null, KeyFlags.SIGN_DATA, 0L));
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
Algorithm.RSA, 4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L));
if (createKeyActivity.mUseSmartCardSettings) {
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
2048, null, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER, 0L));
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
2048, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L));
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
2048, null, KeyFlags.AUTHENTICATION, 0L));
mEditText.setText(R.string.create_key_custom);
} else {
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
4096, null, KeyFlags.CERTIFY_OTHER, 0L));
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
4096, null, KeyFlags.SIGN_DATA, 0L));
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L));
}
String userId = KeyRing.createUserId(
new KeyRing.UserId(createKeyActivity.mName, createKeyActivity.mEmail, null)
);
@ -185,14 +194,11 @@ public class CreateKeyFinalFragment extends Fragment {
private void createKey() {
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_EDIT_KEYRING);
Intent intent = new Intent(getActivity(), KeychainService.class);
intent.setAction(KeychainService.ACTION_EDIT_KEYRING);
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
getActivity(),
getString(R.string.progress_building_key),
ProgressDialog.STYLE_HORIZONTAL,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
@ -227,15 +233,16 @@ public class CreateKeyFinalFragment extends Fragment {
Bundle data = new Bundle();
// get selected key entries
data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, mSaveKeyringParcel);
data.putParcelable(KeychainService.EDIT_KEYRING_PARCEL, mSaveKeyringParcel);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
intent.putExtra(KeychainService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
saveHandler.showProgressDialog(getActivity());
saveHandler.showProgressDialog(getString(R.string.progress_building_key),
ProgressDialog.STYLE_HORIZONTAL, false);
getActivity().startService(intent);
}
@ -243,9 +250,9 @@ public class CreateKeyFinalFragment extends Fragment {
// TODO move into EditKeyOperation
private void uploadKey(final EditKeyResult saveKeyResult) {
// Send all information needed to service to upload key in other thread
final Intent intent = new Intent(getActivity(), KeychainIntentService.class);
final Intent intent = new Intent(getActivity(), KeychainService.class);
intent.setAction(KeychainIntentService.ACTION_UPLOAD_KEYRING);
intent.setAction(KeychainService.ACTION_UPLOAD_KEYRING);
// set data uri as path to keyring
Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(
@ -257,15 +264,12 @@ public class CreateKeyFinalFragment extends Fragment {
// upload to favorite keyserver
String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver);
data.putString(KeychainService.UPLOAD_KEY_SERVER, keyserver);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
intent.putExtra(KeychainService.EXTRA_DATA, data);
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
getActivity(),
getString(R.string.progress_uploading),
ProgressDialog.STYLE_HORIZONTAL,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
@ -287,13 +291,16 @@ public class CreateKeyFinalFragment extends Fragment {
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
// show progress dialog
saveHandler.showProgressDialog(getActivity());
saveHandler.showProgressDialog(
getString(R.string.progress_uploading),
ProgressDialog.STYLE_HORIZONTAL, false);
// start service with intent
getActivity().startService(intent);
}
}

View File

@ -40,11 +40,10 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Preferences;
@ -176,13 +175,9 @@ public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListe
public void importKey() {
// Message is received after decrypting is done in KeychainIntentService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
getActivity(),
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT
) {
// Message is received after decrypting is done in KeychainService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
@ -220,31 +215,34 @@ public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListe
};
// Send all information needed to service to decrypt in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
Intent intent = new Intent(getActivity(), KeychainService.class);
// fill values for this action
Bundle data = new Bundle();
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
intent.setAction(KeychainService.ACTION_IMPORT_KEYRING);
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
keyList.add(new ParcelableKeyRing(mNfcFingerprint, null, null));
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyList);
data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, keyList);
{
Preferences prefs = Preferences.getPreferences(getActivity());
Preferences.CloudSearchPrefs cloudPrefs =
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
data.putString(KeychainService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
}
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
intent.putExtra(KeychainService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
saveHandler.showProgressDialog(getActivity());
saveHandler.showProgressDialog(
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL, false
);
// start service with intent
getActivity().startService(intent);

View File

@ -21,7 +21,9 @@ import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.View;
import android.widget.Toast;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
@ -37,8 +39,6 @@ public class DecryptFilesActivity extends BaseActivity {
// intern
public static final String ACTION_DECRYPT_DATA_OPEN = Constants.INTENT_PREFIX + "DECRYPT_DATA_OPEN";
DecryptFilesFragment mFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -62,19 +62,12 @@ public class DecryptFilesActivity extends BaseActivity {
/**
* Handles all actions with this intent
*
* @param intent
*/
private void handleActions(Bundle savedInstanceState, Intent intent) {
String action = intent.getAction();
String type = intent.getType();
Uri uri = intent.getData();
Bundle mFileFragmentBundle = new Bundle();
/*
* Android's Action
*/
if (Intent.ACTION_SEND.equals(action) && type != null) {
// When sending to Keychain Decrypt via share menu
// Binary via content provider (could also be files)
@ -88,39 +81,27 @@ public class DecryptFilesActivity extends BaseActivity {
action = ACTION_DECRYPT_DATA;
}
/**
* Main Actions
*/
if (ACTION_DECRYPT_DATA.equals(action) && uri != null) {
mFileFragmentBundle.putParcelable(DecryptFilesFragment.ARG_URI, uri);
loadFragment(savedInstanceState, uri, false);
} else if (ACTION_DECRYPT_DATA_OPEN.equals(action)) {
loadFragment(savedInstanceState, null, true);
} else if (ACTION_DECRYPT_DATA.equals(action)) {
Log.e(Constants.TAG,
"Include an Uri with setInputData() in your Intent!");
}
}
private void loadFragment(Bundle savedInstanceState, Uri uri, boolean openDialog) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
// No need to initialize fragments if we are being restored
if (savedInstanceState != null) {
return;
}
// Create an instance of the fragment
mFragment = DecryptFilesFragment.newInstance(uri, openDialog);
// Definitely need a data uri with the decrypt_data intent
if (ACTION_DECRYPT_DATA.equals(action) && uri == null) {
Toast.makeText(this, "No data to decrypt!", Toast.LENGTH_LONG).show();
setResult(Activity.RESULT_CANCELED);
finish();
}
boolean showOpenDialog = ACTION_DECRYPT_DATA_OPEN.equals(action);
DecryptFilesFragment frag = DecryptFilesFragment.newInstance(uri, showOpenDialog);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
getSupportFragmentManager().beginTransaction()
.replace(R.id.decrypt_files_fragment_container, mFragment)
.replace(R.id.decrypt_files_fragment_container, frag)
.commitAllowingStateLoss();
// do it immediately!
getSupportFragmentManager().executePendingTransactions();
}
}

View File

@ -36,12 +36,11 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.Log;
@ -64,8 +63,6 @@ public class DecryptFilesFragment extends DecryptFragment {
private Uri mInputUri = null;
private Uri mOutputUri = null;
private String mCurrentCryptoOperation;
/**
* Creates new instance of this fragment
*/
@ -111,18 +108,26 @@ public class DecryptFilesFragment extends DecryptFragment {
return view;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(ARG_URI, mInputUri);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setInputUri(getArguments().<Uri>getParcelable(ARG_URI));
Bundle state = savedInstanceState != null ? savedInstanceState : getArguments();
setInputUri(state.<Uri>getParcelable(ARG_URI));
if (getArguments().getBoolean(ARG_OPEN_DIRECTLY, false)) {
// should only come from args
if (state.getBoolean(ARG_OPEN_DIRECTLY, false)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT);
} else {
FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*",
REQUEST_CODE_INPUT);
FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*", REQUEST_CODE_INPUT);
}
}
}
@ -144,7 +149,7 @@ public class DecryptFilesFragment extends DecryptFragment {
return;
}
startDecryptFilenames();
cryptoOperation();
}
private String removeEncryptedAppend(String name) {
@ -172,115 +177,6 @@ public class DecryptFilesFragment extends DecryptFragment {
}
}
private void startDecrypt() {
mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_VERIFY;
cryptoOperation(new CryptoInputParcel());
}
private void startDecryptFilenames() {
mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_METADATA;
cryptoOperation(new CryptoInputParcel());
}
@Override
@SuppressLint("HandlerLeak")
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
// Send all information needed to service to decrypt in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
// fill values for this action
Bundle data = new Bundle();
// use current operation, either decrypt metadata or decrypt payload
intent.setAction(mCurrentCryptoOperation);
// data
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri);
data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal());
data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_INPUT_URI, mInputUri);
data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal());
data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri);
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after decrypting is done in KeychainIntentService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
getActivity(),
getString(R.string.progress_decrypting),
ProgressDialog.STYLE_HORIZONTAL,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
// handle pending messages
if (handlePendingMessage(message)) {
return;
}
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
// get returned data bundle
Bundle returnData = message.getData();
DecryptVerifyResult pgpResult =
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
if (pgpResult.success()) {
switch (mCurrentCryptoOperation) {
case KeychainIntentService.ACTION_DECRYPT_METADATA: {
askForOutputFilename(pgpResult.getDecryptMetadata().getFilename());
break;
}
case KeychainIntentService.ACTION_DECRYPT_VERIFY: {
// display signature result in activity
loadVerifyResult(pgpResult);
if (mDeleteAfter.isChecked()) {
// Create and show dialog to delete original file
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri);
deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
setInputUri(null);
}
/*
// A future open after decryption feature
if () {
Intent viewFile = new Intent(Intent.ACTION_VIEW);
viewFile.setInputData(mOutputUri);
startActivity(viewFile);
}
*/
break;
}
default: {
Log.e(Constants.TAG, "Bug: not supported operation!");
break;
}
}
}
pgpResult.createNotify(getActivity()).show(DecryptFilesFragment.this);
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
// show progress dialog
saveHandler.showProgressDialog(getActivity());
// start service with intent
getActivity().startService(intent);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
@ -295,7 +191,7 @@ public class DecryptFilesFragment extends DecryptFragment {
// This happens after output file was selected, so start our operation
if (resultCode == Activity.RESULT_OK && data != null) {
mOutputUri = data.getData();
startDecrypt();
cryptoOperation();
}
return;
}
@ -310,4 +206,20 @@ public class DecryptFilesFragment extends DecryptFragment {
protected void onVerifyLoaded(boolean hideErrorOverlay) {
}
@Override
protected PgpDecryptVerifyInputParcel createOperationInput() {
return new PgpDecryptVerifyInputParcel(mInputUri, mOutputUri).setAllowSymmetricDecryption(true);
}
@Override
protected void onCryptoOperationSuccess(DecryptVerifyResult result) {
// display signature result in activity
loadVerifyResult(result);
// TODO delete after decrypt not implemented!
}
}

View File

@ -43,12 +43,14 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
@ -56,8 +58,9 @@ import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.Preferences;
public abstract class DecryptFragment extends CryptoOperationFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public abstract class DecryptFragment
extends CachingCryptoOperationFragment<PgpDecryptVerifyInputParcel, DecryptVerifyResult>
implements LoaderManager.LoaderCallbacks<Cursor> {
public static final int LOADER_ID_UNIFIED = 0;
public static final String ARG_DECRYPT_VERIFY_RESULT = "decrypt_verify_result";
@ -130,8 +133,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
private void lookupUnknownKey(long unknownKeyId) {
// Message is received after importing is done in KeychainIntentService
// Message is received after importing is done in KeychainService
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(getActivity()) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
@ -162,7 +166,7 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
Preferences prefs = Preferences.getPreferences(getActivity());
Preferences.CloudSearchPrefs cloudPrefs =
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
data.putString(KeychainService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
}
{
@ -171,17 +175,17 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
selectedEntries.add(keyEntry);
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, selectedEntries);
data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, selectedEntries);
}
// Send all information needed to service to query keys in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
Intent intent = new Intent(getActivity(), KeychainService.class);
intent.setAction(KeychainService.ACTION_IMPORT_KEYRING);
intent.putExtra(KeychainService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(serviceHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
getActivity().startService(intent);
}

View File

@ -17,11 +17,8 @@
package org.sufficientlysecure.keychain.ui;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -34,11 +31,7 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.ShareHelper;
@ -116,7 +109,7 @@ public class DecryptTextFragment extends DecryptFragment {
mShowMenuOptions = args.getBoolean(ARG_SHOW_MENU, false);
if (savedInstanceState == null) {
cryptoOperation(new CryptoInputParcel());
cryptoOperation();
}
}
@ -159,80 +152,10 @@ public class DecryptTextFragment extends DecryptFragment {
}
@Override
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
// Send all information needed to service to decrypt in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
// fill values for this action
Bundle data = new Bundle();
intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
// data
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal());
data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, mCiphertext.getBytes());
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after encrypting is done in KeychainIntentService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
getActivity(),
getString(R.string.progress_decrypting),
ProgressDialog.STYLE_HORIZONTAL,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
// handle pending messages
if (handlePendingMessage(message)) {
return;
}
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
// get returned data bundle
Bundle returnData = message.getData();
DecryptVerifyResult pgpResult =
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
if (pgpResult.success()) {
byte[] decryptedMessage = returnData
.getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES);
String displayMessage;
if (pgpResult.getCharset() != null) {
try {
displayMessage = new String(decryptedMessage, pgpResult.getCharset());
} catch (UnsupportedEncodingException e) {
// if we can't decode properly, just fall back to utf-8
displayMessage = new String(decryptedMessage);
}
} else {
displayMessage = new String(decryptedMessage);
}
mText.setText(displayMessage);
// display signature result in activity
loadVerifyResult(pgpResult);
} else {
// TODO: show also invalid layout with different text?
}
pgpResult.createNotify(getActivity()).show(DecryptTextFragment.this);
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
// show progress dialog
saveHandler.showProgressDialog(getActivity());
// start service with intent
getActivity().startService(intent);
protected PgpDecryptVerifyInputParcel createOperationInput() {
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mCiphertext.getBytes());
input.setAllowSymmetricDecryption(true);
return input;
}
@Override
@ -240,4 +163,27 @@ public class DecryptTextFragment extends DecryptFragment {
mShowMenuOptions = hideErrorOverlay;
getActivity().supportInvalidateOptionsMenu();
}
@Override
protected void onCryptoOperationSuccess(DecryptVerifyResult result) {
byte[] decryptedMessage = result.getOutputBytes();
String displayMessage;
if (result.getCharset() != null) {
try {
displayMessage = new String(decryptedMessage, result.getCharset());
} catch (UnsupportedEncodingException e) {
// if we can't decode properly, just fall back to utf-8
displayMessage = new String(decryptedMessage);
}
} else {
displayMessage = new String(decryptedMessage);
}
mText.setText(displayMessage);
// display signature result in activity
loadVerifyResult(result);
}
}

View File

@ -26,6 +26,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcelable;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
@ -39,9 +40,13 @@ import android.widget.ListView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
@ -49,7 +54,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
@ -65,9 +70,8 @@ import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
public class EditKeyFragment extends CryptoOperationFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel, OperationResult>
implements LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
public static final String ARG_SAVE_KEYRING_PARCEL = "save_keyring_parcel";
@ -415,15 +419,65 @@ public class EditKeyFragment extends CryptoOperationFragment implements
mSaveKeyringParcel.mRevokeSubKeys.add(keyId);
}
break;
case EditSubkeyDialogFragment.MESSAGE_STRIP:
case EditSubkeyDialogFragment.MESSAGE_STRIP: {
SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position);
if (secretKeyType == SecretKeyType.GNU_DUMMY) {
// Key is already stripped; this is a no-op.
break;
}
SubkeyChange change = mSaveKeyringParcel.getSubkeyChange(keyId);
if (change == null) {
mSaveKeyringParcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, null));
mSaveKeyringParcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, false));
break;
}
// toggle
change.mDummyStrip = !change.mDummyStrip;
if (change.mDummyStrip && change.mMoveKeyToCard) {
// User had chosen to divert key, but now wants to strip it instead.
change.mMoveKeyToCard = false;
}
break;
}
case EditSubkeyDialogFragment.MESSAGE_KEYTOCARD: {
Activity activity = EditKeyFragment.this.getActivity();
SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position);
if (secretKeyType == SecretKeyType.DIVERT_TO_CARD ||
secretKeyType == SecretKeyType.GNU_DUMMY) {
Notify.create(activity, R.string.edit_key_error_bad_nfc_stripped, Notify.Style.ERROR)
.show((ViewGroup) activity.findViewById(R.id.import_snackbar));
break;
}
int algorithm = mSubkeysAdapter.getAlgorithm(position);
// these are the PGP constants for RSA_GENERAL, RSA_ENCRYPT and RSA_SIGN
if (algorithm != 1 && algorithm != 2 && algorithm != 3) {
Notify.create(activity, R.string.edit_key_error_bad_nfc_algo, Notify.Style.ERROR)
.show((ViewGroup) activity.findViewById(R.id.import_snackbar));
break;
}
if (mSubkeysAdapter.getKeySize(position) != 2048) {
Notify.create(activity, R.string.edit_key_error_bad_nfc_size, Notify.Style.ERROR)
.show((ViewGroup) activity.findViewById(R.id.import_snackbar));
break;
}
SubkeyChange change;
change = mSaveKeyringParcel.getSubkeyChange(keyId);
if (change == null) {
mSaveKeyringParcel.mChangeSubKeys.add(
new SubkeyChange(keyId, false, true)
);
break;
}
// toggle
change.mMoveKeyToCard = !change.mMoveKeyToCard;
if (change.mMoveKeyToCard && change.mDummyStrip) {
// User had chosen to strip key, but now wants to divert it.
change.mDummyStrip = false;
}
break;
}
}
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
}
@ -521,7 +575,7 @@ public class EditKeyFragment extends CryptoOperationFragment implements
addSubkeyDialogFragment.show(getActivity().getSupportFragmentManager(), "addSubkeyDialog");
}
private void returnKeyringParcel() {
protected void returnKeyringParcel() {
if (mSaveKeyringParcel.mAddUserIds.size() == 0) {
Notify.create(getActivity(), R.string.edit_key_error_add_identity, Notify.Style.ERROR).show();
return;
@ -540,76 +594,6 @@ public class EditKeyFragment extends CryptoOperationFragment implements
getActivity().finish();
}
@Override
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
Log.d(Constants.TAG, "cryptoInput:\n" + cryptoInput);
Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel);
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
getActivity(),
getString(R.string.progress_saving),
ProgressDialog.STYLE_HORIZONTAL,
true,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (handlePendingMessage(message)) {
return;
}
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
// get returned data bundle
Bundle returnData = message.getData();
if (returnData == null) {
return;
}
final OperationResult result =
returnData.getParcelable(OperationResult.EXTRA_RESULT);
if (result == null) {
return;
}
// if bad -> display here!
if (!result.success()) {
result.createNotify(getActivity()).show();
return;
}
// if good -> finish, return result to showkey and display there!
Intent intent = new Intent();
intent.putExtra(OperationResult.EXTRA_RESULT, result);
getActivity().setResult(EditKeyActivity.RESULT_OK, intent);
getActivity().finish();
}
}
};
// Send all information needed to service to import key in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_EDIT_KEYRING);
// fill values for this action
Bundle data = new Bundle();
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, mSaveKeyringParcel);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
// show progress dialog
saveHandler.showProgressDialog(getActivity());
// start service with intent
getActivity().startService(intent);
}
/**
* Closes this activity, returning a result parcel with a single error log entry.
*/
@ -624,4 +608,20 @@ public class EditKeyFragment extends CryptoOperationFragment implements
getActivity().finish();
}
@Override
protected SaveKeyringParcel createOperationInput() {
return mSaveKeyringParcel;
}
@Override
protected void onCryptoOperationSuccess(OperationResult result) {
// if good -> finish, return result to showkey and display there!
Intent intent = new Intent();
intent.putExtra(OperationResult.EXTRA_RESULT, result);
getActivity().setResult(EditKeyActivity.RESULT_OK, intent);
getActivity().finish();
}
}

View File

@ -73,12 +73,10 @@ public class EncryptFilesActivity extends EncryptActivity {
uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
}
boolean useArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, false);
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
EncryptFilesFragment encryptFragment = EncryptFilesFragment.newInstance(uris, useArmor);
EncryptFilesFragment encryptFragment = EncryptFilesFragment.newInstance(uris);
transaction.replace(R.id.encrypt_file_container, encryptFragment);
transaction.commit();
}

View File

@ -18,7 +18,6 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@ -26,8 +25,6 @@ import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@ -49,18 +46,17 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.PgpConstants;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.ShareHelper;
import java.io.File;
@ -70,7 +66,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class EncryptFilesFragment extends CryptoOperationFragment {
public class EncryptFilesFragment
extends CachingCryptoOperationFragment<SignEncryptParcel, SignEncryptResult> {
public static final String ARG_DELETE_AFTER_ENCRYPT = "delete_after_encrypt";
public static final String ARG_ENCRYPT_FILENAMES = "encrypt_filenames";
@ -89,7 +86,7 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
private boolean mShareAfterEncrypt;
private ArrayList<Uri> mOutputUris = new ArrayList<>();
private ArrayList<Uri> mOutputUris;
private RecyclerView mSelectedFiles;
@ -99,11 +96,10 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
/**
* Creates new instance of this fragment
*/
public static EncryptFilesFragment newInstance(ArrayList<Uri> uris, boolean useArmor) {
public static EncryptFilesFragment newInstance(ArrayList<Uri> uris) {
EncryptFilesFragment frag = new EncryptFilesFragment();
Bundle args = new Bundle();
args.putBoolean(ARG_USE_ASCII_ARMOR, useArmor);
args.putParcelableArrayList(ARG_URIS, uris);
frag.setArguments(args);
@ -167,11 +163,28 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Preferences prefs = Preferences.getPreferences(getActivity());
Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState;
mDeleteAfterEncrypt = args.getBoolean(ARG_DELETE_AFTER_ENCRYPT, false);
mUseArmor = args.getBoolean(ARG_USE_ASCII_ARMOR, false);
mUseCompression = args.getBoolean(ARG_USE_COMPRESSION, true);
mEncryptFilenames = args.getBoolean(ARG_ENCRYPT_FILENAMES, true);
if (args.containsKey(ARG_USE_ASCII_ARMOR)) {
mUseArmor = args.getBoolean(ARG_USE_ASCII_ARMOR, false);
} else {
mUseArmor = prefs.getUseArmor();
}
if (args.containsKey(ARG_USE_COMPRESSION)) {
mUseCompression = args.getBoolean(ARG_USE_COMPRESSION, true);
} else {
mUseCompression = prefs.getFilesUseCompression();
}
if (args.containsKey(ARG_ENCRYPT_FILENAMES)) {
mEncryptFilenames = args.getBoolean(ARG_ENCRYPT_FILENAMES, true);
} else {
mEncryptFilenames = prefs.getEncryptFilenames();
}
setHasOptionsMenu(true);
}
@ -221,33 +234,6 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
}
}
private void encryptClicked(boolean share) {
if (mFilesModels.isEmpty()) {
Notify.create(getActivity(), R.string.error_no_file_selected,
Notify.Style.ERROR).show(this);
return;
}
if (share) {
mOutputUris.clear();
int filenameCounter = 1;
for (FilesAdapter.ViewModel model : mFilesModels) {
String targetName =
(mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri))
+ (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName));
filenameCounter++;
}
startEncrypt(true);
} else {
if (mFilesModels.size() > 1) {
Notify.create(getActivity(), R.string.error_multi_not_supported,
Notify.Style.ERROR).show(this);
return;
}
showOutputFileDialog();
}
}
public void addFile(Intent data) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
addInputUri(data.getData());
@ -281,17 +267,17 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.encrypt_save: {
encryptClicked(false);
mShareAfterEncrypt = false;
cryptoOperation();
break;
}
case R.id.encrypt_share: {
encryptClicked(true);
mShareAfterEncrypt = true;
cryptoOperation();
break;
}
case R.id.check_use_armor: {
// we can NOT do this for every item, others might care!
item.setChecked(!item.isChecked());
mUseArmor = item.isChecked();
toggleUseArmor(item, !item.isChecked());
break;
}
case R.id.check_delete_after_encrypt: {
@ -300,13 +286,11 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
break;
}
case R.id.check_enable_compression: {
item.setChecked(!item.isChecked());
mUseCompression = item.isChecked();
toggleEnableCompression(item, !item.isChecked());
break;
}
case R.id.check_encrypt_filenames: {
item.setChecked(!item.isChecked());
mEncryptFilenames = item.isChecked();
toggleEncryptFilenamesCheck(item, !item.isChecked());
break;
}
// case R.id.check_hidden_recipients: {
@ -321,7 +305,75 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
return true;
}
public void onEncryptSuccess(final SignEncryptResult result) {
public void toggleUseArmor(MenuItem item, final boolean useArmor) {
mUseArmor = useArmor;
item.setChecked(useArmor);
Notify.create(getActivity(), useArmor
? R.string.snack_armor_on
: R.string.snack_armor_off,
Notify.LENGTH_LONG, Style.OK, new ActionListener() {
@Override
public void onAction() {
Preferences.getPreferences(getActivity()).setUseArmor(useArmor);
Notify.create(getActivity(), useArmor
? R.string.snack_armor_on
: R.string.snack_armor_off,
Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved)
.show(EncryptFilesFragment.this, false);
}
}, R.string.btn_save_default).show(this);
}
public void toggleEnableCompression(MenuItem item, final boolean compress) {
mUseCompression = compress;
item.setChecked(compress);
Notify.create(getActivity(), compress
? R.string.snack_compression_on
: R.string.snack_compression_off,
Notify.LENGTH_LONG, Style.OK, new ActionListener() {
@Override
public void onAction() {
Preferences.getPreferences(getActivity()).setFilesUseCompression(compress);
Notify.create(getActivity(), compress
? R.string.snack_compression_on
: R.string.snack_compression_off,
Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved)
.show(EncryptFilesFragment.this, false);
}
}, R.string.btn_save_default).show(this);
}
public void toggleEncryptFilenamesCheck(MenuItem item, final boolean encryptFilenames) {
mEncryptFilenames = encryptFilenames;
item.setChecked(encryptFilenames);
Notify.create(getActivity(), encryptFilenames
? R.string.snack_encrypt_filenames_on
: R.string.snack_encrypt_filenames_off,
Notify.LENGTH_LONG, Style.OK, new ActionListener() {
@Override
public void onAction() {
Preferences.getPreferences(getActivity()).setEncryptFilenames(encryptFilenames);
Notify.create(getActivity(), encryptFilenames
? R.string.snack_encrypt_filenames_on
: R.string.snack_encrypt_filenames_off,
Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved)
.show(EncryptFilesFragment.this, false);
}
}, R.string.btn_save_default).show(this);
}
@Override
protected void onCryptoOperationSuccess(final SignEncryptResult result) {
if (mDeleteAfterEncrypt) {
DeleteFileDialogFragment deleteFileDialog =
DeleteFileDialogFragment.newInstance(mFilesAdapter.getAsArrayList());
@ -349,29 +401,91 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
result.createNotify(getActivity()).show();
}
}
}
protected SignEncryptParcel createEncryptBundle() {
// prepares mOutputUris, either directly and returns false, or indirectly
// which returns true and will call cryptoOperation after mOutputUris has
// been set at a later point.
private boolean prepareOutputStreams(boolean share) {
if (mFilesModels.isEmpty()) {
Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR)
.show(this);
return null;
return true;
} else if (mFilesModels.size() > 1 && !mShareAfterEncrypt) {
Log.e(Constants.TAG, "Aborting: mInputUris.size() > 1 && !mShareAfterEncrypt");
// This should be impossible...
return null;
} else if (mFilesModels.size() != mOutputUris.size()) {
Log.e(Constants.TAG, "Aborting: mInputUris.size() != mOutputUris.size()");
// This as well
return null;
return true;
}
if (share) {
mOutputUris = new ArrayList<>();
int filenameCounter = 1;
for (FilesAdapter.ViewModel model : mFilesModels) {
String targetName =
(mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri))
+ (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName));
filenameCounter++;
}
return false;
} else {
if (mFilesModels.size() > 1) {
Notify.create(getActivity(), R.string.error_multi_not_supported,
Notify.Style.ERROR).show(this);
return true;
}
showOutputFileDialog();
return true;
}
}
protected SignEncryptParcel createOperationInput() {
SignEncryptParcel actionsParcel = getCachedActionsParcel();
// we have three cases here: nothing cached, cached except output, fully cached
if (actionsParcel == null) {
// clear output uris for now, they will be created by prepareOutputStreams later
mOutputUris = null;
actionsParcel = createIncompleteCryptoInput();
// this is null if invalid, just return in that case
if (actionsParcel == null) {
return null;
}
cacheActionsParcel(actionsParcel);
}
// if it's incomplete, prepare output streams
if (actionsParcel.isIncomplete()) {
// if this is still null, prepare output streams again
if (mOutputUris == null) {
// this may interrupt the flow, and call us again from onActivityResult
if (prepareOutputStreams(mShareAfterEncrypt)) {
return null;
}
}
actionsParcel.addOutputUris(mOutputUris);
cacheActionsParcel(actionsParcel);
}
return actionsParcel;
}
protected SignEncryptParcel createIncompleteCryptoInput() {
// fill values for this action
SignEncryptParcel data = new SignEncryptParcel();
data.addInputUris(mFilesAdapter.getAsArrayList());
data.addOutputUris(mOutputUris);
if (mUseCompression) {
data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
@ -471,68 +585,6 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
return sendIntent;
}
public void startEncrypt(boolean share) {
mShareAfterEncrypt = share;
cryptoOperation();
}
@Override
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
final SignEncryptParcel input = createEncryptBundle();
// this is null if invalid, just return in that case
if (input == null) {
// Notify was created by inputIsValid.
return;
}
// Send all information needed to service to edit key in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
Bundle data = new Bundle();
data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input);
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after encrypting is done in KeychainIntentService
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
getActivity(),
getString(R.string.progress_encrypting),
ProgressDialog.STYLE_HORIZONTAL,
true,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
// handle pending messages
if (handlePendingMessage(message)) {
return;
}
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
SignEncryptResult result =
message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
if (result.success()) {
onEncryptSuccess(result);
} else {
result.createNotify(getActivity()).show();
}
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(serviceHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
// show progress dialog
serviceHandler.showProgressDialog(getActivity());
// start service with intent
getActivity().startService(intent);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
@ -545,9 +597,10 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
case REQUEST_CODE_OUTPUT: {
// This happens after output file was selected, so start our operation
if (resultCode == Activity.RESULT_OK && data != null) {
mOutputUris.clear();
mOutputUris = new ArrayList<>(1);
mOutputUris.add(data.getData());
startEncrypt(false);
mShareAfterEncrypt = false;
cryptoOperation();
}
return;
}

View File

@ -21,7 +21,9 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ViewAnimator;
import com.tokenautocomplete.TokenCompleteTextView.TokenListener;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
@ -33,6 +35,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView;
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
import org.sufficientlysecure.keychain.ui.widget.KeySpinner.OnKeyChangedListener;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
@ -76,6 +79,35 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
mEncryptKeyView.setThreshold(1); // Start working from first character
final ViewAnimator vSignatureIcon = (ViewAnimator) view.findViewById(R.id.result_signature_icon);
mSignKeySpinner.setOnKeyChangedListener(new OnKeyChangedListener() {
@Override
public void onKeyChanged(long masterKeyId) {
int child = masterKeyId != Constants.key.none ? 1 : 0;
if (vSignatureIcon.getDisplayedChild() != child) {
vSignatureIcon.setDisplayedChild(child);
}
}
});
final ViewAnimator vEncryptionIcon = (ViewAnimator) view.findViewById(R.id.result_encryption_icon);
mEncryptKeyView.setTokenListener(new TokenListener() {
@Override
public void onTokenAdded(Object o) {
if (vEncryptionIcon.getDisplayedChild() != 1) {
vEncryptionIcon.setDisplayedChild(1);
}
}
@Override
public void onTokenRemoved(Object o) {
int child = mEncryptKeyView.getObjects().isEmpty() ? 0 : 1;
if (vEncryptionIcon.getDisplayedChild() != child) {
vEncryptionIcon.setDisplayedChild(child);
}
}
});
return view;
}
@ -85,11 +117,12 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
mProviderHelper = new ProviderHelper(getActivity());
// preselect keys given, from state or arguments
long signatureKeyId, encryptionKeyIds[];
if (savedInstanceState == null) {
signatureKeyId = getArguments().getLong(ARG_SINGATURE_KEY_ID);
encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS);
Long signatureKeyId = getArguments().getLong(ARG_SINGATURE_KEY_ID);
if (signatureKeyId == Constants.key.none) {
signatureKeyId = null;
}
long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS);
preselectKeys(signatureKeyId, encryptionKeyIds);
}
@ -98,8 +131,8 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
/**
* If an Intent gives a signatureMasterKeyId and/or encryptionMasterKeyIds, preselect those!
*/
private void preselectKeys(long signatureKeyId, long[] encryptionKeyIds) {
if (signatureKeyId != Constants.key.none) {
private void preselectKeys(Long signatureKeyId, long[] encryptionKeyIds) {
if (signatureKeyId != null) {
try {
CachedPublicKeyRing keyring = mProviderHelper.getCachedPublicKeyRing(
KeyRings.buildUnifiedKeyRingUri(signatureKeyId));
@ -134,7 +167,7 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
@Override
public long getAsymmetricSigningKeyId() {
return mSignKeySpinner.getSelectedItemId();
return mSignKeySpinner.getSelectedKeyId();
}
@Override

View File

@ -18,11 +18,8 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
@ -41,19 +38,19 @@ import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.PgpConstants;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.ShareHelper;
import java.util.HashSet;
import java.util.Set;
public class EncryptTextFragment extends CryptoOperationFragment {
public class EncryptTextFragment
extends CachingCryptoOperationFragment<SignEncryptParcel, SignEncryptResult> {
public static final String ARG_TEXT = "text";
public static final String ARG_USE_COMPRESSION = "use_compression";
@ -131,10 +128,19 @@ public class EncryptTextFragment extends CryptoOperationFragment {
mMessage = getArguments().getString(ARG_TEXT);
}
Preferences prefs = Preferences.getPreferences(getActivity());
Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState;
mUseCompression = args.getBoolean(ARG_USE_COMPRESSION, true);
if (args.containsKey(ARG_USE_COMPRESSION)) {
mUseCompression = args.getBoolean(ARG_USE_COMPRESSION, true);
} else {
mUseCompression = prefs.getTextUseCompression();
}
setHasOptionsMenu(true);
}
@Override
@ -147,12 +153,9 @@ public class EncryptTextFragment extends CryptoOperationFragment {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.isCheckable()) {
item.setChecked(!item.isChecked());
}
switch (item.getItemId()) {
case R.id.check_enable_compression: {
mUseCompression = item.isChecked();
toggleEnableCompression(item, !item.isChecked());
break;
}
// case R.id.check_hidden_recipients: {
@ -161,11 +164,13 @@ public class EncryptTextFragment extends CryptoOperationFragment {
// break;
// }
case R.id.encrypt_copy: {
cryptoOperation(false);
mShareAfterEncrypt = false;
cryptoOperation();
break;
}
case R.id.encrypt_share: {
cryptoOperation(true);
mShareAfterEncrypt = true;
cryptoOperation();
break;
}
default: {
@ -175,21 +180,29 @@ public class EncryptTextFragment extends CryptoOperationFragment {
return true;
}
protected void onEncryptSuccess(SignEncryptResult result) {
if (mShareAfterEncrypt) {
// Share encrypted message/file
startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
} else {
// Copy to clipboard
copyToClipboard(result.getResultBytes());
result.createNotify(getActivity()).show();
// Notify.create(EncryptTextActivity.this,
// R.string.encrypt_sign_clipboard_successful, Notify.Style.OK)
// .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
}
public void toggleEnableCompression(MenuItem item, final boolean compress) {
mUseCompression = compress;
item.setChecked(compress);
Notify.create(getActivity(), compress
? R.string.snack_compression_on
: R.string.snack_compression_off,
Notify.LENGTH_LONG, Style.OK, new ActionListener() {
@Override
public void onAction() {
Preferences.getPreferences(getActivity()).setTextUseCompression(compress);
Notify.create(getActivity(), compress
? R.string.snack_compression_on
: R.string.snack_compression_off,
Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved)
.show(EncryptTextFragment.this, false);
}
}, R.string.btn_save_default).show(this);
}
protected SignEncryptParcel createEncryptBundle() {
protected SignEncryptParcel createOperationInput() {
if (mMessage == null || mMessage.isEmpty()) {
Notify.create(getActivity(), R.string.error_empty_text, Notify.Style.ERROR)
@ -227,7 +240,7 @@ public class EncryptTextFragment extends CryptoOperationFragment {
boolean gotEncryptionKeys = (encryptionKeyIds != null
&& encryptionKeyIds.length > 0);
if (!gotEncryptionKeys && signingKeyId == 0L) {
if (!gotEncryptionKeys && signingKeyId == Constants.key.none) {
Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
.show(this);
return null;
@ -302,65 +315,21 @@ public class EncryptTextFragment extends CryptoOperationFragment {
return sendIntent;
}
public void cryptoOperation(boolean share) {
mShareAfterEncrypt = share;
cryptoOperation();
}
@Override
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
protected void onCryptoOperationSuccess(SignEncryptResult result) {
final SignEncryptParcel input = createEncryptBundle();
// this is null if invalid, just return in that case
if (input == null) {
// Notify was created by inputIsValid.
return;
if (mShareAfterEncrypt) {
// Share encrypted message/file
startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
} else {
// Copy to clipboard
copyToClipboard(result.getResultBytes());
result.createNotify(getActivity()).show();
// Notify.create(EncryptTextActivity.this,
// R.string.encrypt_sign_clipboard_successful, Notify.Style.OK)
// .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
}
// Send all information needed to service to edit key in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
final Bundle data = new Bundle();
data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input);
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Message is received after encrypting is done in KeychainIntentService
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
getActivity(),
getString(R.string.progress_encrypting),
ProgressDialog.STYLE_HORIZONTAL,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (handlePendingMessage(message)) {
return;
}
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
SignEncryptResult result =
message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
if (result.success()) {
onEncryptSuccess(result);
} else {
result.createNotify(getActivity()).show();
}
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(serviceHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
// show progress dialog
serviceHandler.showProgressDialog(getActivity());
// start service with intent
getActivity().startService(intent);
}
}

View File

@ -35,11 +35,9 @@ import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
import org.sufficientlysecure.keychain.service.CloudImportService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
@ -265,7 +263,7 @@ public class ImportKeysActivity extends BaseNfcActivity {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
if (mListFragment != null) {
return;
}
@ -285,7 +283,7 @@ public class ImportKeysActivity extends BaseNfcActivity {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
if (mTopFragment != null) {
return;
}
@ -316,7 +314,7 @@ public class ImportKeysActivity extends BaseNfcActivity {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
if (mTopFragment != null) {
return;
}
@ -383,33 +381,35 @@ public class ImportKeysActivity extends BaseNfcActivity {
* Import keys with mImportData
*/
public void importKeys() {
if (mListFragment.getSelectedEntries().size() == 0) {
Notify.create(this, R.string.error_nothing_import_selected, Notify.Style.ERROR)
.show((ViewGroup) findViewById(R.id.import_snackbar));
return;
}
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(this) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
ImportKeysActivity.this.handleMessage(message);
}
};
// Send all information needed to service to import key in other thread
Intent intent = new Intent(this, KeychainService.class);
intent.setAction(KeychainService.ACTION_IMPORT_KEYRING);
// fill values for this action
Bundle data = new Bundle();
ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState();
if (ls instanceof ImportKeysListFragment.BytesLoaderState) {
Log.d(Constants.TAG, "importKeys started");
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
this,
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL,
true,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
ImportKeysActivity.this.handleMessage(message);
}
};
// TODO: Currently not using CloudImport here due to https://github.com/open-keychain/open-keychain/issues/1221
// Send all information needed to service to import key in other thread
Intent intent = new Intent(this, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
// fill values for this action
Bundle data = new Bundle();
// get DATA from selected key entries
IteratorWithSize<ParcelableKeyRing> selectedEntries = mListFragment.getSelectedData();
@ -423,14 +423,18 @@ public class ImportKeysActivity extends BaseNfcActivity {
new ParcelableFileCache<>(this, "key_import.pcl");
cache.writeCache(selectedEntries);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
intent.putExtra(KeychainService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(serviceHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
// show progress dialog
serviceHandler.showProgressDialog(this);
serviceHandler.showProgressDialog(
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL,
true
);
// start service with intent
startService(intent);
@ -442,27 +446,7 @@ public class ImportKeysActivity extends BaseNfcActivity {
} else if (ls instanceof ImportKeysListFragment.CloudLoaderState) {
ImportKeysListFragment.CloudLoaderState sls = (ImportKeysListFragment.CloudLoaderState) ls;
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
this,
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL,
true,
ProgressDialogFragment.ServiceType.CLOUD_IMPORT) {
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
ImportKeysActivity.this.handleMessage(message);
}
};
// Send all information needed to service to query keys in other thread
Intent intent = new Intent(this, CloudImportService.class);
// fill values for this action
Bundle data = new Bundle();
data.putString(CloudImportService.IMPORT_KEY_SERVER, sls.mCloudPrefs.keyserver);
data.putString(KeychainService.IMPORT_KEY_SERVER, sls.mCloudPrefs.keyserver);
// get selected key entries
ArrayList<ParcelableKeyRing> keys = new ArrayList<>();
@ -475,22 +459,22 @@ public class ImportKeysActivity extends BaseNfcActivity {
);
}
}
data.putParcelableArrayList(CloudImportService.IMPORT_KEY_LIST, keys);
data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, keys);
intent.putExtra(CloudImportService.EXTRA_DATA, data);
intent.putExtra(KeychainService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(serviceHandler);
intent.putExtra(CloudImportService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
// show progress dialog
serviceHandler.showProgressDialog(this);
serviceHandler.showProgressDialog(
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL, true
);
// start service with intent
startService(intent);
} else {
Notify.create(this, R.string.error_nothing_import, Notify.Style.ERROR)
.show((ViewGroup) findViewById(R.id.import_snackbar));
}
}

View File

@ -43,9 +43,8 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
@ -206,13 +205,9 @@ public class ImportKeysProxyActivity extends FragmentActivity {
private void startImportService(ArrayList<ParcelableKeyRing> keyRings) {
// Message is received after importing is done in KeychainIntentService
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
this,
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL,
true,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
// Message is received after importing is done in KeychainService
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(this) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
@ -258,22 +253,24 @@ public class ImportKeysProxyActivity extends FragmentActivity {
Preferences prefs = Preferences.getPreferences(this);
Preferences.CloudSearchPrefs cloudPrefs =
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
data.putString(KeychainService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
}
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyRings);
data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, keyRings);
// Send all information needed to service to query keys in other thread
Intent intent = new Intent(this, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
Intent intent = new Intent(this, KeychainService.class);
intent.setAction(KeychainService.ACTION_IMPORT_KEYRING);
intent.putExtra(KeychainService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(serviceHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
// show progress dialog
serviceHandler.showProgressDialog(this);
serviceHandler.showProgressDialog(
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL, true);
// start service with intent
startService(intent);

View File

@ -64,15 +64,14 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.CloudImportService;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.ExportHelper;
import org.sufficientlysecure.keychain.util.FabContainer;
import org.sufficientlysecure.keychain.util.Log;
@ -555,9 +554,12 @@ public class KeyListFragment extends LoaderFragment
}
private void updateAllKeys() {
Context context = getActivity();
Activity activity = getActivity();
if (activity == null) {
return;
}
ProviderHelper providerHelper = new ProviderHelper(context);
ProviderHelper providerHelper = new ProviderHelper(activity);
Cursor cursor = providerHelper.getContentResolver().query(
KeyRings.buildUnifiedKeyRingsUri(), new String[]{
@ -565,21 +567,25 @@ public class KeyListFragment extends LoaderFragment
}, null, null, null
);
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
while (cursor.moveToNext()) {
byte[] blob = cursor.getBlob(0);//fingerprint column is 0
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
keyList.add(keyEntry);
if (cursor == null) {
Notify.create(activity, R.string.error_loading_keys, Style.ERROR);
return;
}
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
getActivity(),
getString(R.string.progress_updating),
ProgressDialog.STYLE_HORIZONTAL,
true,
ProgressDialogFragment.ServiceType.CLOUD_IMPORT) {
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
try {
while (cursor.moveToNext()) {
byte[] blob = cursor.getBlob(0);//fingerprint column is 0
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
keyList.add(keyEntry);
}
} finally {
cursor.close();
}
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(getActivity()) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
@ -603,7 +609,8 @@ public class KeyListFragment extends LoaderFragment
};
// Send all information needed to service to query keys in other thread
Intent intent = new Intent(getActivity(), CloudImportService.class);
Intent intent = new Intent(getActivity(), KeychainService.class);
intent.setAction(KeychainService.ACTION_IMPORT_KEYRING);
// fill values for this action
Bundle data = new Bundle();
@ -613,31 +620,30 @@ public class KeyListFragment extends LoaderFragment
Preferences prefs = Preferences.getPreferences(getActivity());
Preferences.CloudSearchPrefs cloudPrefs =
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
data.putString(CloudImportService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
data.putString(KeychainService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
}
data.putParcelableArrayList(CloudImportService.IMPORT_KEY_LIST, keyList);
data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, keyList);
intent.putExtra(CloudImportService.EXTRA_DATA, data);
intent.putExtra(KeychainService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(serviceHandler);
intent.putExtra(CloudImportService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
// show progress dialog
serviceHandler.showProgressDialog(getActivity());
serviceHandler.showProgressDialog(
getString(R.string.progress_updating),
ProgressDialog.STYLE_HORIZONTAL, true);
// start service with intent
getActivity().startService(intent);
}
private void consolidate() {
// Message is received after importing is done in KeychainIntentService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
getActivity(),
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
// Message is received after importing is done in KeychainService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
@ -660,21 +666,23 @@ public class KeyListFragment extends LoaderFragment
};
// Send all information needed to service to import key in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
Intent intent = new Intent(getActivity(), KeychainService.class);
intent.setAction(KeychainIntentService.ACTION_CONSOLIDATE);
intent.setAction(KeychainService.ACTION_CONSOLIDATE);
// fill values for this action
Bundle data = new Bundle();
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
intent.putExtra(KeychainService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
// show progress dialog
saveHandler.showProgressDialog(getActivity());
saveHandler.showProgressDialog(
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL, false);
// start service with intent
getActivity().startService(intent);

View File

@ -23,6 +23,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.widget.Toolbar;
import android.view.View;
@ -42,7 +43,7 @@ import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
import org.sufficientlysecure.keychain.util.FabContainer;
import org.sufficientlysecure.keychain.util.Preferences;
public class MainActivity extends BaseNfcActivity implements FabContainer {
public class MainActivity extends BaseNfcActivity implements FabContainer, OnBackStackChangedListener {
private static final int ID_KEYS = 1;
private static final int ID_ENCRYPT_DECRYPT = 2;
@ -50,6 +51,8 @@ public class MainActivity extends BaseNfcActivity implements FabContainer {
private static final int ID_SETTINGS = 4;
private static final int ID_HELP = 5;
public static final String EXTRA_SKIP_FIRST_TIME = "skip_first_time";
public Drawer.Result mDrawerResult;
private Toolbar mToolbar;
@ -113,7 +116,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer {
// if this is the first time show first time activity
Preferences prefs = Preferences.getPreferences(this);
if (prefs.isFirstTime()) {
if (!getIntent().getBooleanExtra(EXTRA_SKIP_FIRST_TIME, false) && prefs.isFirstTime()) {
Intent intent = new Intent(this, CreateKeyActivity.class);
intent.putExtra(CreateKeyActivity.EXTRA_FIRST_TIME, true);
startActivity(intent);
@ -121,6 +124,8 @@ public class MainActivity extends BaseNfcActivity implements FabContainer {
return;
}
getSupportFragmentManager().addOnBackStackChangedListener(this);
Intent data = getIntent();
// If we got an EXTRA_RESULT in the intent, show the notification
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
@ -206,4 +211,25 @@ public class MainActivity extends BaseNfcActivity implements FabContainer {
}
@Override
public void onBackStackChanged() {
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager == null) {
return;
}
Fragment frag = fragmentManager.findFragmentById(R.id.main_fragment_container);
if (frag == null) {
return;
}
// make sure the selected icon is the one shown at this point
if (frag instanceof KeyListFragment) {
mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_KEYS), false);
} else if (frag instanceof EncryptDecryptOverviewFragment) {
mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_ENCRYPT_DECRYPT), false);
} else if (frag instanceof AppsListFragment) {
mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_APPS), false);
}
}
}

View File

@ -12,15 +12,22 @@ import android.view.WindowManager;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
@ -40,6 +47,8 @@ public class NfcOperationActivity extends BaseNfcActivity {
private RequiredInputParcel mRequiredInput;
private Intent mServiceIntent;
private static final byte[] BLANK_FINGERPRINT = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -54,7 +63,9 @@ public class NfcOperationActivity extends BaseNfcActivity {
mServiceIntent = data.getParcelable(EXTRA_SERVICE_INTENT);
// obtain passphrase for this subkey
obtainYubiKeyPin(mRequiredInput);
if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_KEYTOCARD) {
obtainYubiKeyPin(mRequiredInput);
}
}
@Override
@ -85,6 +96,68 @@ public class NfcOperationActivity extends BaseNfcActivity {
}
break;
}
case NFC_KEYTOCARD: {
ProviderHelper providerHelper = new ProviderHelper(this);
CanonicalizedSecretKeyRing secretKeyRing;
try {
secretKeyRing = providerHelper.getCanonicalizedSecretKeyRing(
KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(mRequiredInput.getMasterKeyId())
);
} catch (ProviderHelper.NotFoundException e) {
throw new IOException("Couldn't find subkey for key to card operation.");
}
for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) {
byte[] subkeyBytes = mRequiredInput.mInputHashes[i];
ByteBuffer buf = ByteBuffer.wrap(subkeyBytes);
long subkeyId = buf.getLong();
CanonicalizedSecretKey key = secretKeyRing.getSecretKey(subkeyId);
long keyGenerationTimestampMillis = key.getCreationTime().getTime();
long keyGenerationTimestamp = keyGenerationTimestampMillis / 1000;
byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array();
byte[] cardSerialNumber = Arrays.copyOf(nfcGetAid(), 16);
Passphrase passphrase;
try {
passphrase = PassphraseCacheService.getCachedPassphrase(this,
mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId());
} catch (PassphraseCacheService.KeyNotFoundException e) {
throw new IOException("Unable to get cached passphrase!");
}
if (key.canSign() || key.canCertify()) {
if (shouldPutKey(key.getFingerprint(), 0)) {
nfcPutKey(0xB6, key, passphrase);
nfcPutData(0xCE, timestampBytes);
nfcPutData(0xC7, key.getFingerprint());
} else {
throw new IOException("Key slot occupied; card must be reset to put new signature key.");
}
} else if (key.canEncrypt()) {
if (shouldPutKey(key.getFingerprint(), 1)) {
nfcPutKey(0xB8, key, passphrase);
nfcPutData(0xCF, timestampBytes);
nfcPutData(0xC8, key.getFingerprint());
} else {
throw new IOException("Key slot occupied; card must be reset to put new decryption key.");
}
} else if (key.canAuthenticate()) {
if (shouldPutKey(key.getFingerprint(), 2)) {
nfcPutKey(0xA4, key, passphrase);
nfcPutData(0xD0, timestampBytes);
nfcPutData(0xC9, key.getFingerprint());
} else {
throw new IOException("Key slot occupied; card must be reset to put new authentication key.");
}
} else {
throw new IOException("Inappropriate key flags for smart card key.");
}
inputParcel.addCryptoData(subkeyBytes, cardSerialNumber);
}
}
}
if (mServiceIntent != null) {
@ -99,6 +172,18 @@ public class NfcOperationActivity extends BaseNfcActivity {
finish();
}
private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException {
byte[] cardFingerprint = nfcGetFingerprint(idx);
// Slot is empty, or contains this key already. PUT KEY operation is safe
if (Arrays.equals(cardFingerprint, BLANK_FINGERPRINT) ||
Arrays.equals(cardFingerprint, fingerprint)) {
return true;
}
// Slot already contains a different key; don't overwrite it.
return false;
}
@Override
public void handlePinError() {

View File

@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@ -43,15 +44,15 @@ import android.widget.Toast;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
@ -73,8 +74,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
// special extra for OpenPgpService
public static final String EXTRA_SERVICE_INTENT = "data";
private static final int REQUEST_CODE_ENTER_PATTERN = 2;
private long mSubKeyId;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -91,18 +91,40 @@ public class PassphraseDialogActivity extends FragmentActivity {
// this activity itself has no content view (see manifest)
long keyId;
if (getIntent().hasExtra(EXTRA_SUBKEY_ID)) {
keyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
mSubKeyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
} else {
RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT);
switch (requiredInput.mType) {
case PASSPHRASE_SYMMETRIC: {
keyId = Constants.key.symmetric;
mSubKeyId = Constants.key.symmetric;
break;
}
case PASSPHRASE: {
keyId = requiredInput.getSubKeyId();
// handle empty passphrases by directly returning an empty crypto input parcel
try {
CanonicalizedSecretKeyRing pubRing =
new ProviderHelper(this).getCanonicalizedSecretKeyRing(
requiredInput.getMasterKeyId());
// use empty passphrase for empty passphrase
if (pubRing.getSecretKey(requiredInput.getSubKeyId()).getSecretKeyType() ==
SecretKeyType.PASSPHRASE_EMPTY) {
// also return passphrase back to activity
Intent returnIntent = new Intent();
returnIntent.putExtra(RESULT_CRYPTO_INPUT, new CryptoInputParcel(new Passphrase("")));
setResult(RESULT_OK, returnIntent);
finish();
return;
}
} catch (NotFoundException e) {
Log.e(Constants.TAG, "Key not found?!", e);
setResult(RESULT_CANCELED);
finish();
return;
}
mSubKeyId = requiredInput.getSubKeyId();
break;
}
default: {
@ -111,64 +133,35 @@ public class PassphraseDialogActivity extends FragmentActivity {
}
}
Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_SERVICE_INTENT);
show(this, keyId, serviceIntent);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_ENTER_PATTERN: {
/*
* NOTE that there are 4 possible result codes!!!
*/
switch (resultCode) {
case RESULT_OK:
// The user passed
break;
case RESULT_CANCELED:
// The user cancelled the task
break;
// case LockPatternActivity.RESULT_FAILED:
// // The user failed to enter the pattern
// break;
// case LockPatternActivity.RESULT_FORGOT_PATTERN:
// // The user forgot the pattern and invoked your recovery Activity.
// break;
}
protected void onResume() {
super.onResume();
/*
* In any case, there's always a key EXTRA_RETRY_COUNT, which holds
* the number of tries that the user did.
*/
// int retryCount = data.getIntExtra(
// LockPatternActivity.EXTRA_RETRY_COUNT, 0);
/* Show passphrase dialog to cache a new passphrase the user enters for using it later for
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
* for a symmetric passphrase
*/
break;
}
}
Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_SERVICE_INTENT);
PassphraseDialogFragment frag = new PassphraseDialogFragment();
Bundle args = new Bundle();
args.putLong(EXTRA_SUBKEY_ID, mSubKeyId);
args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent);
frag.setArguments(args);
frag.show(getSupportFragmentManager(), "passphraseDialog");
}
/**
* Shows passphrase dialog to cache a new passphrase the user enters for using it later for
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
* for a symmetric passphrase
*/
public static void show(final FragmentActivity context, final long keyId, final Intent serviceIntent) {
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
public void run() {
// do NOT check if the key even needs a passphrase. that's not our job here.
PassphraseDialogFragment frag = new PassphraseDialogFragment();
Bundle args = new Bundle();
args.putLong(EXTRA_SUBKEY_ID, keyId);
args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent);
@Override
protected void onPause() {
super.onPause();
frag.setArguments(args);
frag.show(context.getSupportFragmentManager(), "passphraseDialog");
}
});
DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag("passphraseDialog");
if (dialog != null) {
dialog.dismiss();
}
}
public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener {
@ -182,9 +175,6 @@ public class PassphraseDialogActivity extends FragmentActivity {
private Intent mServiceIntent;
/**
* Creates dialog
*/
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
@ -245,12 +235,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
userId = null;
}
/* Get key type for message */
// find a master key id for our key
long masterKeyId = new ProviderHelper(activity).getMasterKeyId(mSubKeyId);
CachedPublicKeyRing keyRing = new ProviderHelper(activity).getCachedPublicKeyRing(masterKeyId);
// get the type of key (from the database)
keyType = keyRing.getSecretKeyType(mSubKeyId);
keyType = mSecretRing.getSecretKey(mSubKeyId).getSecretKeyType();
switch (keyType) {
case PASSPHRASE:
message = getString(R.string.passphrase_for, userId);
@ -445,20 +430,16 @@ public class PassphraseDialogActivity extends FragmentActivity {
// note we need no synchronization here, this variable is only accessed in the ui thread
mIsCancelled = true;
getActivity().setResult(RESULT_CANCELED);
getActivity().finish();
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (getActivity() == null) {
return;
}
hideKeyboard();
getActivity().setResult(RESULT_CANCELED);
getActivity().finish();
}
private void hideKeyboard() {
@ -472,11 +453,9 @@ public class PassphraseDialogActivity extends FragmentActivity {
inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
/**
* Associate the "done" button on the soft keyboard with the okay button in the view
*/
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
// Associate the "done" button on the soft keyboard with the okay button in the view
if (EditorInfo.IME_ACTION_DONE == actionId) {
AlertDialog dialog = ((AlertDialog) getDialog());
Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);

View File

@ -38,10 +38,9 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
@ -124,13 +123,9 @@ public class SafeSlingerActivity extends BaseActivity {
final FragmentActivity activity = SafeSlingerActivity.this;
// Message is received after importing is done in KeychainIntentService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
activity,
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL,
true,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
// Message is received after importing is done in KeychainService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(activity) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
@ -176,9 +171,9 @@ public class SafeSlingerActivity extends BaseActivity {
Log.d(Constants.TAG, "importKeys started");
// Send all information needed to service to import key in other thread
Intent intent = new Intent(activity, KeychainIntentService.class);
Intent intent = new Intent(activity, KeychainService.class);
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
intent.setAction(KeychainService.ACTION_IMPORT_KEYRING);
// instead of giving the entries by Intent extra, cache them into a
// file to prevent Java Binder problems on heavy imports
@ -195,14 +190,17 @@ public class SafeSlingerActivity extends BaseActivity {
// fill values for this action
Bundle bundle = new Bundle();
intent.putExtra(KeychainIntentService.EXTRA_DATA, bundle);
intent.putExtra(KeychainService.EXTRA_DATA, bundle);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
// show progress dialog
saveHandler.showProgressDialog(activity);
saveHandler.showProgressDialog(
getString(R.string.progress_importing),
ProgressDialog.STYLE_HORIZONTAL, true
);
// start service with intent
activity.startService(intent);

View File

@ -35,10 +35,9 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
@ -92,9 +91,9 @@ public class UploadKeyActivity extends BaseActivity {
private void uploadKey() {
// Send all information needed to service to upload key in other thread
Intent intent = new Intent(this, KeychainIntentService.class);
Intent intent = new Intent(this, KeychainService.class);
intent.setAction(KeychainIntentService.ACTION_UPLOAD_KEYRING);
intent.setAction(KeychainService.ACTION_UPLOAD_KEYRING);
// set data uri as path to keyring
Uri blobUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
@ -104,16 +103,13 @@ public class UploadKeyActivity extends BaseActivity {
Bundle data = new Bundle();
String server = (String) mKeyServerSpinner.getSelectedItem();
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, server);
data.putString(KeychainService.UPLOAD_KEY_SERVER, server);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
intent.putExtra(KeychainService.EXTRA_DATA, data);
// Message is received after uploading is done in KeychainIntentService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
this,
getString(R.string.progress_uploading),
ProgressDialog.STYLE_HORIZONTAL,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
// Message is received after uploading is done in KeychainService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(this) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
@ -129,10 +125,12 @@ public class UploadKeyActivity extends BaseActivity {
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
// show progress dialog
saveHandler.showProgressDialog(this);
saveHandler.showProgressDialog(
getString(R.string.progress_uploading),
ProgressDialog.STYLE_HORIZONTAL, false);
// start service with intent
startService(intent);

View File

@ -65,7 +65,7 @@ import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
@ -402,8 +402,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements
}
private void startCertifyIntent(Intent intent) {
// Message is received after signing is done in KeychainIntentService
// Message is received after signing is done in KeychainService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(this) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
@ -418,7 +419,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
startActivityForResult(intent, 0);
}
@ -651,8 +652,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements
ArrayList<ParcelableKeyRing> entries = new ArrayList<>();
entries.add(keyEntry);
// Message is received after importing is done in KeychainIntentService
// Message is received after importing is done in KeychainService
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(this) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
@ -682,22 +684,19 @@ public class ViewKeyActivity extends BaseNfcActivity implements
Preferences prefs = Preferences.getPreferences(this);
Preferences.CloudSearchPrefs cloudPrefs =
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
data.putString(KeychainService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
}
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, entries);
data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, entries);
// Send all information needed to service to query keys in other thread
Intent intent = new Intent(this, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
Intent intent = new Intent(this, KeychainService.class);
intent.setAction(KeychainService.ACTION_IMPORT_KEYRING);
intent.putExtra(KeychainService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(serviceHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
// show progress dialog
serviceHandler.showProgressDialog(this);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
// start service with intent
startService(intent);

View File

@ -140,12 +140,17 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
}
});
mKeyNfcButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mNfcHelper.invokeNfcBeam();
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mKeyNfcButton.setVisibility(View.VISIBLE);
mKeyNfcButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mNfcHelper.invokeNfcBeam();
}
});
} else {
mKeyNfcButton.setVisibility(View.GONE);
}
mKeySafeSlingerButton.setOnClickListener(new View.OnClickListener() {
@Override

View File

@ -300,7 +300,7 @@ public class ViewKeyFragment extends LoaderFragment implements
* because the notification triggers faster than the activity closes.
*/
// Avoid NullPointerExceptions...
if (data.getCount() == 0) {
if (data == null || data.getCount() == 0) {
return;
}
// Swap the new cursor in. (The framework will take care of closing the

View File

@ -49,9 +49,8 @@ import com.textuality.keybase.lib.User;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
@ -350,23 +349,19 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
}
private void verify(final Proof proof, final String fingerprint) {
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
Intent intent = new Intent(getActivity(), KeychainService.class);
Bundle data = new Bundle();
intent.setAction(KeychainIntentService.ACTION_VERIFY_KEYBASE_PROOF);
intent.setAction(KeychainService.ACTION_VERIFY_KEYBASE_PROOF);
data.putString(KeychainIntentService.KEYBASE_PROOF, proof.toString());
data.putString(KeychainIntentService.KEYBASE_REQUIRED_FINGERPRINT, fingerprint);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
data.putString(KeychainService.KEYBASE_PROOF, proof.toString());
data.putString(KeychainService.KEYBASE_REQUIRED_FINGERPRINT, fingerprint);
intent.putExtra(KeychainService.EXTRA_DATA, data);
mProofVerifyDetail.setVisibility(View.GONE);
// Create a new Messenger for the communication back after proof work is done
//
ServiceProgressHandler handler = new ServiceProgressHandler(
getActivity(),
getString(R.string.progress_verifying_signature),
ProgressDialog.STYLE_HORIZONTAL,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
ServiceProgressHandler handler = new ServiceProgressHandler(getActivity()) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
@ -451,10 +446,13 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(handler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
// show progress dialog
handler.showProgressDialog(getActivity());
handler.showProgressDialog(
getString(R.string.progress_verifying_signature),
ProgressDialog.STYLE_HORIZONTAL, false
);
// start service with intent
getActivity().startService(intent);

View File

@ -43,7 +43,7 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
@ -129,6 +129,7 @@ public class ViewKeyYubiKeyFragment extends Fragment
public void promoteToSecretKey() {
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
@ -147,25 +148,25 @@ public class ViewKeyYubiKeyFragment extends Fragment
};
// Send all information needed to service to decrypt in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
Intent intent = new Intent(getActivity(), KeychainService.class);
// fill values for this action
intent.setAction(KeychainIntentService.ACTION_PROMOTE_KEYRING);
intent.setAction(KeychainService.ACTION_PROMOTE_KEYRING);
Bundle data = new Bundle();
data.putLong(KeychainIntentService.PROMOTE_MASTER_KEY_ID, mMasterKeyId);
data.putByteArray(KeychainIntentService.PROMOTE_CARD_AID, mCardAid);
data.putLong(KeychainService.PROMOTE_MASTER_KEY_ID, mMasterKeyId);
data.putByteArray(KeychainService.PROMOTE_CARD_AID, mCardAid);
long[] subKeyIds = new long[mFingerprints.length];
for (int i = 0; i < subKeyIds.length; i++) {
subKeyIds[i] = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints[i]);
}
data.putLongArray(KeychainIntentService.PROMOTE_SUBKEY_IDS, subKeyIds);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
data.putLongArray(KeychainService.PROMOTE_SUBKEY_IDS, subKeyIds);
intent.putExtra(KeychainService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
// start service with intent
getActivity().startService(intent);

View File

@ -109,7 +109,7 @@ public class ImportKeysListCloudLoader
ImportKeysListEntry uniqueEntry = searchResult.get(0);
/*
* set fingerprint explicitly after query
* to enforce a check when the key is imported by KeychainIntentService
* to enforce a check when the key is imported by KeychainService
*/
uniqueEntry.setFingerprintHex(fingerprint);
uniqueEntry.setSelected(true);

View File

@ -18,15 +18,17 @@
package org.sufficientlysecure.keychain.ui.adapter;
import java.util.Calendar;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.TimeZone;
import java.util.List;
import android.content.Context;
import android.database.Cursor;
import android.graphics.PorterDuff;
import android.support.v4.widget.CursorAdapter;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
@ -59,7 +61,6 @@ public class KeyAdapter extends CursorAdapter {
KeyRings.VERIFIED,
KeyRings.HAS_ANY_SECRET,
KeyRings.HAS_DUPLICATE_USER_ID,
KeyRings.HAS_ENCRYPT,
KeyRings.FINGERPRINT,
KeyRings.CREATION,
};
@ -71,9 +72,8 @@ public class KeyAdapter extends CursorAdapter {
public static final int INDEX_VERIFIED = 5;
public static final int INDEX_HAS_ANY_SECRET = 6;
public static final int INDEX_HAS_DUPLICATE_USER_ID = 7;
public static final int INDEX_HAS_ENCRYPT = 8;
public static final int INDEX_FINGERPRINT = 9;
public static final int INDEX_CREATION = 10;
public static final int INDEX_FINGERPRINT = 8;
public static final int INDEX_CREATION = 9;
public KeyAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
@ -86,6 +86,7 @@ public class KeyAdapter extends CursorAdapter {
}
public static class KeyItemViewHolder {
public View mView;
public Long mMasterKeyId;
public TextView mMainUserId;
public TextView mMainUserIdRest;
@ -95,6 +96,7 @@ public class KeyAdapter extends CursorAdapter {
public ImageButton mSlingerButton;
public KeyItemViewHolder(View view) {
mView = view;
mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name);
mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email);
mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon);
@ -103,11 +105,10 @@ public class KeyAdapter extends CursorAdapter {
mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation);
}
public void setData(Context context, Cursor cursor, Highlighter highlighter) {
public void setData(Context context, KeyItem item, Highlighter highlighter) {
{ // set name and stuff, common to both key types
String userId = cursor.getString(INDEX_USER_ID);
KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId);
KeyRing.UserId userIdSplit = item.mUserId;
if (userIdSplit.name != null) {
mMainUserId.setText(highlighter.highlight(userIdSplit.name));
} else {
@ -123,30 +124,23 @@ public class KeyAdapter extends CursorAdapter {
{ // set edit button and status, specific by key type
long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
boolean isSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0;
boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0;
boolean hasDuplicate = cursor.getInt(INDEX_HAS_DUPLICATE_USER_ID) != 0;
mMasterKeyId = masterKeyId;
mMasterKeyId = item.mKeyId;
// Note: order is important!
if (isRevoked) {
if (item.mIsRevoked) {
KeyFormattingUtils
.setStatusImage(context, mStatus, null, State.REVOKED, R.color.bg_gray);
mStatus.setVisibility(View.VISIBLE);
mSlinger.setVisibility(View.GONE);
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
} else if (isExpired) {
} else if (item.mIsExpired) {
KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.bg_gray);
mStatus.setVisibility(View.VISIBLE);
mSlinger.setVisibility(View.GONE);
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
} else if (isSecret) {
} else if (item.mIsSecret) {
mStatus.setVisibility(View.GONE);
if (mSlingerButton.hasOnClickListeners()) {
mSlinger.setVisibility(View.VISIBLE);
@ -157,7 +151,7 @@ public class KeyAdapter extends CursorAdapter {
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
} else {
// this is a public key - show if it's verified
if (isVerified) {
if (item.mIsVerified) {
KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED);
mStatus.setVisibility(View.VISIBLE);
} else {
@ -169,9 +163,9 @@ public class KeyAdapter extends CursorAdapter {
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
}
if (hasDuplicate) {
if (item.mHasDuplicate) {
String dateTime = DateUtils.formatDateTime(context,
cursor.getLong(INDEX_CREATION) * 1000,
item.mCreation.getTime(),
DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_SHOW_YEAR
| DateUtils.FORMAT_ABBREV_MONTH);
@ -189,6 +183,10 @@ public class KeyAdapter extends CursorAdapter {
}
public boolean isEnabled(Cursor cursor) {
return true;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(R.layout.key_list_item, parent, false);
@ -203,7 +201,8 @@ public class KeyAdapter extends CursorAdapter {
public void bindView(View view, Context context, Cursor cursor) {
Highlighter highlighter = new Highlighter(context, mQuery);
KeyItemViewHolder h = (KeyItemViewHolder) view.getTag();
h.setData(context, cursor, highlighter);
KeyItem item = new KeyItem(cursor);
h.setData(context, item, highlighter);
}
public boolean isSecretAvailable(int id) {
@ -233,14 +232,16 @@ public class KeyAdapter extends CursorAdapter {
@Override
public long getItemId(int position) {
Cursor cursor = getCursor();
// prevent a crash on rapid cursor changes
if (getCursor().isClosed()) {
if (cursor != null && getCursor().isClosed()) {
return 0L;
}
return super.getItemId(position);
}
public static class KeyItem {
// must be serializable for TokenCompleTextView state
public static class KeyItem implements Serializable {
public final String mUserIdFull;
public final KeyRing.UserId mUserId;
@ -248,6 +249,7 @@ public class KeyAdapter extends CursorAdapter {
public final boolean mHasDuplicate;
public final Date mCreation;
public final String mFingerprint;
public final boolean mIsSecret, mIsRevoked, mIsExpired, mIsVerified;
private KeyItem(Cursor cursor) {
String userId = cursor.getString(INDEX_USER_ID);
@ -258,6 +260,10 @@ public class KeyAdapter extends CursorAdapter {
mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000);
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
cursor.getBlob(INDEX_FINGERPRINT));
mIsSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
mIsRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
mIsExpired = cursor.getInt(INDEX_IS_EXPIRED) > 0;
mIsVerified = cursor.getInt(INDEX_VERIFIED) > 0;
}
public KeyItem(CanonicalizedPublicKeyRing ring) {
@ -270,6 +276,12 @@ public class KeyAdapter extends CursorAdapter {
mCreation = key.getCreationTime();
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
ring.getFingerprint());
mIsRevoked = key.isRevoked();
mIsExpired = key.isExpired();
// these two are actually "don't know"s
mIsSecret = false;
mIsVerified = false;
}
public String getReadableName() {
@ -282,4 +294,11 @@ public class KeyAdapter extends CursorAdapter {
}
public static String[] getProjectionWith(String[] projection) {
List<String> list = new ArrayList<>();
list.addAll(Arrays.asList(PROJECTION));
list.addAll(Arrays.asList(projection));
return list.toArray(new String[list.size()]);
}
}

View File

@ -40,20 +40,19 @@ public class MultiUserIdsAdapter extends CursorAdapter {
private LayoutInflater mInflater;
private final ArrayList<Boolean> mCheckStates;
public MultiUserIdsAdapter(Context context, Cursor c, int flags) {
public MultiUserIdsAdapter(Context context, Cursor c, int flags, ArrayList<Boolean> preselectStates) {
super(context, c, flags);
mInflater = LayoutInflater.from(context);
mCheckStates = new ArrayList<>();
mCheckStates = preselectStates == null ? new ArrayList<Boolean>() : preselectStates;
}
@Override
public Cursor swapCursor(Cursor newCursor) {
mCheckStates.clear();
if (newCursor != null) {
int count = newCursor.getCount();
mCheckStates.ensureCapacity(count);
// initialize to true (use case knowledge: we usually want to sign all uids)
for (int i = 0; i < count; i++) {
// initialize new fields to true (use case knowledge: we usually want to sign all uids)
for (int i = mCheckStates.size(); i < count; i++) {
mCheckStates.add(true);
}
}
@ -151,6 +150,10 @@ public class MultiUserIdsAdapter extends CursorAdapter {
}
public ArrayList<Boolean> getCheckStates() {
return mCheckStates;
}
public ArrayList<CertifyAction> getSelectedCertifyActions() {
LongSparseArray<CertifyAction> actions = new LongSparseArray<>();
for (int i = 0; i < mCheckStates.size(); i++) {

View File

@ -116,6 +116,21 @@ public class SubkeysAdapter extends CursorAdapter {
}
}
public int getAlgorithm(int position) {
mCursor.moveToPosition(position);
return mCursor.getInt(INDEX_ALGORITHM);
}
public int getKeySize(int position) {
mCursor.moveToPosition(position);
return mCursor.getInt(INDEX_KEY_SIZE);
}
public SecretKeyType getSecretKeyType(int position) {
mCursor.moveToPosition(position);
return SecretKeyType.fromNum(mCursor.getInt(INDEX_HAS_SECRET));
}
@Override
public Cursor swapCursor(Cursor newCursor) {
hasAnySecret = false;
@ -164,13 +179,23 @@ public class SubkeysAdapter extends CursorAdapter {
? mSaveKeyringParcel.getSubkeyChange(keyId)
: null;
if (change != null && change.mDummyStrip) {
algorithmStr.append(", ");
final SpannableString boldStripped = new SpannableString(
context.getString(R.string.key_stripped)
);
boldStripped.setSpan(new StyleSpan(Typeface.BOLD), 0, boldStripped.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
algorithmStr.append(boldStripped);
if (change != null && (change.mDummyStrip || change.mMoveKeyToCard)) {
if (change.mDummyStrip) {
algorithmStr.append(", ");
final SpannableString boldStripped = new SpannableString(
context.getString(R.string.key_stripped)
);
boldStripped.setSpan(new StyleSpan(Typeface.BOLD), 0, boldStripped.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
algorithmStr.append(boldStripped);
}
if (change.mMoveKeyToCard) {
algorithmStr.append(", ");
final SpannableString boldDivert = new SpannableString(
context.getString(R.string.key_divert)
);
boldDivert.setSpan(new StyleSpan(Typeface.BOLD), 0, boldDivert.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
algorithmStr.append(boldDivert);
}
} else {
switch (SecretKeyType.fromNum(cursor.getInt(INDEX_HAS_SECRET))) {
case GNU_DUMMY:

View File

@ -19,7 +19,9 @@
package org.sufficientlysecure.keychain.ui.base;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.interfaces.RSAPrivateCrtKey;
import android.app.Activity;
import android.app.PendingIntent;
@ -32,9 +34,12 @@ import android.os.Bundle;
import android.widget.Toast;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.util.Arrays;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
@ -56,12 +61,14 @@ import org.sufficientlysecure.keychain.util.Preferences;
public abstract class BaseNfcActivity extends BaseActivity {
public static final int REQUEST_CODE_PASSPHRASE = 1;
public static final int REQUEST_CODE_PIN = 1;
protected Passphrase mPin;
protected Passphrase mAdminPin;
protected boolean mPw1ValidForMultipleSignatures;
protected boolean mPw1ValidatedForSignature;
protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming?
protected boolean mPw3Validated;
private NfcAdapter mNfcAdapter;
private IsoDep mIsoDep;
@ -88,6 +95,8 @@ public abstract class BaseNfcActivity extends BaseActivity {
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
try {
handleNdefDiscoveredIntent(intent);
} catch (CardException e) {
handleNfcError(e);
} catch (IOException e) {
handleNfcError(e);
}
@ -98,6 +107,81 @@ public abstract class BaseNfcActivity extends BaseActivity {
Log.e(Constants.TAG, "nfc error", e);
Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show();
}
public void handleNfcError(CardException e) {
Log.e(Constants.TAG, "card error", e);
short status = e.getResponseCode();
// When entering a PIN, a status of 63CX indicates X attempts remaining.
if ((status & (short)0xFFF0) == 0x63C0) {
Notify.create(this, getString(R.string.error_pin, status & 0x000F), Style.WARN).show();
return;
}
// Otherwise, all status codes are fixed values.
switch (status) {
// These errors should not occur in everyday use; if they are returned, it means we
// made a mistake sending data to the card, or the card is misbehaving.
case 0x6A80: {
Notify.create(this, getString(R.string.error_nfc_bad_data), Style.WARN).show();
break;
}
case 0x6883: {
Notify.create(this, getString(R.string.error_nfc_chaining_error), Style.WARN).show();
break;
}
case 0x6B00: {
Notify.create(this, getString(R.string.error_nfc_header, "P1/P2"), Style.WARN).show();
break;
}
case 0x6D00: {
Notify.create(this, getString(R.string.error_nfc_header, "INS"), Style.WARN).show();
break;
}
case 0x6E00: {
Notify.create(this, getString(R.string.error_nfc_header, "CLA"), Style.WARN).show();
break;
}
// These error conditions are more likely to be experienced by an end user.
case 0x6285: {
Notify.create(this, getString(R.string.error_nfc_terminated), Style.WARN).show();
break;
}
case 0x6700: {
Notify.create(this, getString(R.string.error_nfc_wrong_length), Style.WARN).show();
break;
}
case 0x6982: {
Notify.create(this, getString(R.string.error_nfc_security_not_satisfied),
Style.WARN).show();
break;
}
case 0x6983: {
Notify.create(this, getString(R.string.error_nfc_authentication_blocked),
Style.WARN).show();
break;
}
case 0x6985: {
Notify.create(this, getString(R.string.error_nfc_conditions_not_satisfied),
Style.WARN).show();
break;
}
// 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases.
case 0x6A88:
case 0x6A83: {
Notify.create(this, getString(R.string.error_nfc_data_not_found), Style.WARN).show();
break;
}
// 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an
// unhandled exception on the smart card.
case 0x6F00: {
Notify.create(this, getString(R.string.error_nfc_unknown), Style.WARN).show();
break;
}
default:
Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show();
}
}
@ -149,7 +233,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
Intent intent = new Intent(this, PassphraseDialogActivity.class);
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT,
RequiredInputParcel.createRequiredPassphrase(requiredInput));
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
startActivityForResult(intent, REQUEST_CODE_PIN);
} catch (KeyNotFoundException e) {
throw new AssertionError(
"tried to find passphrase for non-existing key. this is a programming error!");
@ -164,7 +248,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_PASSPHRASE:
case REQUEST_CODE_PIN: {
if (resultCode != Activity.RESULT_OK) {
setResult(resultCode);
finish();
@ -173,6 +257,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
mPin = input.getPassphrase();
break;
}
default:
super.onActivityResult(requestCode, resultCode, data);
@ -215,14 +300,19 @@ public abstract class BaseNfcActivity extends BaseActivity {
+ "06" // Lc (number of bytes)
+ "D27600012401" // Data (6 bytes)
+ "00"; // Le
if ( ! nfcCommunicate(opening).endsWith(accepted)) { // activate connection
throw new IOException("Initialization failed!");
String response = nfcCommunicate(opening); // activate connection
if ( ! response.endsWith(accepted) ) {
throw new CardException("Initialization failed!", parseCardStatus(response));
}
byte[] pwStatusBytes = nfcGetPwStatusBytes();
mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1);
mPw1ValidatedForSignature = false;
mPw1ValidatedForDecrypt = false;
mPw3Validated = false;
// TODO: Handle non-default Admin PIN
mAdminPin = new Passphrase("12345678");
onNfcPerform();
@ -427,7 +517,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
}
if ( ! "9000".equals(status)) {
throw new IOException("Bad NFC response code: " + status);
throw new CardException("Bad NFC response code: " + status, parseCardStatus(response));
}
// Make sure the signature we received is actually the expected number of bytes long!
@ -472,13 +562,21 @@ public abstract class BaseNfcActivity extends BaseActivity {
return Hex.decode(decryptedSessionKey);
}
/** Verifies the user's PW1 with the appropriate mode.
/** Verifies the user's PW1 or PW3 with the appropriate mode.
*
* @param mode This is 0x81 for signing, 0x82 for everything else
* @param mode For PW1, this is 0x81 for signing, 0x82 for everything else.
* For PW3 (Admin PIN), mode is 0x83.
*/
public void nfcVerifyPIN(int mode) throws IOException {
if (mPin != null) {
byte[] pin = new String(mPin.getCharArray()).getBytes();
if (mPin != null || mode == 0x83) {
byte[] pin;
if (mode == 0x83) {
pin = new String(mAdminPin.getCharArray()).getBytes();
} else {
pin = new String(mPin.getCharArray()).getBytes();
}
// SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
// See specification, page 51
String accepted = "9000";
@ -491,19 +589,233 @@ public abstract class BaseNfcActivity extends BaseActivity {
+ String.format("%02x", mode) // P2
+ String.format("%02x", pin.length) // Lc
+ Hex.toHexString(pin);
if (!nfcCommunicate(login).equals(accepted)) { // login
String response = nfcCommunicate(login); // login
if (!response.equals(accepted)) {
handlePinError();
throw new IOException("Bad PIN!");
throw new CardException("Bad PIN!", parseCardStatus(response));
}
if (mode == 0x81) {
mPw1ValidatedForSignature = true;
} else if (mode == 0x82) {
mPw1ValidatedForDecrypt = true;
} else if (mode == 0x83) {
mPw3Validated = true;
}
}
}
/** Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for
* conformance to the card's requirements for key length.
*
* @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83.
* @param newPinString The new PW1 or PW3.
*/
public void nfcModifyPIN(int pw, String newPinString) throws IOException {
final int MAX_PW1_LENGTH_INDEX = 1;
final int MAX_PW3_LENGTH_INDEX = 3;
byte[] pwStatusBytes = nfcGetPwStatusBytes();
byte[] newPin = newPinString.getBytes();
if (pw == 0x81) {
if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) {
throw new IOException("Invalid PIN length");
}
} else if (pw == 0x83) {
if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) {
throw new IOException("Invalid PIN length");
}
} else {
throw new IOException("Invalid PW index for modify PIN operation");
}
byte[] pin;
if (pw == 0x83) {
pin = new String(mAdminPin.getCharArray()).getBytes();
} else {
pin = new String(mPin.getCharArray()).getBytes();
}
// Command APDU for CHANGE REFERENCE DATA command (page 32)
String changeReferenceDataApdu = "00" // CLA
+ "24" // INS
+ "00" // P1
+ String.format("%02x", pw) // P2
+ String.format("%02x", pin.length + newPin.length) // Lc
+ getHex(pin)
+ getHex(newPin);
String response = nfcCommunicate(changeReferenceDataApdu); // change PIN
if (!response.equals("9000")) {
handlePinError();
throw new CardException("Failed to change PIN", parseCardStatus(response));
}
}
/**
* Stores a data object on the card. Automatically validates the proper PIN for the operation.
* Supported for all data objects < 255 bytes in length. Only the cardholder certificate
* (0x7F21) can exceed this length.
*
* @param dataObject The data object to be stored.
* @param data The data to store in the object
*/
public void nfcPutData(int dataObject, byte[] data) throws IOException {
if (data.length > 254) {
throw new IOException("Cannot PUT DATA with length > 254");
}
if (dataObject == 0x0101 || dataObject == 0x0103) {
if (!mPw1ValidatedForDecrypt) {
nfcVerifyPIN(0x82); // (Verify PW1 for non-signing operations)
}
} else if (!mPw3Validated) {
nfcVerifyPIN(0x83); // (Verify PW3)
}
String putDataApdu = "00" // CLA
+ "DA" // INS
+ String.format("%02x", (dataObject & 0xFF00) >> 8) // P1
+ String.format("%02x", dataObject & 0xFF) // P2
+ String.format("%02x", data.length) // Lc
+ getHex(data);
String response = nfcCommunicate(putDataApdu); // put data
if (!response.equals("9000")) {
throw new CardException("Failed to put data.", parseCardStatus(response));
}
}
/**
* Puts a key on the card in the given slot.
*
* @param slot The slot on the card where the key should be stored:
* 0xB6: Signature Key
* 0xB8: Decipherment Key
* 0xA4: Authentication Key
*/
public void nfcPutKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase)
throws IOException {
if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) {
throw new IOException("Invalid key slot");
}
RSAPrivateCrtKey crtSecretKey = null;
try {
secretKey.unlock(passphrase);
crtSecretKey = secretKey.getCrtSecretKey();
} catch (PgpGeneralException e) {
throw new IOException(e.getMessage());
}
// Shouldn't happen; the UI should block the user from getting an incompatible key this far.
if (crtSecretKey.getModulus().bitLength() > 2048) {
throw new IOException("Key too large to export to smart card.");
}
// Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537.
if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) {
throw new IOException("Invalid public exponent for smart card key.");
}
if (!mPw3Validated) {
nfcVerifyPIN(0x83); // (Verify PW1 with mode 83)
}
byte[] header= Hex.decode(
"4D82" + "03A2" // Extended header list 4D82, length of 930 bytes. (page 23)
+ String.format("%02x", slot) + "00" // CRT to indicate targeted key, no length
+ "7F48" + "15" // Private key template 0x7F48, length 21 (decimal, 0x15 hex)
+ "9103" // Public modulus, length 3
+ "928180" // Prime P, length 128
+ "938180" // Prime Q, length 128
+ "948180" // Coefficient (1/q mod p), length 128
+ "958180" // Prime exponent P (d mod (p - 1)), length 128
+ "968180" // Prime exponent Q (d mod (1 - 1)), length 128
+ "97820100" // Modulus, length 256, last item in private key template
+ "5F48" + "820383");// DO 5F48; 899 bytes of concatenated key data will follow
byte[] dataToSend = new byte[934];
byte[] currentKeyObject;
int offset = 0;
System.arraycopy(header, 0, dataToSend, offset, header.length);
offset += header.length;
currentKeyObject = crtSecretKey.getPublicExponent().toByteArray();
System.arraycopy(currentKeyObject, 0, dataToSend, offset, 3);
offset += 3;
// NOTE: For a 2048-bit key, these lengths are fixed. However, bigint includes a leading 0
// in the array to represent sign, so we take care to set the offset to 1 if necessary.
currentKeyObject = crtSecretKey.getPrimeP().toByteArray();
System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
Arrays.fill(currentKeyObject, (byte)0);
offset += 128;
currentKeyObject = crtSecretKey.getPrimeQ().toByteArray();
System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
Arrays.fill(currentKeyObject, (byte)0);
offset += 128;
currentKeyObject = crtSecretKey.getCrtCoefficient().toByteArray();
System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
Arrays.fill(currentKeyObject, (byte)0);
offset += 128;
currentKeyObject = crtSecretKey.getPrimeExponentP().toByteArray();
System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
Arrays.fill(currentKeyObject, (byte)0);
offset += 128;
currentKeyObject = crtSecretKey.getPrimeExponentQ().toByteArray();
System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
Arrays.fill(currentKeyObject, (byte)0);
offset += 128;
currentKeyObject = crtSecretKey.getModulus().toByteArray();
System.arraycopy(currentKeyObject, currentKeyObject.length - 256, dataToSend, offset, 256);
String putKeyCommand = "10DB3FFF";
String lastPutKeyCommand = "00DB3FFF";
// Now we're ready to communicate with the card.
offset = 0;
String response;
while(offset < dataToSend.length) {
int dataRemaining = dataToSend.length - offset;
if (dataRemaining > 254) {
response = nfcCommunicate(
putKeyCommand + "FE" + Hex.toHexString(dataToSend, offset, 254)
);
offset += 254;
} else {
int length = dataToSend.length - offset;
response = nfcCommunicate(
lastPutKeyCommand + String.format("%02x", length)
+ Hex.toHexString(dataToSend, offset, length));
offset += length;
}
if (!response.endsWith("9000")) {
throw new CardException("Key export to card failed", parseCardStatus(response));
}
}
// Clear array with secret data before we return.
Arrays.fill(dataToSend, (byte) 0);
}
/**
* Parses out the status word from a JavaCard response string.
*
* @param response A hex string with the response from the card
* @return A short indicating the SW1/SW2, or 0 if a status could not be determined.
*/
short parseCardStatus(String response) {
if (response.length() < 4) {
return 0; // invalid input
}
try {
return Short.parseShort(response.substring(response.length() - 4), 16);
} catch (NumberFormatException e) {
return 0;
}
}
/**
* Prints a message to the screen
*
@ -573,4 +885,18 @@ public abstract class BaseNfcActivity extends BaseActivity {
return new String(Hex.encode(raw));
}
public class CardException extends IOException {
private short mResponseCode;
public CardException(String detailMessage, short responseCode) {
super(detailMessage);
mResponseCode = responseCode;
}
public short getResponseCode() {
return mResponseCode;
}
}
}

View File

@ -0,0 +1,53 @@
package org.sufficientlysecure.keychain.ui.base;
import android.os.Bundle;
import android.os.Parcelable;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
public abstract class CachingCryptoOperationFragment <T extends Parcelable, S extends OperationResult>
extends CryptoOperationFragment<T, S> {
public static final String ARG_CACHED_ACTIONS = "cached_actions";
private T mCachedActionsParcel;
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(ARG_CACHED_ACTIONS, mCachedActionsParcel);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mCachedActionsParcel = savedInstanceState.getParcelable(ARG_CACHED_ACTIONS);
}
}
@Override
protected void onCryptoOperationResult(S result) {
super.onCryptoOperationResult(result);
mCachedActionsParcel = null;
}
protected abstract T createOperationInput();
protected T getCachedActionsParcel() {
return mCachedActionsParcel;
}
protected void cacheActionsParcel(T cachedActionsParcel) {
mCachedActionsParcel = cachedActionsParcel;
}
protected void onCryptoOperationCancelled() {
mCachedActionsParcel = null;
}
}

View File

@ -19,23 +19,32 @@
package org.sufficientlysecure.keychain.ui.base;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.service.KeychainNewService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.NfcOperationActivity;
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
/**
* All fragments executing crypto operations need to extend this class.
*/
public abstract class CryptoOperationFragment extends Fragment {
public abstract class CryptoOperationFragment <T extends Parcelable, S extends OperationResult>
extends Fragment {
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
public static final int REQUEST_CODE_NFC = 0x00008002;
@ -43,6 +52,7 @@ public abstract class CryptoOperationFragment extends Fragment {
private void initiateInputActivity(RequiredInputParcel requiredInput) {
switch (requiredInput.mType) {
case NFC_KEYTOCARD:
case NFC_DECRYPT:
case NFC_SIGN: {
Intent intent = new Intent(getActivity(), NfcOperationActivity.class);
@ -97,35 +107,111 @@ public abstract class CryptoOperationFragment extends Fragment {
}
}
public boolean handlePendingMessage(Message message) {
protected void dismissProgress() {
if (message.arg1 == ServiceProgressHandler.MessageStatus.OKAY.ordinal()) {
Bundle data = message.getData();
ProgressDialogFragment progressDialogFragment =
(ProgressDialogFragment) getFragmentManager().findFragmentByTag("progressDialog");
OperationResult result = data.getParcelable(OperationResult.EXTRA_RESULT);
if (result == null || !(result instanceof InputPendingResult)) {
return false;
}
InputPendingResult pendingResult = (InputPendingResult) result;
if (pendingResult.isPending()) {
RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel();
initiateInputActivity(requiredInput);
return true;
}
if (progressDialogFragment == null) {
return;
}
return false;
progressDialogFragment.dismissAllowingStateLoss();
}
protected abstract T createOperationInput();
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
T operationInput = createOperationInput();
if (operationInput == null) {
return;
}
// Send all information needed to service to edit key in other thread
Intent intent = new Intent(getActivity(), KeychainNewService.class);
intent.putExtra(KeychainNewService.EXTRA_OPERATION_INPUT, operationInput);
intent.putExtra(KeychainNewService.EXTRA_CRYPTO_INPUT, cryptoInput);
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
@Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
// get returned data bundle
Bundle returnData = message.getData();
if (returnData == null) {
return;
}
final OperationResult result =
returnData.getParcelable(OperationResult.EXTRA_RESULT);
onHandleResult(result);
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
saveHandler.showProgressDialog(
getString(R.string.progress_building_key),
ProgressDialog.STYLE_HORIZONTAL, false);
getActivity().startService(intent);
}
protected void cryptoOperation() {
cryptoOperation(new CryptoInputParcel());
}
protected abstract void cryptoOperation(CryptoInputParcel cryptoInput);
protected void onCryptoOperationCancelled() {
// Nothing to do here, in most cases
protected void onCryptoOperationResult(S result) {
if (result.success()) {
onCryptoOperationSuccess(result);
} else {
onCryptoOperationError(result);
}
}
abstract protected void onCryptoOperationSuccess(S result);
protected void onCryptoOperationError(S result) {
result.createNotify(getActivity()).show();
}
protected void onCryptoOperationCancelled() {
}
public void onHandleResult(OperationResult result) {
if (result instanceof InputPendingResult) {
InputPendingResult pendingResult = (InputPendingResult) result;
if (pendingResult.isPending()) {
RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel();
initiateInputActivity(requiredInput);
return;
}
}
dismissProgress();
try {
// noinspection unchecked, because type erasure :(
onCryptoOperationResult((S) result);
} catch (ClassCastException e) {
throw new AssertionError("bad return class ("
+ result.getClass().getSimpleName() + "), this is a programming error!");
}
}
}

View File

@ -17,6 +17,12 @@
package org.sufficientlysecure.keychain.ui.dialog;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@ -29,8 +35,8 @@ import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.test.suitebuilder.TestSuiteBuilder;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@ -47,15 +53,6 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.TlsHelper;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
public class AddKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener {
private static final String ARG_MESSENGER = "messenger";
@ -70,20 +67,11 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
private EditText mKeyserverEditText;
private CheckBox mVerifyKeyserverCheckBox;
public static enum FailureReason {
public enum FailureReason {
INVALID_URL,
CONNECTION_FAILED
}
;
/**
* Creates new instance of this dialog fragment
*
* @param title title of dialog
* @param messenger to communicate back after setting the passphrase
* @return
*/
public static AddKeyserverDialogFragment newInstance(Messenger messenger) {
AddKeyserverDialogFragment frag = new AddKeyserverDialogFragment();
Bundle args = new Bundle();
@ -94,9 +82,7 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
return frag;
}
/**
* Creates dialog
*/
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
@ -222,19 +208,23 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
String scheme = keyserverUri.getScheme();
String schemeSpecificPart = keyserverUri.getSchemeSpecificPart();
String fragment = keyserverUri.getFragment();
if (scheme == null) throw new MalformedURLException();
if (scheme.equalsIgnoreCase("hkps")) scheme = "https";
else if (scheme.equalsIgnoreCase("hkp")) scheme = "http";
if (scheme == null) {
throw new MalformedURLException();
}
if ("hkps".equalsIgnoreCase(scheme)) {
scheme = "https";
} else if ("hkp".equalsIgnoreCase(scheme)) {
scheme = "http";
}
URI newKeyserver = new URI(scheme, schemeSpecificPart, fragment);
Log.d("Converted URL", newKeyserver.toString());
TlsHelper.openConnection(newKeyserver.toURL()).getInputStream();
Log.d(Constants.TAG, "Converted URL" + newKeyserver);
// just see if we can get a connection, then immediately close
TlsHelper.openConnection(newKeyserver.toURL()).getInputStream().close();
} catch (TlsHelper.TlsHelperException e) {
reason = FailureReason.CONNECTION_FAILED;
} catch (MalformedURLException e) {
Log.w(Constants.TAG, "Invalid keyserver URL entered by user.");
reason = FailureReason.INVALID_URL;
} catch (URISyntaxException e) {
} catch (MalformedURLException | URISyntaxException e) {
Log.w(Constants.TAG, "Invalid keyserver URL entered by user.");
reason = FailureReason.INVALID_URL;
} catch (IOException e) {

View File

@ -36,7 +36,7 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.util.Log;
@ -130,17 +130,12 @@ public class DeleteKeyDialogFragment extends DialogFragment {
public void onClick(DialogInterface dialog, int which) {
// Send all information needed to service to import key in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
Intent intent = new Intent(getActivity(), KeychainService.class);
intent.setAction(KeychainIntentService.ACTION_DELETE);
intent.setAction(KeychainService.ACTION_DELETE);
// Message is received after importing is done in KeychainIntentService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
getActivity(),
getString(R.string.progress_deleting),
ProgressDialog.STYLE_HORIZONTAL,
true,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
// Message is received after importing is done in KeychainService
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
@Override
public void handleMessage(Message message) {
super.handleMessage(message);
@ -159,16 +154,17 @@ public class DeleteKeyDialogFragment extends DialogFragment {
// fill values for this action
Bundle data = new Bundle();
data.putLongArray(KeychainIntentService.DELETE_KEY_LIST, masterKeyIds);
data.putBoolean(KeychainIntentService.DELETE_IS_SECRET, hasSecret);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
data.putLongArray(KeychainService.DELETE_KEY_LIST, masterKeyIds);
data.putBoolean(KeychainService.DELETE_IS_SECRET, hasSecret);
intent.putExtra(KeychainService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
// show progress dialog
saveHandler.showProgressDialog(getActivity());
saveHandler.showProgressDialog(getString(R.string.progress_deleting),
ProgressDialog.STYLE_HORIZONTAL, true);
// start service with intent
getActivity().startService(intent);

View File

@ -35,6 +35,7 @@ public class EditSubkeyDialogFragment extends DialogFragment {
public static final int MESSAGE_CHANGE_EXPIRY = 1;
public static final int MESSAGE_REVOKE = 2;
public static final int MESSAGE_STRIP = 3;
public static final int MESSAGE_KEYTOCARD = 4;
private Messenger mMessenger;
@ -76,6 +77,9 @@ public class EditSubkeyDialogFragment extends DialogFragment {
case 2:
sendMessageToHandler(MESSAGE_STRIP, null);
break;
case 3:
sendMessageToHandler(MESSAGE_KEYTOCARD, null);
break;
default:
break;
}

Some files were not shown because too many files have changed in this diff Show More