Merge branch 'master' into backup-api
Conflicts: OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java extern/openpgp-api-lib
This commit is contained in:
+6
-15
@@ -1,37 +1,28 @@
|
|||||||
#Android specific
|
# Android specific
|
||||||
bin
|
|
||||||
gen
|
|
||||||
obj
|
|
||||||
libs/armeabi
|
|
||||||
lint.xml
|
lint.xml
|
||||||
local.properties
|
local.properties
|
||||||
release.properties
|
release.properties
|
||||||
ant.properties
|
|
||||||
*.class
|
*.class
|
||||||
*.apk
|
*.apk
|
||||||
|
|
||||||
#Gradle
|
# Gradle
|
||||||
.gradle
|
.gradle
|
||||||
build
|
build
|
||||||
gradle.properties
|
|
||||||
# this is in here because the prepare-tests thing modifies it, and we DON'T
|
|
||||||
# want this to be commited. use git add -f to work on this file.
|
|
||||||
settings.gradle
|
|
||||||
|
|
||||||
#Maven
|
# Maven
|
||||||
target
|
target
|
||||||
pom.xml.*
|
pom.xml.*
|
||||||
|
|
||||||
#Eclipse
|
# Eclipse
|
||||||
.project
|
.project
|
||||||
.classpath
|
.classpath
|
||||||
.settings
|
.settings
|
||||||
.metadata
|
.metadata
|
||||||
|
|
||||||
#IntelliJ IDEA
|
# IntelliJ IDEA
|
||||||
.idea
|
.idea
|
||||||
*.iml
|
*.iml
|
||||||
|
|
||||||
#OS Specific
|
# OS Specific
|
||||||
[Tt]humbs.db
|
[Tt]humbs.db
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ addons:
|
|||||||
hostname: short-hostname
|
hostname: short-hostname
|
||||||
android:
|
android:
|
||||||
components:
|
components:
|
||||||
|
- tools
|
||||||
|
- build-tools-23.0.2
|
||||||
- build-tools-23.0.1
|
- build-tools-23.0.1
|
||||||
- android-23
|
- android-23
|
||||||
- android-22
|
- android-22
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[main]
|
[main]
|
||||||
host = https://www.transifex.com
|
host = https://www.transifex.com
|
||||||
lang_map = he: iw, zh_TW: zh-rTW, es_MX: es-rMX
|
lang_map = he: iw, zh_TW: zh-rTW, es_MX: es-rMX, pt_BR: pt-rBR
|
||||||
|
|
||||||
[open-keychain.strings]
|
[open-keychain.strings]
|
||||||
file_filter = OpenKeychain/src/main/res/values-<lang>/strings.xml
|
file_filter = OpenKeychain/src/main/res/values-<lang>/strings.xml
|
||||||
|
|||||||
+98
-99
@@ -8,11 +8,53 @@ dependencies {
|
|||||||
// NOTE: libraries are pinned to a specific build, see below
|
// NOTE: libraries are pinned to a specific build, see below
|
||||||
|
|
||||||
// from local Android SDK
|
// from local Android SDK
|
||||||
compile 'com.android.support:support-v4:23.2.0'
|
compile 'com.android.support:support-v4:23.3.0'
|
||||||
compile 'com.android.support:appcompat-v7:23.2.0'
|
compile 'com.android.support:appcompat-v7:23.3.0'
|
||||||
compile 'com.android.support:design:23.2.0'
|
compile 'com.android.support:design:23.3.0'
|
||||||
compile 'com.android.support:recyclerview-v7:23.2.0'
|
compile 'com.android.support:recyclerview-v7:23.3.0'
|
||||||
compile 'com.android.support:cardview-v7:23.2.0'
|
compile 'com.android.support:cardview-v7:23.3.0'
|
||||||
|
compile 'com.android.support:support-annotations:23.3.0'
|
||||||
|
|
||||||
|
// JCenter etc.
|
||||||
|
compile 'com.journeyapps:zxing-android-embedded:3.2.0@aar'
|
||||||
|
compile 'com.google.zxing:core:3.2.1'
|
||||||
|
compile 'com.jpardogo.materialtabstrip:library:1.1.0'
|
||||||
|
compile 'com.getbase:floatingactionbutton:1.10.1'
|
||||||
|
compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0'
|
||||||
|
compile 'org.ocpsoft.prettytime:prettytime:4.0.1.Final'
|
||||||
|
compile 'com.splitwise:tokenautocomplete:2.0.7@aar'
|
||||||
|
compile 'se.emilsjolander:stickylistheaders:2.7.0'
|
||||||
|
compile 'org.sufficientlysecure:html-textview:1.5'
|
||||||
|
compile 'org.sufficientlysecure:donations:2.4'
|
||||||
|
compile 'com.nispok:snackbar:2.11.0'
|
||||||
|
compile 'com.squareup.okhttp3:okhttp:3.2.0'
|
||||||
|
compile 'com.squareup.okhttp3:okhttp-urlconnection:3.2.0'
|
||||||
|
compile 'org.apache.james:apache-mime4j-core:0.7.2'
|
||||||
|
compile 'org.apache.james:apache-mime4j-dom:0.7.2'
|
||||||
|
compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0'
|
||||||
|
compile 'com.cocosw:bottomsheet:1.3.0@aar'
|
||||||
|
|
||||||
|
// Material Drawer
|
||||||
|
compile 'com.mikepenz:materialdrawer:5.2.2@aar'
|
||||||
|
compile 'com.mikepenz:fastadapter:1.4.4'
|
||||||
|
compile 'com.mikepenz:materialize:0.8.8'
|
||||||
|
compile 'com.mikepenz:iconics-core:2.5.11@aar'
|
||||||
|
compile 'com.mikepenz:google-material-typeface:2.2.0.1.original@aar'
|
||||||
|
compile 'com.mikepenz:fontawesome-typeface:4.6.0.1@aar'
|
||||||
|
compile 'com.mikepenz:community-material-typeface:1.5.54.1@aar'
|
||||||
|
|
||||||
|
// Nordpol
|
||||||
|
compile 'com.fidesmo:nordpol-android:0.1.18'
|
||||||
|
|
||||||
|
// libs as submodules
|
||||||
|
compile project(':openpgp-api-lib')
|
||||||
|
compile project(':openkeychain-api-lib')
|
||||||
|
compile project(':extern:bouncycastle:core')
|
||||||
|
compile project(':extern:bouncycastle:pg')
|
||||||
|
compile project(':extern:bouncycastle:prov')
|
||||||
|
compile project(':extern:minidns')
|
||||||
|
compile project(':KeybaseLib')
|
||||||
|
compile project(':safeslinger-exchange')
|
||||||
|
|
||||||
// Unit tests in the local JVM with Robolectric
|
// Unit tests in the local JVM with Robolectric
|
||||||
// https://developer.android.com/training/testing/unit-testing/local-unit-tests.html
|
// https://developer.android.com/training/testing/unit-testing/local-unit-tests.html
|
||||||
@@ -25,111 +67,68 @@ dependencies {
|
|||||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||||
|
|
||||||
// UI testing with Espresso
|
// UI testing with Espresso
|
||||||
androidTestCompile 'com.android.support.test:runner:0.4.1'
|
// Force usage of support libs in the test app, since they are internally used by the runner module.
|
||||||
androidTestCompile 'com.android.support.test:rules:0.4.1'
|
// https://github.com/googlesamples/android-testing/blob/master/ui/espresso/BasicSample/app/build.gradle#L28
|
||||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
|
androidTestCompile 'com.android.support:support-annotations:23.3.0'
|
||||||
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.1'
|
androidTestCompile 'com.android.support:appcompat-v7:23.3.0'
|
||||||
androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2.1') {
|
androidTestCompile 'com.android.support:design:23.3.0'
|
||||||
|
androidTestCompile 'com.android.support.test:runner:0.5'
|
||||||
|
androidTestCompile 'com.android.support.test:rules:0.5'
|
||||||
|
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
|
||||||
|
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
|
||||||
|
androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2.2') {
|
||||||
exclude group: 'com.android.support', module: 'appcompat'
|
exclude group: 'com.android.support', module: 'appcompat'
|
||||||
exclude group: 'com.android.support', module: 'support-v4'
|
exclude group: 'com.android.support', module: 'support-v4'
|
||||||
exclude module: 'recyclerview-v7'
|
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
|
|
||||||
configurations.all {
|
|
||||||
resolutionStrategy.force 'com.android.support:support-annotations:23.2.0'
|
|
||||||
}
|
|
||||||
|
|
||||||
// JCenter etc.
|
|
||||||
compile 'com.eftimoff:android-patternview:1.0.5@aar'
|
|
||||||
compile 'com.journeyapps:zxing-android-embedded:3.1.0@aar'
|
|
||||||
compile 'com.google.zxing:core:3.2.1'
|
|
||||||
compile 'com.jpardogo.materialtabstrip:library:1.1.0'
|
|
||||||
compile 'com.getbase:floatingactionbutton:1.10.1'
|
|
||||||
compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0'
|
|
||||||
compile 'org.ocpsoft.prettytime:prettytime:4.0.1.Final'
|
|
||||||
compile 'com.splitwise:tokenautocomplete:2.0.2@aar'
|
|
||||||
compile 'com.github.pinball83:masked-edittext:1.0.3'
|
|
||||||
compile 'se.emilsjolander:stickylistheaders:2.7.0'
|
|
||||||
compile 'org.sufficientlysecure:html-textview:1.3'
|
|
||||||
compile 'org.sufficientlysecure:donations:2.4'
|
|
||||||
compile 'com.nispok:snackbar:2.11.0'
|
|
||||||
compile 'com.squareup.okhttp:okhttp:2.7.1'
|
|
||||||
compile 'com.squareup.okhttp:okhttp-urlconnection:2.7.1'
|
|
||||||
compile 'org.apache.james:apache-mime4j-core:0.7.2'
|
|
||||||
compile 'org.apache.james:apache-mime4j-dom:0.7.2'
|
|
||||||
compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0'
|
|
||||||
compile 'com.cocosw:bottomsheet:1.3.0@aar'
|
|
||||||
|
|
||||||
// Material Drawer
|
|
||||||
compile 'com.mikepenz:materialdrawer:4.6.3@aar'
|
|
||||||
compile 'com.mikepenz:materialize:0.5.1'
|
|
||||||
compile 'com.mikepenz:iconics-core:2.5.3@aar'
|
|
||||||
compile 'com.mikepenz:google-material-typeface:2.2.0.1@aar'
|
|
||||||
compile 'com.mikepenz:fontawesome-typeface:4.5.0.1@aar'
|
|
||||||
compile 'com.mikepenz:community-material-typeface:1.3.41.1@aar'
|
|
||||||
|
|
||||||
// Nordpol
|
|
||||||
compile 'com.fidesmo:nordpol-android:0.1.18'
|
|
||||||
|
|
||||||
// libs as submodules
|
|
||||||
compile project(':extern:openpgp-api-lib:openpgp-api')
|
|
||||||
compile project(':extern:openkeychain-api-lib:openkeychain-intents')
|
|
||||||
compile project(':extern:bouncycastle:core')
|
|
||||||
compile project(':extern:bouncycastle:pg')
|
|
||||||
compile project(':extern:bouncycastle:prov')
|
|
||||||
compile project(':extern:minidns')
|
|
||||||
compile project(':extern:KeybaseLib:Lib')
|
|
||||||
compile project(':extern:safeslinger-exchange:safeslinger-exchange')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output of ./gradlew -q calculateChecksums
|
// Output of ./gradlew -q calculateChecksums
|
||||||
// Comment out the libs referenced as git submodules!
|
// Comment out the libs referenced as git submodules!
|
||||||
dependencyVerification {
|
dependencyVerification {
|
||||||
verify = [
|
verify = [
|
||||||
'com.android.support:support-v4:992666398b80724a2f95ea3d7bf7c3d94fb810dcba7ae1aa6e38c0d6120d2603',
|
'com.android.support:support-v4:1e8b7cc1cb3d6f6a2fd913791a6313df6bbaa470be450384474e906bc234bd49',
|
||||||
'com.android.support:appcompat-v7:14ab04eb2e3f302a082b79c308f6283d21909d1feb831a4e117cdacdad70adb7',
|
'com.android.support:appcompat-v7:dce81c41f76d83fa315617f4bc8ef2f84c5aa54b686f37b559422b939f622490',
|
||||||
'com.android.support:design:e7f2a383ba675cf65d834f27079630a1e373f2f3a08330bec6a7c798ef50b3b2',
|
'com.android.support:design:1023c9d1ca3ecae3e0c54b7678cd275032fe5720bbb4f58e3c8b2f2a595d0051',
|
||||||
'com.android.support:recyclerview-v7:81aad9ff4104a20d8d5cda49dbbe0f2925e7dcd607a689374db843c6f2eca20a',
|
'com.android.support:recyclerview-v7:32b98ca177d9352b19a92af80ff9a3c3589b50232b6722d2bce5514abe90be51',
|
||||||
'com.android.support:cardview-v7:b2c2c070a78fbf7683ab4d84b23f9eecf2af3848f126775d048ae62238b55aed',
|
'com.android.support:cardview-v7:2f592da4dd1db85dab99653474dd139135aa3ca7596dfdbcfef714e0339b603a',
|
||||||
'com.eftimoff:android-patternview:594dde382fb9a445ef0c92d614f6f127727ce699f124de8167929e10f298bf8b',
|
'com.android.support:support-annotations:e9e076f3ea4fb144387c6054a6f69a2f6150ad4b1907897aaf55d6e8f4b8b91e',
|
||||||
'com.journeyapps:zxing-android-embedded:90840a4457e68962fdfb74f691c6a736be7596291001045241901f1f0e6db2ac',
|
'com.journeyapps:zxing-android-embedded:afe4cd51d95ba0fd3a4bfe08c5a160bd32602aa174d511600ac824b6de4c79f1',
|
||||||
'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
|
'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
|
||||||
'com.jpardogo.materialtabstrip:library:24d19232b319f8c73e25793432357919a7ed972186f57a3b2c9093ea74ad8311',
|
'com.jpardogo.materialtabstrip:library:24d19232b319f8c73e25793432357919a7ed972186f57a3b2c9093ea74ad8311',
|
||||||
'com.getbase:floatingactionbutton:3edefa511aac4d90794c7b0496aca59cff2eee1e32679247b4f85acbeee05240',
|
'com.getbase:floatingactionbutton:3edefa511aac4d90794c7b0496aca59cff2eee1e32679247b4f85acbeee05240',
|
||||||
'org.commonjava.googlecode.markdown4j:markdown4j:e952e825d29e1317d96f79f346bfb6786c7c5eef50bd26e54a80823704b62e13',
|
'org.commonjava.googlecode.markdown4j:markdown4j:e952e825d29e1317d96f79f346bfb6786c7c5eef50bd26e54a80823704b62e13',
|
||||||
'org.ocpsoft.prettytime:prettytime:ef7098d973ae78b57d1a22dc37d3b8a771bf030301300e24055d676b6cdc5e75',
|
'org.ocpsoft.prettytime:prettytime:ef7098d973ae78b57d1a22dc37d3b8a771bf030301300e24055d676b6cdc5e75',
|
||||||
'com.splitwise:tokenautocomplete:2fc238424130b42155b5f2e39799a90bbbd13b148850afbe534ab08bb913c7f7',
|
'com.splitwise:tokenautocomplete:f56239588390f103b270b7c12361d99b06313a5a0410dc7f66e241ac4baf9baa',
|
||||||
'com.github.pinball83:masked-edittext:b1913d86482c7066ebb7831696773ac131865dc441cf8a3fc41d3b7d5691724e',
|
|
||||||
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
|
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
|
||||||
|
'org.sufficientlysecure:html-textview:b3799eea5f440b32bdec18360e4ce66f6f77814c8df9d382718506a7da0de961',
|
||||||
'org.sufficientlysecure:donations:96f8197bab26dfe41900d824f10f8f1914519cd62eedb77bdac5b223eccdf0a6',
|
'org.sufficientlysecure:donations:96f8197bab26dfe41900d824f10f8f1914519cd62eedb77bdac5b223eccdf0a6',
|
||||||
'org.sufficientlysecure:html-textview:39048e35894e582adada388e6c00631803283f8defed8e07ad58a5f284f272ee',
|
|
||||||
'com.squareup.okhttp:okhttp:8df336e3e93b22ba8c05da5d94caf968950db845869c7ca16ed682e065135aa8',
|
|
||||||
'com.nispok:snackbar:46b5eb9d630d329e13c2ce00ee9fb115ffb66c23c72cff32ee97eedd76824c6f',
|
'com.nispok:snackbar:46b5eb9d630d329e13c2ce00ee9fb115ffb66c23c72cff32ee97eedd76824c6f',
|
||||||
|
'com.squareup.okhttp3:okhttp:a41cdb7b024c56436a21e38f00b4d12e3b7e01451ffe6c4f545acba805bba03b',
|
||||||
|
'com.squareup.okhttp3:okhttp-urlconnection:7d6598a6665c166e2d4b78956a96056b9be7de192b3c923ccf4695d08e580833',
|
||||||
'org.apache.james:apache-mime4j-core:4d7434c68f94b81a253c12f28e6bbb4d6239c361d6086a46e22e594bb43ac660',
|
'org.apache.james:apache-mime4j-core:4d7434c68f94b81a253c12f28e6bbb4d6239c361d6086a46e22e594bb43ac660',
|
||||||
'com.squareup.okhttp:okhttp-urlconnection:8dce03792fd7b5f089dc4fc0fdcecbbe50ae6cca21cb08a787a3b902a9914111',
|
|
||||||
'org.thoughtcrime.ssl.pinning:AndroidPinning:afa1d74e699257fa75cb109ff29bac50726ef269c6e306bdeffe8223cee06ef4',
|
|
||||||
'org.apache.james:apache-mime4j-dom:7e6b06ee164a1c21b7e477249ea0b74a18fddce44764e5764085f58dd8c34633',
|
'org.apache.james:apache-mime4j-dom:7e6b06ee164a1c21b7e477249ea0b74a18fddce44764e5764085f58dd8c34633',
|
||||||
'com.mikepenz:materialdrawer:4e2644f454cc2ce48b956536d3339957c3f592adb2e0b6dad72d477da29f7677',
|
'org.thoughtcrime.ssl.pinning:AndroidPinning:afa1d74e699257fa75cb109ff29bac50726ef269c6e306bdeffe8223cee06ef4',
|
||||||
'com.cocosw:bottomsheet:4af6112a7f4cad4e2b70e5fdf1edc39f51275523a0f53011a012837dc103e597',
|
'com.cocosw:bottomsheet:4af6112a7f4cad4e2b70e5fdf1edc39f51275523a0f53011a012837dc103e597',
|
||||||
'com.mikepenz:iconics-core:d2495547db9d881168b1b502b1934f6a000ed5086c6c6a7114f3bbcbbb7ec306',
|
'com.mikepenz:materialdrawer:4169462fdde042e2bb53a7c2b4e2334d569d16b2020781ee05741b50e1a2967d',
|
||||||
'com.mikepenz:materialize:2457dbe0b874a422c0a21bc6716cf5af1d5a8d39387857ff7c20855ab5543bf8',
|
'com.mikepenz:fastadapter:1bfc00216d71dfdfe0d8e7a9d92bb97bfaa1794543930e34b1f79d5d7adbddf6',
|
||||||
'com.mikepenz:fontawesome-typeface:69cb09934a83bac607e78a29459868d537f766224b4a65a042d1f84c98c7b05d',
|
'com.mikepenz:materialize:575195b2fa5b2414fb14a59470ee21d8a8cd8355b651e0cf52e477e3ff1cd96c',
|
||||||
'com.mikepenz:google-material-typeface:48b2712de87d542e9b050846e9f602238a367f38e2d5e8ea4557c5b12adfcbec',
|
'com.mikepenz:iconics-core:d57c6b0ecb33d9ed9708da62e07ad8993d80a16f374f7a2018be7837a60b7ed7',
|
||||||
|
'com.mikepenz:google-material-typeface:47eabb0aadcc0f56530c3ca462b671a5cf5251101cef8f581aedf90ca511914a',
|
||||||
|
'com.mikepenz:fontawesome-typeface:033cf3460d8074bd37a1fefc2ff4eac8f2e3db835ec78bf386d46710e4d0827c',
|
||||||
|
'com.mikepenz:community-material-typeface:382e8446fc08fe03cb1e0f91ee329ffd514c113ad22f8389b88424ac71ed5fbb',
|
||||||
'com.fidesmo:nordpol-android:56f43fe2b1676817bcb4085926de14a08282ef6729c855c198d81aec62b20d65',
|
'com.fidesmo:nordpol-android:56f43fe2b1676817bcb4085926de14a08282ef6729c855c198d81aec62b20d65',
|
||||||
'com.mikepenz:community-material-typeface:990acfcfb892a733d36748fe29176bd61dd5ab34bc8ca1c591200e639d955b99',
|
// 'OpenKeychain:openpgp-api-lib:e4f456b77f80886eb2094bd643c8c8c9cf0eb3e8ef919706d7e92da6fc3c5517',
|
||||||
// 'OpenKeychain.extern.bouncycastle:core:b22dfb37e09fb520683dd0ba089351787560a75b59b60822143f633ec984cab5',
|
// 'OpenKeychain:openkeychain-api-lib:cddb2953fc3ec2876923f01acbe91e136eaad1c327450a4fab21274b0edf73d9',
|
||||||
// 'OpenKeychain.extern.openpgp-api-lib:openpgp-api:fbd9a53022747bdc3c5b0926c6f9cbcbee19e766dd96f090a4310dc65026b393',
|
// 'OpenKeychain.extern.bouncycastle:core:d4574e14e78d78b8b0b30b37089f82901a8fb46679915e034fe5d15cc5431c6a',
|
||||||
// 'OpenKeychain.extern.openkeychain-api-lib:openkeychain-intents:9263330c00497b7bb70502160f50c8396228129376f48f4f5656d28360a2edac',
|
// 'OpenKeychain.extern.bouncycastle:pg:064250718891c89d3bfa1e9bdef68bbf1cd2adb83532707754390b42643f2156',
|
||||||
// 'OpenKeychain.extern.bouncycastle:prov:2d93a52e1b519995b18c0a92a1e59a2773d67d9b466a9cce6af5202a66502577',
|
// 'OpenKeychain.extern.bouncycastle:prov:6933802a03ffd53c084415550db834a59540327434a061af39442e00002611f8',
|
||||||
// 'OpenKeychain.extern.bouncycastle:pg:1397025acf36be36d329c0345b136af776be82fe5d6dad70cc06db09d2f02201',
|
// 'OpenKeychain.extern:minidns:357901cf1b93f74f9729f6e3a1dfa5f148d627bf23423c8597ab82be84b3dc9d',
|
||||||
// 'OpenKeychain.extern.safeslinger-exchange:safeslinger-exchange:989fcc0eba663489a41aa166f6bb39f21271d980faddea5f06ab75339e792d10',
|
// 'OpenKeychain:KeybaseLib:cbba456bf50083cde99f9c460fcbd812dbcd7d9e1a5c1df0ee9e080a85802aa8',
|
||||||
'com.android.support:support-annotations:7f21659b084da073b77b6f7fe7ab250c4f23346238d4efdbbbb937e017ae4693',
|
// 'OpenKeychain:safeslinger-exchange:422efcad6868e32ce1446d18f08d728d266a30725fdc09a71f1b78c133bfc051',
|
||||||
// 'OpenKeychain.extern:minidns:109d5851ab351d7628ed62a0ed96b40598952424e56657c17debbeb4a704f0ce',
|
'com.android.support:support-vector-drawable:a4feae56880e385e147dfed3f73593ce61164ec49bb401f34de9a77a17c68a44',
|
||||||
// 'OpenKeychain.extern.KeybaseLib:Lib:c5b1567ff781c311240e83f865c4ba76ae435eb00994529b8364371abf0d76de',
|
'com.android.support:animated-vector-drawable:ddf1f9dc18d38b21b01c21842d4bdf57613ea86398ede8f914f29b5770f4c32b',
|
||||||
'com.android.support:animated-vector-drawable:4d8366192dedc8ca9e6ff62e9be9ba6145166554d61befc9df312e8328836f55',
|
|
||||||
'com.android.support:support-vector-drawable:0f43fc47b0d2797c4e1851aba61ab87a4fd33323348c2bd022821aaac052a266',
|
|
||||||
'com.squareup.okio:okio:114bdc1f47338a68bcbc95abf2f5cdc72beeec91812f2fcd7b521c1937876266',
|
'com.squareup.okio:okio:114bdc1f47338a68bcbc95abf2f5cdc72beeec91812f2fcd7b521c1937876266',
|
||||||
'com.fidesmo:nordpol-core:3de58e850a00bba5b4d3a604d1399bcd89f695ea191ec0b03a57222e18062d15',
|
'com.fidesmo:nordpol-core:3de58e850a00bba5b4d3a604d1399bcd89f695ea191ec0b03a57222e18062d15',
|
||||||
]
|
]
|
||||||
@@ -141,14 +140,12 @@ android {
|
|||||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||||
|
|
||||||
// TODO: remove org.apache dependencies in LinkedTokenResource etc.
|
|
||||||
useLibrary 'org.apache.http.legacy'
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 15
|
minSdkVersion 15
|
||||||
targetSdkVersion 23
|
targetSdkVersion 23
|
||||||
versionCode 38200
|
versionCode 39600
|
||||||
versionName "3.8.2"
|
versionName "3.9.6 beta"
|
||||||
applicationId "org.sufficientlysecure.keychain"
|
applicationId "org.sufficientlysecure.keychain"
|
||||||
// the androidjunitrunner is broken regarding coverage, see here:
|
// the androidjunitrunner is broken regarding coverage, see here:
|
||||||
// https://code.google.com/p/android/issues/detail?id=170607
|
// https://code.google.com/p/android/issues/detail?id=170607
|
||||||
@@ -190,6 +187,12 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
debug {
|
debug {
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
|
||||||
|
// Enable code coverage (Jacoco)
|
||||||
|
testCoverageEnabled true
|
||||||
|
|
||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
|
|
||||||
// Reference them in the java files with e.g. BuildConfig.ACCOUNT_TYPE.
|
// Reference them in the java files with e.g. BuildConfig.ACCOUNT_TYPE.
|
||||||
@@ -203,9 +206,6 @@ android {
|
|||||||
// Github API
|
// Github API
|
||||||
buildConfigField "String", "GITHUB_CLIENT_ID", "\"c942cd81844d94e7e41b\""
|
buildConfigField "String", "GITHUB_CLIENT_ID", "\"c942cd81844d94e7e41b\""
|
||||||
buildConfigField "String", "GITHUB_CLIENT_SECRET", "\"f1dd17e70a0614abbd9310b00a310e23c6c8edff\""
|
buildConfigField "String", "GITHUB_CLIENT_SECRET", "\"f1dd17e70a0614abbd9310b00a310e23c6c8edff\""
|
||||||
|
|
||||||
// Enable code coverage (Jacoco)
|
|
||||||
testCoverageEnabled true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,10 +267,11 @@ android {
|
|||||||
|
|
||||||
dexOptions {
|
dexOptions {
|
||||||
incremental = true
|
incremental = true
|
||||||
// Disable preDexing, causes com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000) on some systems
|
preDexLibraries = true
|
||||||
preDexLibraries = false
|
// dexInProcess requires much RAM, which is not available on all dev systems
|
||||||
|
dexInProcess = false
|
||||||
jumboMode = true
|
jumboMode = true
|
||||||
javaMaxHeapSize "2g"
|
javaMaxHeapSize "4g"
|
||||||
}
|
}
|
||||||
|
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
@@ -284,8 +285,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply plugin: 'spoon'
|
|
||||||
|
|
||||||
task jacocoTestReport(type:JacocoReport, dependsOn: "testFdroidDebugUnitTest") {
|
task jacocoTestReport(type:JacocoReport, dependsOn: "testFdroidDebugUnitTest") {
|
||||||
group = "Reporting"
|
group = "Reporting"
|
||||||
description = "Generate Jacoco coverage reports"
|
description = "Generate Jacoco coverage reports"
|
||||||
|
|||||||
Vendored
+17
-24
@@ -1,28 +1,21 @@
|
|||||||
# Add project specific ProGuard rules here.
|
# Documentation for ProGuard:
|
||||||
# You can edit the include path and order by changing the proguardFiles
|
|
||||||
# directive in build.gradle.
|
|
||||||
#
|
|
||||||
# For more details, see
|
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
# http://proguard.sourceforge.net/
|
||||||
|
|
||||||
# Add any project specific keep options here:
|
#-dontshrink # shrinking enabled, see below
|
||||||
|
#-dontobfuscate # obfuscation enabled for one class (see below)
|
||||||
# If your project uses WebView with JS, uncomment the following
|
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
|
||||||
# class:
|
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|
||||||
|
|
||||||
# Workaround for Samsung Android 4.2 bug
|
|
||||||
# https://code.google.com/p/android/issues/detail?id=78377
|
|
||||||
# https://code.google.com/p/android/issues/detail?id=78377#c188
|
|
||||||
# https://code.google.com/p/android/issues/detail?id=78377#c302
|
|
||||||
-keepattributes **
|
|
||||||
-keep class !android.support.v7.view.menu.**,** {*;}
|
|
||||||
-dontpreverify
|
|
||||||
-dontoptimize
|
-dontoptimize
|
||||||
-dontshrink
|
-dontpreverify
|
||||||
|
-keepattributes **
|
||||||
-dontwarn **
|
-dontwarn **
|
||||||
-dontnote **
|
-dontnote **
|
||||||
|
|
||||||
|
# Rules are defined as negation filters!
|
||||||
|
# (! = negation filter, ** = all subpackages)
|
||||||
|
# Keep everything (** {*;}) except...
|
||||||
|
|
||||||
|
# * Obfuscate android.support.v7.view.menu.** to fix Samsung Android 4.2 bug
|
||||||
|
# https://code.google.com/p/android/issues/detail?id=78377
|
||||||
|
# * Remove unneeded Bouncy Castle packages to be under 64K limit
|
||||||
|
# http://developer.android.com/tools/building/multidex.html
|
||||||
|
-keep class !android.support.v7.view.menu.**,!org.bouncycastle.crypto.tls.**,!org.bouncycastle.pqc.**,!org.bouncycastle.x509.**,** {*;}
|
||||||
|
|||||||
@@ -5,11 +5,6 @@
|
|||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
General remarks
|
|
||||||
===============
|
|
||||||
- Last APG 1 version was 10900 (1.0.9 beta 00)
|
|
||||||
- Keychain starting with versionCode 20000!
|
|
||||||
|
|
||||||
Association of file types to Keychain
|
Association of file types to Keychain
|
||||||
=====================================
|
=====================================
|
||||||
General remarks about file ending conventions:
|
General remarks about file ending conventions:
|
||||||
@@ -48,6 +43,11 @@
|
|||||||
android:name="android.hardware.screen.portrait"
|
android:name="android.hardware.screen.portrait"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
|
||||||
|
<!-- For OTG tokens -->
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.usb.host"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
<!-- TemporaryStorageProvider should be writable by OpenKeychain only, thus signature-level permission -->
|
<!-- TemporaryStorageProvider should be writable by OpenKeychain only, thus signature-level permission -->
|
||||||
<permission
|
<permission
|
||||||
android:name="${applicationId}.WRITE_TEMPORARY_STORAGE"
|
android:name="${applicationId}.WRITE_TEMPORARY_STORAGE"
|
||||||
@@ -81,6 +81,7 @@
|
|||||||
|
|
||||||
<!-- other group (for free) -->
|
<!-- other group (for free) -->
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||||
@@ -94,6 +95,15 @@
|
|||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/Theme.Keychain.Light">
|
android:theme="@style/Theme.Keychain.Light">
|
||||||
|
<!-- broadcast receiver for Wi-Fi Connection -->
|
||||||
|
<receiver
|
||||||
|
android:name=".receiver.NetworkReceiver"
|
||||||
|
android:enabled="false"
|
||||||
|
android:exported="true" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
<!-- singleTop for NFC dispatch, see SecurityTokenOperationActivity -->
|
<!-- singleTop for NFC dispatch, see SecurityTokenOperationActivity -->
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.MainActivity"
|
android:name=".ui.MainActivity"
|
||||||
@@ -503,21 +513,6 @@
|
|||||||
android:theme="@style/Theme.Keychain.Transparent"
|
android:theme="@style/Theme.Keychain.Transparent"
|
||||||
android:windowSoftInputMode="stateHidden">
|
android:windowSoftInputMode="stateHidden">
|
||||||
|
|
||||||
<!-- VIEW with fingerprint scheme:
|
|
||||||
Handle URIs with fingerprints when scanning directly from Barcode Scanner -->
|
|
||||||
<intent-filter android:label="@string/intent_import_key">
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
|
|
||||||
<!-- Android's scheme matcher is case-sensitive, so include most likely variations -->
|
|
||||||
<data android:scheme="openpgp4fpr" />
|
|
||||||
<data android:scheme="OPENPGP4FPR" />
|
|
||||||
<data android:scheme="OpenPGP4FPR" />
|
|
||||||
<data android:scheme="OpenPGP4Fpr" />
|
|
||||||
<data android:scheme="OpenPGP4fpr" />
|
|
||||||
</intent-filter>
|
|
||||||
<!-- IMPORT_KEY without mimeType to allow import with extras Bundle -->
|
<!-- IMPORT_KEY without mimeType to allow import with extras Bundle -->
|
||||||
<intent-filter android:label="@string/intent_import_key">
|
<intent-filter android:label="@string/intent_import_key">
|
||||||
<action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY_FROM_QR_CODE" />
|
<action android:name="org.sufficientlysecure.keychain.action.IMPORT_KEY_FROM_QR_CODE" />
|
||||||
@@ -730,10 +725,35 @@
|
|||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value=".ui.MainActivity" />
|
android:value=".ui.MainActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.RedirectImportKeysActivity"
|
||||||
|
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||||
|
android:label="@string/title_activity_redirect_key"
|
||||||
|
android:theme="@style/Theme.Keychain.Transparent">
|
||||||
|
|
||||||
|
<!-- VIEW with fingerprint scheme:
|
||||||
|
Handle URIs with fingerprints when scanning directly from Barcode Scanner -->
|
||||||
|
<intent-filter android:label="@string/intent_import_key">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
|
<!-- Android's scheme matcher is case-sensitive, so include most likely variations -->
|
||||||
|
<data android:scheme="openpgp4fpr" />
|
||||||
|
<data android:scheme="OPENPGP4FPR" />
|
||||||
|
<data android:scheme="OpenPGP4FPR" />
|
||||||
|
<data android:scheme="OpenPGP4Fpr" />
|
||||||
|
<data android:scheme="OpenPGP4fpr" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.LogDisplayActivity"
|
android:name=".ui.LogDisplayActivity"
|
||||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||||
@@ -806,7 +826,12 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/keyserver_sync_settings_title" />
|
android:label="@string/keyserver_sync_settings_title" />
|
||||||
|
|
||||||
<!-- Internal classes of the remote APIs (not exported!) -->
|
<provider
|
||||||
|
android:name=".remote.KeychainExternalProvider"
|
||||||
|
android:authorities="${applicationId}.provider.exported"
|
||||||
|
android:exported="true" />
|
||||||
|
|
||||||
|
<!-- Internal classes of the remote APIs (not exported) -->
|
||||||
<activity
|
<activity
|
||||||
android:name=".remote.ui.RemoteCreateAccountActivity"
|
android:name=".remote.ui.RemoteCreateAccountActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
@@ -864,6 +889,24 @@
|
|||||||
android:configChanges="keyboardHidden|keyboard"
|
android:configChanges="keyboardHidden|keyboard"
|
||||||
android:label="@string/title_backup" />
|
android:label="@string/title_backup" />
|
||||||
|
|
||||||
|
<!-- Usb interceptor activity -->
|
||||||
|
<activity
|
||||||
|
android:name=".ui.UsbEventReceiverActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/Theme.Keychain.Transparent"
|
||||||
|
android:noHistory="true"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:taskAffinity="com.example.taskAffinityUsbEventReceiver"
|
||||||
|
android:process=":UsbEventReceiverActivityProcess"
|
||||||
|
android:exported="false">
|
||||||
|
|
||||||
|
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||||
|
android:resource="@xml/usb_device_filter" />
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<!-- DEPRECATED service,
|
<!-- DEPRECATED service,
|
||||||
using this service may lead to truncated data being returned to the caller -->
|
using this service may lead to truncated data being returned to the caller -->
|
||||||
<service
|
<service
|
||||||
|
|||||||
+8
-3
@@ -6,15 +6,16 @@
|
|||||||
|
|
||||||
package org.bouncycastle.openpgp.operator.jcajce;
|
package org.bouncycastle.openpgp.operator.jcajce;
|
||||||
|
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.bouncycastle.jcajce.util.NamedJcaJceHelper;
|
import org.bouncycastle.jcajce.util.NamedJcaJceHelper;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
|
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
|
||||||
import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
|
import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
|
||||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class CachingDataDecryptorFactory implements PublicKeyDataDecryptorFactory
|
public class CachingDataDecryptorFactory implements PublicKeyDataDecryptorFactory
|
||||||
{
|
{
|
||||||
private final PublicKeyDataDecryptorFactory mWrappedDecryptor;
|
private final PublicKeyDataDecryptorFactory mWrappedDecryptor;
|
||||||
@@ -59,6 +60,10 @@ public class CachingDataDecryptorFactory implements PublicKeyDataDecryptorFactor
|
|||||||
return mSessionKeyCache.get(bi);
|
return mSessionKeyCache.get(bi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mWrappedDecryptor == null) {
|
||||||
|
throw new IllegalStateException("tried to decrypt without wrapped decryptor, this is a bug!");
|
||||||
|
}
|
||||||
|
|
||||||
byte[] sessionData = mWrappedDecryptor.recoverSessionData(keyAlgorithm, secKeyData);
|
byte[] sessionData = mWrappedDecryptor.recoverSessionData(keyAlgorithm, secKeyData);
|
||||||
mSessionKeyCache.put(bi, sessionData);
|
mSessionKeyCache.put(bi, sessionData);
|
||||||
return sessionData;
|
return sessionData;
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ public final class Constants {
|
|||||||
// keyserver sync settings
|
// keyserver sync settings
|
||||||
public static final String SYNC_CONTACTS = "syncContacts";
|
public static final String SYNC_CONTACTS = "syncContacts";
|
||||||
public static final String SYNC_KEYSERVER = "syncKeyserver";
|
public static final String SYNC_KEYSERVER = "syncKeyserver";
|
||||||
|
public static final String ENABLE_WIFI_SYNC_ONLY = "enableWifiSyncOnly";
|
||||||
// other settings
|
// other settings
|
||||||
public static final String EXPERIMENTAL_ENABLE_WORD_CONFIRM = "experimentalEnableWordConfirm";
|
public static final String EXPERIMENTAL_ENABLE_WORD_CONFIRM = "experimentalEnableWordConfirm";
|
||||||
public static final String EXPERIMENTAL_ENABLE_LINKED_IDENTITIES = "experimentalEnableLinkedIdentities";
|
public static final String EXPERIMENTAL_ENABLE_LINKED_IDENTITIES = "experimentalEnableLinkedIdentities";
|
||||||
|
|||||||
@@ -100,6 +100,11 @@ public class KeychainApplication extends Application {
|
|||||||
// Add OpenKeychain account to Android to link contacts with keys and keyserver sync
|
// Add OpenKeychain account to Android to link contacts with keys and keyserver sync
|
||||||
createAccountIfNecessary(this);
|
createAccountIfNecessary(this);
|
||||||
|
|
||||||
|
if (Preferences.getKeyserverSyncEnabled(this)) {
|
||||||
|
// will update a keyserver sync if the interval has changed
|
||||||
|
KeyserverSyncAdapterService.enableKeyserverSync(this);
|
||||||
|
}
|
||||||
|
|
||||||
// if first time, enable keyserver and contact sync
|
// if first time, enable keyserver and contact sync
|
||||||
if (Preferences.getPreferences(this).isFirstTime()) {
|
if (Preferences.getPreferences(this).isFirstTime()) {
|
||||||
KeyserverSyncAdapterService.enableKeyserverSync(this);
|
KeyserverSyncAdapterService.enableKeyserverSync(this);
|
||||||
|
|||||||
+16
-9
@@ -21,11 +21,12 @@ package org.sufficientlysecure.keychain.keyimport;
|
|||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.squareup.okhttp.OkHttpClient;
|
|
||||||
import com.squareup.okhttp.Request;
|
|
||||||
import com.squareup.okhttp.Response;
|
|
||||||
|
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||||
@@ -33,11 +34,12 @@ import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
|||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
import org.sufficientlysecure.keychain.util.OkHttpClientFactory;
|
||||||
|
import org.sufficientlysecure.keychain.util.TlsHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -104,11 +106,10 @@ public class FacebookKeyserver extends Keyserver {
|
|||||||
String request = String.format(FB_KEY_URL_FORMAT, fbUsername);
|
String request = String.format(FB_KEY_URL_FORMAT, fbUsername);
|
||||||
Log.d(Constants.TAG, "fetching from Facebook with: " + request + " proxy: " + mProxy);
|
Log.d(Constants.TAG, "fetching from Facebook with: " + request + " proxy: " + mProxy);
|
||||||
|
|
||||||
OkHttpClient client = new OkHttpClient();
|
|
||||||
client.setProxy(mProxy);
|
|
||||||
|
|
||||||
URL url = new URL(request);
|
URL url = new URL(request);
|
||||||
|
|
||||||
|
OkHttpClient client = OkHttpClientFactory.getClientPinnedIfAvailable(url, mProxy);
|
||||||
|
|
||||||
Response response = client.newCall(new Request.Builder().url(url).build()).execute();
|
Response response = client.newCall(new Request.Builder().url(url).build()).execute();
|
||||||
|
|
||||||
// contains body both in case of success or failure
|
// contains body both in case of success or failure
|
||||||
@@ -126,6 +127,9 @@ public class FacebookKeyserver extends Keyserver {
|
|||||||
throw new QueryFailedException("Cannot connect to Facebook. "
|
throw new QueryFailedException("Cannot connect to Facebook. "
|
||||||
+ "Check your Internet connection!"
|
+ "Check your Internet connection!"
|
||||||
+ (mProxy == Proxy.NO_PROXY ? "" : " Using proxy " + mProxy));
|
+ (mProxy == Proxy.NO_PROXY ? "" : " Using proxy " + mProxy));
|
||||||
|
} catch (TlsHelper.TlsHelperException e) {
|
||||||
|
Log.e(Constants.TAG, "Exception in cert pinning", e);
|
||||||
|
throw new QueryFailedException("Exception in cert pinning. ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,8 +194,11 @@ public class FacebookKeyserver extends Keyserver {
|
|||||||
return uri.getPathSegments().get(0);
|
return uri.getPathSegments().get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isFacebookHost(Uri uri) {
|
public static boolean isFacebookHost(@Nullable Uri uri) {
|
||||||
|
if (uri == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
String host = uri.getHost();
|
String host = uri.getHost();
|
||||||
return host.equalsIgnoreCase(FB_HOST) || host.equalsIgnoreCase(FB_HOST_WWW);
|
return FB_HOST.equalsIgnoreCase(host) || FB_HOST_WWW.equalsIgnoreCase(host);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-39
@@ -18,16 +18,18 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.keyimport;
|
package org.sufficientlysecure.keychain.keyimport;
|
||||||
|
|
||||||
import com.squareup.okhttp.MediaType;
|
|
||||||
import com.squareup.okhttp.OkHttpClient;
|
|
||||||
import com.squareup.okhttp.Request;
|
|
||||||
import com.squareup.okhttp.RequestBody;
|
|
||||||
import com.squareup.okhttp.Response;
|
|
||||||
|
|
||||||
|
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
import org.sufficientlysecure.keychain.util.OkHttpClientFactory;
|
||||||
import org.sufficientlysecure.keychain.util.TlsHelper;
|
import org.sufficientlysecure.keychain.util.TlsHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -42,7 +44,6 @@ import java.util.Comparator;
|
|||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -199,43 +200,12 @@ public class HkpKeyserver extends Keyserver {
|
|||||||
return mSecure ? "https://" : "http://";
|
return mSecure ? "https://" : "http://";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* returns a client with pinned certificate if necessary
|
|
||||||
*
|
|
||||||
* @param url url to be queried by client
|
|
||||||
* @param proxy proxy to be used by client
|
|
||||||
* @return client with a pinned certificate if necessary
|
|
||||||
*/
|
|
||||||
public static OkHttpClient getClient(URL url, Proxy proxy) throws IOException {
|
|
||||||
OkHttpClient client = new OkHttpClient();
|
|
||||||
|
|
||||||
try {
|
|
||||||
TlsHelper.usePinnedCertificateIfAvailable(client, url);
|
|
||||||
} catch (TlsHelper.TlsHelperException e) {
|
|
||||||
Log.w(Constants.TAG, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't follow any redirects
|
|
||||||
client.setFollowRedirects(false);
|
|
||||||
client.setFollowSslRedirects(false);
|
|
||||||
|
|
||||||
if (proxy != null) {
|
|
||||||
client.setProxy(proxy);
|
|
||||||
client.setConnectTimeout(30000, TimeUnit.MILLISECONDS);
|
|
||||||
} else {
|
|
||||||
client.setProxy(Proxy.NO_PROXY);
|
|
||||||
client.setConnectTimeout(5000, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
client.setReadTimeout(45000, TimeUnit.MILLISECONDS);
|
|
||||||
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String query(String request, @NonNull Proxy proxy) throws QueryFailedException, HttpError {
|
private String query(String request, @NonNull Proxy proxy) throws QueryFailedException, HttpError {
|
||||||
try {
|
try {
|
||||||
URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request);
|
URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request);
|
||||||
Log.d(Constants.TAG, "hkp keyserver query: " + url + " Proxy: " + proxy);
|
Log.d(Constants.TAG, "hkp keyserver query: " + url + " Proxy: " + proxy);
|
||||||
OkHttpClient client = getClient(url, proxy);
|
OkHttpClient client = OkHttpClientFactory.getClientPinnedIfAvailable(url, proxy);
|
||||||
Response response = client.newCall(new Request.Builder().url(url).build()).execute();
|
Response response = client.newCall(new Request.Builder().url(url).build()).execute();
|
||||||
|
|
||||||
String responseBody = response.body().string(); // contains body both in case of success or failure
|
String responseBody = response.body().string(); // contains body both in case of success or failure
|
||||||
@@ -249,6 +219,9 @@ public class HkpKeyserver extends Keyserver {
|
|||||||
Log.e(Constants.TAG, "IOException at HkpKeyserver", e);
|
Log.e(Constants.TAG, "IOException at HkpKeyserver", e);
|
||||||
throw new QueryFailedException("Keyserver '" + mHost + "' is unavailable. Check your Internet connection!" +
|
throw new QueryFailedException("Keyserver '" + mHost + "' is unavailable. Check your Internet connection!" +
|
||||||
(proxy == Proxy.NO_PROXY ? "" : " Using proxy " + proxy));
|
(proxy == Proxy.NO_PROXY ? "" : " Using proxy " + proxy));
|
||||||
|
} catch (TlsHelper.TlsHelperException e) {
|
||||||
|
Log.e(Constants.TAG, "Exception in pinning certs", e);
|
||||||
|
throw new QueryFailedException("Exception in pinning certs");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,6 +386,7 @@ public class HkpKeyserver extends Keyserver {
|
|||||||
Log.d(Constants.TAG, "hkp keyserver add: " + url);
|
Log.d(Constants.TAG, "hkp keyserver add: " + url);
|
||||||
Log.d(Constants.TAG, "params: " + params);
|
Log.d(Constants.TAG, "params: " + params);
|
||||||
|
|
||||||
|
|
||||||
RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), params);
|
RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), params);
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
@@ -422,7 +396,7 @@ public class HkpKeyserver extends Keyserver {
|
|||||||
.post(body)
|
.post(body)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Response response = getClient(url, mProxy).newCall(request).execute();
|
Response response = OkHttpClientFactory.getClientPinnedIfAvailable(url, mProxy).newCall(request).execute();
|
||||||
|
|
||||||
Log.d(Constants.TAG, "response code: " + response.code());
|
Log.d(Constants.TAG, "response code: " + response.code());
|
||||||
Log.d(Constants.TAG, "answer: " + response.body().string());
|
Log.d(Constants.TAG, "answer: " + response.body().string());
|
||||||
@@ -434,6 +408,9 @@ public class HkpKeyserver extends Keyserver {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(Constants.TAG, "IOException", e);
|
Log.e(Constants.TAG, "IOException", e);
|
||||||
throw new AddKeyException();
|
throw new AddKeyException();
|
||||||
|
} catch (TlsHelper.TlsHelperException e) {
|
||||||
|
Log.e(Constants.TAG, "Exception in pinning certs", e);
|
||||||
|
throw new AddKeyException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-38
@@ -2,12 +2,10 @@ package org.sufficientlysecure.keychain.linked;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import org.apache.http.HttpEntity;
|
import okhttp3.CertificatePinner;
|
||||||
import org.apache.http.HttpResponse;
|
import okhttp3.OkHttpClient;
|
||||||
import org.apache.http.client.HttpClient;
|
import okhttp3.Request;
|
||||||
import org.apache.http.client.methods.HttpRequestBase;
|
import okhttp3.Response;
|
||||||
import org.apache.http.impl.client.DefaultHttpClient;
|
|
||||||
import org.apache.http.params.BasicHttpParams;
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource;
|
import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource;
|
||||||
@@ -18,12 +16,9 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp
|
|||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.thoughtcrime.ssl.pinning.util.PinningHelper;
|
import org.sufficientlysecure.keychain.util.OkHttpClientFactory;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -233,46 +228,38 @@ public abstract class LinkedTokenResource extends LinkedResource {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation") // HttpRequestBase is deprecated
|
|
||||||
public static String getResponseBody(Context context, HttpRequestBase request)
|
private static CertificatePinner getCertificatePinner(String hostname, String[] pins){
|
||||||
throws IOException, HttpStatusException {
|
CertificatePinner.Builder builder = new CertificatePinner.Builder();
|
||||||
return getResponseBody(context, request, null);
|
for(String pin : pins){
|
||||||
|
builder.add(hostname,pin);
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation") // HttpRequestBase is deprecated
|
|
||||||
public static String getResponseBody(Context context, HttpRequestBase request, String[] pins)
|
|
||||||
throws IOException, HttpStatusException {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
request.setHeader("User-Agent", "Open Keychain");
|
public static String getResponseBody(Request request, String... pins)
|
||||||
|
throws IOException, HttpStatusException {
|
||||||
|
|
||||||
|
Log.d("Connection to: " + request.url().url().getHost(), "");
|
||||||
HttpClient httpClient;
|
OkHttpClient client;
|
||||||
if (pins == null) {
|
if (pins != null) {
|
||||||
httpClient = new DefaultHttpClient(new BasicHttpParams());
|
client = OkHttpClientFactory.getSimpleClientPinned(getCertificatePinner(request.url().url().getHost(), pins));
|
||||||
} else {
|
} else {
|
||||||
httpClient = PinningHelper.getPinnedHttpClient(context, pins);
|
client = OkHttpClientFactory.getSimpleClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse response = httpClient.execute(request);
|
Response response = client.newCall(request).execute();
|
||||||
int statusCode = response.getStatusLine().getStatusCode();
|
|
||||||
String reason = response.getStatusLine().getReasonPhrase();
|
|
||||||
|
int statusCode = response.code();
|
||||||
|
String reason = response.message();
|
||||||
|
|
||||||
if (statusCode != 200) {
|
if (statusCode != 200) {
|
||||||
throw new HttpStatusException(statusCode, reason);
|
throw new HttpStatusException(statusCode, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpEntity entity = response.getEntity();
|
return response.body().string();
|
||||||
InputStream inputStream = entity.getContent();
|
|
||||||
|
|
||||||
BufferedReader bReader = new BufferedReader(
|
|
||||||
new InputStreamReader(inputStream, "UTF-8"), 8);
|
|
||||||
String line;
|
|
||||||
while ((line = bReader.readLine()) != null) {
|
|
||||||
sb.append(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class HttpStatusException extends Throwable {
|
public static class HttpStatusException extends Throwable {
|
||||||
|
|||||||
+6
-4
@@ -6,7 +6,7 @@ import android.net.Uri;
|
|||||||
import android.support.annotation.DrawableRes;
|
import android.support.annotation.DrawableRes;
|
||||||
import android.support.annotation.StringRes;
|
import android.support.annotation.StringRes;
|
||||||
|
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import okhttp3.Request;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
@@ -32,14 +32,16 @@ public class GenericHttpsResource extends LinkedTokenResource {
|
|||||||
token, "0x" + KeyFormattingUtils.convertFingerprintToHex(fingerprint).substring(24));
|
token, "0x" + KeyFormattingUtils.convertFingerprintToHex(fingerprint).substring(24));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation") // HttpGet is deprecated
|
|
||||||
@Override
|
@Override
|
||||||
protected String fetchResource (Context context, OperationLog log, int indent)
|
protected String fetchResource (Context context, OperationLog log, int indent)
|
||||||
throws HttpStatusException, IOException {
|
throws HttpStatusException, IOException {
|
||||||
|
|
||||||
log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString());
|
log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString());
|
||||||
HttpGet httpGet = new HttpGet(mSubUri);
|
Request request = new Request.Builder()
|
||||||
return getResponseBody(context, httpGet);
|
.url(mSubUri.toURL())
|
||||||
|
.addHeader("User-Agent", "OpenKeychain")
|
||||||
|
.build();
|
||||||
|
return getResponseBody(request);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-14
@@ -6,7 +6,7 @@ import android.net.Uri;
|
|||||||
import android.support.annotation.DrawableRes;
|
import android.support.annotation.DrawableRes;
|
||||||
import android.support.annotation.StringRes;
|
import android.support.annotation.StringRes;
|
||||||
|
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import okhttp3.Request;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@@ -47,7 +47,7 @@ public class GithubResource extends LinkedTokenResource {
|
|||||||
return String.format(context.getResources().getString(R.string.linked_id_github_text), token);
|
return String.format(context.getResources().getString(R.string.linked_id_github_text), token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation") // HttpGet is deprecated
|
|
||||||
@Override
|
@Override
|
||||||
protected String fetchResource (Context context, OperationLog log, int indent)
|
protected String fetchResource (Context context, OperationLog log, int indent)
|
||||||
throws HttpStatusException, IOException, JSONException {
|
throws HttpStatusException, IOException, JSONException {
|
||||||
@@ -55,8 +55,11 @@ public class GithubResource extends LinkedTokenResource {
|
|||||||
log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString());
|
log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString());
|
||||||
indent += 1;
|
indent += 1;
|
||||||
|
|
||||||
HttpGet httpGet = new HttpGet("https://api.github.com/gists/" + mGistId);
|
Request request = new Request.Builder()
|
||||||
String response = getResponseBody(context, httpGet);
|
.url("https://api.github.com/gists/" + mGistId)
|
||||||
|
.addHeader("User-Agent", "OpenKeychain")
|
||||||
|
.build();
|
||||||
|
String response = getResponseBody(request);
|
||||||
|
|
||||||
JSONObject obj = new JSONObject(response);
|
JSONObject obj = new JSONObject(response);
|
||||||
|
|
||||||
@@ -79,7 +82,7 @@ public class GithubResource extends LinkedTokenResource {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated // not used for now, but could be used to pick up earlier posted gist if already present?
|
|
||||||
@SuppressWarnings({ "deprecation", "unused" })
|
@SuppressWarnings({ "deprecation", "unused" })
|
||||||
public static GithubResource searchInGithubStream(
|
public static GithubResource searchInGithubStream(
|
||||||
Context context, String screenName, String needle, OperationLog log) {
|
Context context, String screenName, String needle, OperationLog log) {
|
||||||
@@ -94,12 +97,12 @@ public class GithubResource extends LinkedTokenResource {
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
JSONArray array; {
|
JSONArray array; {
|
||||||
HttpGet httpGet =
|
Request request = new Request.Builder()
|
||||||
new HttpGet("https://api.github.com/users/" + screenName + "/gists");
|
.url("https://api.github.com/users/" + screenName + "/gists")
|
||||||
httpGet.setHeader("Content-Type", "application/json");
|
.addHeader("Content-Type", "application/json")
|
||||||
httpGet.setHeader("User-Agent", "OpenKeychain");
|
.addHeader("User-Agent", "OpenKeychain")
|
||||||
|
.build();
|
||||||
String response = getResponseBody(context, httpGet);
|
String response = getResponseBody(request);
|
||||||
array = new JSONArray(response);
|
array = new JSONArray(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,10 +119,13 @@ public class GithubResource extends LinkedTokenResource {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String id = obj.getString("id");
|
String id = obj.getString("id");
|
||||||
HttpGet httpGet = new HttpGet("https://api.github.com/gists/" + id);
|
|
||||||
httpGet.setHeader("User-Agent", "OpenKeychain");
|
|
||||||
|
|
||||||
JSONObject gistObj = new JSONObject(getResponseBody(context, httpGet));
|
Request request = new Request.Builder()
|
||||||
|
.url("https://api.github.com/gists/" + id)
|
||||||
|
.addHeader("User-Agent", "OpenKeychain")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
JSONObject gistObj = new JSONObject(getResponseBody(request));
|
||||||
JSONObject gistFiles = gistObj.getJSONObject("files");
|
JSONObject gistFiles = gistObj.getJSONObject("files");
|
||||||
Iterator<String> gistIt = gistFiles.keys();
|
Iterator<String> gistIt = gistFiles.keys();
|
||||||
if (!gistIt.hasNext()) {
|
if (!gistIt.hasNext()) {
|
||||||
|
|||||||
+33
-25
@@ -9,9 +9,9 @@ import android.util.Log;
|
|||||||
|
|
||||||
import com.textuality.keybase.lib.JWalk;
|
import com.textuality.keybase.lib.JWalk;
|
||||||
|
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import okhttp3.MediaType;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import okhttp3.Request;
|
||||||
import org.apache.http.entity.StringEntity;
|
import okhttp3.RequestBody;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@@ -84,18 +84,19 @@ public class TwitterResource extends LinkedTokenResource {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpGet httpGet =
|
|
||||||
new HttpGet("https://api.twitter.com/1.1/statuses/show.json"
|
|
||||||
+ "?id=" + mTweetId
|
|
||||||
+ "&include_entities=false");
|
|
||||||
|
|
||||||
// construct a normal HTTPS request and include an Authorization
|
// construct a normal HTTPS request and include an Authorization
|
||||||
// header with the value of Bearer <>
|
// header with the value of Bearer <>
|
||||||
httpGet.setHeader("Authorization", "Bearer " + authToken);
|
Request request = new Request.Builder()
|
||||||
httpGet.setHeader("Content-Type", "application/json");
|
.url("https://api.twitter.com/1.1/statuses/show.json"
|
||||||
|
+ "?id=" + mTweetId
|
||||||
|
+ "&include_entities=false")
|
||||||
|
.addHeader("Authorization", "Bearer " + authToken)
|
||||||
|
.addHeader("Content-Type", "application/json")
|
||||||
|
.addHeader("User-Agent", "OpenKeychain")
|
||||||
|
.build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String response = getResponseBody(context, httpGet, CERT_PINS);
|
String response = getResponseBody(request, CERT_PINS);
|
||||||
JSONObject obj = new JSONObject(response);
|
JSONObject obj = new JSONObject(response);
|
||||||
JSONObject user = obj.getJSONObject("user");
|
JSONObject user = obj.getJSONObject("user");
|
||||||
if (!mHandle.equalsIgnoreCase(user.getString("screen_name"))) {
|
if (!mHandle.equalsIgnoreCase(user.getString("screen_name"))) {
|
||||||
@@ -157,21 +158,20 @@ public class TwitterResource extends LinkedTokenResource {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpGet httpGet =
|
Request request = new Request.Builder()
|
||||||
new HttpGet("https://api.twitter.com/1.1/statuses/user_timeline.json"
|
.url("https://api.twitter.com/1.1/statuses/user_timeline.json"
|
||||||
+ "?screen_name=" + screenName
|
+ "?screen_name=" + screenName
|
||||||
+ "&count=15"
|
+ "&count=15"
|
||||||
+ "&include_rts=false"
|
+ "&include_rts=false"
|
||||||
+ "&trim_user=true"
|
+ "&trim_user=true"
|
||||||
+ "&exclude_replies=true");
|
+ "&exclude_replies=true")
|
||||||
|
.addHeader("Authorization", "Bearer " + authToken)
|
||||||
// construct a normal HTTPS request and include an Authorization
|
.addHeader("Content-Type", "application/json")
|
||||||
// header with the value of Bearer <>
|
.addHeader("User-Agent", "OpenKeychain")
|
||||||
httpGet.setHeader("Authorization", "Bearer " + authToken);
|
.build();
|
||||||
httpGet.setHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String response = getResponseBody(context, httpGet, CERT_PINS);
|
String response = getResponseBody(request, CERT_PINS);
|
||||||
JSONArray array = new JSONArray(response);
|
JSONArray array = new JSONArray(response);
|
||||||
|
|
||||||
for (int i = 0; i < array.length(); i++) {
|
for (int i = 0; i < array.length(); i++) {
|
||||||
@@ -216,12 +216,20 @@ public class TwitterResource extends LinkedTokenResource {
|
|||||||
String base64Encoded = rot13("D293FQqanH0jH29KIaWJER5DomqSGRE2Ewc1LJACn3cbD1c"
|
String base64Encoded = rot13("D293FQqanH0jH29KIaWJER5DomqSGRE2Ewc1LJACn3cbD1c"
|
||||||
+ "Fq1bmqSAQAz5MI2cIHKOuo3cPoRAQI1OyqmIVFJS6LHMXq2g6MRLkIj") + "==";
|
+ "Fq1bmqSAQAz5MI2cIHKOuo3cPoRAQI1OyqmIVFJS6LHMXq2g6MRLkIj") + "==";
|
||||||
|
|
||||||
|
RequestBody requestBody = RequestBody.create(
|
||||||
|
MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8"),
|
||||||
|
"grant_type=client_credentials");
|
||||||
|
|
||||||
// Step 2: Obtain a bearer token
|
// Step 2: Obtain a bearer token
|
||||||
HttpPost httpPost = new HttpPost("https://api.twitter.com/oauth2/token");
|
Request request = new Request.Builder()
|
||||||
httpPost.setHeader("Authorization", "Basic " + base64Encoded);
|
.url("https://api.twitter.com/oauth2/token")
|
||||||
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
|
.addHeader("Authorization", "Basic " + base64Encoded)
|
||||||
httpPost.setEntity(new StringEntity("grant_type=client_credentials"));
|
.addHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
|
||||||
JSONObject rawAuthorization = new JSONObject(getResponseBody(context, httpPost, CERT_PINS));
|
.addHeader("User-Agent", "OpenKeychain")
|
||||||
|
.post(requestBody)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
JSONObject rawAuthorization = new JSONObject(getResponseBody(request, CERT_PINS));
|
||||||
|
|
||||||
// Applications should verify that the value associated with the
|
// Applications should verify that the value associated with the
|
||||||
// token_type key of the returned object is bearer
|
// token_type key of the returned object is bearer
|
||||||
|
|||||||
+2
-2
@@ -47,7 +47,7 @@ import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
|
|||||||
import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
|
import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenSignOperationsBuilder;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
|
|||||||
|
|
||||||
int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0;
|
int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0;
|
||||||
|
|
||||||
NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(
|
SecurityTokenSignOperationsBuilder allRequiredInput = new SecurityTokenSignOperationsBuilder(
|
||||||
cryptoInput.getSignatureTime(), masterKeyId, masterKeyId);
|
cryptoInput.getSignatureTime(), masterKeyId, masterKeyId);
|
||||||
|
|
||||||
// Work through all requested certifications
|
// Work through all requested certifications
|
||||||
|
|||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Alex Fong Jie Wen <alexfongg@gmail.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.operations;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
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.PgpEditKeyResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.service.ChangeUnlockParcel;
|
||||||
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
|
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||||
|
|
||||||
|
|
||||||
|
public class ChangeUnlockOperation extends BaseOperation<ChangeUnlockParcel> {
|
||||||
|
|
||||||
|
public ChangeUnlockOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
|
||||||
|
super(context, providerHelper, progressable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public OperationResult execute(ChangeUnlockParcel unlockParcel, CryptoInputParcel cryptoInput) {
|
||||||
|
OperationResult.OperationLog log = new OperationResult.OperationLog();
|
||||||
|
log.add(OperationResult.LogType.MSG_ED, 0);
|
||||||
|
|
||||||
|
if (unlockParcel == null || unlockParcel.mMasterKeyId == null) {
|
||||||
|
log.add(OperationResult.LogType.MSG_ED_ERROR_NO_PARCEL, 1);
|
||||||
|
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform actual modification
|
||||||
|
PgpEditKeyResult modifyResult;
|
||||||
|
{
|
||||||
|
PgpKeyOperation keyOperations =
|
||||||
|
new PgpKeyOperation(new ProgressScaler(mProgressable, 0, 70, 100));
|
||||||
|
|
||||||
|
try {
|
||||||
|
log.add(OperationResult.LogType.MSG_ED_FETCHING, 1,
|
||||||
|
KeyFormattingUtils.convertKeyIdToHex(unlockParcel.mMasterKeyId));
|
||||||
|
|
||||||
|
CanonicalizedSecretKeyRing secRing =
|
||||||
|
mProviderHelper.getCanonicalizedSecretKeyRing(unlockParcel.mMasterKeyId);
|
||||||
|
modifyResult = keyOperations.modifyKeyRingPassphrase(secRing, cryptoInput, unlockParcel);
|
||||||
|
|
||||||
|
if (modifyResult.isPending()) {
|
||||||
|
// obtain original passphrase from user
|
||||||
|
log.add(modifyResult, 1);
|
||||||
|
return new EditKeyResult(log, modifyResult);
|
||||||
|
}
|
||||||
|
} catch (ProviderHelper.NotFoundException e) {
|
||||||
|
log.add(OperationResult.LogType.MSG_ED_ERROR_KEY_NOT_FOUND, 2);
|
||||||
|
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.add(modifyResult, 1);
|
||||||
|
|
||||||
|
if (!modifyResult.success()) {
|
||||||
|
// error is already logged by modification
|
||||||
|
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot cancel from here on out!
|
||||||
|
mProgressable.setPreventCancel();
|
||||||
|
|
||||||
|
// It's a success, so this must be non-null now
|
||||||
|
UncachedKeyRing ring = modifyResult.getRing();
|
||||||
|
|
||||||
|
SaveKeyringResult saveResult = mProviderHelper
|
||||||
|
.saveSecretKeyRing(ring, new ProgressScaler(mProgressable, 70, 95, 100));
|
||||||
|
log.add(saveResult, 1);
|
||||||
|
|
||||||
|
// If the save operation didn't succeed, exit here
|
||||||
|
if (!saveResult.success()) {
|
||||||
|
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress(R.string.progress_done, 100, 100);
|
||||||
|
log.add(OperationResult.LogType.MSG_ED_SUCCESS, 0);
|
||||||
|
return new EditKeyResult(EditKeyResult.RESULT_OK, log, ring.getMasterKeyId());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+3
-2
@@ -119,8 +119,9 @@ public class InputDataOperation extends BaseOperation<InputDataParcel> {
|
|||||||
|
|
||||||
// inform the storage provider about the mime type for this uri
|
// inform the storage provider about the mime type for this uri
|
||||||
if (decryptResult.getDecryptionMetadata() != null) {
|
if (decryptResult.getDecryptionMetadata() != null) {
|
||||||
TemporaryFileProvider.setMimeType(mContext, currentInputUri,
|
OpenPgpMetadata meta = decryptResult.getDecryptionMetadata();
|
||||||
decryptResult.getDecryptionMetadata().getMimeType());
|
TemporaryFileProvider.setName(mContext, currentInputUri, meta.getFilename());
|
||||||
|
TemporaryFileProvider.setMimeType(mContext, currentInputUri, meta.getMimeType());
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+3
-3
@@ -43,7 +43,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
|||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenSignOperationsBuilder;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType;
|
||||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||||
import org.sufficientlysecure.keychain.util.InputData;
|
import org.sufficientlysecure.keychain.util.InputData;
|
||||||
@@ -80,7 +80,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
|
|||||||
int total = inputBytes != null ? 1 : inputUris.size(), count = 0;
|
int total = inputBytes != null ? 1 : inputUris.size(), count = 0;
|
||||||
ArrayList<PgpSignEncryptResult> results = new ArrayList<>();
|
ArrayList<PgpSignEncryptResult> results = new ArrayList<>();
|
||||||
|
|
||||||
NfcSignOperationsBuilder pendingInputBuilder = null;
|
SecurityTokenSignOperationsBuilder pendingInputBuilder = null;
|
||||||
|
|
||||||
// if signing subkey has not explicitly been set, get first usable subkey capable of signing
|
// if signing subkey has not explicitly been set, get first usable subkey capable of signing
|
||||||
if (input.getSignatureMasterKeyId() != Constants.key.none
|
if (input.getSignatureMasterKeyId() != Constants.key.none
|
||||||
@@ -161,7 +161,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
|
|||||||
return new SignEncryptResult(log, requiredInput, results, cryptoInput);
|
return new SignEncryptResult(log, requiredInput, results, cryptoInput);
|
||||||
}
|
}
|
||||||
if (pendingInputBuilder == null) {
|
if (pendingInputBuilder == null) {
|
||||||
pendingInputBuilder = new NfcSignOperationsBuilder(requiredInput.mSignatureTime,
|
pendingInputBuilder = new SecurityTokenSignOperationsBuilder(requiredInput.mSignatureTime,
|
||||||
input.getSignatureMasterKeyId(), input.getSignatureSubKeyId());
|
input.getSignatureMasterKeyId(), input.getSignatureSubKeyId());
|
||||||
}
|
}
|
||||||
pendingInputBuilder.addAll(requiredInput);
|
pendingInputBuilder.addAll(requiredInput);
|
||||||
|
|||||||
+2
-1
@@ -131,7 +131,8 @@ public class DeleteResult extends InputPendingResult {
|
|||||||
else if (mFail == 0) {
|
else if (mFail == 0) {
|
||||||
str = activity.getString(R.string.delete_nothing);
|
str = activity.getString(R.string.delete_nothing);
|
||||||
} else {
|
} else {
|
||||||
str = activity.getResources().getQuantityString(R.plurals.delete_fail, mFail);
|
str = activity.getResources().getQuantityString(
|
||||||
|
R.plurals.delete_fail, mFail, mFail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -539,6 +539,7 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
// secret key modify
|
// secret key modify
|
||||||
MSG_MF (LogLevel.START, R.string.msg_mr),
|
MSG_MF (LogLevel.START, R.string.msg_mr),
|
||||||
MSG_MF_DIVERT (LogLevel.DEBUG, R.string.msg_mf_divert),
|
MSG_MF_DIVERT (LogLevel.DEBUG, R.string.msg_mf_divert),
|
||||||
|
MSG_MF_ERROR_ALL_KEYS_STRIPPED (LogLevel.ERROR, R.string.msg_mf_error_all_keys_stripped),
|
||||||
MSG_MF_ERROR_DIVERT_NEWSUB (LogLevel.ERROR, R.string.msg_mf_error_divert_newsub),
|
MSG_MF_ERROR_DIVERT_NEWSUB (LogLevel.ERROR, R.string.msg_mf_error_divert_newsub),
|
||||||
MSG_MF_ERROR_DIVERT_SERIAL (LogLevel.ERROR, R.string.msg_mf_error_divert_serial),
|
MSG_MF_ERROR_DIVERT_SERIAL (LogLevel.ERROR, R.string.msg_mf_error_divert_serial),
|
||||||
MSG_MF_ERROR_ENCODE (LogLevel.ERROR, R.string.msg_mf_error_encode),
|
MSG_MF_ERROR_ENCODE (LogLevel.ERROR, R.string.msg_mf_error_encode),
|
||||||
@@ -552,6 +553,7 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
MSG_MF_ERROR_NOOP (LogLevel.ERROR, R.string.msg_mf_error_noop),
|
MSG_MF_ERROR_NOOP (LogLevel.ERROR, R.string.msg_mf_error_noop),
|
||||||
MSG_MF_ERROR_NULL_EXPIRY (LogLevel.ERROR, R.string.msg_mf_error_null_expiry),
|
MSG_MF_ERROR_NULL_EXPIRY (LogLevel.ERROR, R.string.msg_mf_error_null_expiry),
|
||||||
MSG_MF_ERROR_PASSPHRASE_MASTER(LogLevel.ERROR, R.string.msg_mf_error_passphrase_master),
|
MSG_MF_ERROR_PASSPHRASE_MASTER(LogLevel.ERROR, R.string.msg_mf_error_passphrase_master),
|
||||||
|
MSG_MF_ERROR_PASSPHRASES_UNCHANGED(LogLevel.ERROR, R.string.msg_mf_error_passphrases_unchanged),
|
||||||
MSG_MF_ERROR_PAST_EXPIRY(LogLevel.ERROR, R.string.msg_mf_error_past_expiry),
|
MSG_MF_ERROR_PAST_EXPIRY(LogLevel.ERROR, R.string.msg_mf_error_past_expiry),
|
||||||
MSG_MF_ERROR_PGP (LogLevel.ERROR, R.string.msg_mf_error_pgp),
|
MSG_MF_ERROR_PGP (LogLevel.ERROR, R.string.msg_mf_error_pgp),
|
||||||
MSG_MF_ERROR_RESTRICTED(LogLevel.ERROR, R.string.msg_mf_error_restricted),
|
MSG_MF_ERROR_RESTRICTED(LogLevel.ERROR, R.string.msg_mf_error_restricted),
|
||||||
@@ -566,8 +568,6 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_size),
|
MSG_MF_ERROR_BAD_SECURITY_TOKEN_SIZE(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_size),
|
||||||
MSG_MF_ERROR_BAD_SECURITY_TOKEN_STRIPPED(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_stripped),
|
MSG_MF_ERROR_BAD_SECURITY_TOKEN_STRIPPED(LogLevel.ERROR, R.string.edit_key_error_bad_security_token_stripped),
|
||||||
MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master),
|
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),
|
|
||||||
MSG_MF_PASSPHRASE (LogLevel.INFO, R.string.msg_mf_passphrase),
|
MSG_MF_PASSPHRASE (LogLevel.INFO, R.string.msg_mf_passphrase),
|
||||||
MSG_MF_PIN (LogLevel.INFO, R.string.msg_mf_pin),
|
MSG_MF_PIN (LogLevel.INFO, R.string.msg_mf_pin),
|
||||||
MSG_MF_ADMIN_PIN (LogLevel.INFO, R.string.msg_mf_admin_pin),
|
MSG_MF_ADMIN_PIN (LogLevel.INFO, R.string.msg_mf_admin_pin),
|
||||||
@@ -730,6 +730,7 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
MSG_PSE_ERROR_PGP (LogLevel.ERROR, R.string.msg_pse_error_pgp),
|
MSG_PSE_ERROR_PGP (LogLevel.ERROR, R.string.msg_pse_error_pgp),
|
||||||
MSG_PSE_ERROR_SIG (LogLevel.ERROR, R.string.msg_pse_error_sig),
|
MSG_PSE_ERROR_SIG (LogLevel.ERROR, R.string.msg_pse_error_sig),
|
||||||
MSG_PSE_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_pse_error_unlock),
|
MSG_PSE_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_pse_error_unlock),
|
||||||
|
MSG_PSE_ERROR_REVOKED_OR_EXPIRED (LogLevel.ERROR, R.string.msg_pse_error_revoked_or_expired),
|
||||||
MSG_PSE_KEY_OK (LogLevel.OK, R.string.msg_pse_key_ok),
|
MSG_PSE_KEY_OK (LogLevel.OK, R.string.msg_pse_key_ok),
|
||||||
MSG_PSE_KEY_UNKNOWN (LogLevel.DEBUG, R.string.msg_pse_key_unknown),
|
MSG_PSE_KEY_UNKNOWN (LogLevel.DEBUG, R.string.msg_pse_key_unknown),
|
||||||
MSG_PSE_KEY_WARN (LogLevel.WARN, R.string.msg_pse_key_warn),
|
MSG_PSE_KEY_WARN (LogLevel.WARN, R.string.msg_pse_key_warn),
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ public abstract class KeyRing {
|
|||||||
|
|
||||||
private static final Pattern USER_ID_PATTERN = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$");
|
private static final Pattern USER_ID_PATTERN = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$");
|
||||||
|
|
||||||
|
private static final Pattern EMAIL_PATTERN = Pattern.compile("^.*@.*\\..*$");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Splits userId string into naming part, email part, and comment part
|
* Splits userId string into naming part, email part, and comment part
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -71,25 +73,38 @@ public abstract class KeyRing {
|
|||||||
if (!TextUtils.isEmpty(userId)) {
|
if (!TextUtils.isEmpty(userId)) {
|
||||||
final Matcher matcher = USER_ID_PATTERN.matcher(userId);
|
final Matcher matcher = USER_ID_PATTERN.matcher(userId);
|
||||||
if (matcher.matches()) {
|
if (matcher.matches()) {
|
||||||
return new UserId(matcher.group(1), matcher.group(3), matcher.group(2));
|
String name = matcher.group(1).isEmpty() ? null : matcher.group(1);
|
||||||
|
String comment = matcher.group(2);
|
||||||
|
String email = matcher.group(3);
|
||||||
|
if (comment == null && email == null && name != null && EMAIL_PATTERN.matcher(name).matches()) {
|
||||||
|
email = name;
|
||||||
|
name = null;
|
||||||
|
}
|
||||||
|
return new UserId(name, email, comment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new UserId(null, null, null);
|
return new UserId(null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a composed user id. Returns null if name is null!
|
* Returns a composed user id. Returns null if name, email and comment are empty.
|
||||||
*/
|
*/
|
||||||
public static String createUserId(UserId userId) {
|
public static String createUserId(UserId userId) {
|
||||||
String userIdString = userId.name; // consider name a required value
|
StringBuilder userIdBuilder = new StringBuilder();
|
||||||
if (userIdString != null && !TextUtils.isEmpty(userId.comment)) {
|
if (!TextUtils.isEmpty(userId.name)) {
|
||||||
userIdString += " (" + userId.comment + ")";
|
userIdBuilder.append(userId.name);
|
||||||
}
|
}
|
||||||
if (userIdString != null && !TextUtils.isEmpty(userId.email)) {
|
if (!TextUtils.isEmpty(userId.comment)) {
|
||||||
userIdString += " <" + userId.email + ">";
|
userIdBuilder.append(" (");
|
||||||
|
userIdBuilder.append(userId.comment);
|
||||||
|
userIdBuilder.append(")");
|
||||||
}
|
}
|
||||||
|
if (!TextUtils.isEmpty(userId.email)) {
|
||||||
return userIdString;
|
userIdBuilder.append(" <");
|
||||||
|
userIdBuilder.append(userId.email);
|
||||||
|
userIdBuilder.append(">");
|
||||||
|
}
|
||||||
|
return userIdBuilder.length() == 0 ? null : userIdBuilder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class UserId implements Serializable {
|
public static class UserId implements Serializable {
|
||||||
|
|||||||
+13
-9
@@ -26,6 +26,8 @@ public class OpenPgpDecryptionResultBuilder {
|
|||||||
// builder
|
// builder
|
||||||
private boolean mInsecure = false;
|
private boolean mInsecure = false;
|
||||||
private boolean mEncrypted = false;
|
private boolean mEncrypted = false;
|
||||||
|
private byte[] sessionKey;
|
||||||
|
private byte[] decryptedSessionKey;
|
||||||
|
|
||||||
public void setInsecure(boolean insecure) {
|
public void setInsecure(boolean insecure) {
|
||||||
this.mInsecure = insecure;
|
this.mInsecure = insecure;
|
||||||
@@ -36,24 +38,26 @@ public class OpenPgpDecryptionResultBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public OpenPgpDecryptionResult build() {
|
public OpenPgpDecryptionResult build() {
|
||||||
OpenPgpDecryptionResult result = new OpenPgpDecryptionResult();
|
|
||||||
|
|
||||||
if (mInsecure) {
|
if (mInsecure) {
|
||||||
Log.d(Constants.TAG, "RESULT_INSECURE");
|
Log.d(Constants.TAG, "RESULT_INSECURE");
|
||||||
result.setResult(OpenPgpDecryptionResult.RESULT_INSECURE);
|
return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_INSECURE, sessionKey, decryptedSessionKey);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mEncrypted) {
|
if (mEncrypted) {
|
||||||
Log.d(Constants.TAG, "RESULT_ENCRYPTED");
|
Log.d(Constants.TAG, "RESULT_ENCRYPTED");
|
||||||
result.setResult(OpenPgpDecryptionResult.RESULT_ENCRYPTED);
|
return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_ENCRYPTED, sessionKey, decryptedSessionKey);
|
||||||
} else {
|
|
||||||
Log.d(Constants.TAG, "RESULT_NOT_ENCRYPTED");
|
|
||||||
result.setResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
Log.d(Constants.TAG, "RESULT_NOT_ENCRYPTED");
|
||||||
|
return new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setSessionKey(byte[] sessionKey, byte[] decryptedSessionKey) {
|
||||||
|
if ((sessionKey == null) != (decryptedSessionKey == null)) {
|
||||||
|
throw new AssertionError("sessionKey must be null iff decryptedSessionKey is null!");
|
||||||
|
}
|
||||||
|
this.sessionKey = sessionKey;
|
||||||
|
this.decryptedSessionKey = decryptedSessionKey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -37,7 +37,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp
|
|||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenSignOperationsBuilder;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ public class PgpCertifyOperation {
|
|||||||
// get the master subkey (which we certify for)
|
// get the master subkey (which we certify for)
|
||||||
PGPPublicKey publicKey = publicRing.getPublicKey().getPublicKey();
|
PGPPublicKey publicKey = publicRing.getPublicKey().getPublicKey();
|
||||||
|
|
||||||
NfcSignOperationsBuilder requiredInput = new NfcSignOperationsBuilder(creationTimestamp,
|
SecurityTokenSignOperationsBuilder requiredInput = new SecurityTokenSignOperationsBuilder(creationTimestamp,
|
||||||
publicKey.getKeyID(), publicKey.getKeyID());
|
publicKey.getKeyID(), publicKey.getKeyID());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
+55
-25
@@ -26,9 +26,12 @@ import java.io.FileNotFoundException;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.security.SignatureException;
|
import java.security.SignatureException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
@@ -60,7 +63,6 @@ import org.sufficientlysecure.keychain.Constants;
|
|||||||
import org.sufficientlysecure.keychain.Constants.key;
|
import org.sufficientlysecure.keychain.Constants.key;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.operations.BaseOperation;
|
import org.sufficientlysecure.keychain.operations.BaseOperation;
|
||||||
import org.sufficientlysecure.keychain.util.CharsetVerifier;
|
|
||||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
@@ -73,6 +75,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
|||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
|
import org.sufficientlysecure.keychain.util.CharsetVerifier;
|
||||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||||
import org.sufficientlysecure.keychain.util.InputData;
|
import org.sufficientlysecure.keychain.util.InputData;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@@ -197,6 +200,10 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
PGPEncryptedData encryptedData;
|
PGPEncryptedData encryptedData;
|
||||||
InputStream cleartextStream;
|
InputStream cleartextStream;
|
||||||
|
|
||||||
|
// the cached session key
|
||||||
|
byte[] sessionKey;
|
||||||
|
byte[] decryptedSessionKey;
|
||||||
|
|
||||||
int symmetricEncryptionAlgo = 0;
|
int symmetricEncryptionAlgo = 0;
|
||||||
|
|
||||||
boolean skippedDisallowedKey = false;
|
boolean skippedDisallowedKey = false;
|
||||||
@@ -304,6 +311,9 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
|
|
||||||
// if this worked out so far, the data is encrypted
|
// if this worked out so far, the data is encrypted
|
||||||
decryptionResultBuilder.setEncrypted(true);
|
decryptionResultBuilder.setEncrypted(true);
|
||||||
|
if (esResult.sessionKey != null && esResult.decryptedSessionKey != null) {
|
||||||
|
decryptionResultBuilder.setSessionKey(esResult.sessionKey, esResult.decryptedSessionKey);
|
||||||
|
}
|
||||||
|
|
||||||
if (esResult.insecureEncryptionKey) {
|
if (esResult.insecureEncryptionKey) {
|
||||||
log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
|
log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
|
||||||
@@ -545,10 +555,14 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
boolean asymmetricPacketFound = false;
|
boolean asymmetricPacketFound = false;
|
||||||
boolean symmetricPacketFound = false;
|
boolean symmetricPacketFound = false;
|
||||||
boolean anyPacketFound = false;
|
boolean anyPacketFound = false;
|
||||||
|
boolean decryptedSessionKeyAvailable = false;
|
||||||
|
|
||||||
PGPPublicKeyEncryptedData encryptedDataAsymmetric = null;
|
PGPPublicKeyEncryptedData encryptedDataAsymmetric = null;
|
||||||
PGPPBEEncryptedData encryptedDataSymmetric = null;
|
PGPPBEEncryptedData encryptedDataSymmetric = null;
|
||||||
CanonicalizedSecretKey decryptionKey = null;
|
CanonicalizedSecretKey decryptionKey = null;
|
||||||
|
CachingDataDecryptorFactory cachedKeyDecryptorFactory = new CachingDataDecryptorFactory(
|
||||||
|
Constants.BOUNCY_CASTLE_PROVIDER_NAME, cryptoInput.getCryptoData());
|
||||||
|
;
|
||||||
|
|
||||||
Passphrase passphrase = null;
|
Passphrase passphrase = null;
|
||||||
|
|
||||||
@@ -569,6 +583,13 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
log.add(LogType.MSG_DC_ASYM, indent,
|
log.add(LogType.MSG_DC_ASYM, indent,
|
||||||
KeyFormattingUtils.convertKeyIdToHex(subKeyId));
|
KeyFormattingUtils.convertKeyIdToHex(subKeyId));
|
||||||
|
|
||||||
|
decryptedSessionKeyAvailable = cachedKeyDecryptorFactory.hasCachedSessionData(encData);
|
||||||
|
if (decryptedSessionKeyAvailable) {
|
||||||
|
asymmetricPacketFound = true;
|
||||||
|
encryptedDataAsymmetric = encData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
CachedPublicKeyRing cachedPublicKeyRing;
|
CachedPublicKeyRing cachedPublicKeyRing;
|
||||||
try {
|
try {
|
||||||
// get actual keyring object based on master key id
|
// get actual keyring object based on master key id
|
||||||
@@ -746,34 +767,38 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
currentProgress += 2;
|
currentProgress += 2;
|
||||||
updateProgress(R.string.progress_extracting_key, currentProgress, 100);
|
updateProgress(R.string.progress_extracting_key, currentProgress, 100);
|
||||||
|
|
||||||
try {
|
CachingDataDecryptorFactory decryptorFactory;
|
||||||
log.add(LogType.MSG_DC_UNLOCKING, indent + 1);
|
if (decryptedSessionKeyAvailable) {
|
||||||
if (!decryptionKey.unlock(passphrase)) {
|
decryptorFactory = cachedKeyDecryptorFactory;
|
||||||
log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1);
|
} else {
|
||||||
|
try {
|
||||||
|
log.add(LogType.MSG_DC_UNLOCKING, indent + 1);
|
||||||
|
if (!decryptionKey.unlock(passphrase)) {
|
||||||
|
log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1);
|
||||||
|
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
|
||||||
|
}
|
||||||
|
} catch (PgpGeneralException e) {
|
||||||
|
log.add(LogType.MSG_DC_ERROR_EXTRACT_KEY, indent + 1);
|
||||||
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
|
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
|
||||||
}
|
}
|
||||||
} catch (PgpGeneralException e) {
|
|
||||||
log.add(LogType.MSG_DC_ERROR_EXTRACT_KEY, indent + 1);
|
|
||||||
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
|
|
||||||
}
|
|
||||||
|
|
||||||
currentProgress += 2;
|
currentProgress += 2;
|
||||||
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
|
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
|
||||||
|
|
||||||
CachingDataDecryptorFactory decryptorFactory
|
decryptorFactory = decryptionKey.getCachingDecryptorFactory(cryptoInput);
|
||||||
= decryptionKey.getCachingDecryptorFactory(cryptoInput);
|
|
||||||
|
|
||||||
// special case: if the decryptor does not have a session key cached for this encrypted
|
// special case: if the decryptor does not have a session key cached for this encrypted
|
||||||
// data, and can't actually decrypt on its own, return a pending intent
|
// data, and can't actually decrypt on its own, return a pending intent
|
||||||
if (!decryptorFactory.canDecrypt()
|
if (!decryptorFactory.canDecrypt()
|
||||||
&& !decryptorFactory.hasCachedSessionData(encryptedDataAsymmetric)) {
|
&& !decryptorFactory.hasCachedSessionData(encryptedDataAsymmetric)) {
|
||||||
|
|
||||||
log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);
|
|
||||||
return result.with(new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation(
|
|
||||||
decryptionKey.getRing().getMasterKeyId(),
|
|
||||||
decryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0]
|
|
||||||
), cryptoInput));
|
|
||||||
|
|
||||||
|
log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);
|
||||||
|
return result.with(new DecryptVerifyResult(log,
|
||||||
|
RequiredInputParcel.createSecurityTokenDecryptOperation(
|
||||||
|
decryptionKey.getRing().getMasterKeyId(),
|
||||||
|
decryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0]
|
||||||
|
), cryptoInput));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -786,8 +811,13 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
result.symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
|
result.symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
|
||||||
result.encryptedData = encryptedDataAsymmetric;
|
result.encryptedData = encryptedDataAsymmetric;
|
||||||
|
|
||||||
cryptoInput.addCryptoData(decryptorFactory.getCachedSessionKeys());
|
Map<ByteBuffer, byte[]> cachedSessionKeys = decryptorFactory.getCachedSessionKeys();
|
||||||
|
cryptoInput.addCryptoData(cachedSessionKeys);
|
||||||
|
if (cachedSessionKeys.size() >= 1) {
|
||||||
|
Entry<ByteBuffer, byte[]> entry = cachedSessionKeys.entrySet().iterator().next();
|
||||||
|
result.sessionKey = entry.getKey().array();
|
||||||
|
result.decryptedSessionKey = entry.getValue();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// there wasn't even any useful data
|
// there wasn't even any useful data
|
||||||
if (!anyPacketFound) {
|
if (!anyPacketFound) {
|
||||||
|
|||||||
+98
-84
@@ -18,6 +18,23 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.pgp;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.security.spec.ECGenParameterSpec;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Stack;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||||
import org.bouncycastle.bcpg.S2K;
|
import org.bouncycastle.bcpg.S2K;
|
||||||
import org.bouncycastle.bcpg.sig.Features;
|
import org.bouncycastle.bcpg.sig.Features;
|
||||||
@@ -55,15 +72,15 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
|||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
|
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
|
||||||
|
import org.sufficientlysecure.keychain.service.ChangeUnlockParcel;
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
|
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenKeyToCardOperationsBuilder;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcKeyToCardOperationsBuilder;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.SecurityTokenSignOperationsBuilder;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@@ -71,22 +88,6 @@ import org.sufficientlysecure.keychain.util.Passphrase;
|
|||||||
import org.sufficientlysecure.keychain.util.Primes;
|
import org.sufficientlysecure.keychain.util.Primes;
|
||||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
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;
|
|
||||||
import java.security.NoSuchProviderException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.SignatureException;
|
|
||||||
import java.security.spec.ECGenParameterSpec;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Stack;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is the single place where ALL operations that actually modify a PGP public or secret
|
* This class is the single place where ALL operations that actually modify a PGP public or secret
|
||||||
* key take place.
|
* key take place.
|
||||||
@@ -496,10 +497,10 @@ public class PgpKeyOperation {
|
|||||||
OperationLog log,
|
OperationLog log,
|
||||||
int indent) {
|
int indent) {
|
||||||
|
|
||||||
NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder(
|
SecurityTokenSignOperationsBuilder nfcSignOps = new SecurityTokenSignOperationsBuilder(
|
||||||
cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(),
|
cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(),
|
||||||
masterSecretKey.getKeyID());
|
masterSecretKey.getKeyID());
|
||||||
NfcKeyToCardOperationsBuilder nfcKeyToCardOps = new NfcKeyToCardOperationsBuilder(
|
SecurityTokenKeyToCardOperationsBuilder nfcKeyToCardOps = new SecurityTokenKeyToCardOperationsBuilder(
|
||||||
masterSecretKey.getKeyID());
|
masterSecretKey.getKeyID());
|
||||||
|
|
||||||
progress(R.string.progress_modify, 0);
|
progress(R.string.progress_modify, 0);
|
||||||
@@ -1053,13 +1054,13 @@ public class PgpKeyOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 6. If requested, change passphrase
|
// 6. If requested, change passphrase
|
||||||
if (saveParcel.mNewUnlock != null) {
|
if (saveParcel.getChangeUnlockParcel() != null) {
|
||||||
progress(R.string.progress_modify_passphrase, 90);
|
progress(R.string.progress_modify_passphrase, 90);
|
||||||
log.add(LogType.MSG_MF_PASSPHRASE, indent);
|
log.add(LogType.MSG_MF_PASSPHRASE, indent);
|
||||||
indent += 1;
|
indent += 1;
|
||||||
|
|
||||||
sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey,
|
sKR = applyNewPassphrase(sKR, masterPublicKey, cryptoInput.getPassphrase(),
|
||||||
cryptoInput.getPassphrase(), saveParcel.mNewUnlock, log, indent);
|
saveParcel.getChangeUnlockParcel().mNewPassphrase, log, indent);
|
||||||
if (sKR == null) {
|
if (sKR == null) {
|
||||||
// The error has been logged above, just return a bad state
|
// The error has been logged above, just return a bad state
|
||||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||||
@@ -1192,73 +1193,80 @@ public class PgpKeyOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static PGPSecretKeyRing applyNewUnlock(
|
public PgpEditKeyResult modifyKeyRingPassphrase(CanonicalizedSecretKeyRing wsKR,
|
||||||
PGPSecretKeyRing sKR,
|
CryptoInputParcel cryptoInput,
|
||||||
PGPPublicKey masterPublicKey,
|
ChangeUnlockParcel changeUnlockParcel) {
|
||||||
PGPPrivateKey masterPrivateKey,
|
|
||||||
Passphrase passphrase,
|
|
||||||
ChangeUnlockParcel newUnlock,
|
|
||||||
OperationLog log, int indent) throws PGPException {
|
|
||||||
|
|
||||||
if (newUnlock.mNewPassphrase != null) {
|
OperationLog log = new OperationLog();
|
||||||
sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPassphrase, log, indent);
|
int indent = 0;
|
||||||
|
|
||||||
// if there is any old packet with notation data
|
if (changeUnlockParcel.mMasterKeyId == null || changeUnlockParcel.mMasterKeyId != wsKR.getMasterKeyId()) {
|
||||||
if (hasNotationData(sKR)) {
|
log.add(LogType.MSG_MF_ERROR_KEYID, indent);
|
||||||
|
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||||
|
}
|
||||||
|
|
||||||
log.add(LogType.MSG_MF_NOTATION_EMPTY, indent);
|
log.add(LogType.MSG_MF, indent,
|
||||||
|
KeyFormattingUtils.convertKeyIdToHex(wsKR.getMasterKeyId()));
|
||||||
|
indent += 1;
|
||||||
|
progress(R.string.progress_building_key, 0);
|
||||||
|
|
||||||
// add packet with EMPTY notation data (updates old one, but will be stripped later)
|
// We work on bouncycastle object level here
|
||||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
PGPSecretKeyRing sKR = wsKR.getRing();
|
||||||
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
|
PGPSecretKey masterSecretKey = sKR.getSecretKey();
|
||||||
PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO)
|
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
|
||||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
// Make sure the fingerprint matches
|
||||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
if (changeUnlockParcel.mFingerprint == null || !Arrays.equals(changeUnlockParcel.mFingerprint,
|
||||||
{ // set subpackets
|
masterSecretKey.getPublicKey().getFingerprint())) {
|
||||||
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
log.add(LogType.MSG_MF_ERROR_FINGERPRINT, indent);
|
||||||
hashedPacketsGen.setExportable(false, false);
|
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||||
sGen.setHashedSubpackets(hashedPacketsGen.generate());
|
}
|
||||||
|
|
||||||
|
// Find the first unstripped secret key
|
||||||
|
PGPSecretKey nonDummy = firstNonDummySecretKeyID(sKR);
|
||||||
|
if(nonDummy == null) {
|
||||||
|
log.add(OperationResult.LogType.MSG_MF_ERROR_ALL_KEYS_STRIPPED, indent);
|
||||||
|
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cryptoInput.hasPassphrase()) {
|
||||||
|
log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent);
|
||||||
|
|
||||||
|
return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
|
||||||
|
masterSecretKey.getKeyID(), nonDummy.getKeyID(),
|
||||||
|
cryptoInput.getSignatureTime()), cryptoInput);
|
||||||
|
} else {
|
||||||
|
progress(R.string.progress_modify_passphrase, 50);
|
||||||
|
log.add(LogType.MSG_MF_PASSPHRASE, indent);
|
||||||
|
indent += 1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
sKR = applyNewPassphrase(sKR, masterPublicKey, cryptoInput.getPassphrase(),
|
||||||
|
changeUnlockParcel.mNewPassphrase, log, indent);
|
||||||
|
if (sKR == null) {
|
||||||
|
// The error has been logged above, just return a bad state
|
||||||
|
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||||
}
|
}
|
||||||
sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey);
|
} catch (PGPException e) {
|
||||||
PGPSignature emptySig = sGen.generateCertification(masterPublicKey);
|
throw new UnsupportedOperationException("Failed to build encryptor/decryptor!");
|
||||||
|
|
||||||
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig);
|
|
||||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR,
|
|
||||||
PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sKR;
|
indent -= 1;
|
||||||
|
progress(R.string.progress_done, 100);
|
||||||
|
log.add(LogType.MSG_MF_SUCCESS, indent);
|
||||||
|
return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (newUnlock.mNewPin != null) {
|
private static PGPSecretKey firstNonDummySecretKeyID(PGPSecretKeyRing secRing) {
|
||||||
sKR = applyNewPassphrase(sKR, masterPublicKey, passphrase, newUnlock.mNewPin, log, indent);
|
Iterator<PGPSecretKey> secretKeyIterator = secRing.getSecretKeys();
|
||||||
|
|
||||||
log.add(LogType.MSG_MF_NOTATION_PIN, indent);
|
while(secretKeyIterator.hasNext()) {
|
||||||
|
PGPSecretKey secretKey = secretKeyIterator.next();
|
||||||
// add packet with "pin" notation data
|
if(!isDummy(secretKey)){
|
||||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
return secretKey;
|
||||||
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
|
|
||||||
PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO)
|
|
||||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
|
||||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
|
||||||
{ // set subpackets
|
|
||||||
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
|
||||||
hashedPacketsGen.setExportable(false, false);
|
|
||||||
hashedPacketsGen.setNotationData(false, true, "unlock.pin@sufficientlysecure.org", "1");
|
|
||||||
sGen.setHashedSubpackets(hashedPacketsGen.generate());
|
|
||||||
}
|
}
|
||||||
sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey);
|
|
||||||
PGPSignature emptySig = sGen.generateCertification(masterPublicKey);
|
|
||||||
|
|
||||||
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig);
|
|
||||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR,
|
|
||||||
PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey));
|
|
||||||
|
|
||||||
return sKR;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
throw new UnsupportedOperationException("PIN passphrases not yet implemented!");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This method returns true iff the provided keyring has a local direct key signature
|
/** This method returns true iff the provided keyring has a local direct key signature
|
||||||
@@ -1293,9 +1301,9 @@ public class PgpKeyOperation {
|
|||||||
PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
|
PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
|
||||||
PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
|
PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
|
||||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray());
|
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray());
|
||||||
|
boolean keysModified = false;
|
||||||
|
|
||||||
// noinspection unchecked
|
for (PGPSecretKey sKey : new IterableIterator<>(sKR.getSecretKeys())) {
|
||||||
for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) {
|
|
||||||
log.add(LogType.MSG_MF_PASSPHRASE_KEY, indent,
|
log.add(LogType.MSG_MF_PASSPHRASE_KEY, indent,
|
||||||
KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID()));
|
KeyFormattingUtils.convertKeyIdToHex(sKey.getKeyID()));
|
||||||
|
|
||||||
@@ -1307,8 +1315,8 @@ public class PgpKeyOperation {
|
|||||||
ok = true;
|
ok = true;
|
||||||
} catch (PGPException e) {
|
} catch (PGPException e) {
|
||||||
|
|
||||||
// if this is the master key, error!
|
// if the master key failed && it's not stripped, error!
|
||||||
if (sKey.getKeyID() == masterPublicKey.getKeyID()) {
|
if (sKey.getKeyID() == masterPublicKey.getKeyID() && !isDummy(sKey)) {
|
||||||
log.add(LogType.MSG_MF_ERROR_PASSPHRASE_MASTER, indent+1);
|
log.add(LogType.MSG_MF_ERROR_PASSPHRASE_MASTER, indent+1);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1335,7 +1343,13 @@ public class PgpKeyOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
||||||
|
keysModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!keysModified) {
|
||||||
|
// no passphrase was changed
|
||||||
|
log.add(LogType.MSG_MF_ERROR_PASSPHRASES_UNCHANGED, indent+1);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sKR;
|
return sKR;
|
||||||
@@ -1348,7 +1362,7 @@ public class PgpKeyOperation {
|
|||||||
PGPPublicKey masterPublicKey,
|
PGPPublicKey masterPublicKey,
|
||||||
int flags, long expiry,
|
int flags, long expiry,
|
||||||
CryptoInputParcel cryptoInput,
|
CryptoInputParcel cryptoInput,
|
||||||
NfcSignOperationsBuilder nfcSignOps,
|
SecurityTokenSignOperationsBuilder nfcSignOps,
|
||||||
int indent, OperationLog log)
|
int indent, OperationLog log)
|
||||||
throws PGPException, IOException, SignatureException {
|
throws PGPException, IOException, SignatureException {
|
||||||
|
|
||||||
|
|||||||
-12
@@ -38,7 +38,6 @@ public class PgpSignEncryptInputParcel implements Parcelable {
|
|||||||
protected Long mSignatureSubKeyId = null;
|
protected Long mSignatureSubKeyId = null;
|
||||||
protected int mSignatureHashAlgorithm = PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT;
|
protected int mSignatureHashAlgorithm = PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT;
|
||||||
protected long mAdditionalEncryptId = Constants.key.none;
|
protected long mAdditionalEncryptId = Constants.key.none;
|
||||||
protected boolean mFailOnMissingEncryptionKeyIds = false;
|
|
||||||
protected String mCharset;
|
protected String mCharset;
|
||||||
protected boolean mCleartextSignature;
|
protected boolean mCleartextSignature;
|
||||||
protected boolean mDetachedSignature = false;
|
protected boolean mDetachedSignature = false;
|
||||||
@@ -65,7 +64,6 @@ public class PgpSignEncryptInputParcel implements Parcelable {
|
|||||||
mSignatureSubKeyId = source.readInt() == 1 ? source.readLong() : null;
|
mSignatureSubKeyId = source.readInt() == 1 ? source.readLong() : null;
|
||||||
mSignatureHashAlgorithm = source.readInt();
|
mSignatureHashAlgorithm = source.readInt();
|
||||||
mAdditionalEncryptId = source.readLong();
|
mAdditionalEncryptId = source.readLong();
|
||||||
mFailOnMissingEncryptionKeyIds = source.readInt() == 1;
|
|
||||||
mCharset = source.readString();
|
mCharset = source.readString();
|
||||||
mCleartextSignature = source.readInt() == 1;
|
mCleartextSignature = source.readInt() == 1;
|
||||||
mDetachedSignature = source.readInt() == 1;
|
mDetachedSignature = source.readInt() == 1;
|
||||||
@@ -96,7 +94,6 @@ public class PgpSignEncryptInputParcel implements Parcelable {
|
|||||||
}
|
}
|
||||||
dest.writeInt(mSignatureHashAlgorithm);
|
dest.writeInt(mSignatureHashAlgorithm);
|
||||||
dest.writeLong(mAdditionalEncryptId);
|
dest.writeLong(mAdditionalEncryptId);
|
||||||
dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0);
|
|
||||||
dest.writeString(mCharset);
|
dest.writeString(mCharset);
|
||||||
dest.writeInt(mCleartextSignature ? 1 : 0);
|
dest.writeInt(mCleartextSignature ? 1 : 0);
|
||||||
dest.writeInt(mDetachedSignature ? 1 : 0);
|
dest.writeInt(mDetachedSignature ? 1 : 0);
|
||||||
@@ -113,10 +110,6 @@ public class PgpSignEncryptInputParcel implements Parcelable {
|
|||||||
this.mCharset = mCharset;
|
this.mCharset = mCharset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFailOnMissingEncryptionKeyIds() {
|
|
||||||
return mFailOnMissingEncryptionKeyIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getAdditionalEncryptId() {
|
public long getAdditionalEncryptId() {
|
||||||
return mAdditionalEncryptId;
|
return mAdditionalEncryptId;
|
||||||
}
|
}
|
||||||
@@ -207,11 +200,6 @@ public class PgpSignEncryptInputParcel implements Parcelable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PgpSignEncryptInputParcel setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) {
|
|
||||||
mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PgpSignEncryptInputParcel setCleartextSignature(boolean cleartextSignature) {
|
public PgpSignEncryptInputParcel setCleartextSignature(boolean cleartextSignature) {
|
||||||
this.mCleartextSignature = cleartextSignature;
|
this.mCleartextSignature = cleartextSignature;
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
+26
-12
@@ -168,10 +168,17 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
|||||||
try {
|
try {
|
||||||
long signingMasterKeyId = input.getSignatureMasterKeyId();
|
long signingMasterKeyId = input.getSignatureMasterKeyId();
|
||||||
long signingSubKeyId = input.getSignatureSubKeyId();
|
long signingSubKeyId = input.getSignatureSubKeyId();
|
||||||
{
|
|
||||||
CanonicalizedSecretKeyRing signingKeyRing =
|
CanonicalizedSecretKeyRing signingKeyRing =
|
||||||
mProviderHelper.getCanonicalizedSecretKeyRing(signingMasterKeyId);
|
mProviderHelper.getCanonicalizedSecretKeyRing(signingMasterKeyId);
|
||||||
signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId());
|
signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId());
|
||||||
|
|
||||||
|
|
||||||
|
// Make sure key is not expired or revoked
|
||||||
|
if (signingKeyRing.isExpired() || signingKeyRing.isRevoked()
|
||||||
|
|| signingKey.isExpired() || signingKey.isRevoked()) {
|
||||||
|
log.add(LogType.MSG_PSE_ERROR_REVOKED_OR_EXPIRED, indent);
|
||||||
|
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we are allowed to sign here!
|
// Make sure we are allowed to sign here!
|
||||||
@@ -281,16 +288,17 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
|||||||
if (encryptSubKeyIds.isEmpty()) {
|
if (encryptSubKeyIds.isEmpty()) {
|
||||||
log.add(LogType.MSG_PSE_KEY_WARN, indent + 1,
|
log.add(LogType.MSG_PSE_KEY_WARN, indent + 1,
|
||||||
KeyFormattingUtils.convertKeyIdToHex(id));
|
KeyFormattingUtils.convertKeyIdToHex(id));
|
||||||
if (input.isFailOnMissingEncryptionKeyIds()) {
|
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
}
|
||||||
}
|
// Make sure key is not expired or revoked
|
||||||
|
if (keyRing.isExpired() || keyRing.isRevoked()) {
|
||||||
|
log.add(LogType.MSG_PSE_ERROR_REVOKED_OR_EXPIRED, indent);
|
||||||
|
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||||
}
|
}
|
||||||
} catch (ProviderHelper.NotFoundException e) {
|
} catch (ProviderHelper.NotFoundException e) {
|
||||||
log.add(LogType.MSG_PSE_KEY_UNKNOWN, indent + 1,
|
log.add(LogType.MSG_PSE_KEY_UNKNOWN, indent + 1,
|
||||||
KeyFormattingUtils.convertKeyIdToHex(id));
|
KeyFormattingUtils.convertKeyIdToHex(id));
|
||||||
if (input.isFailOnMissingEncryptionKeyIds()) {
|
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -470,7 +478,13 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
|||||||
InputStream in = new BufferedInputStream(inputData.getInputStream());
|
InputStream in = new BufferedInputStream(inputData.getInputStream());
|
||||||
|
|
||||||
if (enableCompression) {
|
if (enableCompression) {
|
||||||
compressGen = new PGPCompressedDataGenerator(input.getCompressionAlgorithm());
|
// Use preferred compression algo
|
||||||
|
int algo = input.getCompressionAlgorithm();
|
||||||
|
if (algo == PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT) {
|
||||||
|
algo = PgpSecurityConstants.DEFAULT_COMPRESSION_ALGORITHM;
|
||||||
|
}
|
||||||
|
|
||||||
|
compressGen = new PGPCompressedDataGenerator(algo);
|
||||||
bcpgOut = new BCPGOutputStream(compressGen.open(out));
|
bcpgOut = new BCPGOutputStream(compressGen.open(out));
|
||||||
} else {
|
} else {
|
||||||
bcpgOut = new BCPGOutputStream(out);
|
bcpgOut = new BCPGOutputStream(out);
|
||||||
@@ -514,7 +528,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
|
|||||||
} catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) {
|
} catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) {
|
||||||
// this secret key diverts to a OpenPGP card, throw exception with hash that will be signed
|
// this secret key diverts to a OpenPGP card, throw exception with hash that will be signed
|
||||||
log.add(LogType.MSG_PSE_PENDING_NFC, indent);
|
log.add(LogType.MSG_PSE_PENDING_NFC, indent);
|
||||||
return new PgpSignEncryptResult(log, RequiredInputParcel.createNfcSignOperation(
|
return new PgpSignEncryptResult(log, RequiredInputParcel.createSecurityTokenSignOperation(
|
||||||
signingKey.getRing().getMasterKeyId(), signingKey.getKeyId(),
|
signingKey.getRing().getMasterKeyId(), signingKey.getKeyId(),
|
||||||
e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()), cryptoInput);
|
e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()), cryptoInput);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ import java.util.Set;
|
|||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import android.support.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||||
import org.bouncycastle.bcpg.SignatureSubpacketTags;
|
import org.bouncycastle.bcpg.SignatureSubpacketTags;
|
||||||
@@ -42,15 +44,22 @@ import org.bouncycastle.bcpg.UserAttributeSubpacketTags;
|
|||||||
import org.bouncycastle.bcpg.sig.KeyFlags;
|
import org.bouncycastle.bcpg.sig.KeyFlags;
|
||||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||||
|
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignatureGenerator;
|
||||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||||
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
|
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
|
||||||
import org.bouncycastle.openpgp.PGPUtil;
|
import org.bouncycastle.openpgp.PGPUtil;
|
||||||
|
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||||
|
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
||||||
|
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
|
||||||
|
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
@@ -1310,4 +1319,37 @@ public class UncachedKeyRing {
|
|||||||
|| algorithm == PGPPublicKey.ECDH;
|
|| algorithm == PGPPublicKey.ECDH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ONLY TO BE USED FOR TESTING!!
|
||||||
|
@VisibleForTesting
|
||||||
|
public static UncachedKeyRing forTestingOnlyAddDummyLocalSignature(
|
||||||
|
UncachedKeyRing uncachedKeyRing, String passphrase) throws Exception {
|
||||||
|
PGPSecretKeyRing sKR = (PGPSecretKeyRing) uncachedKeyRing.mRing;
|
||||||
|
|
||||||
|
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||||
|
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
||||||
|
PGPPrivateKey masterPrivateKey = sKR.getSecretKey().extractPrivateKey(keyDecryptor);
|
||||||
|
PGPPublicKey masterPublicKey = uncachedKeyRing.mRing.getPublicKey();
|
||||||
|
|
||||||
|
// add packet with "pin" notation data
|
||||||
|
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||||
|
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
|
||||||
|
PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO)
|
||||||
|
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||||
|
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||||
|
{ // set subpackets
|
||||||
|
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||||
|
hashedPacketsGen.setExportable(false, false);
|
||||||
|
hashedPacketsGen.setNotationData(false, true, "dummynotationdata", "some data");
|
||||||
|
sGen.setHashedSubpackets(hashedPacketsGen.generate());
|
||||||
|
}
|
||||||
|
sGen.init(PGPSignature.DIRECT_KEY, masterPrivateKey);
|
||||||
|
PGPSignature emptySig = sGen.generateCertification(masterPublicKey);
|
||||||
|
|
||||||
|
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, emptySig);
|
||||||
|
sKR = PGPSecretKeyRing.insertSecretKey(sKR,
|
||||||
|
PGPSecretKey.replacePublicKey(sKR.getSecretKey(), masterPublicKey));
|
||||||
|
|
||||||
|
return new UncachedKeyRing(sKR);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+261
@@ -0,0 +1,261 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
* Copyright (C) 2014-2016 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.provider;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.OperationApplicationException;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
|
||||||
|
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||||
|
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||||
|
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||||
|
|
||||||
|
|
||||||
|
public class ApiDataAccessObject {
|
||||||
|
|
||||||
|
private final SimpleContentResolverInterface mQueryInterface;
|
||||||
|
|
||||||
|
public ApiDataAccessObject(Context context) {
|
||||||
|
final ContentResolver contentResolver = context.getContentResolver();
|
||||||
|
mQueryInterface = new SimpleContentResolverInterface() {
|
||||||
|
@Override
|
||||||
|
public Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs,
|
||||||
|
String sortOrder) {
|
||||||
|
return contentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri insert(Uri contentUri, ContentValues values) {
|
||||||
|
return contentResolver.insert(contentUri, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs) {
|
||||||
|
return contentResolver.update(contentUri, values, where, selectionArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int delete(Uri contentUri, String where, String[] selectionArgs) {
|
||||||
|
return contentResolver.delete(contentUri, where, selectionArgs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiDataAccessObject(SimpleContentResolverInterface queryInterface) {
|
||||||
|
mQueryInterface = queryInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<String> getRegisteredApiApps() {
|
||||||
|
Cursor cursor = mQueryInterface.query(ApiApps.CONTENT_URI, null, null, null, null);
|
||||||
|
|
||||||
|
ArrayList<String> packageNames = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
if (cursor != null) {
|
||||||
|
int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME);
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
do {
|
||||||
|
packageNames.add(cursor.getString(packageNameCol));
|
||||||
|
} while (cursor.moveToNext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return packageNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContentValues contentValueForApiApps(AppSettings appSettings) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
|
||||||
|
values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageCertificate());
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContentValues contentValueForApiAccounts(AccountSettings accSettings) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(KeychainContract.ApiAccounts.ACCOUNT_NAME, accSettings.getAccountName());
|
||||||
|
values.put(KeychainContract.ApiAccounts.KEY_ID, accSettings.getKeyId());
|
||||||
|
|
||||||
|
// DEPRECATED and thus hardcoded
|
||||||
|
values.put(KeychainContract.ApiAccounts.COMPRESSION, CompressionAlgorithmTags.ZLIB);
|
||||||
|
values.put(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM,
|
||||||
|
PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT);
|
||||||
|
values.put(KeychainContract.ApiAccounts.HASH_ALORITHM,
|
||||||
|
PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT);
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertApiApp(AppSettings appSettings) {
|
||||||
|
mQueryInterface.insert(ApiApps.CONTENT_URI,
|
||||||
|
contentValueForApiApps(appSettings));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertApiAccount(Uri uri, AccountSettings accSettings) {
|
||||||
|
mQueryInterface.insert(uri, contentValueForApiAccounts(accSettings));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateApiAccount(Uri uri, AccountSettings accSettings) {
|
||||||
|
if (mQueryInterface.update(uri, contentValueForApiAccounts(accSettings), null,
|
||||||
|
null) <= 0) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be an uri pointing to an account
|
||||||
|
*/
|
||||||
|
public AppSettings getApiAppSettings(Uri uri) {
|
||||||
|
AppSettings settings = null;
|
||||||
|
|
||||||
|
Cursor cursor = mQueryInterface.query(uri, null, null, null, null);
|
||||||
|
try {
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
settings = new AppSettings();
|
||||||
|
settings.setPackageName(cursor.getString(
|
||||||
|
cursor.getColumnIndex(ApiApps.PACKAGE_NAME)));
|
||||||
|
settings.setPackageCertificate(cursor.getBlob(
|
||||||
|
cursor.getColumnIndex(ApiApps.PACKAGE_CERTIFICATE)));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountSettings getApiAccountSettings(Uri accountUri) {
|
||||||
|
AccountSettings settings = null;
|
||||||
|
|
||||||
|
Cursor cursor = mQueryInterface.query(accountUri, null, null, null, null);
|
||||||
|
try {
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
settings = new AccountSettings();
|
||||||
|
|
||||||
|
settings.setAccountName(cursor.getString(
|
||||||
|
cursor.getColumnIndex(KeychainContract.ApiAccounts.ACCOUNT_NAME)));
|
||||||
|
settings.setKeyId(cursor.getLong(
|
||||||
|
cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID)));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Long> getAllKeyIdsForApp(Uri uri) {
|
||||||
|
Set<Long> keyIds = new HashSet<>();
|
||||||
|
|
||||||
|
Cursor cursor = mQueryInterface.query(uri, null, null, null, null);
|
||||||
|
try {
|
||||||
|
if (cursor != null) {
|
||||||
|
int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID);
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
keyIds.add(cursor.getLong(keyIdColumn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashSet<Long> getAllowedKeyIdsForApp(Uri uri) {
|
||||||
|
HashSet<Long> keyIds = new HashSet<>();
|
||||||
|
|
||||||
|
Cursor cursor = mQueryInterface.query(uri, null, null, null, null);
|
||||||
|
try {
|
||||||
|
if (cursor != null) {
|
||||||
|
int keyIdColumn = cursor.getColumnIndex(ApiAllowedKeys.KEY_ID);
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
keyIds.add(cursor.getLong(keyIdColumn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveAllowedKeyIdsForApp(Uri uri, Set<Long> allowedKeyIds)
|
||||||
|
throws RemoteException, OperationApplicationException {
|
||||||
|
// wipe whole table of allowed keys for this account
|
||||||
|
mQueryInterface.delete(uri, null, null);
|
||||||
|
|
||||||
|
// re-insert allowed key ids
|
||||||
|
for (Long keyId : allowedKeyIds) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(ApiAllowedKeys.KEY_ID, keyId);
|
||||||
|
mQueryInterface.insert(uri, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAllowedKeyIdForApp(Uri uri, long allowedKeyId) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(ApiAllowedKeys.KEY_ID, allowedKeyId);
|
||||||
|
mQueryInterface.insert(uri, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getApiAppCertificate(String packageName) {
|
||||||
|
Uri queryUri = ApiApps.buildByPackageNameUri(packageName);
|
||||||
|
|
||||||
|
String[] projection = new String[]{ApiApps.PACKAGE_CERTIFICATE};
|
||||||
|
|
||||||
|
Cursor cursor = mQueryInterface.query(queryUri, projection, null, null, null);
|
||||||
|
try {
|
||||||
|
byte[] signature = null;
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
int signatureCol = 0;
|
||||||
|
|
||||||
|
signature = cursor.getBlob(signatureCol);
|
||||||
|
}
|
||||||
|
return signature;
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+6
@@ -60,6 +60,9 @@ public class KeychainContract {
|
|||||||
String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID
|
String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID
|
||||||
String TYPE = "type"; // not a database id
|
String TYPE = "type"; // not a database id
|
||||||
String USER_ID = "user_id"; // not a database id
|
String USER_ID = "user_id"; // not a database id
|
||||||
|
String NAME = "name";
|
||||||
|
String EMAIL = "email";
|
||||||
|
String COMMENT = "comment";
|
||||||
String ATTRIBUTE_DATA = "attribute_data"; // not a database id
|
String ATTRIBUTE_DATA = "attribute_data"; // not a database id
|
||||||
String RANK = "rank"; // ONLY used for sorting! no key, no nothing!
|
String RANK = "rank"; // ONLY used for sorting! no key, no nothing!
|
||||||
String IS_PRIMARY = "is_primary";
|
String IS_PRIMARY = "is_primary";
|
||||||
@@ -359,6 +362,9 @@ public class KeychainContract {
|
|||||||
|
|
||||||
public static class Certs implements CertsColumns, BaseColumns {
|
public static class Certs implements CertsColumns, BaseColumns {
|
||||||
public static final String USER_ID = UserPacketsColumns.USER_ID;
|
public static final String USER_ID = UserPacketsColumns.USER_ID;
|
||||||
|
public static final String NAME = UserPacketsColumns.NAME;
|
||||||
|
public static final String EMAIL = UserPacketsColumns.EMAIL;
|
||||||
|
public static final String COMMENT = UserPacketsColumns.COMMENT;
|
||||||
public static final String SIGNER_UID = "signer_user_id";
|
public static final String SIGNER_UID = "signer_user_id";
|
||||||
|
|
||||||
public static final int UNVERIFIED = 0;
|
public static final int UNVERIFIED = 0;
|
||||||
|
|||||||
+21
-10
@@ -54,7 +54,7 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public class KeychainDatabase extends SQLiteOpenHelper {
|
public class KeychainDatabase extends SQLiteOpenHelper {
|
||||||
private static final String DATABASE_NAME = "openkeychain.db";
|
private static final String DATABASE_NAME = "openkeychain.db";
|
||||||
private static final int DATABASE_VERSION = 14;
|
private static final int DATABASE_VERSION = 17;
|
||||||
static Boolean apgHack = false;
|
static Boolean apgHack = false;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
|
|
||||||
@@ -115,6 +115,9 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
+ UserPacketsColumns.MASTER_KEY_ID + " INTEGER, "
|
+ UserPacketsColumns.MASTER_KEY_ID + " INTEGER, "
|
||||||
+ UserPacketsColumns.TYPE + " INT, "
|
+ UserPacketsColumns.TYPE + " INT, "
|
||||||
+ UserPacketsColumns.USER_ID + " TEXT, "
|
+ UserPacketsColumns.USER_ID + " TEXT, "
|
||||||
|
+ UserPacketsColumns.NAME + " TEXT, "
|
||||||
|
+ UserPacketsColumns.EMAIL + " TEXT, "
|
||||||
|
+ UserPacketsColumns.COMMENT + " TEXT, "
|
||||||
+ UserPacketsColumns.ATTRIBUTE_DATA + " BLOB, "
|
+ UserPacketsColumns.ATTRIBUTE_DATA + " BLOB, "
|
||||||
|
|
||||||
+ UserPacketsColumns.IS_PRIMARY + " INTEGER, "
|
+ UserPacketsColumns.IS_PRIMARY + " INTEGER, "
|
||||||
@@ -276,37 +279,45 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
db.execSQL("ALTER TABLE user_ids ADD COLUMN type INTEGER");
|
db.execSQL("ALTER TABLE user_ids ADD COLUMN type INTEGER");
|
||||||
db.execSQL("ALTER TABLE user_ids ADD COLUMN attribute_data BLOB");
|
db.execSQL("ALTER TABLE user_ids ADD COLUMN attribute_data BLOB");
|
||||||
case 7:
|
case 7:
|
||||||
// consolidate
|
|
||||||
case 8:
|
|
||||||
// new table for allowed key ids in API
|
// new table for allowed key ids in API
|
||||||
try {
|
try {
|
||||||
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
|
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// never mind, the column probably already existed
|
// never mind, the column probably already existed
|
||||||
}
|
}
|
||||||
case 9:
|
case 8:
|
||||||
// tbale name for user_ids changed to user_packets
|
// tbale name for user_ids changed to user_packets
|
||||||
db.execSQL("DROP TABLE IF EXISTS certs");
|
db.execSQL("DROP TABLE IF EXISTS certs");
|
||||||
db.execSQL("DROP TABLE IF EXISTS user_ids");
|
db.execSQL("DROP TABLE IF EXISTS user_ids");
|
||||||
db.execSQL(CREATE_USER_PACKETS);
|
db.execSQL(CREATE_USER_PACKETS);
|
||||||
db.execSQL(CREATE_CERTS);
|
db.execSQL(CREATE_CERTS);
|
||||||
case 10:
|
case 9:
|
||||||
// do nothing here, just consolidate
|
// do nothing here, just consolidate
|
||||||
case 11:
|
case 10:
|
||||||
// fix problems in database, see #1402 for details
|
// fix problems in database, see #1402 for details
|
||||||
// https://github.com/open-keychain/open-keychain/issues/1402
|
// https://github.com/open-keychain/open-keychain/issues/1402
|
||||||
db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3");
|
db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3");
|
||||||
case 12:
|
case 11:
|
||||||
db.execSQL(CREATE_UPDATE_KEYS);
|
db.execSQL(CREATE_UPDATE_KEYS);
|
||||||
case 13:
|
case 12:
|
||||||
// do nothing here, just consolidate
|
// do nothing here, just consolidate
|
||||||
case 14:
|
case 13:
|
||||||
db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ");");
|
db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ");");
|
||||||
db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", "
|
db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", "
|
||||||
+ UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");");
|
+ UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");");
|
||||||
db.execSQL("CREATE INDEX verified_certs ON certs ("
|
db.execSQL("CREATE INDEX verified_certs ON certs ("
|
||||||
+ CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");");
|
+ CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");");
|
||||||
|
case 14:
|
||||||
|
db.execSQL("ALTER TABLE user_packets ADD COLUMN name TEXT");
|
||||||
|
db.execSQL("ALTER TABLE user_packets ADD COLUMN email TEXT");
|
||||||
|
db.execSQL("ALTER TABLE user_packets ADD COLUMN comment TEXT");
|
||||||
|
case 15:
|
||||||
|
db.execSQL("CREATE INDEX uids_by_name ON user_packets (name COLLATE NOCASE)");
|
||||||
|
db.execSQL("CREATE INDEX uids_by_email ON user_packets (email COLLATE NOCASE)");
|
||||||
|
if (oldVersion == 14) {
|
||||||
|
// no consolidate necessary
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// always do consolidate after upgrade
|
// always do consolidate after upgrade
|
||||||
|
|||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.provider;
|
||||||
|
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.BaseColumns;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
|
||||||
|
|
||||||
|
public class KeychainExternalContract {
|
||||||
|
|
||||||
|
// this is in KeychainExternalContract already, but we want to be double
|
||||||
|
// sure this isn't mixed up with the internal one!
|
||||||
|
public static final String CONTENT_AUTHORITY_EXTERNAL = Constants.PROVIDER_AUTHORITY + ".exported";
|
||||||
|
|
||||||
|
private static final Uri BASE_CONTENT_URI_EXTERNAL = Uri
|
||||||
|
.parse("content://" + CONTENT_AUTHORITY_EXTERNAL);
|
||||||
|
|
||||||
|
public static final String BASE_EMAIL_STATUS = "email_status";
|
||||||
|
|
||||||
|
public static class EmailStatus implements BaseColumns {
|
||||||
|
public static final String EMAIL_ADDRESS = "email_address";
|
||||||
|
public static final String EMAIL_STATUS = "email_status";
|
||||||
|
|
||||||
|
public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon()
|
||||||
|
.appendPath(BASE_EMAIL_STATUS).build();
|
||||||
|
|
||||||
|
public static final String CONTENT_TYPE
|
||||||
|
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.email_status";
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeychainExternalContract() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+13
-8
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
|
||||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
* Copyright (C) 2014-2016 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -308,13 +308,18 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
projectionMap.put(KeyRings.ALGORITHM, Tables.KEYS + "." + Keys.ALGORITHM);
|
projectionMap.put(KeyRings.ALGORITHM, Tables.KEYS + "." + Keys.ALGORITHM);
|
||||||
projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT);
|
projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT);
|
||||||
projectionMap.put(KeyRings.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID);
|
projectionMap.put(KeyRings.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID);
|
||||||
|
projectionMap.put(KeyRings.NAME, Tables.USER_PACKETS + "." + UserPackets.NAME);
|
||||||
|
projectionMap.put(KeyRings.EMAIL, Tables.USER_PACKETS + "." + UserPackets.EMAIL);
|
||||||
|
projectionMap.put(KeyRings.COMMENT, Tables.USER_PACKETS + "." + UserPackets.COMMENT);
|
||||||
projectionMap.put(KeyRings.HAS_DUPLICATE_USER_ID,
|
projectionMap.put(KeyRings.HAS_DUPLICATE_USER_ID,
|
||||||
"(EXISTS (SELECT * FROM " + Tables.USER_PACKETS + " AS dups"
|
"(EXISTS (SELECT * FROM " + Tables.USER_PACKETS + " AS dups"
|
||||||
+ " WHERE dups." + UserPackets.MASTER_KEY_ID
|
+ " WHERE dups." + UserPackets.MASTER_KEY_ID
|
||||||
+ " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
|
+ " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
|
||||||
+ " AND dups." + UserPackets.RANK + " = 0"
|
+ " AND dups." + UserPackets.RANK + " = 0"
|
||||||
+ " AND dups." + UserPackets.USER_ID
|
+ " AND dups." + UserPackets.NAME
|
||||||
+ " = "+ Tables.USER_PACKETS + "." + UserPackets.USER_ID
|
+ " = " + Tables.USER_PACKETS + "." + UserPackets.NAME + " COLLATE NOCASE"
|
||||||
|
+ " AND dups." + UserPackets.EMAIL
|
||||||
|
+ " = " + Tables.USER_PACKETS + "." + UserPackets.EMAIL + " COLLATE NOCASE"
|
||||||
+ ")) AS " + KeyRings.HAS_DUPLICATE_USER_ID);
|
+ ")) AS " + KeyRings.HAS_DUPLICATE_USER_ID);
|
||||||
projectionMap.put(KeyRings.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED);
|
projectionMap.put(KeyRings.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED);
|
||||||
projectionMap.put(KeyRings.PUBKEY_DATA,
|
projectionMap.put(KeyRings.PUBKEY_DATA,
|
||||||
@@ -451,12 +456,12 @@ public class KeychainProvider extends ContentProvider {
|
|||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
emailWhere += " OR ";
|
emailWhere += " OR ";
|
||||||
}
|
}
|
||||||
emailWhere += "tmp." + UserPackets.USER_ID + " LIKE ";
|
|
||||||
// match '*<email>', so it has to be at the *end* of the user id
|
|
||||||
if (match == KEY_RINGS_FIND_BY_EMAIL) {
|
if (match == KEY_RINGS_FIND_BY_EMAIL) {
|
||||||
emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">");
|
emailWhere += "tmp." + UserPackets.EMAIL + " LIKE "
|
||||||
|
+ DatabaseUtils.sqlEscapeString(chunks[i]);
|
||||||
} else {
|
} else {
|
||||||
emailWhere += DatabaseUtils.sqlEscapeString("%" + chunks[i] + "%");
|
emailWhere += "tmp." + UserPackets.USER_ID + " LIKE "
|
||||||
|
+ DatabaseUtils.sqlEscapeString("%" + chunks[i] + "%");
|
||||||
}
|
}
|
||||||
gotCondition = true;
|
gotCondition = true;
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-202
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.provider;
|
package org.sufficientlysecure.keychain.provider;
|
||||||
|
|
||||||
|
|
||||||
import android.content.ContentProviderOperation;
|
import android.content.ContentProviderOperation;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
@@ -29,19 +30,12 @@ import android.os.RemoteException;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.util.LongSparseArray;
|
import android.support.v4.util.LongSparseArray;
|
||||||
|
|
||||||
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
|
||||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
|
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
|
||||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||||
import org.sufficientlysecure.keychain.operations.ImportOperation;
|
import org.sufficientlysecure.keychain.operations.ImportOperation;
|
||||||
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
||||||
@@ -51,24 +45,25 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
|||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||||
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
|
||||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
||||||
|
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
|
||||||
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
import org.sufficientlysecure.keychain.util.ProgressFixedScaler;
|
import org.sufficientlysecure.keychain.util.ProgressFixedScaler;
|
||||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||||
import org.sufficientlysecure.keychain.util.Utf8Util;
|
import org.sufficientlysecure.keychain.util.Utf8Util;
|
||||||
@@ -80,10 +75,8 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -459,11 +452,13 @@ public class ProviderHelper {
|
|||||||
mIndent += 1;
|
mIndent += 1;
|
||||||
for (byte[] rawUserId : masterKey.getUnorderedRawUserIds()) {
|
for (byte[] rawUserId : masterKey.getUnorderedRawUserIds()) {
|
||||||
String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId);
|
String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId);
|
||||||
|
|
||||||
UserPacketItem item = new UserPacketItem();
|
UserPacketItem item = new UserPacketItem();
|
||||||
uids.add(item);
|
uids.add(item);
|
||||||
|
KeyRing.UserId splitUserId = KeyRing.splitUserId(userId);
|
||||||
item.userId = userId;
|
item.userId = userId;
|
||||||
|
item.name = splitUserId.name;
|
||||||
|
item.email = splitUserId.email;
|
||||||
|
item.comment = splitUserId.comment;
|
||||||
int unknownCerts = 0;
|
int unknownCerts = 0;
|
||||||
|
|
||||||
log(LogType.MSG_IP_UID_PROCESSING, userId);
|
log(LogType.MSG_IP_UID_PROCESSING, userId);
|
||||||
@@ -753,6 +748,9 @@ public class ProviderHelper {
|
|||||||
private static class UserPacketItem implements Comparable<UserPacketItem> {
|
private static class UserPacketItem implements Comparable<UserPacketItem> {
|
||||||
Integer type;
|
Integer type;
|
||||||
String userId;
|
String userId;
|
||||||
|
String name;
|
||||||
|
String email;
|
||||||
|
String comment;
|
||||||
byte[] attributeData;
|
byte[] attributeData;
|
||||||
boolean isPrimary = false;
|
boolean isPrimary = false;
|
||||||
WrappedSignature selfCert;
|
WrappedSignature selfCert;
|
||||||
@@ -1444,6 +1442,9 @@ public class ProviderHelper {
|
|||||||
values.put(UserPackets.MASTER_KEY_ID, masterKeyId);
|
values.put(UserPackets.MASTER_KEY_ID, masterKeyId);
|
||||||
values.put(UserPackets.TYPE, item.type);
|
values.put(UserPackets.TYPE, item.type);
|
||||||
values.put(UserPackets.USER_ID, item.userId);
|
values.put(UserPackets.USER_ID, item.userId);
|
||||||
|
values.put(UserPackets.NAME, item.name);
|
||||||
|
values.put(UserPackets.EMAIL, item.email);
|
||||||
|
values.put(UserPackets.COMMENT, item.comment);
|
||||||
values.put(UserPackets.ATTRIBUTE_DATA, item.attributeData);
|
values.put(UserPackets.ATTRIBUTE_DATA, item.attributeData);
|
||||||
values.put(UserPackets.IS_PRIMARY, item.isPrimary);
|
values.put(UserPackets.IS_PRIMARY, item.isPrimary);
|
||||||
values.put(UserPackets.IS_REVOKED, item.selfRevocation != null);
|
values.put(UserPackets.IS_REVOKED, item.selfRevocation != null);
|
||||||
@@ -1481,191 +1482,6 @@ public class ProviderHelper {
|
|||||||
return mContentResolver.insert(UpdatedKeys.CONTENT_URI, values);
|
return mContentResolver.insert(UpdatedKeys.CONTENT_URI, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArrayList<String> getRegisteredApiApps() {
|
|
||||||
Cursor cursor = mContentResolver.query(ApiApps.CONTENT_URI, null, null, null, null);
|
|
||||||
|
|
||||||
ArrayList<String> packageNames = new ArrayList<>();
|
|
||||||
try {
|
|
||||||
if (cursor != null) {
|
|
||||||
int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME);
|
|
||||||
if (cursor.moveToFirst()) {
|
|
||||||
do {
|
|
||||||
packageNames.add(cursor.getString(packageNameCol));
|
|
||||||
} while (cursor.moveToNext());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return packageNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ContentValues contentValueForApiApps(AppSettings appSettings) {
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
|
|
||||||
values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageCertificate());
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ContentValues contentValueForApiAccounts(AccountSettings accSettings) {
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(KeychainContract.ApiAccounts.ACCOUNT_NAME, accSettings.getAccountName());
|
|
||||||
values.put(KeychainContract.ApiAccounts.KEY_ID, accSettings.getKeyId());
|
|
||||||
|
|
||||||
// DEPRECATED and thus hardcoded
|
|
||||||
values.put(KeychainContract.ApiAccounts.COMPRESSION, CompressionAlgorithmTags.ZLIB);
|
|
||||||
values.put(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM,
|
|
||||||
PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT);
|
|
||||||
values.put(KeychainContract.ApiAccounts.HASH_ALORITHM,
|
|
||||||
PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT);
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void insertApiApp(AppSettings appSettings) {
|
|
||||||
mContentResolver.insert(KeychainContract.ApiApps.CONTENT_URI,
|
|
||||||
contentValueForApiApps(appSettings));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void insertApiAccount(Uri uri, AccountSettings accSettings) {
|
|
||||||
mContentResolver.insert(uri, contentValueForApiAccounts(accSettings));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateApiAccount(Uri uri, AccountSettings accSettings) {
|
|
||||||
if (mContentResolver.update(uri, contentValueForApiAccounts(accSettings), null,
|
|
||||||
null) <= 0) {
|
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Must be an uri pointing to an account
|
|
||||||
*/
|
|
||||||
public AppSettings getApiAppSettings(Uri uri) {
|
|
||||||
AppSettings settings = null;
|
|
||||||
|
|
||||||
Cursor cursor = mContentResolver.query(uri, null, null, null, null);
|
|
||||||
try {
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
settings = new AppSettings();
|
|
||||||
settings.setPackageName(cursor.getString(
|
|
||||||
cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
|
|
||||||
settings.setPackageCertificate(cursor.getBlob(
|
|
||||||
cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_CERTIFICATE)));
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AccountSettings getApiAccountSettings(Uri accountUri) {
|
|
||||||
AccountSettings settings = null;
|
|
||||||
|
|
||||||
Cursor cursor = mContentResolver.query(accountUri, null, null, null, null);
|
|
||||||
try {
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
settings = new AccountSettings();
|
|
||||||
|
|
||||||
settings.setAccountName(cursor.getString(
|
|
||||||
cursor.getColumnIndex(KeychainContract.ApiAccounts.ACCOUNT_NAME)));
|
|
||||||
settings.setKeyId(cursor.getLong(
|
|
||||||
cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID)));
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Long> getAllKeyIdsForApp(Uri uri) {
|
|
||||||
Set<Long> keyIds = new HashSet<>();
|
|
||||||
|
|
||||||
Cursor cursor = mContentResolver.query(uri, null, null, null, null);
|
|
||||||
try {
|
|
||||||
if (cursor != null) {
|
|
||||||
int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID);
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
keyIds.add(cursor.getLong(keyIdColumn));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HashSet<Long> getAllowedKeyIdsForApp(Uri uri) {
|
|
||||||
HashSet<Long> keyIds = new HashSet<>();
|
|
||||||
|
|
||||||
Cursor cursor = mContentResolver.query(uri, null, null, null, null);
|
|
||||||
try {
|
|
||||||
if (cursor != null) {
|
|
||||||
int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAllowedKeys.KEY_ID);
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
keyIds.add(cursor.getLong(keyIdColumn));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveAllowedKeyIdsForApp(Uri uri, Set<Long> allowedKeyIds)
|
|
||||||
throws RemoteException, OperationApplicationException {
|
|
||||||
// wipe whole table of allowed keys for this account
|
|
||||||
mContentResolver.delete(uri, null, null);
|
|
||||||
|
|
||||||
// re-insert allowed key ids
|
|
||||||
for (Long keyId : allowedKeyIds) {
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(ApiAllowedKeys.KEY_ID, keyId);
|
|
||||||
mContentResolver.insert(uri, values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addAllowedKeyIdForApp(Uri uri, long allowedKeyId) {
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(ApiAllowedKeys.KEY_ID, allowedKeyId);
|
|
||||||
mContentResolver.insert(uri, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getApiAppCertificate(String packageName) {
|
|
||||||
Uri queryUri = ApiApps.buildByPackageNameUri(packageName);
|
|
||||||
|
|
||||||
String[] projection = new String[]{ApiApps.PACKAGE_CERTIFICATE};
|
|
||||||
|
|
||||||
Cursor cursor = mContentResolver.query(queryUri, projection, null, null, null);
|
|
||||||
try {
|
|
||||||
byte[] signature = null;
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
int signatureCol = 0;
|
|
||||||
|
|
||||||
signature = cursor.getBlob(signatureCol);
|
|
||||||
}
|
|
||||||
return signature;
|
|
||||||
} finally {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContentResolver getContentResolver() {
|
public ContentResolver getContentResolver() {
|
||||||
return mContentResolver;
|
return mContentResolver;
|
||||||
}
|
}
|
||||||
|
|||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.provider;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
/** This interface contains the principal methods for database access
|
||||||
|
* from {#android.content.ContentResolver}. It is used to allow substitution
|
||||||
|
* of a ContentResolver in DAOs.
|
||||||
|
*
|
||||||
|
* @see ApiDataAccessObject
|
||||||
|
*/
|
||||||
|
public interface SimpleContentResolverInterface {
|
||||||
|
Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
|
||||||
|
|
||||||
|
Uri insert(Uri contentUri, ContentValues values);
|
||||||
|
|
||||||
|
int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs);
|
||||||
|
|
||||||
|
int delete(Uri contentUri, String where, String[] selectionArgs);
|
||||||
|
}
|
||||||
+11
-2
@@ -99,6 +99,12 @@ public class TemporaryFileProvider extends ContentProvider {
|
|||||||
return context.getContentResolver().insert(CONTENT_URI, contentValues);
|
return context.getContentResolver().insert(CONTENT_URI, contentValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int setName(Context context, Uri uri, String name) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(TemporaryFileColumns.COLUMN_NAME, name);
|
||||||
|
return context.getContentResolver().update(uri, values, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
public static int setMimeType(Context context, Uri uri, String mimetype) {
|
public static int setMimeType(Context context, Uri uri, String mimetype) {
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(TemporaryFileColumns.COLUMN_TYPE, mimetype);
|
values.put(TemporaryFileColumns.COLUMN_TYPE, mimetype);
|
||||||
@@ -283,8 +289,11 @@ public class TemporaryFileProvider extends ContentProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||||
if (values.size() != 1 || !values.containsKey(TemporaryFileColumns.COLUMN_TYPE)) {
|
if (values.size() != 1) {
|
||||||
throw new UnsupportedOperationException("Update supported only for type field!");
|
throw new UnsupportedOperationException("Update supported only for one field at a time!");
|
||||||
|
}
|
||||||
|
if (!values.containsKey(TemporaryFileColumns.COLUMN_NAME) && !values.containsKey(TemporaryFileColumns.COLUMN_TYPE)) {
|
||||||
|
throw new UnsupportedOperationException("Update supported only for name and type field!");
|
||||||
}
|
}
|
||||||
if (selection != null || selectionArgs != null) {
|
if (selection != null || selectionArgs != null) {
|
||||||
throw new UnsupportedOperationException("Update supported only for plain uri!");
|
throw new UnsupportedOperationException("Update supported only for plain uri!");
|
||||||
|
|||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
package org.sufficientlysecure.keychain.receiver;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
public class NetworkReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
|
||||||
|
ConnectivityManager conn = (ConnectivityManager)
|
||||||
|
context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
NetworkInfo networkInfo = conn.getActiveNetworkInfo();
|
||||||
|
boolean isTypeWifi = (networkInfo.getType() == ConnectivityManager.TYPE_WIFI);
|
||||||
|
boolean isConnected = networkInfo.isConnected();
|
||||||
|
|
||||||
|
if (isTypeWifi && isConnected) {
|
||||||
|
|
||||||
|
// broadcaster receiver disabled
|
||||||
|
setWifiReceiverComponent(false, context);
|
||||||
|
Intent serviceIntent = new Intent(context, KeyserverSyncAdapterService.class);
|
||||||
|
serviceIntent.setAction(KeyserverSyncAdapterService.ACTION_SYNC_NOW);
|
||||||
|
context.startService(serviceIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWifiReceiverComponent(Boolean isEnabled, Context context) {
|
||||||
|
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
ComponentName compName = new ComponentName(context,
|
||||||
|
NetworkReceiver.class);
|
||||||
|
|
||||||
|
if (isEnabled) {
|
||||||
|
pm.setComponentEnabledSetting(compName,
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
|
||||||
|
Log.d(Constants.TAG, "Wifi Receiver is enabled!");
|
||||||
|
} else {
|
||||||
|
pm.setComponentEnabledSetting(compName,
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
||||||
|
Log.d(Constants.TAG, "Wifi Receiver is disabled!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-5
@@ -51,10 +51,10 @@ public class ApiPendingIntentFactory {
|
|||||||
CryptoInputParcel cryptoInput) {
|
CryptoInputParcel cryptoInput) {
|
||||||
|
|
||||||
switch (requiredInput.mType) {
|
switch (requiredInput.mType) {
|
||||||
case NFC_MOVE_KEY_TO_CARD:
|
case SECURITY_TOKEN_MOVE_KEY_TO_CARD:
|
||||||
case NFC_DECRYPT:
|
case SECURITY_TOKEN_DECRYPT:
|
||||||
case NFC_SIGN: {
|
case SECURITY_TOKEN_SIGN: {
|
||||||
return createNfcOperationPendingIntent(data, requiredInput, cryptoInput);
|
return createSecurityTokenOperationPendingIntent(data, requiredInput, cryptoInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
case PASSPHRASE: {
|
case PASSPHRASE: {
|
||||||
@@ -66,7 +66,7 @@ public class ApiPendingIntentFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PendingIntent createNfcOperationPendingIntent(Intent data, RequiredInputParcel requiredInput, CryptoInputParcel cryptoInput) {
|
private PendingIntent createSecurityTokenOperationPendingIntent(Intent data, RequiredInputParcel requiredInput, CryptoInputParcel cryptoInput) {
|
||||||
Intent intent = new Intent(mContext, RemoteSecurityTokenOperationActivity.class);
|
Intent intent = new Intent(mContext, RemoteSecurityTokenOperationActivity.class);
|
||||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||||
intent.putExtra(RemoteSecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
|
intent.putExtra(RemoteSecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
|
||||||
|
|||||||
+40
-37
@@ -18,6 +18,10 @@
|
|||||||
package org.sufficientlysecure.keychain.remote;
|
package org.sufficientlysecure.keychain.remote;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -33,15 +37,10 @@ import org.openintents.openpgp.OpenPgpError;
|
|||||||
import org.openintents.openpgp.util.OpenPgpApi;
|
import org.openintents.openpgp.util.OpenPgpApi;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract service class for remote APIs that handle app registration and user input.
|
* Abstract service class for remote APIs that handle app registration and user input.
|
||||||
@@ -49,13 +48,13 @@ import java.util.Arrays;
|
|||||||
public class ApiPermissionHelper {
|
public class ApiPermissionHelper {
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final ProviderHelper mProviderHelper;
|
private final ApiDataAccessObject mApiDao;
|
||||||
private PackageManager mPackageManager;
|
private PackageManager mPackageManager;
|
||||||
|
|
||||||
public ApiPermissionHelper(Context context) {
|
public ApiPermissionHelper(Context context, ApiDataAccessObject apiDao) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mPackageManager = context.getPackageManager();
|
mPackageManager = context.getPackageManager();
|
||||||
mProviderHelper = new ProviderHelper(context);
|
mApiDao = apiDao;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class WrongPackageCertificateException extends Exception {
|
public static class WrongPackageCertificateException extends Exception {
|
||||||
@@ -66,14 +65,24 @@ public class ApiPermissionHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns true iff the caller is allowed, or false on any type of problem.
|
||||||
|
* This method should only be used in cases where error handling is dealt with separately.
|
||||||
|
*/
|
||||||
|
protected boolean isAllowedIgnoreErrors() {
|
||||||
|
try {
|
||||||
|
return isCallerAllowed();
|
||||||
|
} catch (WrongPackageCertificateException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if caller is allowed to access the API
|
* Checks if caller is allowed to access the API
|
||||||
*
|
*
|
||||||
* @return null if caller is allowed, or a Bundle with a PendingIntent
|
* @return null if caller is allowed, or a Bundle with a PendingIntent
|
||||||
*/
|
*/
|
||||||
protected Intent isAllowed(Intent data) {
|
protected Intent isAllowedOrReturnIntent(Intent data) {
|
||||||
ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(mContext);
|
ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(mContext);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isCallerAllowed()) {
|
if (isCallerAllowed()) {
|
||||||
return null;
|
return null;
|
||||||
@@ -168,7 +177,7 @@ public class ApiPermissionHelper {
|
|||||||
|
|
||||||
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName);
|
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName);
|
||||||
|
|
||||||
return mProviderHelper.getApiAccountSettings(uri); // can be null!
|
return mApiDao.getApiAccountSettings(uri); // can be null!
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@@ -224,35 +233,29 @@ public class ApiPermissionHelper {
|
|||||||
private boolean isPackageAllowed(String packageName) throws WrongPackageCertificateException {
|
private boolean isPackageAllowed(String packageName) throws WrongPackageCertificateException {
|
||||||
Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName);
|
Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName);
|
||||||
|
|
||||||
ArrayList<String> allowedPkgs = mProviderHelper.getRegisteredApiApps();
|
byte[] storedPackageCert = mApiDao.getApiAppCertificate(packageName);
|
||||||
Log.d(Constants.TAG, "allowed: " + allowedPkgs);
|
|
||||||
|
|
||||||
// check if package is allowed to use our service
|
boolean isKnownPackage = storedPackageCert != null;
|
||||||
if (allowedPkgs.contains(packageName)) {
|
if (!isKnownPackage) {
|
||||||
Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName);
|
Log.d(Constants.TAG, "Package is NOT allowed! packageName: " + packageName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName);
|
||||||
|
|
||||||
// check package signature
|
byte[] currentPackageCert;
|
||||||
byte[] currentCert;
|
try {
|
||||||
try {
|
currentPackageCert = getPackageCertificate(packageName);
|
||||||
currentCert = getPackageCertificate(packageName);
|
} catch (NameNotFoundException e) {
|
||||||
} catch (NameNotFoundException e) {
|
throw new WrongPackageCertificateException(e.getMessage());
|
||||||
throw new WrongPackageCertificateException(e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] storedCert = mProviderHelper.getApiAppCertificate(packageName);
|
|
||||||
if (Arrays.equals(currentCert, storedCert)) {
|
|
||||||
Log.d(Constants.TAG,
|
|
||||||
"Package certificate is correct! (equals certificate from database)");
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
throw new WrongPackageCertificateException(
|
|
||||||
"PACKAGE NOT ALLOWED! Certificate wrong! (Certificate not " +
|
|
||||||
"equals certificate from database)");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(Constants.TAG, "Package is NOT allowed! packageName: " + packageName);
|
boolean packageCertMatchesStored = Arrays.equals(currentPackageCert, storedPackageCert);
|
||||||
return false;
|
if (packageCertMatchesStored) {
|
||||||
|
Log.d(Constants.TAG,"Package certificate matches expected.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new WrongPackageCertificateException("PACKAGE NOT ALLOWED DUE TO CERTIFICATE MISMATCH!");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+284
@@ -0,0 +1,284 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.remote;
|
||||||
|
|
||||||
|
|
||||||
|
import java.security.AccessControlException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import android.content.ContentProvider;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.UriMatcher;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.DatabaseUtils;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteQueryBuilder;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainExternalContract;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus;
|
||||||
|
import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
|
||||||
|
public class KeychainExternalProvider extends ContentProvider implements SimpleContentResolverInterface {
|
||||||
|
private static final int EMAIL_STATUS = 101;
|
||||||
|
private static final int API_APPS = 301;
|
||||||
|
private static final int API_APPS_BY_PACKAGE_NAME = 302;
|
||||||
|
|
||||||
|
|
||||||
|
private UriMatcher mUriMatcher;
|
||||||
|
private ApiPermissionHelper mApiPermissionHelper;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build and return a {@link UriMatcher} that catches all {@link Uri} variations supported by
|
||||||
|
* this {@link ContentProvider}.
|
||||||
|
*/
|
||||||
|
protected UriMatcher buildUriMatcher() {
|
||||||
|
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||||
|
|
||||||
|
String authority = KeychainExternalContract.CONTENT_AUTHORITY_EXTERNAL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* list email_status
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* email_status/
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS, EMAIL_STATUS);
|
||||||
|
|
||||||
|
matcher.addURI(KeychainContract.CONTENT_AUTHORITY, KeychainContract.BASE_API_APPS, API_APPS);
|
||||||
|
matcher.addURI(KeychainContract.CONTENT_AUTHORITY, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME);
|
||||||
|
|
||||||
|
return matcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeychainDatabase mKeychainDatabase;
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public boolean onCreate() {
|
||||||
|
mUriMatcher = buildUriMatcher();
|
||||||
|
mApiPermissionHelper = new ApiPermissionHelper(getContext(), new ApiDataAccessObject(this));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeychainDatabase getDb() {
|
||||||
|
if(mKeychainDatabase == null)
|
||||||
|
mKeychainDatabase = new KeychainDatabase(getContext());
|
||||||
|
return mKeychainDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getType(@NonNull Uri uri) {
|
||||||
|
final int match = mUriMatcher.match(uri);
|
||||||
|
switch (match) {
|
||||||
|
case EMAIL_STATUS:
|
||||||
|
return EmailStatus.CONTENT_TYPE;
|
||||||
|
|
||||||
|
case API_APPS:
|
||||||
|
return ApiApps.CONTENT_TYPE;
|
||||||
|
|
||||||
|
case API_APPS_BY_PACKAGE_NAME:
|
||||||
|
return ApiApps.CONTENT_ITEM_TYPE;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||||
|
String sortOrder) {
|
||||||
|
Log.v(Constants.TAG, "query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")");
|
||||||
|
|
||||||
|
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
|
||||||
|
|
||||||
|
int match = mUriMatcher.match(uri);
|
||||||
|
|
||||||
|
String groupBy = null;
|
||||||
|
|
||||||
|
switch (match) {
|
||||||
|
case EMAIL_STATUS: {
|
||||||
|
boolean callerIsAllowed = mApiPermissionHelper.isAllowedIgnoreErrors();
|
||||||
|
if (!callerIsAllowed) {
|
||||||
|
throw new AccessControlException("An application must register before use of KeychainExternalProvider!");
|
||||||
|
}
|
||||||
|
|
||||||
|
HashMap<String, String> projectionMap = new HashMap<>();
|
||||||
|
projectionMap.put(EmailStatus._ID, "email AS _id");
|
||||||
|
projectionMap.put(EmailStatus.EMAIL_ADDRESS,
|
||||||
|
Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.EMAIL_ADDRESS);
|
||||||
|
// we take the minimum (>0) here, where "1" is "verified by known secret key", "2" is "self-certified"
|
||||||
|
projectionMap.put(EmailStatus.EMAIL_STATUS, "CASE ( MIN (" + Certs.VERIFIED + " ) ) "
|
||||||
|
// remap to keep this provider contract independent from our internal representation
|
||||||
|
+ " WHEN " + Certs.VERIFIED_SELF + " THEN 1"
|
||||||
|
+ " WHEN " + Certs.VERIFIED_SECRET + " THEN 2"
|
||||||
|
+ " END AS " + EmailStatus.EMAIL_STATUS);
|
||||||
|
qb.setProjectionMap(projectionMap);
|
||||||
|
|
||||||
|
if (projection == null) {
|
||||||
|
throw new IllegalArgumentException("Please provide a projection!");
|
||||||
|
}
|
||||||
|
|
||||||
|
qb.setTables(
|
||||||
|
Tables.USER_PACKETS
|
||||||
|
+ " INNER JOIN " + Tables.CERTS + " ON ("
|
||||||
|
+ Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = "
|
||||||
|
+ Tables.CERTS + "." + Certs.MASTER_KEY_ID
|
||||||
|
+ " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = "
|
||||||
|
+ Tables.CERTS + "." + Certs.RANK
|
||||||
|
// verified == 0 has no self-cert, which is basically an error case. never return that!
|
||||||
|
+ " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0"
|
||||||
|
+ ")"
|
||||||
|
);
|
||||||
|
qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.USER_ID + " IS NOT NULL");
|
||||||
|
// in case there are multiple verifying certificates
|
||||||
|
groupBy = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + ", "
|
||||||
|
+ Tables.USER_PACKETS + "." + UserPackets.USER_ID;
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(sortOrder)) {
|
||||||
|
sortOrder = EmailStatus.EMAIL_ADDRESS + " ASC, " + EmailStatus.EMAIL_STATUS + " DESC";
|
||||||
|
}
|
||||||
|
|
||||||
|
// uri to watch is all /key_rings/
|
||||||
|
uri = KeyRings.CONTENT_URI;
|
||||||
|
|
||||||
|
boolean gotCondition = false;
|
||||||
|
String emailWhere = "";
|
||||||
|
// JAVA ♥
|
||||||
|
for (int i = 0; i < selectionArgs.length; ++i) {
|
||||||
|
if (selectionArgs[i].length() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (i != 0) {
|
||||||
|
emailWhere += " OR ";
|
||||||
|
}
|
||||||
|
emailWhere += UserPackets.USER_ID + " LIKE ";
|
||||||
|
// match '*<email>', so it has to be at the *end* of the user id
|
||||||
|
emailWhere += DatabaseUtils.sqlEscapeString("%<" + selectionArgs[i] + ">");
|
||||||
|
gotCondition = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gotCondition) {
|
||||||
|
qb.appendWhere(" AND (" + emailWhere + ")");
|
||||||
|
} else {
|
||||||
|
// TODO better way to do this?
|
||||||
|
Log.e(Constants.TAG, "Malformed find by email query!");
|
||||||
|
qb.appendWhere(" AND 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case API_APPS_BY_PACKAGE_NAME: {
|
||||||
|
String requestedPackageName = uri.getLastPathSegment();
|
||||||
|
checkIfPackageBelongsToCaller(getContext(), requestedPackageName);
|
||||||
|
|
||||||
|
qb.setTables(Tables.API_APPS);
|
||||||
|
qb.appendWhere(ApiApps.PACKAGE_NAME + " = ");
|
||||||
|
qb.appendWhereEscapeString(requestedPackageName);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no sort order is specified use the default
|
||||||
|
String orderBy;
|
||||||
|
if (TextUtils.isEmpty(sortOrder)) {
|
||||||
|
orderBy = null;
|
||||||
|
} else {
|
||||||
|
orderBy = sortOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
SQLiteDatabase db = getDb().getReadableDatabase();
|
||||||
|
|
||||||
|
Cursor cursor = qb.query(db, projection, selection, null, groupBy, null, orderBy);
|
||||||
|
if (cursor != null) {
|
||||||
|
// Tell the cursor what uri to watch, so it knows when its source data changes
|
||||||
|
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(Constants.TAG,
|
||||||
|
"Query: " + qb.buildQuery(projection, selection, null, null, orderBy, null));
|
||||||
|
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkIfPackageBelongsToCaller(Context context, String requestedPackageName) {
|
||||||
|
int callerUid = Binder.getCallingUid();
|
||||||
|
String[] callerPackageNames = context.getPackageManager().getPackagesForUid(callerUid);
|
||||||
|
if (callerPackageNames == null) {
|
||||||
|
throw new IllegalStateException("Failed to retrieve caller package name, this is an error!");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean packageBelongsToCaller = false;
|
||||||
|
for (String p : callerPackageNames) {
|
||||||
|
if (p.equals(requestedPackageName)) {
|
||||||
|
packageBelongsToCaller = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!packageBelongsToCaller) {
|
||||||
|
throw new SecurityException("ExternalProvider may only check status of caller package!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri insert(@NonNull Uri uri, ContentValues values) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int delete(@NonNull Uri uri, String additionalSelection, String[] selectionArgs) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+139
-59
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
* Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
* Copyright (C) 2016 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,11 +18,24 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.remote;
|
package org.sufficientlysecure.keychain.remote;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
@@ -43,12 +57,15 @@ import org.sufficientlysecure.keychain.operations.results.ExportResult;
|
|||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
|
||||||
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.KeyRing.UserId;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;
|
import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
|
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
|
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
@@ -61,18 +78,12 @@ import org.sufficientlysecure.keychain.util.InputData;
|
|||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class OpenPgpService extends Service {
|
public class OpenPgpService extends Service {
|
||||||
|
|
||||||
static final String[] EMAIL_SEARCH_PROJECTION = new String[]{
|
public static final List<Integer> SUPPORTED_VERSIONS =
|
||||||
|
Collections.unmodifiableList(Arrays.asList(3, 4, 5, 6, 7, 8, 9, 10, 11));
|
||||||
|
|
||||||
|
static final String[] KEY_SEARCH_PROJECTION = new String[]{
|
||||||
KeyRings._ID,
|
KeyRings._ID,
|
||||||
KeyRings.MASTER_KEY_ID,
|
KeyRings.MASTER_KEY_ID,
|
||||||
KeyRings.IS_EXPIRED,
|
KeyRings.IS_EXPIRED,
|
||||||
@@ -80,35 +91,50 @@ public class OpenPgpService extends Service {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// do not pre-select revoked or expired keys
|
// do not pre-select revoked or expired keys
|
||||||
static final String EMAIL_SEARCH_WHERE = Tables.KEYS + "." + KeychainContract.KeyRings.IS_REVOKED
|
static final String KEY_SEARCH_WHERE = Tables.KEYS + "." + KeychainContract.KeyRings.IS_REVOKED
|
||||||
+ " = 0 AND " + KeychainContract.KeyRings.IS_EXPIRED + " = 0";
|
+ " = 0 AND " + KeychainContract.KeyRings.IS_EXPIRED + " = 0";
|
||||||
|
|
||||||
private ApiPermissionHelper mApiPermissionHelper;
|
private ApiPermissionHelper mApiPermissionHelper;
|
||||||
private ProviderHelper mProviderHelper;
|
private ProviderHelper mProviderHelper;
|
||||||
|
private ApiDataAccessObject mApiDao;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
mApiPermissionHelper = new ApiPermissionHelper(this);
|
mApiPermissionHelper = new ApiPermissionHelper(this, new ApiDataAccessObject(this));
|
||||||
mProviderHelper = new ProviderHelper(this);
|
mProviderHelper = new ProviderHelper(this);
|
||||||
|
mApiDao = new ApiDataAccessObject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static class KeyIdResult {
|
||||||
* Search database for key ids based on emails.
|
final Intent mResultIntent;
|
||||||
*/
|
final HashSet<Long> mKeyIds;
|
||||||
private Intent returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds) {
|
|
||||||
|
KeyIdResult(Intent resultIntent) {
|
||||||
|
mResultIntent = resultIntent;
|
||||||
|
mKeyIds = null;
|
||||||
|
}
|
||||||
|
KeyIdResult(HashSet<Long> keyIds) {
|
||||||
|
mResultIntent = null;
|
||||||
|
mKeyIds = keyIds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyIdResult returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds, boolean isOpportunistic) {
|
||||||
boolean noUserIdsCheck = (encryptionUserIds == null || encryptionUserIds.length == 0);
|
boolean noUserIdsCheck = (encryptionUserIds == null || encryptionUserIds.length == 0);
|
||||||
boolean missingUserIdsCheck = false;
|
boolean missingUserIdsCheck = false;
|
||||||
boolean duplicateUserIdsCheck = false;
|
boolean duplicateUserIdsCheck = false;
|
||||||
|
|
||||||
ArrayList<Long> keyIds = new ArrayList<>();
|
HashSet<Long> keyIds = new HashSet<>();
|
||||||
ArrayList<String> missingEmails = new ArrayList<>();
|
ArrayList<String> missingEmails = new ArrayList<>();
|
||||||
ArrayList<String> duplicateEmails = new ArrayList<>();
|
ArrayList<String> duplicateEmails = new ArrayList<>();
|
||||||
if (!noUserIdsCheck) {
|
if (!noUserIdsCheck) {
|
||||||
for (String email : encryptionUserIds) {
|
for (String rawUserId : encryptionUserIds) {
|
||||||
|
UserId userId = KeyRing.splitUserId(rawUserId);
|
||||||
|
String email = userId.email != null ? userId.email : rawUserId;
|
||||||
// try to find the key for this specific email
|
// try to find the key for this specific email
|
||||||
Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email);
|
Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email);
|
||||||
Cursor cursor = getContentResolver().query(uri, EMAIL_SEARCH_PROJECTION, EMAIL_SEARCH_WHERE, null, null);
|
Cursor cursor = getContentResolver().query(uri, KEY_SEARCH_PROJECTION, KEY_SEARCH_WHERE, null, null);
|
||||||
try {
|
try {
|
||||||
// result should be one entry containing the key id
|
// result should be one entry containing the key id
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
@@ -137,15 +163,17 @@ public class OpenPgpService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert ArrayList<Long> to long[]
|
if (isOpportunistic && (noUserIdsCheck || missingUserIdsCheck)) {
|
||||||
long[] keyIdsArray = new long[keyIds.size()];
|
Intent result = new Intent();
|
||||||
for (int i = 0; i < keyIdsArray.length; i++) {
|
result.putExtra(OpenPgpApi.RESULT_ERROR,
|
||||||
keyIdsArray[i] = keyIds.get(i);
|
new OpenPgpError(OpenPgpError.OPPORTUNISTIC_MISSING_KEYS, "missing keys in opportunistic mode"));
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||||
|
return new KeyIdResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noUserIdsCheck || missingUserIdsCheck || duplicateUserIdsCheck) {
|
if (noUserIdsCheck || missingUserIdsCheck || duplicateUserIdsCheck) {
|
||||||
// allow the user to verify pub key selection
|
// convert ArrayList<Long> to long[]
|
||||||
|
long[] keyIdsArray = getUnboxedLongArray(keyIds);
|
||||||
ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(getBaseContext());
|
ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(getBaseContext());
|
||||||
PendingIntent pi = piFactory.createSelectPublicKeyPendingIntent(data, keyIdsArray,
|
PendingIntent pi = piFactory.createSelectPublicKeyPendingIntent(data, keyIdsArray,
|
||||||
missingEmails, duplicateEmails, noUserIdsCheck);
|
missingEmails, duplicateEmails, noUserIdsCheck);
|
||||||
@@ -154,19 +182,15 @@ public class OpenPgpService extends Service {
|
|||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||||
return result;
|
return new KeyIdResult(result);
|
||||||
} else {
|
|
||||||
// everything was easy, we have exactly one key for every email
|
|
||||||
|
|
||||||
if (keyIdsArray.length == 0) {
|
|
||||||
Log.e(Constants.TAG, "keyIdsArray.length == 0, should never happen!");
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent result = new Intent();
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_KEY_IDS, keyIdsArray);
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// everything was easy, we have exactly one key for every email
|
||||||
|
if (keyIds.isEmpty()) {
|
||||||
|
Log.e(Constants.TAG, "keyIdsArray.length == 0, should never happen!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new KeyIdResult(keyIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Intent signImpl(Intent data, InputStream inputStream,
|
private Intent signImpl(Intent data, InputStream inputStream,
|
||||||
@@ -280,20 +304,31 @@ public class OpenPgpService extends Service {
|
|||||||
compressionId = PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED;
|
compressionId = PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// first try to get key ids from non-ambiguous key id extra
|
long[] keyIds;
|
||||||
long[] keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
|
{
|
||||||
if (keyIds == null) {
|
HashSet<Long> encryptKeyIds = new HashSet<>();
|
||||||
// get key ids based on given user ids
|
|
||||||
String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
|
|
||||||
// give params through to activity...
|
|
||||||
Intent result = returnKeyIdsFromEmails(data, userIds);
|
|
||||||
|
|
||||||
if (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0) == OpenPgpApi.RESULT_CODE_SUCCESS) {
|
// get key ids based on given user ids
|
||||||
keyIds = result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS);
|
if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS)) {
|
||||||
} else {
|
String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
|
||||||
// if not success -> result contains a PendingIntent for user interaction
|
boolean isOpportunistic = data.getBooleanExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, false);
|
||||||
return result;
|
// give params through to activity...
|
||||||
|
KeyIdResult result = returnKeyIdsFromEmails(data, userIds, isOpportunistic);
|
||||||
|
|
||||||
|
if (result.mResultIntent != null) {
|
||||||
|
return result.mResultIntent;
|
||||||
|
}
|
||||||
|
encryptKeyIds.addAll(result.mKeyIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add key ids from non-ambiguous key id extra
|
||||||
|
if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
|
||||||
|
for (long keyId : data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
|
||||||
|
encryptKeyIds.add(keyId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyIds = getUnboxedLongArray(encryptKeyIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this is not correct!
|
// TODO this is not correct!
|
||||||
@@ -305,8 +340,7 @@ public class OpenPgpService extends Service {
|
|||||||
.setVersionHeader(null)
|
.setVersionHeader(null)
|
||||||
.setCompressionAlgorithm(compressionId)
|
.setCompressionAlgorithm(compressionId)
|
||||||
.setSymmetricEncryptionAlgorithm(PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT)
|
.setSymmetricEncryptionAlgorithm(PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT)
|
||||||
.setEncryptionMasterKeyIds(keyIds)
|
.setEncryptionMasterKeyIds(keyIds);
|
||||||
.setFailOnMissingEncryptionKeyIds(true);
|
|
||||||
|
|
||||||
if (sign) {
|
if (sign) {
|
||||||
|
|
||||||
@@ -405,11 +439,11 @@ public class OpenPgpService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
|
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
|
||||||
HashSet<Long> allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp(
|
HashSet<Long> allowedKeyIds = mApiDao.getAllowedKeyIdsForApp(
|
||||||
KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
|
KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
|
||||||
|
|
||||||
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) {
|
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) {
|
||||||
allowedKeyIds.addAll(mProviderHelper.getAllKeyIdsForApp(
|
allowedKeyIds.addAll(mApiDao.getAllKeyIdsForApp(
|
||||||
ApiAccounts.buildBaseUri(currentPkg)));
|
ApiAccounts.buildBaseUri(currentPkg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,6 +456,15 @@ public class OpenPgpService extends Service {
|
|||||||
cryptoInput.mPassphrase =
|
cryptoInput.mPassphrase =
|
||||||
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
|
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
|
||||||
}
|
}
|
||||||
|
if (data.hasExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT_WRAPPER)) {
|
||||||
|
// this is wrapped in a Bundle to avoid ClassLoader problems
|
||||||
|
Bundle wrapperBundle = data.getBundleExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT_WRAPPER);
|
||||||
|
wrapperBundle.setClassLoader(getClassLoader());
|
||||||
|
OpenPgpDecryptionResult decryptionResult = wrapperBundle.getParcelable(OpenPgpApi.EXTRA_DECRYPTION_RESULT);
|
||||||
|
if (decryptionResult != null && decryptionResult.hasDecryptedSessionKey()) {
|
||||||
|
cryptoInput.addCryptoData(decryptionResult.sessionKey, decryptionResult.decryptedSessionKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
|
byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
|
||||||
|
|
||||||
@@ -582,7 +625,8 @@ public class OpenPgpService extends Service {
|
|||||||
try {
|
try {
|
||||||
// try to find key, throws NotFoundException if not in db!
|
// try to find key, throws NotFoundException if not in db!
|
||||||
CanonicalizedPublicKeyRing keyRing =
|
CanonicalizedPublicKeyRing keyRing =
|
||||||
mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId);
|
mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||||
|
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(masterKeyId));
|
||||||
|
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||||
@@ -669,7 +713,21 @@ public class OpenPgpService extends Service {
|
|||||||
} else {
|
} else {
|
||||||
// get key ids based on given user ids
|
// get key ids based on given user ids
|
||||||
String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
|
String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
|
||||||
return returnKeyIdsFromEmails(data, userIds);
|
KeyIdResult keyResult = returnKeyIdsFromEmails(data, userIds, false);
|
||||||
|
if (keyResult.mResultIntent != null) {
|
||||||
|
return keyResult.mResultIntent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyResult.mKeyIds == null) {
|
||||||
|
throw new AssertionError("one of requiredUserInteraction and keyIds must be non-null, this is a bug!");
|
||||||
|
}
|
||||||
|
|
||||||
|
long[] keyIds = getUnboxedLongArray(keyResult.mKeyIds);
|
||||||
|
|
||||||
|
Intent resultIntent = new Intent();
|
||||||
|
resultIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||||
|
resultIntent.putExtra(OpenPgpApi.RESULT_KEY_IDS, keyIds);
|
||||||
|
return resultIntent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,6 +774,26 @@ public class OpenPgpService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static long[] getUnboxedLongArray(@NonNull Collection<Long> arrayList) {
|
||||||
|
long[] result = new long[arrayList.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (Long e : arrayList) {
|
||||||
|
result[i++] = e;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent checkPermissionImpl(@NonNull Intent data) {
|
||||||
|
Intent permissionIntent = mApiPermissionHelper.isAllowedOrReturnIntent(data);
|
||||||
|
if (permissionIntent != null) {
|
||||||
|
return permissionIntent;
|
||||||
|
}
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private Intent getSignKeyMasterId(Intent data) {
|
private Intent getSignKeyMasterId(Intent data) {
|
||||||
// NOTE: Accounts are deprecated on API version >= 7
|
// NOTE: Accounts are deprecated on API version >= 7
|
||||||
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) {
|
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) {
|
||||||
@@ -765,20 +843,19 @@ public class OpenPgpService extends Service {
|
|||||||
|
|
||||||
// version code is required and needs to correspond to version code of service!
|
// version code is required and needs to correspond to version code of service!
|
||||||
// History of versions in openpgp-api's CHANGELOG.md
|
// History of versions in openpgp-api's CHANGELOG.md
|
||||||
List<Integer> supportedVersions = Arrays.asList(3, 4, 5, 6, 7, 8, 9, 10);
|
if (!SUPPORTED_VERSIONS.contains(data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1))) {
|
||||||
if (!supportedVersions.contains(data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1))) {
|
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
OpenPgpError error = new OpenPgpError
|
OpenPgpError error = new OpenPgpError
|
||||||
(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!\n"
|
(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!\n"
|
||||||
+ "used API version: " + data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) + "\n"
|
+ "used API version: " + data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) + "\n"
|
||||||
+ "supported API versions: " + supportedVersions);
|
+ "supported API versions: " + SUPPORTED_VERSIONS);
|
||||||
result.putExtra(OpenPgpApi.RESULT_ERROR, error);
|
result.putExtra(OpenPgpApi.RESULT_ERROR, error);
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if caller is allowed to access OpenKeychain
|
// check if caller is allowed to access OpenKeychain
|
||||||
Intent result = mApiPermissionHelper.isAllowed(data);
|
Intent result = mApiPermissionHelper.isAllowedOrReturnIntent(data);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -845,6 +922,9 @@ public class OpenPgpService extends Service {
|
|||||||
|
|
||||||
String action = data.getAction();
|
String action = data.getAction();
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
case OpenPgpApi.ACTION_CHECK_PERMISSION: {
|
||||||
|
return checkPermissionImpl(data);
|
||||||
|
}
|
||||||
case OpenPgpApi.ACTION_CLEARTEXT_SIGN: {
|
case OpenPgpApi.ACTION_CLEARTEXT_SIGN: {
|
||||||
return signImpl(data, inputStream, outputStream, true);
|
return signImpl(data, inputStream, outputStream, true);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -29,7 +29,7 @@ import org.sufficientlysecure.keychain.R;
|
|||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||||
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
|
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@@ -98,7 +98,7 @@ public class AccountSettingsActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadData(Uri accountUri) {
|
private void loadData(Uri accountUri) {
|
||||||
AccountSettings settings = new ProviderHelper(this).getApiAccountSettings(accountUri);
|
AccountSettings settings = new ApiDataAccessObject(this).getApiAccountSettings(accountUri);
|
||||||
mAccountSettingsFragment.setAccSettings(settings);
|
mAccountSettingsFragment.setAccSettings(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ public class AccountSettingsActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void save() {
|
private void save() {
|
||||||
new ProviderHelper(this).updateApiAccount(mAccountUri, mAccountSettingsFragment.getAccSettings());
|
new ApiDataAccessObject(this).updateApiAccount(mAccountUri, mAccountSettingsFragment.getAccSettings());
|
||||||
SingletonResult result = new SingletonResult(
|
SingletonResult result = new SingletonResult(
|
||||||
SingletonResult.RESULT_OK, LogType.MSG_ACC_SAVED);
|
SingletonResult.RESULT_OK, LogType.MSG_ACC_SAVED);
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
|
|||||||
+2
-2
@@ -37,8 +37,8 @@ import org.bouncycastle.util.encoders.Hex;
|
|||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
|
||||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment;
|
||||||
@@ -182,7 +182,7 @@ public class AppSettingsActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadData(Bundle savedInstanceState, Uri appUri) {
|
private void loadData(Bundle savedInstanceState, Uri appUri) {
|
||||||
mAppSettings = new ProviderHelper(this).getApiAppSettings(appUri);
|
mAppSettings = new ApiDataAccessObject(this).getApiAppSettings(appUri);
|
||||||
|
|
||||||
// get application name and icon from package manager
|
// get application name and icon from package manager
|
||||||
String appName;
|
String appName;
|
||||||
|
|||||||
+5
-5
@@ -36,8 +36,8 @@ import android.widget.ListView;
|
|||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround;
|
import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.KeySelectableAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.KeySelectableAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.widget.FixedListView;
|
import org.sufficientlysecure.keychain.ui.widget.FixedListView;
|
||||||
@@ -47,7 +47,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
|
|||||||
private static final String ARG_DATA_URI = "uri";
|
private static final String ARG_DATA_URI = "uri";
|
||||||
|
|
||||||
private KeySelectableAdapter mAdapter;
|
private KeySelectableAdapter mAdapter;
|
||||||
private ProviderHelper mProviderHelper;
|
private ApiDataAccessObject mApiDao;
|
||||||
|
|
||||||
private Uri mDataUri;
|
private Uri mDataUri;
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
mProviderHelper = new ProviderHelper(getActivity());
|
mApiDao = new ApiDataAccessObject(getActivity());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -107,7 +107,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
|
|||||||
// application this would come from a resource.
|
// application this would come from a resource.
|
||||||
setEmptyText(getString(R.string.list_empty));
|
setEmptyText(getString(R.string.list_empty));
|
||||||
|
|
||||||
Set<Long> checked = mProviderHelper.getAllKeyIdsForApp(mDataUri);
|
Set<Long> checked = mApiDao.getAllKeyIdsForApp(mDataUri);
|
||||||
mAdapter = new KeySelectableAdapter(getActivity(), null, 0, checked);
|
mAdapter = new KeySelectableAdapter(getActivity(), null, 0, checked);
|
||||||
setListAdapter(mAdapter);
|
setListAdapter(mAdapter);
|
||||||
getListView().setOnItemClickListener(mAdapter);
|
getListView().setOnItemClickListener(mAdapter);
|
||||||
@@ -141,7 +141,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
|
|||||||
|
|
||||||
public void saveAllowedKeys() {
|
public void saveAllowedKeys() {
|
||||||
try {
|
try {
|
||||||
mProviderHelper.saveAllowedKeyIdsForApp(mDataUri, getSelectedMasterKeyIds());
|
mApiDao.saveAllowedKeyIdsForApp(mDataUri, getSelectedMasterKeyIds());
|
||||||
} catch (RemoteException | OperationApplicationException e) {
|
} catch (RemoteException | OperationApplicationException e) {
|
||||||
Log.e(Constants.TAG, "Problem saving allowed key ids!", e);
|
Log.e(Constants.TAG, "Problem saving allowed key ids!", e);
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-5
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.remote.ui;
|
package org.sufficientlysecure.keychain.remote.ui;
|
||||||
|
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -25,8 +26,8 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
|
||||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
@@ -56,7 +57,7 @@ public class RemoteCreateAccountActivity extends BaseActivity {
|
|||||||
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
|
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
|
||||||
final String accName = extras.getString(EXTRA_ACC_NAME);
|
final String accName = extras.getString(EXTRA_ACC_NAME);
|
||||||
|
|
||||||
final ProviderHelper providerHelper = new ProviderHelper(this);
|
final ApiDataAccessObject apiDao = new ApiDataAccessObject(this);
|
||||||
|
|
||||||
mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
|
mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||||
R.id.api_account_settings_fragment);
|
R.id.api_account_settings_fragment);
|
||||||
@@ -65,7 +66,7 @@ public class RemoteCreateAccountActivity extends BaseActivity {
|
|||||||
|
|
||||||
// update existing?
|
// update existing?
|
||||||
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(packageName, accName);
|
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(packageName, accName);
|
||||||
AccountSettings settings = providerHelper.getApiAccountSettings(uri);
|
AccountSettings settings = apiDao.getApiAccountSettings(uri);
|
||||||
if (settings == null) {
|
if (settings == null) {
|
||||||
// create new account
|
// create new account
|
||||||
settings = new AccountSettings(accName);
|
settings = new AccountSettings(accName);
|
||||||
@@ -94,11 +95,11 @@ public class RemoteCreateAccountActivity extends BaseActivity {
|
|||||||
if (mUpdateExistingAccount) {
|
if (mUpdateExistingAccount) {
|
||||||
Uri baseUri = KeychainContract.ApiAccounts.buildBaseUri(packageName);
|
Uri baseUri = KeychainContract.ApiAccounts.buildBaseUri(packageName);
|
||||||
Uri accountUri = baseUri.buildUpon().appendEncodedPath(accName).build();
|
Uri accountUri = baseUri.buildUpon().appendEncodedPath(accName).build();
|
||||||
providerHelper.updateApiAccount(
|
apiDao.updateApiAccount(
|
||||||
accountUri,
|
accountUri,
|
||||||
mAccSettingsFragment.getAccSettings());
|
mAccSettingsFragment.getAccSettings());
|
||||||
} else {
|
} else {
|
||||||
providerHelper.insertApiAccount(
|
apiDao.insertApiAccount(
|
||||||
KeychainContract.ApiAccounts.buildBaseUri(packageName),
|
KeychainContract.ApiAccounts.buildBaseUri(packageName),
|
||||||
mAccSettingsFragment.getAccSettings());
|
mAccSettingsFragment.getAccSettings());
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -17,13 +17,14 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.remote.ui;
|
package org.sufficientlysecure.keychain.remote.ui;
|
||||||
|
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@@ -52,7 +53,7 @@ public class RemoteRegisterActivity extends BaseActivity {
|
|||||||
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
|
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
|
||||||
Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName);
|
Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName);
|
||||||
|
|
||||||
final ProviderHelper providerHelper = new ProviderHelper(this);
|
final ApiDataAccessObject apiDao = new ApiDataAccessObject(this);
|
||||||
|
|
||||||
mAppSettingsHeaderFragment = (AppSettingsHeaderFragment) getSupportFragmentManager().findFragmentById(
|
mAppSettingsHeaderFragment = (AppSettingsHeaderFragment) getSupportFragmentManager().findFragmentById(
|
||||||
R.id.api_app_settings_fragment);
|
R.id.api_app_settings_fragment);
|
||||||
@@ -67,8 +68,7 @@ public class RemoteRegisterActivity extends BaseActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
// Allow
|
// Allow
|
||||||
|
apiDao.insertApiApp(mAppSettingsHeaderFragment.getAppSettings());
|
||||||
providerHelper.insertApiApp(mAppSettingsHeaderFragment.getAppSettings());
|
|
||||||
|
|
||||||
// give data through for new service call
|
// give data through for new service call
|
||||||
Intent resultData = extras.getParcelable(EXTRA_DATA);
|
Intent resultData = extras.getParcelable(EXTRA_DATA);
|
||||||
|
|||||||
+5
-4
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.remote.ui;
|
package org.sufficientlysecure.keychain.remote.ui;
|
||||||
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -36,9 +37,9 @@ import org.openintents.openpgp.util.OpenPgpApi;
|
|||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround;
|
import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.widget.FixedListView;
|
import org.sufficientlysecure.keychain.ui.widget.FixedListView;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@@ -49,7 +50,7 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen
|
|||||||
public static final String ARG_DATA = "data";
|
public static final String ARG_DATA = "data";
|
||||||
|
|
||||||
private SelectKeyCursorAdapter mAdapter;
|
private SelectKeyCursorAdapter mAdapter;
|
||||||
private ProviderHelper mProviderHelper;
|
private ApiDataAccessObject mApiDao;
|
||||||
|
|
||||||
private Uri mDataUri;
|
private Uri mDataUri;
|
||||||
|
|
||||||
@@ -72,7 +73,7 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
mProviderHelper = new ProviderHelper(getActivity());
|
mApiDao = new ApiDataAccessObject(getActivity());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -116,7 +117,7 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen
|
|||||||
|
|
||||||
Uri allowedKeysUri = mDataUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build();
|
Uri allowedKeysUri = mDataUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build();
|
||||||
Log.d(Constants.TAG, "allowedKeysUri: " + allowedKeysUri);
|
Log.d(Constants.TAG, "allowedKeysUri: " + allowedKeysUri);
|
||||||
mProviderHelper.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId);
|
mApiDao.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId);
|
||||||
|
|
||||||
resultData.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, masterKeyId);
|
resultData.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, masterKeyId);
|
||||||
|
|
||||||
|
|||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.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.securitytoken;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class CardException extends IOException {
|
||||||
|
private short mResponseCode;
|
||||||
|
|
||||||
|
public CardException(String detailMessage, short responseCode) {
|
||||||
|
super(detailMessage);
|
||||||
|
mResponseCode = responseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getResponseCode() {
|
||||||
|
return mResponseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.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.securitytoken;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||||
|
|
||||||
|
public enum KeyType {
|
||||||
|
SIGN(0, 0xB6, 0xCE, 0xC7),
|
||||||
|
ENCRYPT(1, 0xB8, 0xCF, 0xC8),
|
||||||
|
AUTH(2, 0xA4, 0xD0, 0xC9),;
|
||||||
|
|
||||||
|
private final int mIdx;
|
||||||
|
private final int mSlot;
|
||||||
|
private final int mTimestampObjectId;
|
||||||
|
private final int mFingerprintObjectId;
|
||||||
|
|
||||||
|
KeyType(final int idx, final int slot, final int timestampObjectId, final int fingerprintObjectId) {
|
||||||
|
this.mIdx = idx;
|
||||||
|
this.mSlot = slot;
|
||||||
|
this.mTimestampObjectId = timestampObjectId;
|
||||||
|
this.mFingerprintObjectId = fingerprintObjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyType from(final CanonicalizedSecretKey key) {
|
||||||
|
if (key.canSign() || key.canCertify()) {
|
||||||
|
return SIGN;
|
||||||
|
} else if (key.canEncrypt()) {
|
||||||
|
return ENCRYPT;
|
||||||
|
} else if (key.canAuthenticate()) {
|
||||||
|
return AUTH;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIdx() {
|
||||||
|
return mIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getmSlot() {
|
||||||
|
return mSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTimestampObjectId() {
|
||||||
|
return mTimestampObjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getmFingerprintObjectId() {
|
||||||
|
return mFingerprintObjectId;
|
||||||
|
}
|
||||||
|
}
|
||||||
+113
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.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.securitytoken;
|
||||||
|
|
||||||
|
import android.nfc.Tag;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import nordpol.IsoCard;
|
||||||
|
import nordpol.android.AndroidCard;
|
||||||
|
|
||||||
|
public class NfcTransport implements Transport {
|
||||||
|
// timeout is set to 100 seconds to avoid cancellation during calculation
|
||||||
|
private static final int TIMEOUT = 100 * 1000;
|
||||||
|
private final Tag mTag;
|
||||||
|
private IsoCard mIsoCard;
|
||||||
|
|
||||||
|
public NfcTransport(Tag tag) {
|
||||||
|
this.mTag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transmit and receive data
|
||||||
|
* @param data data to transmit
|
||||||
|
* @return received data
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public byte[] transceive(final byte[] data) throws IOException {
|
||||||
|
return mIsoCard.transceive(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect and release connection
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
// Not supported
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConnected() {
|
||||||
|
return mIsoCard != null && mIsoCard.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Transport supports persistent connections e.g connections which can
|
||||||
|
* handle multiple operations in one session
|
||||||
|
* @return true if transport supports persistent connections
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isPersistentConnectionAllowed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to NFC device.
|
||||||
|
* <p/>
|
||||||
|
* On general communication, see also
|
||||||
|
* http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx
|
||||||
|
* <p/>
|
||||||
|
* References to pages are generally related to the OpenPGP Application
|
||||||
|
* on ISO SmartCard Systems specification.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void connect() throws IOException {
|
||||||
|
mIsoCard = AndroidCard.get(mTag);
|
||||||
|
if (mIsoCard == null) {
|
||||||
|
throw new BaseSecurityTokenActivity.IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)");
|
||||||
|
}
|
||||||
|
|
||||||
|
mIsoCard.setTimeout(TIMEOUT);
|
||||||
|
mIsoCard.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
final NfcTransport that = (NfcTransport) o;
|
||||||
|
|
||||||
|
if (mTag != null ? !mTag.equals(that.mTag) : that.mTag != null) return false;
|
||||||
|
if (mIsoCard != null ? !mIsoCard.equals(that.mIsoCard) : that.mIsoCard != null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = mTag != null ? mTag.hashCode() : 0;
|
||||||
|
result = 31 * result + (mIsoCard != null ? mIsoCard.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
+756
@@ -0,0 +1,756 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
|
||||||
|
* Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||||
|
* Copyright (C) 2013-2014 Signe Rüsch
|
||||||
|
* Copyright (C) 2013-2014 Philipp Jakubeit
|
||||||
|
*
|
||||||
|
* 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.securitytoken;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.bouncycastle.bcpg.HashAlgorithmTags;
|
||||||
|
import org.bouncycastle.util.Arrays;
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
|
import org.sufficientlysecure.keychain.util.Iso7816TLV;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.interfaces.RSAPrivateCrtKey;
|
||||||
|
|
||||||
|
import nordpol.Apdu;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
|
||||||
|
* devices.
|
||||||
|
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
|
||||||
|
*/
|
||||||
|
public class SecurityTokenHelper {
|
||||||
|
// Fidesmo constants
|
||||||
|
private static final String FIDESMO_APPS_AID_PREFIX = "A000000617";
|
||||||
|
|
||||||
|
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};
|
||||||
|
private Transport mTransport;
|
||||||
|
|
||||||
|
private Passphrase mPin;
|
||||||
|
private Passphrase mAdminPin;
|
||||||
|
private boolean mPw1ValidForMultipleSignatures;
|
||||||
|
private boolean mPw1ValidatedForSignature;
|
||||||
|
private boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming?
|
||||||
|
private boolean mPw3Validated;
|
||||||
|
|
||||||
|
protected SecurityTokenHelper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SecurityTokenHelper getInstance() {
|
||||||
|
return LazyHolder.SECURITY_TOKEN_HELPER;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getHex(byte[] raw) {
|
||||||
|
return new String(Hex.encode(raw));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHolderName(String name) {
|
||||||
|
try {
|
||||||
|
String slength;
|
||||||
|
int ilength;
|
||||||
|
name = name.substring(6);
|
||||||
|
slength = name.substring(0, 2);
|
||||||
|
ilength = Integer.parseInt(slength, 16) * 2;
|
||||||
|
name = name.substring(2, ilength + 2);
|
||||||
|
name = (new String(Hex.decode(name))).replace('<', ' ');
|
||||||
|
return name;
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
// try-catch for https://github.com/FluffyKaon/OpenPGP-Card
|
||||||
|
// Note: This should not happen, but happens with
|
||||||
|
// https://github.com/FluffyKaon/OpenPGP-Card, thus return an empty string for now!
|
||||||
|
|
||||||
|
Log.e(Constants.TAG, "Couldn't get holder name, returning empty string!", e);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Passphrase getPin() {
|
||||||
|
return mPin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPin(final Passphrase pin) {
|
||||||
|
this.mPin = pin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Passphrase getAdminPin() {
|
||||||
|
return mAdminPin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAdminPin(final Passphrase adminPin) {
|
||||||
|
this.mAdminPin = adminPin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException {
|
||||||
|
long keyGenerationTimestamp = secretKey.getCreationTime().getTime() / 1000;
|
||||||
|
byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array();
|
||||||
|
KeyType keyType = KeyType.from(secretKey);
|
||||||
|
|
||||||
|
if (keyType == null) {
|
||||||
|
throw new IOException("Inappropriate key flags for smart card key.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slot is empty, or contains this key already. PUT KEY operation is safe
|
||||||
|
boolean canPutKey = isSlotEmpty(keyType)
|
||||||
|
|| keyMatchesFingerPrint(keyType, secretKey.getFingerprint());
|
||||||
|
|
||||||
|
if (!canPutKey) {
|
||||||
|
throw new IOException(String.format("Key slot occupied; card must be reset to put new %s key.",
|
||||||
|
keyType.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
putKey(keyType.getmSlot(), secretKey, passphrase);
|
||||||
|
putData(keyType.getmFingerprintObjectId(), secretKey.getFingerprint());
|
||||||
|
putData(keyType.getTimestampObjectId(), timestampBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSlotEmpty(KeyType keyType) throws IOException {
|
||||||
|
// Note: special case: This should not happen, but happens with
|
||||||
|
// https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true
|
||||||
|
if (getKeyFingerprint(keyType) == null) return true;
|
||||||
|
|
||||||
|
return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException {
|
||||||
|
return java.util.Arrays.equals(getKeyFingerprint(keyType), fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to device and select pgp applet
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public void connectToDevice() throws IOException {
|
||||||
|
// Connect on transport layer
|
||||||
|
mTransport.connect();
|
||||||
|
|
||||||
|
// Connect on smartcard layer
|
||||||
|
|
||||||
|
// SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
|
||||||
|
// See specification, page 51
|
||||||
|
String accepted = "9000";
|
||||||
|
|
||||||
|
// Command APDU (page 51) for SELECT FILE command (page 29)
|
||||||
|
String opening =
|
||||||
|
"00" // CLA
|
||||||
|
+ "A4" // INS
|
||||||
|
+ "04" // P1
|
||||||
|
+ "00" // P2
|
||||||
|
+ "06" // Lc (number of bytes)
|
||||||
|
+ "D27600012401" // Data (6 bytes)
|
||||||
|
+ "00"; // Le
|
||||||
|
String response = communicate(opening); // activate connection
|
||||||
|
if (!response.endsWith(accepted)) {
|
||||||
|
throw new CardException("Initialization failed!", parseCardStatus(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] pwStatusBytes = getPwStatusBytes();
|
||||||
|
mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1);
|
||||||
|
mPw1ValidatedForSignature = false;
|
||||||
|
mPw1ValidatedForDecrypt = false;
|
||||||
|
mPw3Validated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for
|
||||||
|
* conformance to the token's requirements for key length.
|
||||||
|
*
|
||||||
|
* @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83.
|
||||||
|
* @param newPin The new PW1 or PW3.
|
||||||
|
*/
|
||||||
|
public void modifyPin(int pw, byte[] newPin) throws IOException {
|
||||||
|
final int MAX_PW1_LENGTH_INDEX = 1;
|
||||||
|
final int MAX_PW3_LENGTH_INDEX = 3;
|
||||||
|
|
||||||
|
byte[] pwStatusBytes = getPwStatusBytes();
|
||||||
|
|
||||||
|
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 = mAdminPin.toStringUnsafe().getBytes();
|
||||||
|
} else {
|
||||||
|
pin = mPin.toStringUnsafe().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 = communicate(changeReferenceDataApdu); // change PIN
|
||||||
|
if (!response.equals("9000")) {
|
||||||
|
throw new CardException("Failed to change PIN", parseCardStatus(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call DECIPHER command
|
||||||
|
*
|
||||||
|
* @param encryptedSessionKey the encoded session key
|
||||||
|
* @return the decoded session key
|
||||||
|
*/
|
||||||
|
public byte[] decryptSessionKey(byte[] encryptedSessionKey) throws IOException {
|
||||||
|
if (!mPw1ValidatedForDecrypt) {
|
||||||
|
verifyPin(0x82); // (Verify PW1 with mode 82 for decryption)
|
||||||
|
}
|
||||||
|
|
||||||
|
String firstApdu = "102a8086fe";
|
||||||
|
String secondApdu = "002a808603";
|
||||||
|
String le = "00";
|
||||||
|
|
||||||
|
byte[] one = new byte[254];
|
||||||
|
// leave out first byte:
|
||||||
|
System.arraycopy(encryptedSessionKey, 1, one, 0, one.length);
|
||||||
|
|
||||||
|
byte[] two = new byte[encryptedSessionKey.length - 1 - one.length];
|
||||||
|
for (int i = 0; i < two.length; i++) {
|
||||||
|
two[i] = encryptedSessionKey[i + one.length + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
communicate(firstApdu + getHex(one));
|
||||||
|
String second = communicate(secondApdu + getHex(two) + le);
|
||||||
|
|
||||||
|
String decryptedSessionKey = getDataField(second);
|
||||||
|
|
||||||
|
return Hex.decode(decryptedSessionKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the user's PW1 or PW3 with the appropriate mode.
|
||||||
|
*
|
||||||
|
* @param mode For PW1, this is 0x81 for signing, 0x82 for everything else.
|
||||||
|
* For PW3 (Admin PIN), mode is 0x83.
|
||||||
|
*/
|
||||||
|
private void verifyPin(int mode) throws IOException {
|
||||||
|
if (mPin != null || mode == 0x83) {
|
||||||
|
|
||||||
|
byte[] pin;
|
||||||
|
if (mode == 0x83) {
|
||||||
|
pin = mAdminPin.toStringUnsafe().getBytes();
|
||||||
|
} else {
|
||||||
|
pin = mPin.toStringUnsafe().getBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
|
||||||
|
// See specification, page 51
|
||||||
|
String accepted = "9000";
|
||||||
|
String response = tryPin(mode, pin); // login
|
||||||
|
if (!response.equals(accepted)) {
|
||||||
|
throw new CardException("Bad PIN!", parseCardStatus(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == 0x81) {
|
||||||
|
mPw1ValidatedForSignature = true;
|
||||||
|
} else if (mode == 0x82) {
|
||||||
|
mPw1ValidatedForDecrypt = true;
|
||||||
|
} else if (mode == 0x83) {
|
||||||
|
mPw3Validated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a data object on the token. 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
|
||||||
|
*/
|
||||||
|
private void putData(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) {
|
||||||
|
verifyPin(0x82); // (Verify PW1 for non-signing operations)
|
||||||
|
}
|
||||||
|
} else if (!mPw3Validated) {
|
||||||
|
verifyPin(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 = communicate(putDataApdu); // put data
|
||||||
|
if (!response.equals("9000")) {
|
||||||
|
throw new CardException("Failed to put data.", parseCardStatus(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts a key on the token in the given slot.
|
||||||
|
*
|
||||||
|
* @param slot The slot on the token where the key should be stored:
|
||||||
|
* 0xB6: Signature Key
|
||||||
|
* 0xB8: Decipherment Key
|
||||||
|
* 0xA4: Authentication Key
|
||||||
|
*/
|
||||||
|
private void putKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase)
|
||||||
|
throws IOException {
|
||||||
|
if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) {
|
||||||
|
throw new IOException("Invalid key slot");
|
||||||
|
}
|
||||||
|
|
||||||
|
RSAPrivateCrtKey crtSecretKey;
|
||||||
|
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 Security Token.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 Security Token.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mPw3Validated) {
|
||||||
|
verifyPin(0x83); // (Verify PW3 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 token.
|
||||||
|
offset = 0;
|
||||||
|
String response;
|
||||||
|
while (offset < dataToSend.length) {
|
||||||
|
int dataRemaining = dataToSend.length - offset;
|
||||||
|
if (dataRemaining > 254) {
|
||||||
|
response = communicate(
|
||||||
|
putKeyCommand + "FE" + Hex.toHexString(dataToSend, offset, 254)
|
||||||
|
);
|
||||||
|
offset += 254;
|
||||||
|
} else {
|
||||||
|
int length = dataToSend.length - offset;
|
||||||
|
response = communicate(
|
||||||
|
lastPutKeyCommand + String.format("%02x", length)
|
||||||
|
+ Hex.toHexString(dataToSend, offset, length));
|
||||||
|
offset += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.endsWith("9000")) {
|
||||||
|
throw new CardException("Key export to Security Token failed", parseCardStatus(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear array with secret data before we return.
|
||||||
|
Arrays.fill(dataToSend, (byte) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return fingerprints of all keys from application specific data stored
|
||||||
|
* on tag, or null if data not available.
|
||||||
|
*
|
||||||
|
* @return The fingerprints of all subkeys in a contiguous byte array.
|
||||||
|
*/
|
||||||
|
public byte[] getFingerprints() throws IOException {
|
||||||
|
String data = "00CA006E00";
|
||||||
|
byte[] buf = mTransport.transceive(Hex.decode(data));
|
||||||
|
|
||||||
|
Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true);
|
||||||
|
Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlv.prettyPrint());
|
||||||
|
|
||||||
|
Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5);
|
||||||
|
if (fptlv == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return fptlv.mV;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the PW Status Bytes from the token. This is a simple DO; no TLV decoding needed.
|
||||||
|
*
|
||||||
|
* @return Seven bytes in fixed format, plus 0x9000 status word at the end.
|
||||||
|
*/
|
||||||
|
private byte[] getPwStatusBytes() throws IOException {
|
||||||
|
String data = "00CA00C400";
|
||||||
|
return mTransport.transceive(Hex.decode(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getAid() throws IOException {
|
||||||
|
String info = "00CA004F00";
|
||||||
|
return mTransport.transceive(Hex.decode(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() throws IOException {
|
||||||
|
String info = "00CA006500";
|
||||||
|
return getHolderName(communicate(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call COMPUTE DIGITAL SIGNATURE command and returns the MPI value
|
||||||
|
*
|
||||||
|
* @param hash the hash for signing
|
||||||
|
* @return a big integer representing the MPI for the given hash
|
||||||
|
*/
|
||||||
|
public byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException {
|
||||||
|
if (!mPw1ValidatedForSignature) {
|
||||||
|
verifyPin(0x81); // (Verify PW1 with mode 81 for signing)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dsi, including Lc
|
||||||
|
String dsi;
|
||||||
|
|
||||||
|
Log.i(Constants.TAG, "Hash: " + hashAlgo);
|
||||||
|
switch (hashAlgo) {
|
||||||
|
case HashAlgorithmTags.SHA1:
|
||||||
|
if (hash.length != 20) {
|
||||||
|
throw new IOException("Bad hash length (" + hash.length + ", expected 10!");
|
||||||
|
}
|
||||||
|
dsi = "23" // Lc
|
||||||
|
+ "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes
|
||||||
|
+ "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes
|
||||||
|
+ "0605" + "2B0E03021A" // OID of SHA1
|
||||||
|
+ "0500" // TLV coding of ZERO
|
||||||
|
+ "0414" + getHex(hash); // 0x14 are 20 hash bytes
|
||||||
|
break;
|
||||||
|
case HashAlgorithmTags.RIPEMD160:
|
||||||
|
if (hash.length != 20) {
|
||||||
|
throw new IOException("Bad hash length (" + hash.length + ", expected 20!");
|
||||||
|
}
|
||||||
|
dsi = "233021300906052B2403020105000414" + getHex(hash);
|
||||||
|
break;
|
||||||
|
case HashAlgorithmTags.SHA224:
|
||||||
|
if (hash.length != 28) {
|
||||||
|
throw new IOException("Bad hash length (" + hash.length + ", expected 28!");
|
||||||
|
}
|
||||||
|
dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash);
|
||||||
|
break;
|
||||||
|
case HashAlgorithmTags.SHA256:
|
||||||
|
if (hash.length != 32) {
|
||||||
|
throw new IOException("Bad hash length (" + hash.length + ", expected 32!");
|
||||||
|
}
|
||||||
|
dsi = "333031300D060960864801650304020105000420" + getHex(hash);
|
||||||
|
break;
|
||||||
|
case HashAlgorithmTags.SHA384:
|
||||||
|
if (hash.length != 48) {
|
||||||
|
throw new IOException("Bad hash length (" + hash.length + ", expected 48!");
|
||||||
|
}
|
||||||
|
dsi = "433041300D060960864801650304020205000430" + getHex(hash);
|
||||||
|
break;
|
||||||
|
case HashAlgorithmTags.SHA512:
|
||||||
|
if (hash.length != 64) {
|
||||||
|
throw new IOException("Bad hash length (" + hash.length + ", expected 64!");
|
||||||
|
}
|
||||||
|
dsi = "533051300D060960864801650304020305000440" + getHex(hash);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IOException("Not supported hash algo!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37)
|
||||||
|
String apdu =
|
||||||
|
"002A9E9A" // CLA, INS, P1, P2
|
||||||
|
+ dsi // digital signature input
|
||||||
|
+ "00"; // Le
|
||||||
|
|
||||||
|
String response = communicate(apdu);
|
||||||
|
|
||||||
|
if (response.length() < 4) {
|
||||||
|
throw new CardException("Bad response", (short) 0);
|
||||||
|
}
|
||||||
|
// split up response into signature and status
|
||||||
|
String status = response.substring(response.length() - 4);
|
||||||
|
String signature = response.substring(0, response.length() - 4);
|
||||||
|
|
||||||
|
// while we are getting 0x61 status codes, retrieve more data
|
||||||
|
while (status.substring(0, 2).equals("61")) {
|
||||||
|
Log.d(Constants.TAG, "requesting more data, status " + status);
|
||||||
|
// Send GET RESPONSE command
|
||||||
|
response = communicate("00C00000" + status.substring(2));
|
||||||
|
status = response.substring(response.length() - 4);
|
||||||
|
signature += response.substring(0, response.length() - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(Constants.TAG, "final response:" + status);
|
||||||
|
|
||||||
|
if (!mPw1ValidForMultipleSignatures) {
|
||||||
|
mPw1ValidatedForSignature = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!"9000".equals(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!
|
||||||
|
if (signature.length() != 256 && signature.length() != 512) {
|
||||||
|
throw new IOException("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Hex.decode(signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transceive data via NFC encoded as Hex
|
||||||
|
*/
|
||||||
|
private String communicate(String apdu) throws IOException {
|
||||||
|
return getHex(mTransport.transceive(Hex.decode(apdu)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transport getTransport() {
|
||||||
|
return mTransport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTransport(Transport mTransport) {
|
||||||
|
this.mTransport = mTransport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFidesmoToken() {
|
||||||
|
if (isConnected()) { // Check if we can still talk to the card
|
||||||
|
try {
|
||||||
|
// By trying to select any apps that have the Fidesmo AID prefix we can
|
||||||
|
// see if it is a Fidesmo device or not
|
||||||
|
byte[] mSelectResponse = mTransport.transceive(Apdu.select(FIDESMO_APPS_AID_PREFIX));
|
||||||
|
// Compare the status returned by our select with the OK status code
|
||||||
|
return Apdu.hasStatus(mSelectResponse, Apdu.OK_APDU);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Constants.TAG, "Card communication failed!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a key on the card in the given slot. If the slot is 0xB6 (the signature key),
|
||||||
|
* this command also has the effect of resetting the digital signature counter.
|
||||||
|
* NOTE: This does not set the key fingerprint data object! After calling this command, you
|
||||||
|
* must construct a public key packet using the returned public key data objects, compute the
|
||||||
|
* key fingerprint, and store it on the card using: putData(0xC8, key.getFingerprint())
|
||||||
|
*
|
||||||
|
* @param slot The slot on the card where the key should be generated:
|
||||||
|
* 0xB6: Signature Key
|
||||||
|
* 0xB8: Decipherment Key
|
||||||
|
* 0xA4: Authentication Key
|
||||||
|
* @return the public key data objects, in TLV format. For RSA this will be the public modulus
|
||||||
|
* (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required.
|
||||||
|
*/
|
||||||
|
public byte[] generateKey(int slot) throws IOException {
|
||||||
|
if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) {
|
||||||
|
throw new IOException("Invalid key slot");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mPw3Validated) {
|
||||||
|
verifyPin(0x83); // (Verify PW3 with mode 83)
|
||||||
|
}
|
||||||
|
|
||||||
|
String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000";
|
||||||
|
String getResponseApdu = "00C00000";
|
||||||
|
|
||||||
|
String first = communicate(generateKeyApdu);
|
||||||
|
String second = communicate(getResponseApdu);
|
||||||
|
|
||||||
|
if (!second.endsWith("9000")) {
|
||||||
|
throw new IOException("On-card key generation failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
String publicKeyData = getDataField(first) + getDataField(second);
|
||||||
|
|
||||||
|
Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData);
|
||||||
|
|
||||||
|
return Hex.decode(publicKeyData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDataField(String output) {
|
||||||
|
return output.substring(0, output.length() - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String tryPin(int mode, byte[] pin) throws IOException {
|
||||||
|
// Command APDU for VERIFY command (page 32)
|
||||||
|
String login =
|
||||||
|
"00" // CLA
|
||||||
|
+ "20" // INS
|
||||||
|
+ "00" // P1
|
||||||
|
+ String.format("%02x", mode) // P2
|
||||||
|
+ String.format("%02x", pin.length) // Lc
|
||||||
|
+ Hex.toHexString(pin);
|
||||||
|
|
||||||
|
return communicate(login);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets security token, which deletes all keys and data objects.
|
||||||
|
* This works by entering a wrong PIN and then Admin PIN 4 times respectively.
|
||||||
|
* Afterwards, the token is reactivated.
|
||||||
|
*/
|
||||||
|
public void resetAndWipeToken() throws IOException {
|
||||||
|
String accepted = "9000";
|
||||||
|
|
||||||
|
// try wrong PIN 4 times until counter goes to C0
|
||||||
|
byte[] pin = "XXXXXX".getBytes();
|
||||||
|
for (int i = 0; i <= 4; i++) {
|
||||||
|
String response = tryPin(0x81, pin);
|
||||||
|
if (response.equals(accepted)) { // Should NOT accept!
|
||||||
|
throw new CardException("Should never happen, XXXXXX has been accepted!", parseCardStatus(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try wrong Admin PIN 4 times until counter goes to C0
|
||||||
|
byte[] adminPin = "XXXXXXXX".getBytes();
|
||||||
|
for (int i = 0; i <= 4; i++) {
|
||||||
|
String response = tryPin(0x83, adminPin);
|
||||||
|
if (response.equals(accepted)) { // Should NOT accept!
|
||||||
|
throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reactivate token!
|
||||||
|
String reactivate1 = "00" + "e6" + "00" + "00";
|
||||||
|
String reactivate2 = "00" + "44" + "00" + "00";
|
||||||
|
String response1 = communicate(reactivate1);
|
||||||
|
String response2 = communicate(reactivate2);
|
||||||
|
if (!response1.equals(accepted) || !response2.equals(accepted)) {
|
||||||
|
throw new CardException("Reactivating failed!", parseCardStatus(response1));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the fingerprint from application specific data stored on tag, or
|
||||||
|
* null if it doesn't exist.
|
||||||
|
*
|
||||||
|
* @param keyType key type
|
||||||
|
* @return The fingerprint of the requested key, or null if not found.
|
||||||
|
*/
|
||||||
|
public byte[] getKeyFingerprint(@NonNull KeyType keyType) throws IOException {
|
||||||
|
byte[] data = getFingerprints();
|
||||||
|
if (data == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the master key fingerprint
|
||||||
|
ByteBuffer fpbuf = ByteBuffer.wrap(data);
|
||||||
|
byte[] fp = new byte[20];
|
||||||
|
fpbuf.position(keyType.getIdx() * 20);
|
||||||
|
fpbuf.get(fp, 0, 20);
|
||||||
|
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPersistentConnectionAllowed() {
|
||||||
|
return mTransport != null && mTransport.isPersistentConnectionAllowed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return mTransport != null && mTransport.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LazyHolder {
|
||||||
|
private static final SecurityTokenHelper SECURITY_TOKEN_HELPER = new SecurityTokenHelper();
|
||||||
|
}
|
||||||
|
}
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.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.securitytoken;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction for transmitting APDU commands
|
||||||
|
*/
|
||||||
|
public interface Transport {
|
||||||
|
/**
|
||||||
|
* Transmit and receive data
|
||||||
|
* @param data data to transmit
|
||||||
|
* @return received data
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
byte[] transceive(byte[] data) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect and release connection
|
||||||
|
*/
|
||||||
|
void release();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if device is was connected to and still is connected
|
||||||
|
* @return connection status
|
||||||
|
*/
|
||||||
|
boolean isConnected();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Transport supports persistent connections e.g connections which can
|
||||||
|
* handle multiple operations in one session
|
||||||
|
* @return true if transport supports persistent connections
|
||||||
|
*/
|
||||||
|
boolean isPersistentConnectionAllowed();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to device
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
void connect() throws IOException;
|
||||||
|
}
|
||||||
+304
@@ -0,0 +1,304 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.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.securitytoken;
|
||||||
|
|
||||||
|
import android.hardware.usb.UsbConstants;
|
||||||
|
import android.hardware.usb.UsbDevice;
|
||||||
|
import android.hardware.usb.UsbDeviceConnection;
|
||||||
|
import android.hardware.usb.UsbEndpoint;
|
||||||
|
import android.hardware.usb.UsbInterface;
|
||||||
|
import android.hardware.usb.UsbManager;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import org.bouncycastle.util.Arrays;
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on USB CCID Specification rev. 1.1
|
||||||
|
* http://www.usb.org/developers/docs/devclass_docs/DWG_Smart-Card_CCID_Rev110.pdf
|
||||||
|
* Implements small subset of these features
|
||||||
|
*/
|
||||||
|
public class UsbTransport implements Transport {
|
||||||
|
private static final int USB_CLASS_SMARTCARD = 11;
|
||||||
|
private static final int TIMEOUT = 20 * 1000; // 20s
|
||||||
|
|
||||||
|
private final UsbManager mUsbManager;
|
||||||
|
private final UsbDevice mUsbDevice;
|
||||||
|
private UsbInterface mUsbInterface;
|
||||||
|
private UsbEndpoint mBulkIn;
|
||||||
|
private UsbEndpoint mBulkOut;
|
||||||
|
private UsbDeviceConnection mConnection;
|
||||||
|
private byte mCounter;
|
||||||
|
|
||||||
|
public UsbTransport(UsbDevice usbDevice, UsbManager usbManager) {
|
||||||
|
mUsbDevice = usbDevice;
|
||||||
|
mUsbManager = usbManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage ICC power, Yubikey requires to power on ICC
|
||||||
|
* Spec: 6.1.1 PC_to_RDR_IccPowerOn; 6.1.2 PC_to_RDR_IccPowerOff
|
||||||
|
*
|
||||||
|
* @param on true to turn ICC on, false to turn it off
|
||||||
|
* @throws UsbTransportException
|
||||||
|
*/
|
||||||
|
private void setIccPower(boolean on) throws UsbTransportException {
|
||||||
|
final byte[] iccPowerCommand = {
|
||||||
|
(byte) (on ? 0x62 : 0x63),
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00,
|
||||||
|
mCounter++,
|
||||||
|
0x00,
|
||||||
|
0x00, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
sendRaw(iccPowerCommand);
|
||||||
|
byte[] bytes;
|
||||||
|
do {
|
||||||
|
bytes = receive();
|
||||||
|
} while (isDataBlockNotReady(bytes));
|
||||||
|
checkDataBlockResponse(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get first class 11 (Chip/Smartcard) interface of the device
|
||||||
|
*
|
||||||
|
* @param device {@link UsbDevice} which will be searched
|
||||||
|
* @return {@link UsbInterface} of smartcard or null if it doesn't exist
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static UsbInterface getSmartCardInterface(UsbDevice device) {
|
||||||
|
for (int i = 0; i < device.getInterfaceCount(); i++) {
|
||||||
|
UsbInterface anInterface = device.getInterface(i);
|
||||||
|
if (anInterface.getInterfaceClass() == USB_CLASS_SMARTCARD) {
|
||||||
|
return anInterface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get device's bulk-in and bulk-out endpoints
|
||||||
|
*
|
||||||
|
* @param usbInterface usb device interface
|
||||||
|
* @return pair of builk-in and bulk-out endpoints respectively
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private static Pair<UsbEndpoint, UsbEndpoint> getIoEndpoints(final UsbInterface usbInterface) {
|
||||||
|
UsbEndpoint bulkIn = null, bulkOut = null;
|
||||||
|
for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
|
||||||
|
final UsbEndpoint endpoint = usbInterface.getEndpoint(i);
|
||||||
|
if (endpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) {
|
||||||
|
bulkIn = endpoint;
|
||||||
|
} else if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) {
|
||||||
|
bulkOut = endpoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Pair<>(bulkIn, bulkOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release interface and disconnect
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
if (mConnection != null) {
|
||||||
|
mConnection.releaseInterface(mUsbInterface);
|
||||||
|
mConnection.close();
|
||||||
|
mConnection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(Constants.TAG, "Usb transport disconnected");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if device is was connected to and still is connected
|
||||||
|
* @return true if device is connected
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isConnected() {
|
||||||
|
return mConnection != null && mUsbManager.getDeviceList().containsValue(mUsbDevice) &&
|
||||||
|
mConnection.getSerial() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Transport supports persistent connections e.g connections which can
|
||||||
|
* handle multiple operations in one session
|
||||||
|
* @return true if transport supports persistent connections
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isPersistentConnectionAllowed() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to OTG device
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void connect() throws IOException {
|
||||||
|
mCounter = 0;
|
||||||
|
mUsbInterface = getSmartCardInterface(mUsbDevice);
|
||||||
|
if (mUsbInterface == null) {
|
||||||
|
// Shouldn't happen as we whitelist only class 11 devices
|
||||||
|
throw new UsbTransportException("USB error - device doesn't have class 11 interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
final Pair<UsbEndpoint, UsbEndpoint> ioEndpoints = getIoEndpoints(mUsbInterface);
|
||||||
|
mBulkIn = ioEndpoints.first;
|
||||||
|
mBulkOut = ioEndpoints.second;
|
||||||
|
|
||||||
|
if (mBulkIn == null || mBulkOut == null) {
|
||||||
|
throw new UsbTransportException("USB error - invalid class 11 interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
mConnection = mUsbManager.openDevice(mUsbDevice);
|
||||||
|
if (mConnection == null) {
|
||||||
|
throw new UsbTransportException("USB error - failed to connect to device");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mConnection.claimInterface(mUsbInterface, true)) {
|
||||||
|
throw new UsbTransportException("USB error - failed to claim interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
setIccPower(true);
|
||||||
|
Log.d(Constants.TAG, "Usb transport connected");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transmit and receive data
|
||||||
|
* @param data data to transmit
|
||||||
|
* @return received data
|
||||||
|
* @throws UsbTransportException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public byte[] transceive(byte[] data) throws UsbTransportException {
|
||||||
|
sendXfrBlock(data);
|
||||||
|
byte[] bytes;
|
||||||
|
do {
|
||||||
|
bytes = receive();
|
||||||
|
} while (isDataBlockNotReady(bytes));
|
||||||
|
|
||||||
|
checkDataBlockResponse(bytes);
|
||||||
|
// Discard header
|
||||||
|
return Arrays.copyOfRange(bytes, 10, bytes.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transmits XfrBlock
|
||||||
|
* 6.1.4 PC_to_RDR_XfrBlock
|
||||||
|
* @param payload payload to transmit
|
||||||
|
* @throws UsbTransportException
|
||||||
|
*/
|
||||||
|
private void sendXfrBlock(byte[] payload) throws UsbTransportException {
|
||||||
|
int l = payload.length;
|
||||||
|
byte[] data = Arrays.concatenate(new byte[]{
|
||||||
|
0x6f,
|
||||||
|
(byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24),
|
||||||
|
0x00,
|
||||||
|
mCounter++,
|
||||||
|
0x00,
|
||||||
|
0x00, 0x00},
|
||||||
|
payload);
|
||||||
|
|
||||||
|
int send = 0;
|
||||||
|
while (send < data.length) {
|
||||||
|
final int len = Math.min(mBulkIn.getMaxPacketSize(), data.length - send);
|
||||||
|
sendRaw(Arrays.copyOfRange(data, send, send + len));
|
||||||
|
send += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] receive() throws UsbTransportException {
|
||||||
|
byte[] buffer = new byte[mBulkIn.getMaxPacketSize()];
|
||||||
|
byte[] result = null;
|
||||||
|
int readBytes = 0, totalBytes = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT);
|
||||||
|
if (res < 0) {
|
||||||
|
throw new UsbTransportException("USB error - failed to receive response " + res);
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
if (res < 10) {
|
||||||
|
throw new UsbTransportException("USB-CCID error - failed to receive CCID header");
|
||||||
|
}
|
||||||
|
totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10;
|
||||||
|
result = new byte[totalBytes];
|
||||||
|
}
|
||||||
|
System.arraycopy(buffer, 0, result, readBytes, res);
|
||||||
|
readBytes += res;
|
||||||
|
} while (readBytes < totalBytes);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendRaw(final byte[] data) throws UsbTransportException {
|
||||||
|
final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT);
|
||||||
|
if (tr1 != data.length) {
|
||||||
|
throw new UsbTransportException("USB error - failed to transmit data " + tr1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte getStatus(byte[] bytes) {
|
||||||
|
return (byte) ((bytes[7] >> 6) & 0x03);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkDataBlockResponse(byte[] bytes) throws UsbTransportException {
|
||||||
|
final byte status = getStatus(bytes);
|
||||||
|
if (status != 0) {
|
||||||
|
throw new UsbTransportException("USB-CCID error - status " + status + " error code: " + Hex.toHexString(bytes, 8, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDataBlockNotReady(byte[] bytes) {
|
||||||
|
return getStatus(bytes) == 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
final UsbTransport that = (UsbTransport) o;
|
||||||
|
|
||||||
|
return mUsbDevice != null ? mUsbDevice.equals(that.mUsbDevice) : that.mUsbDevice == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return mUsbDevice != null ? mUsbDevice.hashCode() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UsbDevice getUsbDevice() {
|
||||||
|
return mUsbDevice;
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.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.securitytoken;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class UsbTransportException extends IOException {
|
||||||
|
public UsbTransportException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public UsbTransportException(final String detailMessage) {
|
||||||
|
super(detailMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UsbTransportException(final String message, final Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UsbTransportException(final Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 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.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
|
|
||||||
|
public class ChangeUnlockParcel implements Parcelable {
|
||||||
|
|
||||||
|
// the master key id of keyring.
|
||||||
|
public Long mMasterKeyId;
|
||||||
|
// the key fingerprint, for safety.
|
||||||
|
public byte[] mFingerprint;
|
||||||
|
// The new passphrase to use
|
||||||
|
public final Passphrase mNewPassphrase;
|
||||||
|
|
||||||
|
public ChangeUnlockParcel(Passphrase newPassphrase) {
|
||||||
|
mNewPassphrase = newPassphrase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChangeUnlockParcel(Long masterKeyId, byte[] fingerprint, Passphrase newPassphrase) {
|
||||||
|
if (newPassphrase == null) {
|
||||||
|
throw new AssertionError("newPassphrase must be non-null. THIS IS A BUG!");
|
||||||
|
}
|
||||||
|
|
||||||
|
mMasterKeyId = masterKeyId;
|
||||||
|
mFingerprint = fingerprint;
|
||||||
|
mNewPassphrase = newPassphrase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChangeUnlockParcel(Parcel source) {
|
||||||
|
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
|
||||||
|
mFingerprint = source.createByteArray();
|
||||||
|
mNewPassphrase = source.readParcelable(Passphrase.class.getClassLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel destination, int flags) {
|
||||||
|
destination.writeInt(mMasterKeyId == null ? 0 : 1);
|
||||||
|
if (mMasterKeyId != null) {
|
||||||
|
destination.writeLong(mMasterKeyId);
|
||||||
|
}
|
||||||
|
destination.writeByteArray(mFingerprint);
|
||||||
|
destination.writeParcelable(mNewPassphrase, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<ChangeUnlockParcel> CREATOR = new Creator<ChangeUnlockParcel>() {
|
||||||
|
public ChangeUnlockParcel createFromParcel(final Parcel source) {
|
||||||
|
return new ChangeUnlockParcel(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChangeUnlockParcel[] newArray(final int size) {
|
||||||
|
return new ChangeUnlockParcel[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
String out = "mMasterKeyId: " + mMasterKeyId + "\n";
|
||||||
|
out += "passphrase (" + mNewPassphrase + ")";
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+8
@@ -143,6 +143,14 @@ public class ContactSyncAdapterService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void requestContactsSync() {
|
public static void requestContactsSync() {
|
||||||
|
// if user has disabled automatic sync, do nothing
|
||||||
|
boolean isSyncEnabled = ContentResolver.getSyncAutomatically(new Account
|
||||||
|
(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), ContactsContract.AUTHORITY);
|
||||||
|
|
||||||
|
if (!isSyncEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Bundle extras = new Bundle();
|
Bundle extras = new Bundle();
|
||||||
// no need to wait, do it immediately
|
// no need to wait, do it immediately
|
||||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
|
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
|
||||||
|
|||||||
+3
@@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.operations.BackupOperation;
|
|||||||
import org.sufficientlysecure.keychain.operations.ImportOperation;
|
import org.sufficientlysecure.keychain.operations.ImportOperation;
|
||||||
import org.sufficientlysecure.keychain.operations.KeybaseVerificationOperation;
|
import org.sufficientlysecure.keychain.operations.KeybaseVerificationOperation;
|
||||||
import org.sufficientlysecure.keychain.operations.InputDataOperation;
|
import org.sufficientlysecure.keychain.operations.InputDataOperation;
|
||||||
|
import org.sufficientlysecure.keychain.operations.ChangeUnlockOperation;
|
||||||
import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
|
import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
|
||||||
import org.sufficientlysecure.keychain.operations.RevokeOperation;
|
import org.sufficientlysecure.keychain.operations.RevokeOperation;
|
||||||
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
|
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
|
||||||
@@ -116,6 +117,8 @@ public class KeychainService extends Service implements Progressable {
|
|||||||
op = new PgpDecryptVerifyOperation(outerThis, new ProviderHelper(outerThis), outerThis);
|
op = new PgpDecryptVerifyOperation(outerThis, new ProviderHelper(outerThis), outerThis);
|
||||||
} else if (inputParcel instanceof SaveKeyringParcel) {
|
} else if (inputParcel instanceof SaveKeyringParcel) {
|
||||||
op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
|
op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
|
||||||
|
} else if (inputParcel instanceof ChangeUnlockParcel) {
|
||||||
|
op = new ChangeUnlockOperation(outerThis, new ProviderHelper(outerThis), outerThis);
|
||||||
} else if (inputParcel instanceof RevokeKeyringParcel) {
|
} else if (inputParcel instanceof RevokeKeyringParcel) {
|
||||||
op = new RevokeOperation(outerThis, new ProviderHelper(outerThis), outerThis);
|
op = new RevokeOperation(outerThis, new ProviderHelper(outerThis), outerThis);
|
||||||
} else if (inputParcel instanceof CertifyActionsParcel) {
|
} else if (inputParcel instanceof CertifyActionsParcel) {
|
||||||
|
|||||||
+47
-8
@@ -11,11 +11,14 @@ import android.content.ContentProviderClient;
|
|||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.PeriodicSync;
|
||||||
import android.content.SyncResult;
|
import android.content.SyncResult;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -35,6 +38,7 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
|||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.receiver.NetworkReceiver;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity;
|
import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
@@ -68,7 +72,7 @@ public class KeyserverSyncAdapterService extends Service {
|
|||||||
|
|
||||||
private static final String ACTION_IGNORE_TOR = "ignore_tor";
|
private static final String ACTION_IGNORE_TOR = "ignore_tor";
|
||||||
private static final String ACTION_UPDATE_ALL = "update_all";
|
private static final String ACTION_UPDATE_ALL = "update_all";
|
||||||
private static final String ACTION_SYNC_NOW = "sync_now";
|
public static final String ACTION_SYNC_NOW = "sync_now";
|
||||||
private static final String ACTION_DISMISS_NOTIFICATION = "cancel_sync";
|
private static final String ACTION_DISMISS_NOTIFICATION = "cancel_sync";
|
||||||
private static final String ACTION_START_ORBOT = "start_orbot";
|
private static final String ACTION_START_ORBOT = "start_orbot";
|
||||||
private static final String ACTION_CANCEL = "cancel";
|
private static final String ACTION_CANCEL = "cancel";
|
||||||
@@ -176,8 +180,25 @@ public class KeyserverSyncAdapterService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
public void onPerformSync(Account account, Bundle extras, String authority,
|
public void onPerformSync(Account account, Bundle extras, String authority,
|
||||||
ContentProviderClient provider, SyncResult syncResult) {
|
ContentProviderClient provider, SyncResult syncResult) {
|
||||||
Log.d(Constants.TAG, "Performing a keyserver sync!");
|
|
||||||
|
|
||||||
|
Preferences prefs = Preferences.getPreferences(getContext());
|
||||||
|
|
||||||
|
// for a wifi-ONLY sync
|
||||||
|
if (prefs.getWifiOnlySync()) {
|
||||||
|
|
||||||
|
ConnectivityManager connMgr = (ConnectivityManager)
|
||||||
|
getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
|
||||||
|
boolean isNotOnWifi = !(networkInfo.getType() == ConnectivityManager.TYPE_WIFI);
|
||||||
|
boolean isNotConnected = !(networkInfo.isConnected());
|
||||||
|
|
||||||
|
// if Wi-Fi connection doesn't exist then receiver is enabled
|
||||||
|
if (isNotOnWifi && isNotConnected) {
|
||||||
|
new NetworkReceiver().setWifiReceiverComponent(true, getContext());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(Constants.TAG, "Performing a keyserver sync!");
|
||||||
PowerManager pm = (PowerManager) KeyserverSyncAdapterService.this
|
PowerManager pm = (PowerManager) KeyserverSyncAdapterService.this
|
||||||
.getSystemService(Context.POWER_SERVICE);
|
.getSystemService(Context.POWER_SERVICE);
|
||||||
@SuppressWarnings("deprecation") // our min is API 15, deprecated only in 20
|
@SuppressWarnings("deprecation") // our min is API 15, deprecated only in 20
|
||||||
@@ -509,6 +530,10 @@ public class KeyserverSyncAdapterService extends Service {
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a new sync if one does not exist, or updates an existing sync if the sync interval
|
||||||
|
* has changed.
|
||||||
|
*/
|
||||||
public static void enableKeyserverSync(Context context) {
|
public static void enableKeyserverSync(Context context) {
|
||||||
Account account = KeychainApplication.createAccountIfNecessary(context);
|
Account account = KeychainApplication.createAccountIfNecessary(context);
|
||||||
|
|
||||||
@@ -519,12 +544,26 @@ public class KeyserverSyncAdapterService extends Service {
|
|||||||
|
|
||||||
ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 1);
|
ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 1);
|
||||||
ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY, true);
|
ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY, true);
|
||||||
ContentResolver.addPeriodicSync(
|
|
||||||
account,
|
boolean intervalChanged = false;
|
||||||
Constants.PROVIDER_AUTHORITY,
|
boolean syncExists = Preferences.getKeyserverSyncEnabled(context);
|
||||||
new Bundle(),
|
|
||||||
SYNC_INTERVAL
|
if (syncExists) {
|
||||||
);
|
long oldInterval = ContentResolver.getPeriodicSyncs(
|
||||||
|
account, Constants.PROVIDER_AUTHORITY).get(0).period;
|
||||||
|
if (oldInterval != SYNC_INTERVAL) {
|
||||||
|
intervalChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!syncExists || intervalChanged) {
|
||||||
|
ContentResolver.addPeriodicSync(
|
||||||
|
account,
|
||||||
|
Constants.PROVIDER_AUTHORITY,
|
||||||
|
new Bundle(),
|
||||||
|
SYNC_INTERVAL
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSyncEnabled() {
|
private boolean isSyncEnabled() {
|
||||||
|
|||||||
+15
-60
@@ -49,8 +49,6 @@ public class SaveKeyringParcel implements Parcelable {
|
|||||||
// the key fingerprint, for safety. MUST be null for a new key.
|
// the key fingerprint, for safety. MUST be null for a new key.
|
||||||
public byte[] mFingerprint;
|
public byte[] mFingerprint;
|
||||||
|
|
||||||
public ChangeUnlockParcel mNewUnlock;
|
|
||||||
|
|
||||||
public ArrayList<String> mAddUserIds;
|
public ArrayList<String> mAddUserIds;
|
||||||
public ArrayList<WrappedUserAttribute> mAddUserAttribute;
|
public ArrayList<WrappedUserAttribute> mAddUserAttribute;
|
||||||
public ArrayList<SubkeyAdd> mAddSubKeys;
|
public ArrayList<SubkeyAdd> mAddSubKeys;
|
||||||
@@ -70,6 +68,9 @@ public class SaveKeyringParcel implements Parcelable {
|
|||||||
private boolean mUploadAtomic;
|
private boolean mUploadAtomic;
|
||||||
private String mKeyserver;
|
private String mKeyserver;
|
||||||
|
|
||||||
|
// private because we have to set other details like key id
|
||||||
|
private ChangeUnlockParcel mNewUnlock;
|
||||||
|
|
||||||
public SaveKeyringParcel() {
|
public SaveKeyringParcel() {
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
@@ -102,6 +103,18 @@ public class SaveKeyringParcel implements Parcelable {
|
|||||||
mKeyserver = keysever;
|
mKeyserver = keysever;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setNewUnlock(ChangeUnlockParcel parcel) {
|
||||||
|
mNewUnlock = parcel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChangeUnlockParcel getChangeUnlockParcel() {
|
||||||
|
if(mNewUnlock != null) {
|
||||||
|
mNewUnlock.mMasterKeyId = mMasterKeyId;
|
||||||
|
mNewUnlock.mFingerprint = mFingerprint;
|
||||||
|
}
|
||||||
|
return mNewUnlock;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isUpload() {
|
public boolean isUpload() {
|
||||||
return mUpload;
|
return mUpload;
|
||||||
}
|
}
|
||||||
@@ -344,64 +357,6 @@ public class SaveKeyringParcel implements Parcelable {
|
|||||||
// BRAINPOOL_P256, BRAINPOOL_P384, BRAINPOOL_P512
|
// BRAINPOOL_P256, BRAINPOOL_P384, BRAINPOOL_P512
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This subclass contains information on how the passphrase should be changed.
|
|
||||||
*
|
|
||||||
* If no changes are to be made, this class should NOT be used!
|
|
||||||
*
|
|
||||||
* At this point, there must be *exactly one* non-null value here, which specifies the type
|
|
||||||
* of unlocking mechanism to use.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static class ChangeUnlockParcel implements Parcelable {
|
|
||||||
|
|
||||||
// The new passphrase to use
|
|
||||||
public final Passphrase mNewPassphrase;
|
|
||||||
// A new pin to use. Must only contain [0-9]+
|
|
||||||
public final Passphrase mNewPin;
|
|
||||||
|
|
||||||
public ChangeUnlockParcel(Passphrase newPassphrase) {
|
|
||||||
this(newPassphrase, null);
|
|
||||||
}
|
|
||||||
public ChangeUnlockParcel(Passphrase newPassphrase, Passphrase newPin) {
|
|
||||||
if (newPassphrase == null && newPin == null) {
|
|
||||||
throw new RuntimeException("Cannot set both passphrase and pin. THIS IS A BUG!");
|
|
||||||
}
|
|
||||||
mNewPassphrase = newPassphrase;
|
|
||||||
mNewPin = newPin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChangeUnlockParcel(Parcel source) {
|
|
||||||
mNewPassphrase = source.readParcelable(Passphrase.class.getClassLoader());
|
|
||||||
mNewPin = source.readParcelable(Passphrase.class.getClassLoader());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel destination, int flags) {
|
|
||||||
destination.writeParcelable(mNewPassphrase, flags);
|
|
||||||
destination.writeParcelable(mNewPin, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Creator<ChangeUnlockParcel> CREATOR = new Creator<ChangeUnlockParcel>() {
|
|
||||||
public ChangeUnlockParcel createFromParcel(final Parcel source) {
|
|
||||||
return new ChangeUnlockParcel(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChangeUnlockParcel[] newArray(final int size) {
|
|
||||||
return new ChangeUnlockParcel[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return mNewPassphrase != null
|
|
||||||
? ("passphrase (" + mNewPassphrase + ")")
|
|
||||||
: ("pin (" + mNewPin + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -17,18 +17,18 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.service.input;
|
package org.sufficientlysecure.keychain.service.input;
|
||||||
|
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.util.ParcelableProxy;
|
import org.sufficientlysecure.keychain.util.ParcelableProxy;
|
||||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
|
|
||||||
import java.net.Proxy;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a base class for the input of crypto operations.
|
* This is a base class for the input of crypto operations.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+16
-16
@@ -14,8 +14,8 @@ import java.util.Date;
|
|||||||
public class RequiredInputParcel implements Parcelable {
|
public class RequiredInputParcel implements Parcelable {
|
||||||
|
|
||||||
public enum RequiredInputType {
|
public enum RequiredInputType {
|
||||||
PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, NFC_SIGN, NFC_DECRYPT,
|
PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, SECURITY_TOKEN_SIGN, SECURITY_TOKEN_DECRYPT,
|
||||||
NFC_MOVE_KEY_TO_CARD, NFC_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY,
|
SECURITY_TOKEN_MOVE_KEY_TO_CARD, SECURITY_TOKEN_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY,
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date mSignatureTime;
|
public Date mSignatureTime;
|
||||||
@@ -89,22 +89,22 @@ public class RequiredInputParcel implements Parcelable {
|
|||||||
return new RequiredInputParcel(RequiredInputType.ENABLE_ORBOT, null, null, null, 0L, 0L);
|
return new RequiredInputParcel(RequiredInputType.ENABLE_ORBOT, null, null, null, 0L, 0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RequiredInputParcel createNfcSignOperation(
|
public static RequiredInputParcel createSecurityTokenSignOperation(
|
||||||
long masterKeyId, long subKeyId,
|
long masterKeyId, long subKeyId,
|
||||||
byte[] inputHash, int signAlgo, Date signatureTime) {
|
byte[] inputHash, int signAlgo, Date signatureTime) {
|
||||||
return new RequiredInputParcel(RequiredInputType.NFC_SIGN,
|
return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_SIGN,
|
||||||
new byte[][] { inputHash }, new int[] { signAlgo },
|
new byte[][] { inputHash }, new int[] { signAlgo },
|
||||||
signatureTime, masterKeyId, subKeyId);
|
signatureTime, masterKeyId, subKeyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RequiredInputParcel createNfcDecryptOperation(
|
public static RequiredInputParcel createSecurityTokenDecryptOperation(
|
||||||
long masterKeyId, long subKeyId, byte[] encryptedSessionKey) {
|
long masterKeyId, long subKeyId, byte[] encryptedSessionKey) {
|
||||||
return new RequiredInputParcel(RequiredInputType.NFC_DECRYPT,
|
return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_DECRYPT,
|
||||||
new byte[][] { encryptedSessionKey }, null, null, masterKeyId, subKeyId);
|
new byte[][] { encryptedSessionKey }, null, null, masterKeyId, subKeyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RequiredInputParcel createNfcReset() {
|
public static RequiredInputParcel createSecurityTokenReset() {
|
||||||
return new RequiredInputParcel(RequiredInputType.NFC_RESET_CARD,
|
return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_RESET_CARD,
|
||||||
null, null, null, null, null);
|
null, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,14 +188,14 @@ public class RequiredInputParcel implements Parcelable {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static class NfcSignOperationsBuilder {
|
public static class SecurityTokenSignOperationsBuilder {
|
||||||
Date mSignatureTime;
|
Date mSignatureTime;
|
||||||
ArrayList<Integer> mSignAlgos = new ArrayList<>();
|
ArrayList<Integer> mSignAlgos = new ArrayList<>();
|
||||||
ArrayList<byte[]> mInputHashes = new ArrayList<>();
|
ArrayList<byte[]> mInputHashes = new ArrayList<>();
|
||||||
long mMasterKeyId;
|
long mMasterKeyId;
|
||||||
long mSubKeyId;
|
long mSubKeyId;
|
||||||
|
|
||||||
public NfcSignOperationsBuilder(Date signatureTime, long masterKeyId, long subKeyId) {
|
public SecurityTokenSignOperationsBuilder(Date signatureTime, long masterKeyId, long subKeyId) {
|
||||||
mSignatureTime = signatureTime;
|
mSignatureTime = signatureTime;
|
||||||
mMasterKeyId = masterKeyId;
|
mMasterKeyId = masterKeyId;
|
||||||
mSubKeyId = subKeyId;
|
mSubKeyId = subKeyId;
|
||||||
@@ -209,7 +209,7 @@ public class RequiredInputParcel implements Parcelable {
|
|||||||
signAlgos[i] = mSignAlgos.get(i);
|
signAlgos[i] = mSignAlgos.get(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RequiredInputParcel(RequiredInputType.NFC_SIGN,
|
return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_SIGN,
|
||||||
inputHashes, signAlgos, mSignatureTime, mMasterKeyId, mSubKeyId);
|
inputHashes, signAlgos, mSignatureTime, mMasterKeyId, mSubKeyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +222,7 @@ public class RequiredInputParcel implements Parcelable {
|
|||||||
if (!mSignatureTime.equals(input.mSignatureTime)) {
|
if (!mSignatureTime.equals(input.mSignatureTime)) {
|
||||||
throw new AssertionError("input times must match, this is a programming error!");
|
throw new AssertionError("input times must match, this is a programming error!");
|
||||||
}
|
}
|
||||||
if (input.mType != RequiredInputType.NFC_SIGN) {
|
if (input.mType != RequiredInputType.SECURITY_TOKEN_SIGN) {
|
||||||
throw new AssertionError("operation types must match, this is a progrmming error!");
|
throw new AssertionError("operation types must match, this is a progrmming error!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,13 +238,13 @@ public class RequiredInputParcel implements Parcelable {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class NfcKeyToCardOperationsBuilder {
|
public static class SecurityTokenKeyToCardOperationsBuilder {
|
||||||
ArrayList<byte[]> mSubkeysToExport = new ArrayList<>();
|
ArrayList<byte[]> mSubkeysToExport = new ArrayList<>();
|
||||||
Long mMasterKeyId;
|
Long mMasterKeyId;
|
||||||
byte[] mPin;
|
byte[] mPin;
|
||||||
byte[] mAdminPin;
|
byte[] mAdminPin;
|
||||||
|
|
||||||
public NfcKeyToCardOperationsBuilder(Long masterKeyId) {
|
public SecurityTokenKeyToCardOperationsBuilder(Long masterKeyId) {
|
||||||
mMasterKeyId = masterKeyId;
|
mMasterKeyId = masterKeyId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +264,7 @@ public class RequiredInputParcel implements Parcelable {
|
|||||||
ByteBuffer buf = ByteBuffer.wrap(mSubkeysToExport.get(0));
|
ByteBuffer buf = ByteBuffer.wrap(mSubkeysToExport.get(0));
|
||||||
|
|
||||||
// We need to pass in a subkey here...
|
// We need to pass in a subkey here...
|
||||||
return new RequiredInputParcel(RequiredInputType.NFC_MOVE_KEY_TO_CARD,
|
return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD,
|
||||||
inputData, null, null, mMasterKeyId, buf.getLong());
|
inputData, null, null, mMasterKeyId, buf.getLong());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +287,7 @@ public class RequiredInputParcel implements Parcelable {
|
|||||||
if (!mMasterKeyId.equals(input.mMasterKeyId)) {
|
if (!mMasterKeyId.equals(input.mMasterKeyId)) {
|
||||||
throw new AssertionError("Master keys must match, this is a programming error!");
|
throw new AssertionError("Master keys must match, this is a programming error!");
|
||||||
}
|
}
|
||||||
if (input.mType != RequiredInputType.NFC_MOVE_KEY_TO_CARD) {
|
if (input.mType != RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD) {
|
||||||
throw new AssertionError("Operation types must match, this is a programming error!");
|
throw new AssertionError("Operation types must match, this is a programming error!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+132
-75
@@ -18,14 +18,6 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import android.animation.ArgbEvaluator;
|
import android.animation.ArgbEvaluator;
|
||||||
import android.animation.ValueAnimator;
|
import android.animation.ValueAnimator;
|
||||||
import android.animation.ValueAnimator.AnimatorUpdateListener;
|
import android.animation.ValueAnimator.AnimatorUpdateListener;
|
||||||
@@ -43,7 +35,7 @@ import android.support.v4.app.FragmentActivity;
|
|||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
|
import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.InputType;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -53,12 +45,9 @@ import android.view.View;
|
|||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.animation.AccelerateInterpolator;
|
import android.view.animation.AccelerateInterpolator;
|
||||||
import android.view.inputmethod.EditorInfo;
|
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.github.pinball83.maskededittext.MaskedEditText;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.operations.results.ExportResult;
|
import org.sufficientlysecure.keychain.operations.results.ExportResult;
|
||||||
@@ -73,6 +62,14 @@ import org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator;
|
|||||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringParcel, ExportResult>
|
public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringParcel, ExportResult>
|
||||||
implements OnBackStackChangedListener {
|
implements OnBackStackChangedListener {
|
||||||
|
|
||||||
@@ -100,7 +97,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
|||||||
String mBackupCode;
|
String mBackupCode;
|
||||||
private boolean mExecuteBackupOperation;
|
private boolean mExecuteBackupOperation;
|
||||||
|
|
||||||
private MaskedEditText mCodeEditText;
|
private EditText[] mCodeEditText;
|
||||||
|
|
||||||
private ToolableViewAnimator mStatusAnimator, mTitleAnimator, mCodeFieldsAnimator;
|
private ToolableViewAnimator mStatusAnimator, mTitleAnimator, mCodeFieldsAnimator;
|
||||||
private Integer mBackStackLevel;
|
private Integer mBackStackLevel;
|
||||||
@@ -152,8 +149,13 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
|||||||
boolean newCheckedState = !item.isChecked();
|
boolean newCheckedState = !item.isChecked();
|
||||||
item.setChecked(newCheckedState);
|
item.setChecked(newCheckedState);
|
||||||
mDebugModeAcceptAnyCode = newCheckedState;
|
mDebugModeAcceptAnyCode = newCheckedState;
|
||||||
if (newCheckedState) {
|
if (newCheckedState && TextUtils.isEmpty(mCodeEditText[0].getText())) {
|
||||||
mCodeEditText.setText("ABCD-EFGH-IJKL-MNOP-QRST-UVWX");
|
mCodeEditText[0].setText("ABCD");
|
||||||
|
mCodeEditText[1].setText("EFGH");
|
||||||
|
mCodeEditText[2].setText("IJKL");
|
||||||
|
mCodeEditText[3].setText("MNOP");
|
||||||
|
mCodeEditText[4].setText("QRST");
|
||||||
|
mCodeEditText[5].setText("UVWX");
|
||||||
Notify.create(getActivity(), "Actual backup code is all 'A's", Style.WARN).show();
|
Notify.create(getActivity(), "Actual backup code is all 'A's", Style.WARN).show();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -177,11 +179,9 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
|||||||
mTitleAnimator.setDisplayedChild(1, animate);
|
mTitleAnimator.setDisplayedChild(1, animate);
|
||||||
mStatusAnimator.setDisplayedChild(1, animate);
|
mStatusAnimator.setDisplayedChild(1, animate);
|
||||||
mCodeFieldsAnimator.setDisplayedChild(1, animate);
|
mCodeFieldsAnimator.setDisplayedChild(1, animate);
|
||||||
// use non-breaking spaces to enlarge the empty EditText appropriately
|
for (EditText editText : mCodeEditText) {
|
||||||
String empty = "\u00a0\u00a0\u00a0\u00a0-\u00a0\u00a0\u00a0\u00a0" +
|
editText.setText("");
|
||||||
"-\u00a0\u00a0\u00a0\u00a0-\u00a0\u00a0\u00a0\u00a0" +
|
}
|
||||||
"-\u00a0\u00a0\u00a0\u00a0-\u00a0\u00a0\u00a0\u00a0";
|
|
||||||
mCodeEditText.setText(empty);
|
|
||||||
|
|
||||||
pushBackStackEntry();
|
pushBackStackEntry();
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
|||||||
hideKeyboard();
|
hideKeyboard();
|
||||||
|
|
||||||
if (animate) {
|
if (animate) {
|
||||||
@ColorInt int black = mCodeEditText.getCurrentTextColor();
|
@ColorInt int black = mCodeEditText[0].getCurrentTextColor();
|
||||||
@ColorInt int red = getResources().getColor(R.color.android_red_dark);
|
@ColorInt int red = getResources().getColor(R.color.android_red_dark);
|
||||||
animateFlashText(mCodeEditText, black, red, false);
|
animateFlashText(mCodeEditText, black, red, false);
|
||||||
}
|
}
|
||||||
@@ -214,14 +214,18 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
|||||||
|
|
||||||
hideKeyboard();
|
hideKeyboard();
|
||||||
|
|
||||||
mCodeEditText.setEnabled(false);
|
for (EditText editText : mCodeEditText) {
|
||||||
|
editText.setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
@ColorInt int green = getResources().getColor(R.color.android_green_dark);
|
@ColorInt int green = getResources().getColor(R.color.android_green_dark);
|
||||||
if (animate) {
|
if (animate) {
|
||||||
@ColorInt int black = mCodeEditText.getCurrentTextColor();
|
@ColorInt int black = mCodeEditText[0].getCurrentTextColor();
|
||||||
animateFlashText(mCodeEditText, black, green, true);
|
animateFlashText(mCodeEditText, black, green, true);
|
||||||
} else {
|
} else {
|
||||||
mCodeEditText.setTextColor(green);
|
for (TextView textView : mCodeEditText) {
|
||||||
|
textView.setTextColor(green);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
popBackStackNoAction();
|
popBackStackNoAction();
|
||||||
@@ -257,23 +261,39 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
|||||||
mExportSecret = args.getBoolean(ARG_EXPORT_SECRET);
|
mExportSecret = args.getBoolean(ARG_EXPORT_SECRET);
|
||||||
mExecuteBackupOperation = args.getBoolean(ARG_EXECUTE_BACKUP_OPERATION, true);
|
mExecuteBackupOperation = args.getBoolean(ARG_EXECUTE_BACKUP_OPERATION, true);
|
||||||
|
|
||||||
// NOTE: order of these method calls matter, see setupAutomaticLinebreak()
|
mCodeEditText = new EditText[6];
|
||||||
mCodeEditText = (MaskedEditText) view.findViewById(R.id.backup_code_input);
|
mCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1);
|
||||||
mCodeEditText.setInputType(
|
mCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2);
|
||||||
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
|
mCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3);
|
||||||
setupAutomaticLinebreak(mCodeEditText);
|
mCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4);
|
||||||
mCodeEditText.setImeOptions(EditorInfo.IME_ACTION_DONE);
|
mCodeEditText[4] = (EditText) view.findViewById(R.id.backup_code_5);
|
||||||
|
mCodeEditText[5] = (EditText) view.findViewById(R.id.backup_code_6);
|
||||||
|
|
||||||
|
{
|
||||||
|
TextView[] codeDisplayText = new TextView[6];
|
||||||
|
codeDisplayText[0] = (TextView) view.findViewById(R.id.backup_code_display_1);
|
||||||
|
codeDisplayText[1] = (TextView) view.findViewById(R.id.backup_code_display_2);
|
||||||
|
codeDisplayText[2] = (TextView) view.findViewById(R.id.backup_code_display_3);
|
||||||
|
codeDisplayText[3] = (TextView) view.findViewById(R.id.backup_code_display_4);
|
||||||
|
codeDisplayText[4] = (TextView) view.findViewById(R.id.backup_code_display_5);
|
||||||
|
codeDisplayText[5] = (TextView) view.findViewById(R.id.backup_code_display_6);
|
||||||
|
|
||||||
|
// set backup code in code TextViews
|
||||||
|
char[] backupCode = mBackupCode.toCharArray();
|
||||||
|
for (int i = 0; i < codeDisplayText.length; i++) {
|
||||||
|
codeDisplayText[i].setText(backupCode, i * 5, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set background to null in TextViews - this will retain padding from EditText style!
|
||||||
|
for (TextView textView : codeDisplayText) {
|
||||||
|
// noinspection deprecation, setBackground(Drawable) is API level >=16
|
||||||
|
textView.setBackgroundDrawable(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupEditTextFocusNext(mCodeEditText);
|
||||||
setupEditTextSuccessListener(mCodeEditText);
|
setupEditTextSuccessListener(mCodeEditText);
|
||||||
|
|
||||||
TextView codeDisplayText = (TextView) view.findViewById(R.id.backup_code_display);
|
|
||||||
setupAutomaticLinebreak(codeDisplayText);
|
|
||||||
|
|
||||||
// set background to null in TextViews - this will retain padding from EditText style!
|
|
||||||
// noinspection deprecation, setBackground(Drawable) is API level >=16
|
|
||||||
codeDisplayText.setBackgroundDrawable(null);
|
|
||||||
|
|
||||||
codeDisplayText.setText(mBackupCode);
|
|
||||||
|
|
||||||
mStatusAnimator = (ToolableViewAnimator) view.findViewById(R.id.status_animator);
|
mStatusAnimator = (ToolableViewAnimator) view.findViewById(R.id.status_animator);
|
||||||
mTitleAnimator = (ToolableViewAnimator) view.findViewById(R.id.title_animator);
|
mTitleAnimator = (ToolableViewAnimator) view.findViewById(R.id.title_animator);
|
||||||
mCodeFieldsAnimator = (ToolableViewAnimator) view.findViewById(R.id.code_animator);
|
mCodeFieldsAnimator = (ToolableViewAnimator) view.findViewById(R.id.code_animator);
|
||||||
@@ -350,67 +370,76 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
|||||||
outState.putInt(ARG_BACK_STACK, mBackStackLevel == null ? -1 : mBackStackLevel);
|
outState.putInt(ARG_BACK_STACK, mBackStackLevel == null ? -1 : mBackStackLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void setupEditTextSuccessListener(final EditText[] backupCodes) {
|
||||||
* Automatic line break with max 6 lines for smaller displays
|
for (EditText backupCode : backupCodes) {
|
||||||
* <p/>
|
|
||||||
* NOTE: I was not able to get this behaviour using XML!
|
|
||||||
* Looks like the order of these method calls matter, see http://stackoverflow.com/a/11171307
|
|
||||||
*/
|
|
||||||
private void setupAutomaticLinebreak(TextView textview) {
|
|
||||||
textview.setSingleLine(true);
|
|
||||||
textview.setMaxLines(6);
|
|
||||||
textview.setHorizontallyScrolling(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupEditTextSuccessListener(final MaskedEditText backupCode) {
|
backupCode.addTextChangedListener(new TextWatcher() {
|
||||||
backupCode.addTextChangedListener(new TextWatcher() {
|
@Override
|
||||||
@Override
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s) {
|
|
||||||
boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT
|
|
||||||
|| mCurrentState == BackupCodeState.STATE_INPUT_ERROR;
|
|
||||||
boolean partIsComplete = (backupCode.getText().toString().indexOf(' ') == -1)
|
|
||||||
&& (backupCode.getText().toString().indexOf('\u00a0') == -1);
|
|
||||||
if (!inInputState || !partIsComplete) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkIfCodeIsCorrect(backupCode);
|
@Override
|
||||||
}
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
});
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) {
|
||||||
|
if (s.length() > 4) {
|
||||||
|
throw new AssertionError("max length of each field is 4!");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT
|
||||||
|
|| mCurrentState == BackupCodeState.STATE_INPUT_ERROR;
|
||||||
|
boolean partIsComplete = s.length() == 4;
|
||||||
|
if (!inInputState || !partIsComplete) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkIfCodeIsCorrect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkIfCodeIsCorrect(EditText backupCode) {
|
private void checkIfCodeIsCorrect() {
|
||||||
|
|
||||||
if (Constants.DEBUG && mDebugModeAcceptAnyCode) {
|
if (Constants.DEBUG && mDebugModeAcceptAnyCode) {
|
||||||
switchState(BackupCodeState.STATE_OK, true);
|
switchState(BackupCodeState.STATE_OK, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backupCode.toString().equals(mBackupCode)) {
|
StringBuilder backupCodeInput = new StringBuilder(26);
|
||||||
|
for (EditText editText : mCodeEditText) {
|
||||||
|
if (editText.getText().length() < 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
backupCodeInput.append(editText.getText());
|
||||||
|
backupCodeInput.append('-');
|
||||||
|
}
|
||||||
|
backupCodeInput.deleteCharAt(backupCodeInput.length() - 1);
|
||||||
|
|
||||||
|
// if they don't match, do nothing
|
||||||
|
if (backupCodeInput.toString().equals(mBackupCode)) {
|
||||||
switchState(BackupCodeState.STATE_OK, true);
|
switchState(BackupCodeState.STATE_OK, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switchState(BackupCodeState.STATE_INPUT_ERROR, true);
|
switchState(BackupCodeState.STATE_INPUT_ERROR, true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void animateFlashText(
|
private static void animateFlashText(
|
||||||
final TextView textView, int color1, int color2, boolean staySecondColor) {
|
final TextView[] textViews, int color1, int color2, boolean staySecondColor) {
|
||||||
|
|
||||||
ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), color1, color2);
|
ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), color1, color2);
|
||||||
anim.addUpdateListener(new AnimatorUpdateListener() {
|
anim.addUpdateListener(new AnimatorUpdateListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationUpdate(ValueAnimator animator) {
|
public void onAnimationUpdate(ValueAnimator animator) {
|
||||||
textView.setTextColor((Integer) animator.getAnimatedValue());
|
for (TextView textView : textViews) {
|
||||||
|
textView.setTextColor((Integer) animator.getAnimatedValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
anim.setRepeatMode(ValueAnimator.REVERSE);
|
anim.setRepeatMode(ValueAnimator.REVERSE);
|
||||||
@@ -421,6 +450,34 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void setupEditTextFocusNext(final EditText[] backupCodes) {
|
||||||
|
for (int i = 0; i < backupCodes.length - 1; i++) {
|
||||||
|
|
||||||
|
final int next = i + 1;
|
||||||
|
|
||||||
|
backupCodes[i].addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
boolean inserting = before < count;
|
||||||
|
boolean cursorAtEnd = (start + count) == 4;
|
||||||
|
|
||||||
|
if (inserting && cursorAtEnd) {
|
||||||
|
backupCodes[next].requestFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void pushBackStackEntry() {
|
private void pushBackStackEntry() {
|
||||||
if (mBackStackLevel != null) {
|
if (mBackStackLevel != null) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
+2
-1
@@ -28,7 +28,8 @@ import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
|||||||
public class CertifyKeyActivity extends BaseActivity {
|
public class CertifyKeyActivity extends BaseActivity {
|
||||||
|
|
||||||
public static final String EXTRA_RESULT = "operation_result";
|
public static final String EXTRA_RESULT = "operation_result";
|
||||||
public static final String EXTRA_KEY_IDS = "extra_key_ids";
|
// For sending masterKeyIds to MultiUserIdsFragment to display list of keys
|
||||||
|
public static final String EXTRA_KEY_IDS = MultiUserIdsFragment.EXTRA_KEY_IDS ;
|
||||||
public static final String EXTRA_CERTIFY_KEY_ID = "certify_key_id";
|
public static final String EXTRA_CERTIFY_KEY_ID = "certify_key_id";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+8
-172
@@ -62,58 +62,26 @@ import java.util.ArrayList;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
public class CertifyKeyFragment
|
public class CertifyKeyFragment
|
||||||
extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult>
|
extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult> {
|
||||||
implements LoaderManager.LoaderCallbacks<Cursor> {
|
|
||||||
|
|
||||||
public static final String ARG_CHECK_STATES = "check_states";
|
|
||||||
|
|
||||||
private CheckBox mUploadKeyCheckbox;
|
private CheckBox mUploadKeyCheckbox;
|
||||||
ListView mUserIds;
|
|
||||||
|
|
||||||
private CertifyKeySpinner mCertifyKeySpinner;
|
private CertifyKeySpinner mCertifyKeySpinner;
|
||||||
|
|
||||||
private long[] mPubMasterKeyIds;
|
private MultiUserIdsFragment mMultiUserIdsFragment;
|
||||||
|
|
||||||
public static final String[] USER_IDS_PROJECTION = new String[]{
|
|
||||||
UserPackets._ID,
|
|
||||||
UserPackets.MASTER_KEY_ID,
|
|
||||||
UserPackets.USER_ID,
|
|
||||||
UserPackets.IS_PRIMARY,
|
|
||||||
UserPackets.IS_REVOKED
|
|
||||||
};
|
|
||||||
private static final int INDEX_MASTER_KEY_ID = 1;
|
|
||||||
private static final int INDEX_USER_ID = 2;
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private static final int INDEX_IS_PRIMARY = 3;
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private static final int INDEX_IS_REVOKED = 4;
|
|
||||||
|
|
||||||
private MultiUserIdsAdapter mUserIdsAdapter;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(CertifyKeyActivity.EXTRA_KEY_IDS);
|
if (savedInstanceState == null) {
|
||||||
if (mPubMasterKeyIds == null) {
|
|
||||||
Log.e(Constants.TAG, "List of key ids to certify missing!");
|
|
||||||
getActivity().finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
// preselect certify key id if given
|
||||||
long certifyKeyId = getActivity().getIntent()
|
long certifyKeyId = getActivity().getIntent()
|
||||||
.getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none);
|
.getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none);
|
||||||
if (certifyKeyId != Constants.key.none) {
|
if (certifyKeyId != Constants.key.none) {
|
||||||
try {
|
try {
|
||||||
CachedPublicKeyRing key = (new ProviderHelper(getActivity())).getCachedPublicKeyRing(certifyKeyId);
|
CachedPublicKeyRing key = (new ProviderHelper(getActivity()))
|
||||||
|
.getCachedPublicKeyRing(certifyKeyId);
|
||||||
if (key.canCertify()) {
|
if (key.canCertify()) {
|
||||||
mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId);
|
mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId);
|
||||||
}
|
}
|
||||||
@@ -121,15 +89,8 @@ public class CertifyKeyFragment
|
|||||||
Log.e(Constants.TAG, "certify certify check failed", e);
|
Log.e(Constants.TAG, "certify certify check failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates);
|
|
||||||
mUserIds.setAdapter(mUserIdsAdapter);
|
|
||||||
mUserIds.setDividerHeight(0);
|
|
||||||
|
|
||||||
getLoaderManager().initLoader(0, null, this);
|
|
||||||
|
|
||||||
OperationResult result = getActivity().getIntent().getParcelableExtra(CertifyKeyActivity.EXTRA_RESULT);
|
OperationResult result = getActivity().getIntent().getParcelableExtra(CertifyKeyActivity.EXTRA_RESULT);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
// display result from import
|
// display result from import
|
||||||
@@ -137,22 +98,14 @@ public class CertifyKeyFragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.certify_key_fragment, null);
|
View view = inflater.inflate(R.layout.certify_key_fragment, null);
|
||||||
|
|
||||||
mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner);
|
mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner);
|
||||||
mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox);
|
mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox);
|
||||||
mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids);
|
mMultiUserIdsFragment = (MultiUserIdsFragment)
|
||||||
|
getChildFragmentManager().findFragmentById(R.id.multi_user_ids_fragment);
|
||||||
|
|
||||||
// make certify image gray, like action icons
|
// make certify image gray, like action icons
|
||||||
ImageView vActionCertifyImage =
|
ImageView vActionCertifyImage =
|
||||||
@@ -183,128 +136,11 @@ public class CertifyKeyFragment
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
|
||||||
Uri uri = UserPackets.buildUserIdsUri();
|
|
||||||
|
|
||||||
String selection, ids[];
|
|
||||||
{
|
|
||||||
// generate placeholders and string selection args
|
|
||||||
ids = new String[mPubMasterKeyIds.length];
|
|
||||||
StringBuilder placeholders = new StringBuilder("?");
|
|
||||||
for (int i = 0; i < mPubMasterKeyIds.length; i++) {
|
|
||||||
ids[i] = Long.toString(mPubMasterKeyIds[i]);
|
|
||||||
if (i != 0) {
|
|
||||||
placeholders.append(",?");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// put together selection string
|
|
||||||
selection = UserPackets.IS_REVOKED + " = 0" + " AND "
|
|
||||||
+ Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID
|
|
||||||
+ " IN (" + placeholders + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CursorLoader(getActivity(), uri,
|
|
||||||
USER_IDS_PROJECTION, selection, ids,
|
|
||||||
Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " ASC"
|
|
||||||
+ ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " ASC"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
|
||||||
|
|
||||||
MatrixCursor matrix = new MatrixCursor(new String[]{
|
|
||||||
"_id", "user_data", "grouped"
|
|
||||||
}) {
|
|
||||||
@Override
|
|
||||||
public byte[] getBlob(int column) {
|
|
||||||
return super.getBlob(column);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
data.moveToFirst();
|
|
||||||
|
|
||||||
long lastMasterKeyId = 0;
|
|
||||||
String lastName = "";
|
|
||||||
ArrayList<String> uids = new ArrayList<>();
|
|
||||||
|
|
||||||
boolean header = true;
|
|
||||||
|
|
||||||
// Iterate over all rows
|
|
||||||
while (!data.isAfterLast()) {
|
|
||||||
long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
|
||||||
String userId = data.getString(INDEX_USER_ID);
|
|
||||||
KeyRing.UserId pieces = KeyRing.splitUserId(userId);
|
|
||||||
|
|
||||||
// Two cases:
|
|
||||||
|
|
||||||
boolean grouped = masterKeyId == lastMasterKeyId;
|
|
||||||
boolean subGrouped = data.isFirst() || grouped && lastName.equals(pieces.name);
|
|
||||||
// Remember for next loop
|
|
||||||
lastName = pieces.name;
|
|
||||||
|
|
||||||
Log.d(Constants.TAG, Long.toString(masterKeyId, 16) + (grouped ? "grouped" : "not grouped"));
|
|
||||||
|
|
||||||
if (!subGrouped) {
|
|
||||||
// 1. This name should NOT be grouped with the previous, so we flush the buffer
|
|
||||||
|
|
||||||
Parcel p = Parcel.obtain();
|
|
||||||
p.writeStringList(uids);
|
|
||||||
byte[] d = p.marshall();
|
|
||||||
p.recycle();
|
|
||||||
|
|
||||||
matrix.addRow(new Object[]{
|
|
||||||
lastMasterKeyId, d, header ? 1 : 0
|
|
||||||
});
|
|
||||||
// indicate that we have a header for this masterKeyId
|
|
||||||
header = false;
|
|
||||||
|
|
||||||
// Now clear the buffer, and add the new user id, for the next round
|
|
||||||
uids.clear();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. This name should be grouped with the previous, just add to buffer
|
|
||||||
uids.add(userId);
|
|
||||||
lastMasterKeyId = masterKeyId;
|
|
||||||
|
|
||||||
// If this one wasn't grouped, the next one's gotta be a header
|
|
||||||
if (!grouped) {
|
|
||||||
header = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regardless of the outcome, move to next entry
|
|
||||||
data.moveToNext();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is anything left in the buffer, flush it one last time
|
|
||||||
if (!uids.isEmpty()) {
|
|
||||||
|
|
||||||
Parcel p = Parcel.obtain();
|
|
||||||
p.writeStringList(uids);
|
|
||||||
byte[] d = p.marshall();
|
|
||||||
p.recycle();
|
|
||||||
|
|
||||||
matrix.addRow(new Object[]{
|
|
||||||
lastMasterKeyId, d, header ? 1 : 0
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
mUserIdsAdapter.swapCursor(matrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(Loader<Cursor> loader) {
|
|
||||||
mUserIdsAdapter.swapCursor(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CertifyActionsParcel createOperationInput() {
|
public CertifyActionsParcel createOperationInput() {
|
||||||
|
|
||||||
// Bail out if there is not at least one user id selected
|
// Bail out if there is not at least one user id selected
|
||||||
ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions();
|
ArrayList<CertifyAction> certifyActions = mMultiUserIdsFragment.getSelectedCertifyActions();
|
||||||
if (certifyActions.isEmpty()) {
|
if (certifyActions.isEmpty()) {
|
||||||
Notify.create(getActivity(), "No identities selected!",
|
Notify.create(getActivity(), "No identities selected!",
|
||||||
Notify.Style.ERROR).show();
|
Notify.Style.ERROR).show();
|
||||||
|
|||||||
+36
-28
@@ -21,6 +21,7 @@ import android.content.Intent;
|
|||||||
import android.nfc.NfcAdapter;
|
import android.nfc.NfcAdapter;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
import android.support.v4.app.FragmentTransaction;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
@@ -28,7 +29,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
|||||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
@@ -36,7 +37,7 @@ import org.sufficientlysecure.keychain.util.Preferences;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class CreateKeyActivity extends BaseSecurityTokenNfcActivity {
|
public class CreateKeyActivity extends BaseSecurityTokenActivity {
|
||||||
|
|
||||||
public static final String EXTRA_NAME = "name";
|
public static final String EXTRA_NAME = "name";
|
||||||
public static final String EXTRA_EMAIL = "email";
|
public static final String EXTRA_EMAIL = "email";
|
||||||
@@ -47,9 +48,9 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity {
|
|||||||
public static final String EXTRA_SECURITY_TOKEN_PIN = "yubi_key_pin";
|
public static final String EXTRA_SECURITY_TOKEN_PIN = "yubi_key_pin";
|
||||||
public static final String EXTRA_SECURITY_TOKEN_ADMIN_PIN = "yubi_key_admin_pin";
|
public static final String EXTRA_SECURITY_TOKEN_ADMIN_PIN = "yubi_key_admin_pin";
|
||||||
|
|
||||||
public static final String EXTRA_NFC_USER_ID = "nfc_user_id";
|
public static final String EXTRA_SECURITY_TOKEN_USER_ID = "nfc_user_id";
|
||||||
public static final String EXTRA_NFC_AID = "nfc_aid";
|
public static final String EXTRA_SECURITY_TOKEN_AID = "nfc_aid";
|
||||||
public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints";
|
public static final String EXTRA_SECURITY_FINGERPRINTS = "nfc_fingerprints";
|
||||||
|
|
||||||
public static final String FRAGMENT_TAG = "currentFragment";
|
public static final String FRAGMENT_TAG = "currentFragment";
|
||||||
|
|
||||||
@@ -66,8 +67,8 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity {
|
|||||||
|
|
||||||
|
|
||||||
byte[] mScannedFingerprints;
|
byte[] mScannedFingerprints;
|
||||||
byte[] mNfcAid;
|
byte[] mSecurityTokenAid;
|
||||||
String mNfcUserId;
|
String mSecurityTokenUserId;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -77,7 +78,7 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity {
|
|||||||
// NOTE: ACTION_NDEF_DISCOVERED and not ACTION_TAG_DISCOVERED like in BaseNfcActivity
|
// NOTE: ACTION_NDEF_DISCOVERED and not ACTION_TAG_DISCOVERED like in BaseNfcActivity
|
||||||
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
|
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
|
||||||
|
|
||||||
mTagDispatcher.interceptIntent(getIntent());
|
mNfcTagDispatcher.interceptIntent(getIntent());
|
||||||
|
|
||||||
setTitle(R.string.title_manage_my_keys);
|
setTitle(R.string.title_manage_my_keys);
|
||||||
|
|
||||||
@@ -107,10 +108,10 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity {
|
|||||||
mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false);
|
mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false);
|
||||||
mCreateSecurityToken = intent.getBooleanExtra(EXTRA_CREATE_SECURITY_TOKEN, false);
|
mCreateSecurityToken = intent.getBooleanExtra(EXTRA_CREATE_SECURITY_TOKEN, false);
|
||||||
|
|
||||||
if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) {
|
if (intent.hasExtra(EXTRA_SECURITY_FINGERPRINTS)) {
|
||||||
byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS);
|
byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_SECURITY_FINGERPRINTS);
|
||||||
String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID);
|
String nfcUserId = intent.getStringExtra(EXTRA_SECURITY_TOKEN_USER_ID);
|
||||||
byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
|
byte[] nfcAid = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_AID);
|
||||||
|
|
||||||
if (containsKeys(nfcFingerprints)) {
|
if (containsKeys(nfcFingerprints)) {
|
||||||
Fragment frag = CreateSecurityTokenImportResetFragment.newInstance(
|
Fragment frag = CreateSecurityTokenImportResetFragment.newInstance(
|
||||||
@@ -143,24 +144,32 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doNfcInBackground() throws IOException {
|
protected void doSecurityTokenInBackground() throws IOException {
|
||||||
if (mCurrentFragment instanceof NfcListenerFragment) {
|
if (mCurrentFragment instanceof SecurityTokenListenerFragment) {
|
||||||
((NfcListenerFragment) mCurrentFragment).doNfcInBackground();
|
((SecurityTokenListenerFragment) mCurrentFragment).doSecurityTokenInBackground();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mScannedFingerprints = nfcGetFingerprints();
|
mScannedFingerprints = mSecurityTokenHelper.getFingerprints();
|
||||||
mNfcAid = nfcGetAid();
|
mSecurityTokenAid = mSecurityTokenHelper.getAid();
|
||||||
mNfcUserId = nfcGetUserId();
|
mSecurityTokenUserId = mSecurityTokenHelper.getUserId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onNfcPostExecute() {
|
protected void onSecurityTokenPostExecute() {
|
||||||
if (mCurrentFragment instanceof NfcListenerFragment) {
|
if (mCurrentFragment instanceof SecurityTokenListenerFragment) {
|
||||||
((NfcListenerFragment) mCurrentFragment).onNfcPostExecute();
|
((SecurityTokenListenerFragment) mCurrentFragment).onSecurityTokenPostExecute();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We don't want get back to wait activity mainly because it looks weird with otg token
|
||||||
|
if (mCurrentFragment instanceof CreateSecurityTokenWaitFragment) {
|
||||||
|
// hack from http://stackoverflow.com/a/11253987
|
||||||
|
CreateSecurityTokenWaitFragment.sDisableFragmentAnimations = true;
|
||||||
|
getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||||
|
CreateSecurityTokenWaitFragment.sDisableFragmentAnimations = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (containsKeys(mScannedFingerprints)) {
|
if (containsKeys(mScannedFingerprints)) {
|
||||||
try {
|
try {
|
||||||
long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mScannedFingerprints);
|
long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mScannedFingerprints);
|
||||||
@@ -169,15 +178,15 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity {
|
|||||||
|
|
||||||
Intent intent = new Intent(this, ViewKeyActivity.class);
|
Intent intent = new Intent(this, ViewKeyActivity.class);
|
||||||
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid);
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
||||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId);
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
|
||||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mScannedFingerprints);
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mScannedFingerprints);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
finish();
|
finish();
|
||||||
|
|
||||||
} catch (PgpKeyNotFoundException e) {
|
} catch (PgpKeyNotFoundException e) {
|
||||||
Fragment frag = CreateSecurityTokenImportResetFragment.newInstance(
|
Fragment frag = CreateSecurityTokenImportResetFragment.newInstance(
|
||||||
mScannedFingerprints, mNfcAid, mNfcUserId);
|
mScannedFingerprints, mSecurityTokenAid, mSecurityTokenUserId);
|
||||||
loadFragment(frag, FragAction.TO_RIGHT);
|
loadFragment(frag, FragAction.TO_RIGHT);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -252,12 +261,11 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity {
|
|||||||
|
|
||||||
// do it immediately!
|
// do it immediately!
|
||||||
getSupportFragmentManager().executePendingTransactions();
|
getSupportFragmentManager().executePendingTransactions();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NfcListenerFragment {
|
interface SecurityTokenListenerFragment {
|
||||||
void doNfcInBackground() throws IOException;
|
void doSecurityTokenInBackground() throws IOException;
|
||||||
void onNfcPostExecute();
|
void onSecurityTokenPostExecute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+8
-27
@@ -44,7 +44,6 @@ import org.sufficientlysecure.keychain.ui.widget.EmailEditText;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class CreateKeyEmailFragment extends Fragment {
|
public class CreateKeyEmailFragment extends Fragment {
|
||||||
private CreateKeyActivity mCreateKeyActivity;
|
private CreateKeyActivity mCreateKeyActivity;
|
||||||
@@ -52,10 +51,6 @@ public class CreateKeyEmailFragment extends Fragment {
|
|||||||
private ArrayList<EmailAdapter.ViewModel> mAdditionalEmailModels = new ArrayList<>();
|
private ArrayList<EmailAdapter.ViewModel> mAdditionalEmailModels = new ArrayList<>();
|
||||||
private EmailAdapter mEmailAdapter;
|
private EmailAdapter mEmailAdapter;
|
||||||
|
|
||||||
// NOTE: Do not use more complicated pattern like defined in android.util.Patterns.EMAIL_ADDRESS
|
|
||||||
// EMAIL_ADDRESS fails for mails with umlauts for example
|
|
||||||
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\S]+@[\\S]+\\.[a-z]+$");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new instance of this fragment
|
* Creates new instance of this fragment
|
||||||
*/
|
*/
|
||||||
@@ -76,16 +71,15 @@ public class CreateKeyEmailFragment extends Fragment {
|
|||||||
* @return true if EditText is not empty
|
* @return true if EditText is not empty
|
||||||
*/
|
*/
|
||||||
private boolean isMainEmailValid(EditText editText) {
|
private boolean isMainEmailValid(EditText editText) {
|
||||||
boolean output = true;
|
if (editText.getText().length() == 0) {
|
||||||
if (!checkEmail(editText.getText().toString(), false)) {
|
|
||||||
editText.setError(getString(R.string.create_key_empty));
|
editText.setError(getString(R.string.create_key_empty));
|
||||||
editText.requestFocus();
|
editText.requestFocus();
|
||||||
output = false;
|
return false;
|
||||||
} else {
|
} else if (!checkEmail(editText.getText().toString(), false)){
|
||||||
editText.setError(null);
|
return false;
|
||||||
}
|
}
|
||||||
|
editText.setError(null);
|
||||||
return output;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -146,10 +140,9 @@ public class CreateKeyEmailFragment extends Fragment {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private boolean checkEmail(String email, boolean additionalEmail) {
|
private boolean checkEmail(String email, boolean additionalEmail) {
|
||||||
// check for email format or if the user did any input
|
if (email.isEmpty()) {
|
||||||
if (!isEmailFormatValid(email)) {
|
|
||||||
Notify.create(getActivity(),
|
Notify.create(getActivity(),
|
||||||
getString(R.string.create_key_email_invalid_email),
|
getString(R.string.create_key_email_empty_email),
|
||||||
Notify.LENGTH_LONG, Notify.Style.ERROR).show(CreateKeyEmailFragment.this);
|
Notify.LENGTH_LONG, Notify.Style.ERROR).show(CreateKeyEmailFragment.this);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -166,18 +159,6 @@ public class CreateKeyEmailFragment extends Fragment {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the email format
|
|
||||||
* Uses the default Android Email Pattern
|
|
||||||
*
|
|
||||||
* @param email
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private boolean isEmailFormatValid(String email) {
|
|
||||||
// check for email format or if the user did any input
|
|
||||||
return !(email.length() == 0 || !EMAIL_PATTERN.matcher(email).matches());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for duplicated emails inside the additional email adapter.
|
* Checks for duplicated emails inside the additional email adapter.
|
||||||
*
|
*
|
||||||
|
|||||||
+47
-9
@@ -44,9 +44,9 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
|||||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.service.ChangeUnlockParcel;
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
|
|
||||||
import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
|
import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
||||||
@@ -57,6 +57,7 @@ import org.sufficientlysecure.keychain.util.Preferences;
|
|||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class CreateKeyFinalFragment extends Fragment {
|
public class CreateKeyFinalFragment extends Fragment {
|
||||||
|
|
||||||
@@ -81,6 +82,10 @@ public class CreateKeyFinalFragment extends Fragment {
|
|||||||
private OperationResult mQueuedFinishResult;
|
private OperationResult mQueuedFinishResult;
|
||||||
private EditKeyResult mQueuedDisplayResult;
|
private EditKeyResult mQueuedDisplayResult;
|
||||||
|
|
||||||
|
// NOTE: Do not use more complicated pattern like defined in android.util.Patterns.EMAIL_ADDRESS
|
||||||
|
// EMAIL_ADDRESS fails for mails with umlauts for example
|
||||||
|
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\S]+@[\\S]+\\.[a-z]+$");
|
||||||
|
|
||||||
public static CreateKeyFinalFragment newInstance() {
|
public static CreateKeyFinalFragment newInstance() {
|
||||||
CreateKeyFinalFragment frag = new CreateKeyFinalFragment();
|
CreateKeyFinalFragment frag = new CreateKeyFinalFragment();
|
||||||
frag.setRetainInstance(true);
|
frag.setRetainInstance(true);
|
||||||
@@ -106,7 +111,11 @@ public class CreateKeyFinalFragment extends Fragment {
|
|||||||
CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity();
|
CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity();
|
||||||
|
|
||||||
// set values
|
// set values
|
||||||
mNameEdit.setText(createKeyActivity.mName);
|
if (createKeyActivity.mName != null) {
|
||||||
|
mNameEdit.setText(createKeyActivity.mName);
|
||||||
|
} else {
|
||||||
|
mNameEdit.setText(getString(R.string.user_id_no_name));
|
||||||
|
}
|
||||||
if (createKeyActivity.mAdditionalEmails != null && createKeyActivity.mAdditionalEmails.size() > 0) {
|
if (createKeyActivity.mAdditionalEmails != null && createKeyActivity.mAdditionalEmails.size() > 0) {
|
||||||
String emailText = createKeyActivity.mEmail + ", ";
|
String emailText = createKeyActivity.mEmail + ", ";
|
||||||
Iterator<?> it = createKeyActivity.mAdditionalEmails.iterator();
|
Iterator<?> it = createKeyActivity.mAdditionalEmails.iterator();
|
||||||
@@ -122,6 +131,8 @@ public class CreateKeyFinalFragment extends Fragment {
|
|||||||
mEmailEdit.setText(createKeyActivity.mEmail);
|
mEmailEdit.setText(createKeyActivity.mEmail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkEmailValidity();
|
||||||
|
|
||||||
mCreateButton.setOnClickListener(new View.OnClickListener() {
|
mCreateButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
@@ -278,18 +289,20 @@ public class CreateKeyFinalFragment extends Fragment {
|
|||||||
2048, null, KeyFlags.AUTHENTICATION, 0L));
|
2048, null, KeyFlags.AUTHENTICATION, 0L));
|
||||||
|
|
||||||
// use empty passphrase
|
// use empty passphrase
|
||||||
saveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase(), null);
|
saveKeyringParcel.setNewUnlock(new ChangeUnlockParcel(new Passphrase()));
|
||||||
} else {
|
} else {
|
||||||
saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
|
saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
|
||||||
4096, null, KeyFlags.CERTIFY_OTHER, 0L));
|
3072, null, KeyFlags.CERTIFY_OTHER, 0L));
|
||||||
saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
|
saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
|
||||||
4096, null, KeyFlags.SIGN_DATA, 0L));
|
3072, null, KeyFlags.SIGN_DATA, 0L));
|
||||||
saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
|
saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
|
||||||
4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L));
|
3072, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L));
|
||||||
|
|
||||||
saveKeyringParcel.mNewUnlock = createKeyActivity.mPassphrase != null
|
if (createKeyActivity.mPassphrase != null) {
|
||||||
? new ChangeUnlockParcel(createKeyActivity.mPassphrase, null)
|
saveKeyringParcel.setNewUnlock(new ChangeUnlockParcel(createKeyActivity.mPassphrase));
|
||||||
: null;
|
} else {
|
||||||
|
saveKeyringParcel.setNewUnlock(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
String userId = KeyRing.createUserId(
|
String userId = KeyRing.createUserId(
|
||||||
new KeyRing.UserId(createKeyActivity.mName, createKeyActivity.mEmail, null)
|
new KeyRing.UserId(createKeyActivity.mName, createKeyActivity.mEmail, null)
|
||||||
@@ -309,6 +322,31 @@ public class CreateKeyFinalFragment extends Fragment {
|
|||||||
return saveKeyringParcel;
|
return saveKeyringParcel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkEmailValidity() {
|
||||||
|
CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity();
|
||||||
|
|
||||||
|
boolean emailsValid = true;
|
||||||
|
if (!EMAIL_PATTERN.matcher(createKeyActivity.mEmail).matches()) {
|
||||||
|
emailsValid = false;
|
||||||
|
}
|
||||||
|
if (createKeyActivity.mAdditionalEmails != null && createKeyActivity.mAdditionalEmails.size() > 0) {
|
||||||
|
for (Iterator<?> it = createKeyActivity.mAdditionalEmails.iterator(); it.hasNext(); ) {
|
||||||
|
if (!EMAIL_PATTERN.matcher(it.next().toString()).matches()) {
|
||||||
|
emailsValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!emailsValid) {
|
||||||
|
mEmailEdit.setError(getString(R.string.create_key_final_email_valid_warning));
|
||||||
|
mEmailEdit.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
mNameEdit.requestFocus(); // Workaround to remove focus from email
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void createKey() {
|
private void createKey() {
|
||||||
CreateKeyActivity activity = (CreateKeyActivity) getActivity();
|
CreateKeyActivity activity = (CreateKeyActivity) getActivity();
|
||||||
if (activity == null) {
|
if (activity == null) {
|
||||||
|
|||||||
+4
-29
@@ -18,13 +18,11 @@
|
|||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.EditText;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
||||||
@@ -50,27 +48,6 @@ public class CreateKeyNameFragment extends Fragment {
|
|||||||
return frag;
|
return frag;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if text of given EditText is not empty. If it is empty an error is
|
|
||||||
* set and the EditText gets the focus.
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
* @param editText
|
|
||||||
* @return true if EditText is not empty
|
|
||||||
*/
|
|
||||||
private static boolean isEditTextNotEmpty(Context context, EditText editText) {
|
|
||||||
boolean output = true;
|
|
||||||
if (editText.getText().length() == 0) {
|
|
||||||
editText.setError(context.getString(R.string.create_key_empty));
|
|
||||||
editText.requestFocus();
|
|
||||||
output = false;
|
|
||||||
} else {
|
|
||||||
editText.setError(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.create_key_name_fragment, container, false);
|
View view = inflater.inflate(R.layout.create_key_name_fragment, container, false);
|
||||||
@@ -109,13 +86,11 @@ public class CreateKeyNameFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void nextClicked() {
|
private void nextClicked() {
|
||||||
if (isEditTextNotEmpty(getActivity(), mNameEdit)) {
|
// save state
|
||||||
// save state
|
mCreateKeyActivity.mName = mNameEdit.getText().length() == 0 ? null : mNameEdit.getText().toString();
|
||||||
mCreateKeyActivity.mName = mNameEdit.getText().toString();
|
|
||||||
|
|
||||||
CreateKeyEmailFragment frag = CreateKeyEmailFragment.newInstance();
|
CreateKeyEmailFragment frag = CreateKeyEmailFragment.newInstance();
|
||||||
mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
|
mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-9
@@ -25,7 +25,6 @@ import java.util.ArrayList;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -43,7 +42,7 @@ import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
|||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
||||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment;
|
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.SecurityTokenListenerFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
|
import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
@@ -51,7 +50,7 @@ import org.sufficientlysecure.keychain.util.Preferences;
|
|||||||
|
|
||||||
public class CreateSecurityTokenImportResetFragment
|
public class CreateSecurityTokenImportResetFragment
|
||||||
extends QueueingCryptoOperationFragment<ImportKeyringParcel, ImportKeyResult>
|
extends QueueingCryptoOperationFragment<ImportKeyringParcel, ImportKeyResult>
|
||||||
implements NfcListenerFragment {
|
implements SecurityTokenListenerFragment {
|
||||||
|
|
||||||
private static final int REQUEST_CODE_RESET = 0x00005001;
|
private static final int REQUEST_CODE_RESET = 0x00005001;
|
||||||
|
|
||||||
@@ -231,7 +230,7 @@ public class CreateSecurityTokenImportResetFragment
|
|||||||
|
|
||||||
public void resetCard() {
|
public void resetCard() {
|
||||||
Intent intent = new Intent(getActivity(), SecurityTokenOperationActivity.class);
|
Intent intent = new Intent(getActivity(), SecurityTokenOperationActivity.class);
|
||||||
RequiredInputParcel resetP = RequiredInputParcel.createNfcReset();
|
RequiredInputParcel resetP = RequiredInputParcel.createSecurityTokenReset();
|
||||||
intent.putExtra(SecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, resetP);
|
intent.putExtra(SecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, resetP);
|
||||||
intent.putExtra(SecurityTokenOperationActivity.EXTRA_CRYPTO_INPUT, new CryptoInputParcel());
|
intent.putExtra(SecurityTokenOperationActivity.EXTRA_CRYPTO_INPUT, new CryptoInputParcel());
|
||||||
startActivityForResult(intent, REQUEST_CODE_RESET);
|
startActivityForResult(intent, REQUEST_CODE_RESET);
|
||||||
@@ -248,11 +247,11 @@ public class CreateSecurityTokenImportResetFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doNfcInBackground() throws IOException {
|
public void doSecurityTokenInBackground() throws IOException {
|
||||||
|
|
||||||
mTokenFingerprints = mCreateKeyActivity.nfcGetFingerprints();
|
mTokenFingerprints = mCreateKeyActivity.getSecurityTokenHelper().getFingerprints();
|
||||||
mTokenAid = mCreateKeyActivity.nfcGetAid();
|
mTokenAid = mCreateKeyActivity.getSecurityTokenHelper().getAid();
|
||||||
mTokenUserId = mCreateKeyActivity.nfcGetUserId();
|
mTokenUserId = mCreateKeyActivity.getSecurityTokenHelper().getUserId();
|
||||||
|
|
||||||
byte[] fp = new byte[20];
|
byte[] fp = new byte[20];
|
||||||
ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20);
|
ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20);
|
||||||
@@ -260,7 +259,7 @@ public class CreateSecurityTokenImportResetFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNfcPostExecute() {
|
public void onSecurityTokenPostExecute() {
|
||||||
|
|
||||||
setData();
|
setData();
|
||||||
|
|
||||||
|
|||||||
+30
-4
@@ -17,22 +17,35 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.animation.Animation;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
||||||
|
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
|
||||||
|
|
||||||
public class CreateSecurityTokenWaitFragment extends Fragment {
|
public class CreateSecurityTokenWaitFragment extends Fragment {
|
||||||
|
|
||||||
|
public static boolean sDisableFragmentAnimations = false;
|
||||||
|
|
||||||
CreateKeyActivity mCreateKeyActivity;
|
CreateKeyActivity mCreateKeyActivity;
|
||||||
View mBackButton;
|
View mBackButton;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
if (this.getActivity() instanceof BaseSecurityTokenActivity) {
|
||||||
|
((BaseSecurityTokenActivity) this.getActivity()).checkDeviceConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.create_security_token_wait_fragment, container, false);
|
View view = inflater.inflate(R.layout.create_security_token_wait_fragment, container, false);
|
||||||
@@ -50,9 +63,22 @@ public class CreateSecurityTokenWaitFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity) {
|
public void onAttach(Context context) {
|
||||||
super.onAttach(activity);
|
super.onAttach(context);
|
||||||
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
mCreateKeyActivity = (CreateKeyActivity) getActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hack from http://stackoverflow.com/a/11253987
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
|
||||||
|
if (sDisableFragmentAnimations) {
|
||||||
|
Animation a = new Animation() {};
|
||||||
|
a.setDuration(0);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
return super.onCreateAnimation(transit, enter, nextAnim);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-57
@@ -35,6 +35,7 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||||
@@ -49,8 +50,8 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
|||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
||||||
|
import org.sufficientlysecure.keychain.service.ChangeUnlockParcel;
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
|
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
|
||||||
@@ -128,7 +129,7 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.edit_key_fragment, null);
|
View view = inflater.inflate(R.layout.edit_key_fragment, superContainer, false);
|
||||||
|
|
||||||
mUserIdsList = (ListView) view.findViewById(R.id.edit_key_user_ids);
|
mUserIdsList = (ListView) view.findViewById(R.id.edit_key_user_ids);
|
||||||
mSubkeysList = (ListView) view.findViewById(R.id.edit_key_keys);
|
mSubkeysList = (ListView) view.findViewById(R.id.edit_key_keys);
|
||||||
@@ -338,10 +339,8 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring
|
|||||||
Bundle data = message.getData();
|
Bundle data = message.getData();
|
||||||
|
|
||||||
// cache new returned passphrase!
|
// cache new returned passphrase!
|
||||||
mSaveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(
|
mSaveKeyringParcel.setNewUnlock(new ChangeUnlockParcel(
|
||||||
(Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE),
|
(Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE)));
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -441,50 +440,45 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_CARD: {
|
case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_SECURITY_TOKEN: {
|
||||||
// TODO: enable later when Admin PIN handling is resolved
|
SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position);
|
||||||
Notify.create(getActivity(),
|
if (secretKeyType == SecretKeyType.DIVERT_TO_CARD ||
|
||||||
"This feature will be available in an upcoming OpenKeychain version.",
|
secretKeyType == SecretKeyType.GNU_DUMMY) {
|
||||||
Notify.Style.WARN).show();
|
Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_stripped, Notify.Style.ERROR)
|
||||||
break;
|
.show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Activity activity = EditKeyFragment.this.getActivity();
|
int algorithm = mSubkeysAdapter.getAlgorithm(position);
|
||||||
// SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position);
|
if (algorithm != PublicKeyAlgorithmTags.RSA_GENERAL
|
||||||
// if (secretKeyType == SecretKeyType.DIVERT_TO_CARD ||
|
&& algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT
|
||||||
// secretKeyType == SecretKeyType.GNU_DUMMY) {
|
&& algorithm != PublicKeyAlgorithmTags.RSA_SIGN) {
|
||||||
// Notify.create(activity, R.string.edit_key_error_bad_nfc_stripped, Notify.Style.ERROR)
|
Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR)
|
||||||
// .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
|
.show();
|
||||||
// break;
|
break;
|
||||||
// }
|
}
|
||||||
// int algorithm = mSubkeysAdapter.getAlgorithm(position);
|
|
||||||
// // these are the PGP constants for RSA_GENERAL, RSA_ENCRYPT and RSA_SIGN
|
if (mSubkeysAdapter.getKeySize(position) != 2048) {
|
||||||
// if (algorithm != 1 && algorithm != 2 && algorithm != 3) {
|
Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR)
|
||||||
// Notify.create(activity, R.string.edit_key_error_bad_nfc_algo, Notify.Style.ERROR)
|
.show();
|
||||||
// .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
|
break;
|
||||||
// break;
|
}
|
||||||
// }
|
|
||||||
// if (mSubkeysAdapter.getKeySize(position) != 2048) {
|
SubkeyChange change;
|
||||||
// Notify.create(activity, R.string.edit_key_error_bad_nfc_size, Notify.Style.ERROR)
|
change = mSaveKeyringParcel.getSubkeyChange(keyId);
|
||||||
// .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
|
if (change == null) {
|
||||||
// break;
|
mSaveKeyringParcel.mChangeSubKeys.add(
|
||||||
// }
|
new SubkeyChange(keyId, false, true)
|
||||||
//
|
);
|
||||||
//
|
break;
|
||||||
// SubkeyChange change;
|
}
|
||||||
// change = mSaveKeyringParcel.getSubkeyChange(keyId);
|
// toggle
|
||||||
// if (change == null) {
|
change.mMoveKeyToSecurityToken = !change.mMoveKeyToSecurityToken;
|
||||||
// mSaveKeyringParcel.mChangeSubKeys.add(
|
if (change.mMoveKeyToSecurityToken && change.mDummyStrip) {
|
||||||
// new SubkeyChange(keyId, false, true)
|
// User had chosen to strip key, but now wants to divert it.
|
||||||
// );
|
change.mDummyStrip = false;
|
||||||
// break;
|
}
|
||||||
// }
|
break;
|
||||||
// // toggle
|
|
||||||
// change.mMoveKeyToSecurityToken = !change.mMoveKeyToSecurityToken;
|
|
||||||
// if (change.mMoveKeyToSecurityToken && change.mDummyStrip) {
|
|
||||||
// // User had chosen to strip key, but now wants to divert it.
|
|
||||||
// change.mDummyStrip = false;
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
|
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
|
||||||
@@ -562,15 +556,9 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addSubkey() {
|
private void addSubkey() {
|
||||||
boolean willBeMasterKey;
|
// new subkey will never be a masterkey, as masterkey cannot be removed
|
||||||
if (mSubkeysAdapter != null) {
|
|
||||||
willBeMasterKey = mSubkeysAdapter.getCount() == 0 && mSubkeysAddedAdapter.getCount() == 0;
|
|
||||||
} else {
|
|
||||||
willBeMasterKey = mSubkeysAddedAdapter.getCount() == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddSubkeyDialogFragment addSubkeyDialogFragment =
|
AddSubkeyDialogFragment addSubkeyDialogFragment =
|
||||||
AddSubkeyDialogFragment.newInstance(willBeMasterKey);
|
AddSubkeyDialogFragment.newInstance(false);
|
||||||
addSubkeyDialogFragment
|
addSubkeyDialogFragment
|
||||||
.setOnAlgorithmSelectedListener(
|
.setOnAlgorithmSelectedListener(
|
||||||
new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() {
|
new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() {
|
||||||
|
|||||||
+4
-2
@@ -247,10 +247,10 @@ public class EncryptFilesFragment
|
|||||||
try {
|
try {
|
||||||
mFilesAdapter.add(inputUri);
|
mFilesAdapter.add(inputUri);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
String fileName = FileHelper.getFilename(getActivity(), inputUri);
|
||||||
Notify.create(getActivity(),
|
Notify.create(getActivity(),
|
||||||
getActivity().getString(R.string.error_file_added_already, FileHelper.getFilename(getActivity(), inputUri)),
|
getActivity().getString(R.string.error_file_added_already, fileName),
|
||||||
Notify.Style.ERROR).show(this);
|
Notify.Style.ERROR).show(this);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove from pending input uris
|
// remove from pending input uris
|
||||||
@@ -729,6 +729,8 @@ public class EncryptFilesFragment
|
|||||||
// make sure this is correct at this point
|
// make sure this is correct at this point
|
||||||
mAfterEncryptAction = AfterEncryptAction.SAVE;
|
mAfterEncryptAction = AfterEncryptAction.SAVE;
|
||||||
cryptoOperation(new CryptoInputParcel(new Date()));
|
cryptoOperation(new CryptoInputParcel(new Date()));
|
||||||
|
} else if (resultCode == Activity.RESULT_CANCELED) {
|
||||||
|
onCryptoOperationCancelled();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-3
@@ -21,6 +21,7 @@ import android.os.Bundle;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.ViewAnimator;
|
import android.widget.ViewAnimator;
|
||||||
|
|
||||||
import com.tokenautocomplete.TokenCompleteTextView;
|
import com.tokenautocomplete.TokenCompleteTextView;
|
||||||
@@ -79,9 +80,6 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
|
|||||||
mSignKeySpinner = (KeySpinner) view.findViewById(R.id.sign);
|
mSignKeySpinner = (KeySpinner) view.findViewById(R.id.sign);
|
||||||
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
|
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
|
||||||
mEncryptKeyView.setThreshold(1); // Start working from first character
|
mEncryptKeyView.setThreshold(1); // Start working from first character
|
||||||
// TODO: workaround for bug in TokenAutoComplete,
|
|
||||||
// see https://github.com/open-keychain/open-keychain/issues/1636
|
|
||||||
mEncryptKeyView.setDeletionStyle(TokenCompleteTextView.TokenDeleteStyle.ToString);
|
|
||||||
|
|
||||||
final ViewAnimator vSignatureIcon = (ViewAnimator) view.findViewById(R.id.result_signature_icon);
|
final ViewAnimator vSignatureIcon = (ViewAnimator) view.findViewById(R.id.result_signature_icon);
|
||||||
mSignKeySpinner.setOnKeyChangedListener(new OnKeyChangedListener() {
|
mSignKeySpinner.setOnKeyChangedListener(new OnKeyChangedListener() {
|
||||||
@@ -112,6 +110,14 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ImageView addRecipientImgView = (ImageView) view.findViewById(R.id.add_recipient);
|
||||||
|
addRecipientImgView.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
mEncryptKeyView.showAllKeys();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-5
@@ -131,8 +131,11 @@ public class ImportKeysActivity extends BaseActivity
|
|||||||
if (Intent.ACTION_VIEW.equals(action)) {
|
if (Intent.ACTION_VIEW.equals(action)) {
|
||||||
if (FacebookKeyserver.isFacebookHost(dataUri)) {
|
if (FacebookKeyserver.isFacebookHost(dataUri)) {
|
||||||
action = ACTION_IMPORT_KEY_FROM_FACEBOOK;
|
action = ACTION_IMPORT_KEY_FROM_FACEBOOK;
|
||||||
} else if ("http".equals(scheme) || "https".equals(scheme)) {
|
} else if ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) {
|
||||||
action = ACTION_SEARCH_KEYSERVER_FROM_URL;
|
action = ACTION_SEARCH_KEYSERVER_FROM_URL;
|
||||||
|
} else if ("openpgp4fpr".equalsIgnoreCase(scheme)) {
|
||||||
|
action = ACTION_IMPORT_KEY_FROM_KEYSERVER;
|
||||||
|
extras.putString(EXTRA_FINGERPRINT, dataUri.getSchemeSpecificPart());
|
||||||
} else {
|
} else {
|
||||||
// Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
|
// Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
|
||||||
// delegate action to ACTION_IMPORT_KEY
|
// delegate action to ACTION_IMPORT_KEY
|
||||||
@@ -413,11 +416,18 @@ public class ImportKeysActivity extends BaseActivity
|
|||||||
intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
|
intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
|
||||||
setResult(RESULT_OK, intent);
|
setResult(RESULT_OK, intent);
|
||||||
finish();
|
finish();
|
||||||
return;
|
} else if (result.isOkNew() || result.isOkUpdated()) {
|
||||||
}
|
// User has successfully imported a key, hide first time dialog
|
||||||
|
Preferences.getPreferences(this).setFirstTime(false);
|
||||||
|
|
||||||
result.createNotify(ImportKeysActivity.this)
|
// Close activities opened for importing keys and go to the list of keys
|
||||||
.show((ViewGroup) findViewById(R.id.import_snackbar));
|
Intent intent = new Intent(this, MainActivity.class);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
startActivity(intent);
|
||||||
|
} else {
|
||||||
|
result.createNotify(ImportKeysActivity.this)
|
||||||
|
.show((ViewGroup) findViewById(R.id.import_snackbar));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// methods from CryptoOperationHelper.Callback
|
// methods from CryptoOperationHelper.Callback
|
||||||
|
|||||||
+97
-6
@@ -17,29 +17,46 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.ContentResolver;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||||
|
import org.sufficientlysecure.keychain.ui.ImportKeysListFragment.BytesLoaderState;
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
public class ImportKeysFileFragment extends Fragment {
|
public class ImportKeysFileFragment extends Fragment {
|
||||||
private ImportKeysActivity mImportActivity;
|
private ImportKeysActivity mImportActivity;
|
||||||
private View mBrowse;
|
private View mBrowse;
|
||||||
private View mClipboardButton;
|
private View mClipboardButton;
|
||||||
|
|
||||||
public static final int REQUEST_CODE_FILE = 0x00007003;
|
private Uri mCurrentUri;
|
||||||
|
|
||||||
|
private static final int REQUEST_CODE_FILE = 0x00007003;
|
||||||
|
private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new instance of this fragment
|
* Creates new instance of this fragment
|
||||||
@@ -83,10 +100,10 @@ public class ImportKeysFileFragment extends Fragment {
|
|||||||
sendText = clipboardText.toString();
|
sendText = clipboardText.toString();
|
||||||
sendText = PgpHelper.getPgpKeyContent(sendText);
|
sendText = PgpHelper.getPgpKeyContent(sendText);
|
||||||
if (sendText == null) {
|
if (sendText == null) {
|
||||||
Notify.create(mImportActivity, "Bad data!", Style.ERROR).show();
|
Notify.create(mImportActivity, R.string.error_bad_data, Style.ERROR).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mImportActivity.loadCallback(new ImportKeysListFragment.BytesLoaderState(sendText.getBytes(), null));
|
mImportActivity.loadCallback(new BytesLoaderState(sendText.getBytes(), null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -106,11 +123,12 @@ public class ImportKeysFileFragment extends Fragment {
|
|||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case REQUEST_CODE_FILE: {
|
case REQUEST_CODE_FILE: {
|
||||||
if (resultCode == Activity.RESULT_OK && data != null && data.getData() != null) {
|
if (resultCode == Activity.RESULT_OK && data != null && data.getData() != null) {
|
||||||
|
mCurrentUri = data.getData();
|
||||||
|
|
||||||
// load data
|
if (checkAndRequestReadPermission(mCurrentUri)) {
|
||||||
mImportActivity.loadCallback(new ImportKeysListFragment.BytesLoaderState(null, data.getData()));
|
startImportingKeys();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,4 +139,77 @@ public class ImportKeysFileFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startImportingKeys() {
|
||||||
|
boolean isEncrypted;
|
||||||
|
try {
|
||||||
|
isEncrypted = FileHelper.isEncryptedFile(mImportActivity, mCurrentUri);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Constants.TAG, "Error opening file", e);
|
||||||
|
|
||||||
|
Notify.create(mImportActivity, R.string.error_bad_data, Style.ERROR).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEncrypted) {
|
||||||
|
Intent intent = new Intent(mImportActivity, DecryptActivity.class);
|
||||||
|
intent.setAction(Intent.ACTION_VIEW);
|
||||||
|
intent.setData(mCurrentUri);
|
||||||
|
startActivity(intent);
|
||||||
|
} else {
|
||||||
|
mImportActivity.loadCallback(new BytesLoaderState(null, mCurrentUri));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris.
|
||||||
|
* <p/>
|
||||||
|
* This method returns true on Android < 6, or if permission is already granted. It
|
||||||
|
* requests the permission and returns false otherwise.
|
||||||
|
* <p/>
|
||||||
|
* see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html
|
||||||
|
*/
|
||||||
|
private boolean checkAndRequestReadPermission(final Uri uri) {
|
||||||
|
if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
== PackageManager.PERMISSION_GRANTED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestPermissions(
|
||||||
|
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||||
|
REQUEST_PERMISSION_READ_EXTERNAL_STORAGE);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode,
|
||||||
|
@NonNull String[] permissions,
|
||||||
|
@NonNull int[] grantResults) {
|
||||||
|
|
||||||
|
if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean permissionWasGranted = grantResults.length > 0
|
||||||
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
||||||
|
|
||||||
|
if (permissionWasGranted) {
|
||||||
|
startImportingKeys();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getActivity(), R.string.error_denied_storage_permission, Toast.LENGTH_LONG).show();
|
||||||
|
getActivity().setResult(Activity.RESULT_CANCELED);
|
||||||
|
getActivity().finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-1
@@ -330,9 +330,16 @@ public class ImportKeysListFragment extends ListFragment implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadNew(LoaderState loaderState) {
|
public void loadNew(LoaderState loaderState) {
|
||||||
|
|
||||||
mLoaderState = loaderState;
|
mLoaderState = loaderState;
|
||||||
|
|
||||||
|
if (mLoaderState instanceof BytesLoaderState) {
|
||||||
|
BytesLoaderState ls = (BytesLoaderState) mLoaderState;
|
||||||
|
|
||||||
|
if ( ls.mDataUri != null && ! checkAndRequestReadPermission(ls.mDataUri)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
restartLoaders();
|
restartLoaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,11 +40,11 @@ import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
|
|||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
import org.sufficientlysecure.keychain.remote.ui.AppsListFragment;
|
import org.sufficientlysecure.keychain.remote.ui.AppsListFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
|
||||||
import org.sufficientlysecure.keychain.util.FabContainer;
|
import org.sufficientlysecure.keychain.util.FabContainer;
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
|
|
||||||
public class MainActivity extends BaseSecurityTokenNfcActivity implements FabContainer, OnBackStackChangedListener {
|
public class MainActivity extends BaseSecurityTokenActivity implements FabContainer, OnBackStackChangedListener {
|
||||||
|
|
||||||
static final int ID_KEYS = 1;
|
static final int ID_KEYS = 1;
|
||||||
static final int ID_ENCRYPT_DECRYPT = 2;
|
static final int ID_ENCRYPT_DECRYPT = 2;
|
||||||
@@ -90,8 +90,9 @@ public class MainActivity extends BaseSecurityTokenNfcActivity implements FabCon
|
|||||||
@Override
|
@Override
|
||||||
public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
|
public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
|
||||||
if (drawerItem != null) {
|
if (drawerItem != null) {
|
||||||
|
PrimaryDrawerItem item = (PrimaryDrawerItem) drawerItem;
|
||||||
Intent intent = null;
|
Intent intent = null;
|
||||||
switch (drawerItem.getIdentifier()) {
|
switch ((int) item.getIdentifier()) {
|
||||||
case ID_KEYS:
|
case ID_KEYS:
|
||||||
onKeysSelected();
|
onKeysSelected();
|
||||||
break;
|
break;
|
||||||
|
|||||||
+223
@@ -0,0 +1,223 @@
|
|||||||
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.MatrixCursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.LoaderManager;
|
||||||
|
import android.support.v4.content.CursorLoader;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||||
|
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class MultiUserIdsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>{
|
||||||
|
public static final String ARG_CHECK_STATES = "check_states";
|
||||||
|
public static final String EXTRA_KEY_IDS = "extra_key_ids";
|
||||||
|
private boolean checkboxVisibility = true;
|
||||||
|
|
||||||
|
ListView mUserIds;
|
||||||
|
private MultiUserIdsAdapter mUserIdsAdapter;
|
||||||
|
|
||||||
|
private long[] mPubMasterKeyIds;
|
||||||
|
|
||||||
|
public static final String[] USER_IDS_PROJECTION = new String[]{
|
||||||
|
KeychainContract.UserPackets._ID,
|
||||||
|
KeychainContract.UserPackets.MASTER_KEY_ID,
|
||||||
|
KeychainContract.UserPackets.USER_ID,
|
||||||
|
KeychainContract.UserPackets.IS_PRIMARY,
|
||||||
|
KeychainContract.UserPackets.IS_REVOKED
|
||||||
|
};
|
||||||
|
private static final int INDEX_MASTER_KEY_ID = 1;
|
||||||
|
private static final int INDEX_USER_ID = 2;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static final int INDEX_IS_PRIMARY = 3;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static final int INDEX_IS_REVOKED = 4;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.multi_user_ids_fragment, null);
|
||||||
|
|
||||||
|
mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(EXTRA_KEY_IDS);
|
||||||
|
if (mPubMasterKeyIds == null) {
|
||||||
|
Log.e(Constants.TAG, "List of key ids to certify missing!");
|
||||||
|
getActivity().finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<Boolean> checkedStates = null;
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
checkedStates = (ArrayList<Boolean>) savedInstanceState.getSerializable(ARG_CHECK_STATES);
|
||||||
|
}
|
||||||
|
|
||||||
|
mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates, checkboxVisibility);
|
||||||
|
mUserIds.setAdapter(mUserIdsAdapter);
|
||||||
|
mUserIds.setDividerHeight(0);
|
||||||
|
|
||||||
|
getLoaderManager().initLoader(0, null, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<CertifyActionsParcel.CertifyAction> getSelectedCertifyActions() {
|
||||||
|
if (!checkboxVisibility) {
|
||||||
|
throw new AssertionError("Item selection not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mUserIdsAdapter.getSelectedCertifyActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
|
Uri uri = KeychainContract.UserPackets.buildUserIdsUri();
|
||||||
|
|
||||||
|
String selection, ids[];
|
||||||
|
{
|
||||||
|
// generate placeholders and string selection args
|
||||||
|
ids = new String[mPubMasterKeyIds.length];
|
||||||
|
StringBuilder placeholders = new StringBuilder("?");
|
||||||
|
for (int i = 0; i < mPubMasterKeyIds.length; i++) {
|
||||||
|
ids[i] = Long.toString(mPubMasterKeyIds[i]);
|
||||||
|
if (i != 0) {
|
||||||
|
placeholders.append(",?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// put together selection string
|
||||||
|
selection = KeychainContract.UserPackets.IS_REVOKED + " = 0" + " AND "
|
||||||
|
+ KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.MASTER_KEY_ID
|
||||||
|
+ " IN (" + placeholders + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CursorLoader(getActivity(), uri,
|
||||||
|
USER_IDS_PROJECTION, selection, ids,
|
||||||
|
KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.MASTER_KEY_ID + " ASC"
|
||||||
|
+ ", " + KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.USER_ID + " ASC"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||||
|
|
||||||
|
MatrixCursor matrix = new MatrixCursor(new String[]{
|
||||||
|
"_id", "user_data", "grouped"
|
||||||
|
}) {
|
||||||
|
@Override
|
||||||
|
public byte[] getBlob(int column) {
|
||||||
|
return super.getBlob(column);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
data.moveToFirst();
|
||||||
|
|
||||||
|
long lastMasterKeyId = 0;
|
||||||
|
String lastName = "";
|
||||||
|
ArrayList<String> uids = new ArrayList<>();
|
||||||
|
|
||||||
|
boolean header = true;
|
||||||
|
|
||||||
|
// Iterate over all rows
|
||||||
|
while (!data.isAfterLast()) {
|
||||||
|
long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||||
|
String userId = data.getString(INDEX_USER_ID);
|
||||||
|
KeyRing.UserId pieces = KeyRing.splitUserId(userId);
|
||||||
|
|
||||||
|
// Two cases:
|
||||||
|
|
||||||
|
boolean grouped = masterKeyId == lastMasterKeyId;
|
||||||
|
boolean subGrouped = data.isFirst() || grouped && lastName.equals(pieces.name);
|
||||||
|
// Remember for next loop
|
||||||
|
lastName = pieces.name;
|
||||||
|
|
||||||
|
Log.d(Constants.TAG, Long.toString(masterKeyId, 16) + (grouped ? "grouped" : "not grouped"));
|
||||||
|
|
||||||
|
if (!subGrouped) {
|
||||||
|
// 1. This name should NOT be grouped with the previous, so we flush the buffer
|
||||||
|
|
||||||
|
Parcel p = Parcel.obtain();
|
||||||
|
p.writeStringList(uids);
|
||||||
|
byte[] d = p.marshall();
|
||||||
|
p.recycle();
|
||||||
|
|
||||||
|
matrix.addRow(new Object[]{
|
||||||
|
lastMasterKeyId, d, header ? 1 : 0
|
||||||
|
});
|
||||||
|
// indicate that we have a header for this masterKeyId
|
||||||
|
header = false;
|
||||||
|
|
||||||
|
// Now clear the buffer, and add the new user id, for the next round
|
||||||
|
uids.clear();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. This name should be grouped with the previous, just add to buffer
|
||||||
|
uids.add(userId);
|
||||||
|
lastMasterKeyId = masterKeyId;
|
||||||
|
|
||||||
|
// If this one wasn't grouped, the next one's gotta be a header
|
||||||
|
if (!grouped) {
|
||||||
|
header = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regardless of the outcome, move to next entry
|
||||||
|
data.moveToNext();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is anything left in the buffer, flush it one last time
|
||||||
|
if (!uids.isEmpty()) {
|
||||||
|
|
||||||
|
Parcel p = Parcel.obtain();
|
||||||
|
p.writeStringList(uids);
|
||||||
|
byte[] d = p.marshall();
|
||||||
|
p.recycle();
|
||||||
|
|
||||||
|
matrix.addRow(new Object[]{
|
||||||
|
lastMasterKeyId, d, header ? 1 : 0
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
mUserIdsAdapter.swapCursor(matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Cursor> loader) {
|
||||||
|
mUserIdsAdapter.swapCursor(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCheckboxVisibility(boolean checkboxVisibility) {
|
||||||
|
this.checkboxVisibility = checkboxVisibility;
|
||||||
|
}
|
||||||
|
}
|
||||||
+75
-43
@@ -46,8 +46,6 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import android.widget.ViewAnimator;
|
import android.widget.ViewAnimator;
|
||||||
|
|
||||||
import com.github.pinball83.maskededittext.MaskedEditText;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||||
@@ -60,7 +58,6 @@ import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
|||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
||||||
import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;
|
|
||||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||||
@@ -157,7 +154,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener {
|
public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener {
|
||||||
private EditText mPassphraseEditText;
|
private EditText mPassphraseEditText;
|
||||||
private TextView mPassphraseText;
|
private TextView mPassphraseText;
|
||||||
private MaskedEditText mBackupCodeEditText;
|
private EditText[] mBackupCodeEditText;
|
||||||
|
|
||||||
private boolean mIsCancelled = false;
|
private boolean mIsCancelled = false;
|
||||||
private RequiredInputParcel mRequiredInput;
|
private RequiredInputParcel mRequiredInput;
|
||||||
@@ -184,13 +181,15 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
View view = inflater.inflate(R.layout.passphrase_dialog_backup_code, null);
|
View view = inflater.inflate(R.layout.passphrase_dialog_backup_code, null);
|
||||||
alert.setView(view);
|
alert.setView(view);
|
||||||
|
|
||||||
mBackupCodeEditText = (MaskedEditText) view.findViewById(R.id.backup_code);
|
mBackupCodeEditText = new EditText[6];
|
||||||
// NOTE: order of these method calls matter, see setupAutomaticLinebreak()
|
mBackupCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1);
|
||||||
mBackupCodeEditText.setInputType(
|
mBackupCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2);
|
||||||
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
|
mBackupCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3);
|
||||||
setupAutomaticLinebreak(mBackupCodeEditText);
|
mBackupCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4);
|
||||||
mBackupCodeEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
|
mBackupCodeEditText[4] = (EditText) view.findViewById(R.id.backup_code_5);
|
||||||
mBackupCodeEditText.setOnEditorActionListener(this);
|
mBackupCodeEditText[5] = (EditText) view.findViewById(R.id.backup_code_6);
|
||||||
|
|
||||||
|
setupEditTextFocusNext(mBackupCodeEditText);
|
||||||
|
|
||||||
AlertDialog dialog = alert.create();
|
AlertDialog dialog = alert.create();
|
||||||
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
|
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
|
||||||
@@ -281,28 +280,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
mPassphraseText.setText(message);
|
mPassphraseText.setText(message);
|
||||||
mPassphraseEditText.setHint(hint);
|
mPassphraseEditText.setHint(hint);
|
||||||
|
|
||||||
// Hack to open keyboard.
|
openKeyboard(mPassphraseEditText);
|
||||||
// This is the only method that I found to work across all Android versions
|
|
||||||
// http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
|
|
||||||
// Notes: * onCreateView can't be used because we want to add buttons to the dialog
|
|
||||||
// * opening in onActivityCreated does not work on Android 4.4
|
|
||||||
mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onFocusChange(View v, boolean hasFocus) {
|
|
||||||
mPassphraseEditText.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (getActivity() == null || mPassphraseEditText == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
InputMethodManager imm = (InputMethodManager) getActivity()
|
|
||||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
||||||
imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mPassphraseEditText.requestFocus();
|
|
||||||
|
|
||||||
mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
|
mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
|
||||||
mPassphraseEditText.setOnEditorActionListener(this);
|
mPassphraseEditText.setOnEditorActionListener(this);
|
||||||
@@ -325,17 +303,62 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatic line break with max 6 lines for smaller displays
|
* Hack to open keyboard.
|
||||||
* <p/>
|
* This is the only method that I found to work across all Android versions
|
||||||
* NOTE: I was not able to get this behaviour using XML!
|
* http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
|
||||||
* Looks like the order of these method calls matter, see http://stackoverflow.com/a/11171307
|
* Notes:
|
||||||
|
* * onCreateView can't be used because we want to add buttons to the dialog
|
||||||
|
* * opening in onActivityCreated does not work on Android 4.4
|
||||||
*/
|
*/
|
||||||
private void setupAutomaticLinebreak(TextView textview) {
|
private void openKeyboard(final TextView textView) {
|
||||||
textview.setSingleLine(true);
|
textView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||||
textview.setMaxLines(6);
|
@Override
|
||||||
textview.setHorizontallyScrolling(false);
|
public void onFocusChange(View v, boolean hasFocus) {
|
||||||
|
textView.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (getActivity() == null || textView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InputMethodManager imm = (InputMethodManager) getActivity()
|
||||||
|
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
imm.showSoftInput(textView, InputMethodManager.SHOW_IMPLICIT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
textView.requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void setupEditTextFocusNext(final EditText[] backupCodes) {
|
||||||
|
for (int i = 0; i < backupCodes.length - 1; i++) {
|
||||||
|
|
||||||
|
final int next = i + 1;
|
||||||
|
|
||||||
|
backupCodes[i].addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
boolean inserting = before < count;
|
||||||
|
boolean cursorAtEnd = (start + count) == 4;
|
||||||
|
|
||||||
|
if (inserting && cursorAtEnd) {
|
||||||
|
backupCodes[next].requestFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
@@ -347,8 +370,17 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
|
||||||
if (mRequiredInput.mType == RequiredInputType.BACKUP_CODE) {
|
if (mRequiredInput.mType == RequiredInputType.BACKUP_CODE) {
|
||||||
Passphrase passphrase =
|
StringBuilder backupCodeInput = new StringBuilder(26);
|
||||||
new Passphrase(mBackupCodeEditText.getText().toString());
|
for (EditText editText : mBackupCodeEditText) {
|
||||||
|
if (editText.getText().length() < 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
backupCodeInput.append(editText.getText());
|
||||||
|
backupCodeInput.append('-');
|
||||||
|
}
|
||||||
|
backupCodeInput.deleteCharAt(backupCodeInput.length() - 1);
|
||||||
|
|
||||||
|
Passphrase passphrase = new Passphrase(backupCodeInput.toString());
|
||||||
finishCaching(passphrase);
|
finishCaching(passphrase);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.ui;
|
||||||
|
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||||
|
|
||||||
|
public class RedirectImportKeysActivity extends BaseActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
startQrCodeCaptureActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startQrCodeCaptureActivity() {
|
||||||
|
final Intent scanQrCode = new Intent(this, ImportKeysProxyActivity.class);
|
||||||
|
scanQrCode.setAction(ImportKeysProxyActivity.ACTION_QR_CODE_API);
|
||||||
|
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setTitle(R.string.redirect_import_key_title)
|
||||||
|
.setMessage(R.string.redirect_import_key_message)
|
||||||
|
.setPositiveButton(R.string.redirect_import_key_yes, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
// directly scan with OpenKeychain
|
||||||
|
startActivity(scanQrCode);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.redirect_import_key_no, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
// close window
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
+87
-93
@@ -3,6 +3,7 @@
|
|||||||
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||||
* Copyright (C) 2013-2014 Signe Rüsch
|
* Copyright (C) 2013-2014 Signe Rüsch
|
||||||
* Copyright (C) 2013-2014 Philipp Jakubeit
|
* Copyright (C) 2013-2014 Philipp Jakubeit
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -35,10 +36,12 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
|||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.securitytoken.KeyType;
|
||||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.OrientationUtils;
|
import org.sufficientlysecure.keychain.util.OrientationUtils;
|
||||||
@@ -55,7 +58,7 @@ import nordpol.android.NfcGuideView;
|
|||||||
* NFC devices.
|
* NFC devices.
|
||||||
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
|
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
|
||||||
*/
|
*/
|
||||||
public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity {
|
public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity {
|
||||||
|
|
||||||
public static final String EXTRA_REQUIRED_INPUT = "required_input";
|
public static final String EXTRA_REQUIRED_INPUT = "required_input";
|
||||||
public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
|
public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
|
||||||
@@ -69,8 +72,6 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
|
|||||||
|
|
||||||
private RequiredInputParcel mRequiredInput;
|
private RequiredInputParcel mRequiredInput;
|
||||||
|
|
||||||
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};
|
|
||||||
|
|
||||||
private CryptoInputParcel mInputParcel;
|
private CryptoInputParcel mInputParcel;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -137,9 +138,33 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
|
|||||||
|
|
||||||
private void obtainPassphraseIfRequired() {
|
private void obtainPassphraseIfRequired() {
|
||||||
// obtain passphrase for this subkey
|
// obtain passphrase for this subkey
|
||||||
if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD
|
if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD
|
||||||
&& mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_RESET_CARD) {
|
&& mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SECURITY_TOKEN_RESET_CARD) {
|
||||||
obtainSecurityTokenPin(mRequiredInput);
|
obtainSecurityTokenPin(mRequiredInput);
|
||||||
|
checkPinAvailability();
|
||||||
|
} else {
|
||||||
|
checkDeviceConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
if (REQUEST_CODE_PIN == requestCode) {
|
||||||
|
checkPinAvailability();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPinAvailability() {
|
||||||
|
try {
|
||||||
|
Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this,
|
||||||
|
mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId());
|
||||||
|
if (passphrase != null) {
|
||||||
|
checkDeviceConnection();
|
||||||
|
}
|
||||||
|
} catch (PassphraseCacheService.KeyNotFoundException e) {
|
||||||
|
throw new AssertionError(
|
||||||
|
"tried to find passphrase for non-existing key. this is a programming error!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,39 +174,53 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNfcPreExecute() {
|
public void onSecurityTokenPreExecute() {
|
||||||
// start with indeterminate progress
|
// start with indeterminate progress
|
||||||
vAnimator.setDisplayedChild(1);
|
vAnimator.setDisplayedChild(1);
|
||||||
nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.TRANSFERRING);
|
nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.TRANSFERRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doNfcInBackground() throws IOException {
|
protected void doSecurityTokenInBackground() throws IOException {
|
||||||
|
|
||||||
switch (mRequiredInput.mType) {
|
switch (mRequiredInput.mType) {
|
||||||
case NFC_DECRYPT: {
|
case SECURITY_TOKEN_DECRYPT: {
|
||||||
|
long tokenKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(
|
||||||
|
mSecurityTokenHelper.getKeyFingerprint(KeyType.ENCRYPT));
|
||||||
|
|
||||||
|
if (tokenKeyId != mRequiredInput.getSubKeyId()) {
|
||||||
|
throw new IOException(getString(R.string.error_wrong_security_token));
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < mRequiredInput.mInputData.length; i++) {
|
for (int i = 0; i < mRequiredInput.mInputData.length; i++) {
|
||||||
byte[] encryptedSessionKey = mRequiredInput.mInputData[i];
|
byte[] encryptedSessionKey = mRequiredInput.mInputData[i];
|
||||||
byte[] decryptedSessionKey = nfcDecryptSessionKey(encryptedSessionKey);
|
byte[] decryptedSessionKey = mSecurityTokenHelper.decryptSessionKey(encryptedSessionKey);
|
||||||
mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey);
|
mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NFC_SIGN: {
|
case SECURITY_TOKEN_SIGN: {
|
||||||
|
long tokenKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(
|
||||||
|
mSecurityTokenHelper.getKeyFingerprint(KeyType.SIGN));
|
||||||
|
|
||||||
|
if (tokenKeyId != mRequiredInput.getSubKeyId()) {
|
||||||
|
throw new IOException(getString(R.string.error_wrong_security_token));
|
||||||
|
}
|
||||||
|
|
||||||
mInputParcel.addSignatureTime(mRequiredInput.mSignatureTime);
|
mInputParcel.addSignatureTime(mRequiredInput.mSignatureTime);
|
||||||
|
|
||||||
for (int i = 0; i < mRequiredInput.mInputData.length; i++) {
|
for (int i = 0; i < mRequiredInput.mInputData.length; i++) {
|
||||||
byte[] hash = mRequiredInput.mInputData[i];
|
byte[] hash = mRequiredInput.mInputData[i];
|
||||||
int algo = mRequiredInput.mSignAlgos[i];
|
int algo = mRequiredInput.mSignAlgos[i];
|
||||||
byte[] signedHash = nfcCalculateSignature(hash, algo);
|
byte[] signedHash = mSecurityTokenHelper.calculateSignature(hash, algo);
|
||||||
mInputParcel.addCryptoData(hash, signedHash);
|
mInputParcel.addCryptoData(hash, signedHash);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NFC_MOVE_KEY_TO_CARD: {
|
case SECURITY_TOKEN_MOVE_KEY_TO_CARD: {
|
||||||
// TODO: assume PIN and Admin PIN to be default for this operation
|
// TODO: assume PIN and Admin PIN to be default for this operation
|
||||||
mPin = new Passphrase("123456");
|
mSecurityTokenHelper.setPin(new Passphrase("123456"));
|
||||||
mAdminPin = new Passphrase("12345678");
|
mSecurityTokenHelper.setAdminPin(new Passphrase("12345678"));
|
||||||
|
|
||||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||||
CanonicalizedSecretKeyRing secretKeyRing;
|
CanonicalizedSecretKeyRing secretKeyRing;
|
||||||
@@ -202,11 +241,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
|
|||||||
long subkeyId = buf.getLong();
|
long subkeyId = buf.getLong();
|
||||||
|
|
||||||
CanonicalizedSecretKey key = secretKeyRing.getSecretKey(subkeyId);
|
CanonicalizedSecretKey key = secretKeyRing.getSecretKey(subkeyId);
|
||||||
|
byte[] tokenSerialNumber = Arrays.copyOf(mSecurityTokenHelper.getAid(), 16);
|
||||||
long keyGenerationTimestampMillis = key.getCreationTime().getTime();
|
|
||||||
long keyGenerationTimestamp = keyGenerationTimestampMillis / 1000;
|
|
||||||
byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array();
|
|
||||||
byte[] tokenSerialNumber = Arrays.copyOf(nfcGetAid(), 16);
|
|
||||||
|
|
||||||
Passphrase passphrase;
|
Passphrase passphrase;
|
||||||
try {
|
try {
|
||||||
@@ -216,46 +251,20 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
|
|||||||
throw new IOException("Unable to get cached passphrase!");
|
throw new IOException("Unable to get cached passphrase!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.canSign() || key.canCertify()) {
|
mSecurityTokenHelper.changeKey(key, passphrase);
|
||||||
if (shouldPutKey(key.getFingerprint(), 0)) {
|
|
||||||
nfcPutKey(0xB6, key, passphrase);
|
|
||||||
nfcPutData(0xCE, timestampBytes);
|
|
||||||
nfcPutData(0xC7, key.getFingerprint());
|
|
||||||
} else {
|
|
||||||
throw new IOException("Key slot occupied; token 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; token 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; token must be reset to put new authentication key.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IOException("Inappropriate key flags for Security Token key.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Is this really used anywhere?
|
// TODO: Is this really used anywhere?
|
||||||
mInputParcel.addCryptoData(subkeyBytes, tokenSerialNumber);
|
mInputParcel.addCryptoData(subkeyBytes, tokenSerialNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
// change PINs afterwards
|
// change PINs afterwards
|
||||||
nfcModifyPIN(0x81, newPin);
|
mSecurityTokenHelper.modifyPin(0x81, newPin);
|
||||||
nfcModifyPIN(0x83, newAdminPin);
|
mSecurityTokenHelper.modifyPin(0x83, newAdminPin);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NFC_RESET_CARD: {
|
case SECURITY_TOKEN_RESET_CARD: {
|
||||||
nfcResetCard();
|
mSecurityTokenHelper.resetAndWipeToken();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -267,7 +276,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final void onNfcPostExecute() {
|
protected final void onSecurityTokenPostExecute() {
|
||||||
handleResult(mInputParcel);
|
handleResult(mInputParcel);
|
||||||
|
|
||||||
// show finish
|
// show finish
|
||||||
@@ -275,28 +284,33 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
|
|||||||
|
|
||||||
nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.DONE);
|
nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.DONE);
|
||||||
|
|
||||||
new AsyncTask<Void, Void, Void>() {
|
if (mSecurityTokenHelper.isPersistentConnectionAllowed()) {
|
||||||
@Override
|
// Just close
|
||||||
protected Void doInBackground(Void... params) {
|
finish();
|
||||||
// check all 200ms if Security Token has been taken away
|
} else {
|
||||||
while (true) {
|
new AsyncTask<Void, Void, Void>() {
|
||||||
if (isNfcConnected()) {
|
@Override
|
||||||
try {
|
protected Void doInBackground(Void... params) {
|
||||||
Thread.sleep(200);
|
// check all 200ms if Security Token has been taken away
|
||||||
} catch (InterruptedException ignored) {
|
while (true) {
|
||||||
|
if (isSecurityTokenConnected()) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(200);
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Void result) {
|
protected void onPostExecute(Void result) {
|
||||||
super.onPostExecute(result);
|
super.onPostExecute(result);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
}.execute();
|
}.execute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -311,7 +325,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onNfcError(String error) {
|
protected void onSecurityTokenError(String error) {
|
||||||
pauseTagHandling();
|
pauseTagHandling();
|
||||||
|
|
||||||
vErrorText.setText(error + "\n\n" + getString(R.string.security_token_nfc_try_again_text));
|
vErrorText.setText(error + "\n\n" + getString(R.string.security_token_nfc_try_again_text));
|
||||||
@@ -321,31 +335,11 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNfcPinError(String error) {
|
public void onSecurityTokenPinError(String error) {
|
||||||
onNfcError(error);
|
onSecurityTokenError(error);
|
||||||
|
|
||||||
// clear (invalid) passphrase
|
// clear (invalid) passphrase
|
||||||
PassphraseCacheService.clearCachedPassphrase(
|
PassphraseCacheService.clearCachedPassphrase(
|
||||||
this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId());
|
this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException {
|
|
||||||
byte[] tokenFingerprint = nfcGetMasterKeyFingerprint(idx);
|
|
||||||
|
|
||||||
// Note: special case: This should not happen, but happens with
|
|
||||||
// https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true
|
|
||||||
if (tokenFingerprint == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slot is empty, or contains this key already. PUT KEY operation is safe
|
|
||||||
if (Arrays.equals(tokenFingerprint, BLANK_FINGERPRINT) ||
|
|
||||||
Arrays.equals(tokenFingerprint, fingerprint)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slot already contains a different key; don't overwrite it.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-7
@@ -19,8 +19,6 @@
|
|||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.accounts.Account;
|
import android.accounts.Account;
|
||||||
import android.accounts.AccountManager;
|
import android.accounts.AccountManager;
|
||||||
@@ -49,6 +47,7 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.KeychainApplication;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity;
|
import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity;
|
||||||
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
|
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
|
||||||
@@ -59,6 +58,8 @@ import org.sufficientlysecure.keychain.util.Log;
|
|||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
|
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class SettingsActivity extends AppCompatPreferenceActivity {
|
public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||||
|
|
||||||
public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005;
|
public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005;
|
||||||
@@ -405,7 +406,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This fragment shows the keyserver/contacts sync preferences
|
* This fragment shows the keyserver/wifi-only-sync/contacts sync preferences
|
||||||
*/
|
*/
|
||||||
public static class SyncPrefsFragment extends PresetPreferenceFragment {
|
public static class SyncPrefsFragment extends PresetPreferenceFragment {
|
||||||
|
|
||||||
@@ -422,8 +423,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
super.onResume();
|
super.onResume();
|
||||||
// this needs to be done in onResume since the user can change sync values from Android
|
// this needs to be done in onResume since the user can change sync values from Android
|
||||||
// settings and we need to reflect that change when the user navigates back
|
// settings and we need to reflect that change when the user navigates back
|
||||||
AccountManager manager = AccountManager.get(getActivity());
|
final Account account = KeychainApplication.createAccountIfNecessary(getActivity());
|
||||||
final Account account = manager.getAccountsByType(Constants.ACCOUNT_TYPE)[0];
|
|
||||||
// for keyserver sync
|
// for keyserver sync
|
||||||
initializeSyncCheckBox(
|
initializeSyncCheckBox(
|
||||||
(SwitchPreference) findPreference(Constants.Pref.SYNC_KEYSERVER),
|
(SwitchPreference) findPreference(Constants.Pref.SYNC_KEYSERVER),
|
||||||
@@ -441,8 +441,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
private void initializeSyncCheckBox(final SwitchPreference syncCheckBox,
|
private void initializeSyncCheckBox(final SwitchPreference syncCheckBox,
|
||||||
final Account account,
|
final Account account,
|
||||||
final String authority) {
|
final String authority) {
|
||||||
boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority)
|
// account is null if it could not be created for some reason
|
||||||
&& checkContactsPermission(authority);
|
boolean syncEnabled =
|
||||||
|
account != null
|
||||||
|
&& ContentResolver.getSyncAutomatically(account, authority)
|
||||||
|
&& checkContactsPermission(authority);
|
||||||
syncCheckBox.setChecked(syncEnabled);
|
syncCheckBox.setChecked(syncEnabled);
|
||||||
setSummary(syncCheckBox, authority, syncEnabled);
|
setSummary(syncCheckBox, authority, syncEnabled);
|
||||||
|
|
||||||
@@ -464,6 +467,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (account == null) {
|
||||||
|
// if account could not be created for some reason,
|
||||||
|
// we can't have our sync
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// disable syncs
|
// disable syncs
|
||||||
ContentResolver.setSyncAutomatically(account, authority, false);
|
ContentResolver.setSyncAutomatically(account, authority, false);
|
||||||
// immediately delete any linked contacts
|
// immediately delete any linked contacts
|
||||||
|
|||||||
+4
-3
@@ -40,6 +40,7 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.AddEditKeyserverDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.AddEditKeyserverDialogFragment;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperAdapter;
|
import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperViewHolder;
|
import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperViewHolder;
|
||||||
import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperDragCallback;
|
import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperDragCallback;
|
||||||
@@ -312,19 +313,19 @@ public class SettingsKeyserverFragment extends Fragment implements RecyclerItemC
|
|||||||
public void showAsSelectedKeyserver() {
|
public void showAsSelectedKeyserver() {
|
||||||
isSelectedKeyserver = true;
|
isSelectedKeyserver = true;
|
||||||
selectedServerLabel.setVisibility(View.VISIBLE);
|
selectedServerLabel.setVisibility(View.VISIBLE);
|
||||||
outerLayout.setBackgroundColor(getResources().getColor(R.color.android_green_dark));
|
outerLayout.setBackgroundColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorPrimaryDark));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showAsUnselectedKeyserver() {
|
public void showAsUnselectedKeyserver() {
|
||||||
isSelectedKeyserver = false;
|
isSelectedKeyserver = false;
|
||||||
selectedServerLabel.setVisibility(View.GONE);
|
selectedServerLabel.setVisibility(View.GONE);
|
||||||
outerLayout.setBackgroundColor(Color.WHITE);
|
outerLayout.setBackgroundColor(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected() {
|
public void onItemSelected() {
|
||||||
selectedServerLabel.setVisibility(View.GONE);
|
selectedServerLabel.setVisibility(View.GONE);
|
||||||
itemView.setBackgroundColor(Color.LTGRAY);
|
itemView.setBackgroundColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorBrightToolbar));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+7
-14
@@ -31,10 +31,7 @@ import android.widget.Spinner;
|
|||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.operations.results.UploadResult;
|
import org.sufficientlysecure.keychain.operations.results.UploadResult;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
|
||||||
import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
|
import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||||
@@ -53,7 +50,6 @@ public class UploadKeyActivity extends BaseActivity
|
|||||||
|
|
||||||
// CryptoOperationHelper.Callback vars
|
// CryptoOperationHelper.Callback vars
|
||||||
private String mKeyserver;
|
private String mKeyserver;
|
||||||
private long mMasterKeyId;
|
|
||||||
private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper;
|
private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -63,6 +59,10 @@ public class UploadKeyActivity extends BaseActivity
|
|||||||
mUploadButton = findViewById(R.id.upload_key_action_upload);
|
mUploadButton = findViewById(R.id.upload_key_action_upload);
|
||||||
mKeyServerSpinner = (Spinner) findViewById(R.id.upload_key_keyserver);
|
mKeyServerSpinner = (Spinner) findViewById(R.id.upload_key_keyserver);
|
||||||
|
|
||||||
|
MultiUserIdsFragment mMultiUserIdsFragment = (MultiUserIdsFragment)
|
||||||
|
getSupportFragmentManager().findFragmentById(R.id.multi_user_ids_fragment);
|
||||||
|
mMultiUserIdsFragment.setCheckboxVisibility(false);
|
||||||
|
|
||||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
|
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
|
||||||
android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
|
android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
|
||||||
.getKeyServers()
|
.getKeyServers()
|
||||||
@@ -89,15 +89,6 @@ public class UploadKeyActivity extends BaseActivity
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
mMasterKeyId = new ProviderHelper(this).getCachedPublicKeyRing(
|
|
||||||
KeyRings.buildUnifiedKeyRingUri(mDataUri)).getMasterKeyId();
|
|
||||||
} catch (PgpKeyNotFoundException e) {
|
|
||||||
Log.e(Constants.TAG, "Intent data pointed to bad key!");
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -136,7 +127,9 @@ public class UploadKeyActivity extends BaseActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UploadKeyringParcel createOperationInput() {
|
public UploadKeyringParcel createOperationInput() {
|
||||||
return new UploadKeyringParcel(mKeyserver, mMasterKeyId);
|
long[] masterKeyIds = getIntent().getLongArrayExtra(MultiUserIdsFragment.EXTRA_KEY_IDS);
|
||||||
|
|
||||||
|
return new UploadKeyringParcel(mKeyserver, masterKeyIds[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.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.ui;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.hardware.usb.UsbDevice;
|
||||||
|
import android.hardware.usb.UsbManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
public class UsbEventReceiverActivity extends Activity {
|
||||||
|
public static final String ACTION_USB_PERMISSION =
|
||||||
|
"org.sufficientlysecure.keychain.ui.USB_PERMISSION";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
final UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
if (intent != null) {
|
||||||
|
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
|
||||||
|
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
|
||||||
|
|
||||||
|
Log.d(Constants.TAG, "Requesting permission for " + usbDevice.getDeviceName());
|
||||||
|
usbManager.requestPermission(usbDevice,
|
||||||
|
PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the activity
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
-30
@@ -32,6 +32,7 @@ import android.app.ActivityOptions;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.nfc.NfcAdapter;
|
import android.nfc.NfcAdapter;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
@@ -80,11 +81,11 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
|||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
||||||
|
import org.sufficientlysecure.keychain.service.ChangeUnlockParcel;
|
||||||
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.ViewKeyFragment.PostponeType;
|
import org.sufficientlysecure.keychain.ui.ViewKeyFragment.PostponeType;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||||
@@ -102,7 +103,7 @@ import org.sufficientlysecure.keychain.util.Passphrase;
|
|||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
|
|
||||||
|
|
||||||
public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
|
public class ViewKeyActivity extends BaseSecurityTokenActivity implements
|
||||||
LoaderManager.LoaderCallbacks<Cursor>,
|
LoaderManager.LoaderCallbacks<Cursor>,
|
||||||
CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
|
CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
|
||||||
|
|
||||||
@@ -129,8 +130,8 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
|
|||||||
private String mKeyserver;
|
private String mKeyserver;
|
||||||
private ArrayList<ParcelableKeyRing> mKeyList;
|
private ArrayList<ParcelableKeyRing> mKeyList;
|
||||||
private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mImportOpHelper;
|
private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mImportOpHelper;
|
||||||
private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mEditOpHelper;
|
private CryptoOperationHelper<ChangeUnlockParcel, EditKeyResult> mEditOpHelper;
|
||||||
private SaveKeyringParcel mSaveKeyringParcel;
|
private ChangeUnlockParcel mChangeUnlockParcel;
|
||||||
|
|
||||||
private TextView mStatusText;
|
private TextView mStatusText;
|
||||||
private ImageView mStatusImage;
|
private ImageView mStatusImage;
|
||||||
@@ -170,9 +171,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
|
|||||||
private byte[] mFingerprint;
|
private byte[] mFingerprint;
|
||||||
private String mFingerprintString;
|
private String mFingerprintString;
|
||||||
|
|
||||||
private byte[] mNfcFingerprints;
|
private byte[] mSecurityTokenFingerprints;
|
||||||
private String mNfcUserId;
|
private String mSecurityTokenUserId;
|
||||||
private byte[] mNfcAid;
|
private byte[] mSecurityTokenAid;
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
@SuppressLint("InflateParams")
|
||||||
@Override
|
@Override
|
||||||
@@ -428,13 +429,11 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void changePassword() {
|
private void changePassword() {
|
||||||
mSaveKeyringParcel = new SaveKeyringParcel(mMasterKeyId, mFingerprint);
|
CryptoOperationHelper.Callback<ChangeUnlockParcel, EditKeyResult> editKeyCallback
|
||||||
|
= new CryptoOperationHelper.Callback<ChangeUnlockParcel, EditKeyResult>() {
|
||||||
CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult> editKeyCallback
|
|
||||||
= new CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult>() {
|
|
||||||
@Override
|
@Override
|
||||||
public SaveKeyringParcel createOperationInput() {
|
public ChangeUnlockParcel createOperationInput() {
|
||||||
return mSaveKeyringParcel;
|
return mChangeUnlockParcel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -468,9 +467,10 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
|
|||||||
Bundle data = message.getData();
|
Bundle data = message.getData();
|
||||||
|
|
||||||
// use new passphrase!
|
// use new passphrase!
|
||||||
mSaveKeyringParcel.mNewUnlock = new SaveKeyringParcel.ChangeUnlockParcel(
|
mChangeUnlockParcel = new ChangeUnlockParcel(
|
||||||
(Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE),
|
mMasterKeyId,
|
||||||
null
|
mFingerprint,
|
||||||
|
(Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE)
|
||||||
);
|
);
|
||||||
|
|
||||||
mEditOpHelper.cryptoOperation();
|
mEditOpHelper.cryptoOperation();
|
||||||
@@ -646,17 +646,17 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doNfcInBackground() throws IOException {
|
protected void doSecurityTokenInBackground() throws IOException {
|
||||||
|
|
||||||
mNfcFingerprints = nfcGetFingerprints();
|
mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints();
|
||||||
mNfcUserId = nfcGetUserId();
|
mSecurityTokenUserId = mSecurityTokenHelper.getUserId();
|
||||||
mNfcAid = nfcGetAid();
|
mSecurityTokenAid = mSecurityTokenHelper.getAid();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onNfcPostExecute() {
|
protected void onSecurityTokenPostExecute() {
|
||||||
|
|
||||||
long tokenId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
|
long tokenId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@@ -667,7 +667,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
|
|||||||
|
|
||||||
// if the master key of that key matches this one, just show the token dialog
|
// if the master key of that key matches this one, just show the token dialog
|
||||||
if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprintString)) {
|
if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprintString)) {
|
||||||
showSecurityTokenFragment(mNfcFingerprints, mNfcUserId, mNfcAid);
|
showSecurityTokenFragment(mSecurityTokenFingerprints, mSecurityTokenUserId, mSecurityTokenAid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,9 +680,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
|
|||||||
Intent intent = new Intent(
|
Intent intent = new Intent(
|
||||||
ViewKeyActivity.this, ViewKeyActivity.class);
|
ViewKeyActivity.this, ViewKeyActivity.class);
|
||||||
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid);
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
||||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId);
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
|
||||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mNfcFingerprints);
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
@@ -695,9 +695,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
|
|||||||
public void onAction() {
|
public void onAction() {
|
||||||
Intent intent = new Intent(
|
Intent intent = new Intent(
|
||||||
ViewKeyActivity.this, CreateKeyActivity.class);
|
ViewKeyActivity.this, CreateKeyActivity.class);
|
||||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid);
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
||||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId);
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
|
||||||
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mNfcFingerprints);
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
@@ -924,6 +924,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
mPhoto.setImageBitmap(photo);
|
mPhoto.setImageBitmap(photo);
|
||||||
|
mPhoto.setColorFilter(getResources().getColor(R.color.toolbar_photo_tint), PorterDuff.Mode.SRC_ATOP);
|
||||||
mPhotoLayout.setVisibility(View.VISIBLE);
|
mPhotoLayout.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+12
-1
@@ -238,7 +238,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
|
|||||||
|
|
||||||
// let user choose application
|
// let user choose application
|
||||||
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
||||||
sendIntent.setType("text/plain");
|
sendIntent.setType(Constants.MIME_TYPE_KEYS);
|
||||||
|
|
||||||
// NOTE: Don't use Intent.EXTRA_TEXT to send the key
|
// NOTE: Don't use Intent.EXTRA_TEXT to send the key
|
||||||
// better send it via a Uri!
|
// better send it via a Uri!
|
||||||
@@ -455,8 +455,19 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void uploadToKeyserver() {
|
private void uploadToKeyserver() {
|
||||||
|
long keyId;
|
||||||
|
try {
|
||||||
|
keyId = new ProviderHelper(getActivity())
|
||||||
|
.getCachedPublicKeyRing(mDataUri)
|
||||||
|
.extractOrGetMasterKeyId();
|
||||||
|
} catch (PgpKeyNotFoundException e) {
|
||||||
|
Log.e(Constants.TAG, "key not found!", e);
|
||||||
|
Notify.create(getActivity(), "key not found", Style.ERROR).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
Intent uploadIntent = new Intent(getActivity(), UploadKeyActivity.class);
|
Intent uploadIntent = new Intent(getActivity(), UploadKeyActivity.class);
|
||||||
uploadIntent.setData(mDataUri);
|
uploadIntent.setData(mDataUri);
|
||||||
|
uploadIntent.putExtra(MultiUserIdsFragment.EXTRA_KEY_IDS, new long[]{keyId});
|
||||||
startActivityForResult(uploadIntent, 0);
|
startActivityForResult(uploadIntent, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+39
-43
@@ -39,6 +39,7 @@ import android.widget.AdapterView;
|
|||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.ViewAnimator;
|
import android.widget.ViewAnimator;
|
||||||
|
|
||||||
|
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||||
@@ -346,50 +347,45 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_CARD: {
|
case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_SECURITY_TOKEN: {
|
||||||
// TODO: enable later when Admin PIN handling is resolved
|
SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position);
|
||||||
Notify.create(getActivity(),
|
if (secretKeyType == SecretKeyType.DIVERT_TO_CARD ||
|
||||||
"This feature will be available in an upcoming OpenKeychain version.",
|
secretKeyType == SecretKeyType.GNU_DUMMY) {
|
||||||
Notify.Style.WARN).show();
|
Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_stripped, Notify.Style.ERROR)
|
||||||
break;
|
.show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Activity activity = EditKeyFragment.this.getActivity();
|
int algorithm = mSubkeysAdapter.getAlgorithm(position);
|
||||||
// SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position);
|
if (algorithm != PublicKeyAlgorithmTags.RSA_GENERAL
|
||||||
// if (secretKeyType == SecretKeyType.DIVERT_TO_CARD ||
|
&& algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT
|
||||||
// secretKeyType == SecretKeyType.GNU_DUMMY) {
|
&& algorithm != PublicKeyAlgorithmTags.RSA_SIGN) {
|
||||||
// Notify.create(activity, R.string.edit_key_error_bad_nfc_stripped, Notify.Style.ERROR)
|
Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR)
|
||||||
// .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
|
.show();
|
||||||
// break;
|
break;
|
||||||
// }
|
}
|
||||||
// int algorithm = mSubkeysAdapter.getAlgorithm(position);
|
|
||||||
// // these are the PGP constants for RSA_GENERAL, RSA_ENCRYPT and RSA_SIGN
|
if (mSubkeysAdapter.getKeySize(position) != 2048) {
|
||||||
// if (algorithm != 1 && algorithm != 2 && algorithm != 3) {
|
Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR)
|
||||||
// Notify.create(activity, R.string.edit_key_error_bad_nfc_algo, Notify.Style.ERROR)
|
.show();
|
||||||
// .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
|
break;
|
||||||
// break;
|
}
|
||||||
// }
|
|
||||||
// if (mSubkeysAdapter.getKeySize(position) != 2048) {
|
SubkeyChange change;
|
||||||
// Notify.create(activity, R.string.edit_key_error_bad_nfc_size, Notify.Style.ERROR)
|
change = mEditModeSaveKeyringParcel.getSubkeyChange(keyId);
|
||||||
// .show((ViewGroup) activity.findViewById(R.id.import_snackbar));
|
if (change == null) {
|
||||||
// break;
|
mEditModeSaveKeyringParcel.mChangeSubKeys.add(
|
||||||
// }
|
new SubkeyChange(keyId, false, true)
|
||||||
//
|
);
|
||||||
//
|
break;
|
||||||
// SubkeyChange change;
|
}
|
||||||
// change = mSaveKeyringParcel.getSubkeyChange(keyId);
|
// toggle
|
||||||
// if (change == null) {
|
change.mMoveKeyToSecurityToken = !change.mMoveKeyToSecurityToken;
|
||||||
// mSaveKeyringParcel.mChangeSubKeys.add(
|
if (change.mMoveKeyToSecurityToken && change.mDummyStrip) {
|
||||||
// new SubkeyChange(keyId, false, true)
|
// User had chosen to strip key, but now wants to divert it.
|
||||||
// );
|
change.mDummyStrip = false;
|
||||||
// break;
|
}
|
||||||
// }
|
break;
|
||||||
// // toggle
|
|
||||||
// change.mMoveKeyToSecurityToken = !change.mMoveKeyToSecurityToken;
|
|
||||||
// if (change.mMoveKeyToSecurityToken && change.mDummyStrip) {
|
|
||||||
// // User had chosen to strip key, but now wants to divert it.
|
|
||||||
// change.mDummyStrip = false;
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
|
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
|
||||||
|
|||||||
+1
@@ -141,6 +141,7 @@ public class ImportKeysListLoader
|
|||||||
OperationResult.OperationLog log = new OperationResult.OperationLog();
|
OperationResult.OperationLog log = new OperationResult.OperationLog();
|
||||||
log.add(OperationResult.LogType.MSG_GET_NO_VALID_KEYS, 0);
|
log.add(OperationResult.LogType.MSG_GET_NO_VALID_KEYS, 0);
|
||||||
GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_ERROR_NO_VALID_KEYS, log);
|
GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_ERROR_NO_VALID_KEYS, log);
|
||||||
|
mData.clear();
|
||||||
mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult);
|
mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-8
@@ -66,6 +66,7 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
KeyRings.HAS_DUPLICATE_USER_ID,
|
KeyRings.HAS_DUPLICATE_USER_ID,
|
||||||
KeyRings.FINGERPRINT,
|
KeyRings.FINGERPRINT,
|
||||||
KeyRings.CREATION,
|
KeyRings.CREATION,
|
||||||
|
KeyRings.HAS_ENCRYPT
|
||||||
};
|
};
|
||||||
|
|
||||||
public static final int INDEX_MASTER_KEY_ID = 1;
|
public static final int INDEX_MASTER_KEY_ID = 1;
|
||||||
@@ -77,6 +78,7 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
public static final int INDEX_HAS_DUPLICATE_USER_ID = 7;
|
public static final int INDEX_HAS_DUPLICATE_USER_ID = 7;
|
||||||
public static final int INDEX_FINGERPRINT = 8;
|
public static final int INDEX_FINGERPRINT = 8;
|
||||||
public static final int INDEX_CREATION = 9;
|
public static final int INDEX_CREATION = 9;
|
||||||
|
public static final int INDEX_HAS_ENCRYPT = 10;
|
||||||
|
|
||||||
public KeyAdapter(Context context, Cursor c, int flags) {
|
public KeyAdapter(Context context, Cursor c, int flags) {
|
||||||
super(context, c, flags);
|
super(context, c, flags);
|
||||||
@@ -289,6 +291,7 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
public final KeyRing.UserId mUserId;
|
public final KeyRing.UserId mUserId;
|
||||||
public final long mKeyId;
|
public final long mKeyId;
|
||||||
public final boolean mHasDuplicate;
|
public final boolean mHasDuplicate;
|
||||||
|
public final boolean mHasEncrypt;
|
||||||
public final Date mCreation;
|
public final Date mCreation;
|
||||||
public final String mFingerprint;
|
public final String mFingerprint;
|
||||||
public final boolean mIsSecret, mIsRevoked, mIsExpired, mIsVerified;
|
public final boolean mIsSecret, mIsRevoked, mIsExpired, mIsVerified;
|
||||||
@@ -299,6 +302,7 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
mUserIdFull = userId;
|
mUserIdFull = userId;
|
||||||
mKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
|
mKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
|
||||||
mHasDuplicate = cursor.getLong(INDEX_HAS_DUPLICATE_USER_ID) > 0;
|
mHasDuplicate = cursor.getLong(INDEX_HAS_DUPLICATE_USER_ID) > 0;
|
||||||
|
mHasEncrypt = cursor.getInt(INDEX_HAS_ENCRYPT) != 0;
|
||||||
mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000);
|
mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000);
|
||||||
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
|
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
|
||||||
cursor.getBlob(INDEX_FINGERPRINT));
|
cursor.getBlob(INDEX_FINGERPRINT));
|
||||||
@@ -315,6 +319,7 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
mUserIdFull = userId;
|
mUserIdFull = userId;
|
||||||
mKeyId = ring.getMasterKeyId();
|
mKeyId = ring.getMasterKeyId();
|
||||||
mHasDuplicate = false;
|
mHasDuplicate = false;
|
||||||
|
mHasEncrypt = key.getKeyRing().getEncryptIds().size() > 0;
|
||||||
mCreation = key.getCreationTime();
|
mCreation = key.getCreationTime();
|
||||||
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
|
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
|
||||||
ring.getFingerprint());
|
ring.getFingerprint());
|
||||||
@@ -333,14 +338,6 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
return mUserId.email;
|
return mUserId.email;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: workaround for bug in TokenAutoComplete,
|
|
||||||
// see https://github.com/open-keychain/open-keychain/issues/1636
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return " ";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String[] getProjectionWith(String[] projection) {
|
public static String[] getProjectionWith(String[] projection) {
|
||||||
|
|||||||
+7
@@ -39,6 +39,7 @@ import java.util.ArrayList;
|
|||||||
public class MultiUserIdsAdapter extends CursorAdapter {
|
public class MultiUserIdsAdapter extends CursorAdapter {
|
||||||
private LayoutInflater mInflater;
|
private LayoutInflater mInflater;
|
||||||
private final ArrayList<Boolean> mCheckStates;
|
private final ArrayList<Boolean> mCheckStates;
|
||||||
|
private boolean checkboxVisibility = true;
|
||||||
|
|
||||||
public MultiUserIdsAdapter(Context context, Cursor c, int flags, ArrayList<Boolean> preselectStates) {
|
public MultiUserIdsAdapter(Context context, Cursor c, int flags, ArrayList<Boolean> preselectStates) {
|
||||||
super(context, c, flags);
|
super(context, c, flags);
|
||||||
@@ -46,6 +47,11 @@ public class MultiUserIdsAdapter extends CursorAdapter {
|
|||||||
mCheckStates = preselectStates == null ? new ArrayList<Boolean>() : preselectStates;
|
mCheckStates = preselectStates == null ? new ArrayList<Boolean>() : preselectStates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MultiUserIdsAdapter(Context context, Cursor c, int flags, ArrayList<Boolean> preselectStates, boolean checkboxVisibility) {
|
||||||
|
this(context,c,flags,preselectStates);
|
||||||
|
this.checkboxVisibility = checkboxVisibility;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor swapCursor(Cursor newCursor) {
|
public Cursor swapCursor(Cursor newCursor) {
|
||||||
if (newCursor != null) {
|
if (newCursor != null) {
|
||||||
@@ -138,6 +144,7 @@ public class MultiUserIdsAdapter extends CursorAdapter {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
vCheckBox.setClickable(false);
|
vCheckBox.setClickable(false);
|
||||||
|
vCheckBox.setVisibility(checkboxVisibility?View.VISIBLE:View.GONE);
|
||||||
|
|
||||||
View vUidBody = view.findViewById(R.id.user_id_body);
|
View vUidBody = view.findViewById(R.id.user_id_body);
|
||||||
vUidBody.setClickable(true);
|
vUidBody.setClickable(true);
|
||||||
|
|||||||
+38
-10
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui.adapter;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.support.v4.app.FragmentActivity;
|
||||||
import android.text.format.DateFormat;
|
import android.text.format.DateFormat;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -32,6 +33,7 @@ import android.widget.TextView;
|
|||||||
import org.bouncycastle.bcpg.sig.KeyFlags;
|
import org.bouncycastle.bcpg.sig.KeyFlags;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||||
|
import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
@@ -65,10 +67,11 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
|
|||||||
public SaveKeyringParcel.SubkeyAdd mModel;
|
public SaveKeyringParcel.SubkeyAdd mModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public View getView(final int position, View convertView, ViewGroup parent) {
|
public View getView(final int position, View convertView, ViewGroup parent) {
|
||||||
if (convertView == null) {
|
if (convertView == null) {
|
||||||
// Not recycled, inflate a new view
|
// Not recycled, inflate a new view
|
||||||
convertView = mInflater.inflate(R.layout.view_key_adv_subkey_item, null);
|
convertView = mInflater.inflate(R.layout.view_key_adv_subkey_item, parent, false);
|
||||||
final ViewHolder holder = new ViewHolder();
|
final ViewHolder holder = new ViewHolder();
|
||||||
holder.vKeyId = (TextView) convertView.findViewById(R.id.subkey_item_key_id);
|
holder.vKeyId = (TextView) convertView.findViewById(R.id.subkey_item_key_id);
|
||||||
holder.vKeyDetails = (TextView) convertView.findViewById(R.id.subkey_item_details);
|
holder.vKeyDetails = (TextView) convertView.findViewById(R.id.subkey_item_details);
|
||||||
@@ -88,16 +91,8 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
|
|||||||
vStatus.setVisibility(View.GONE);
|
vStatus.setVisibility(View.GONE);
|
||||||
|
|
||||||
convertView.setTag(holder);
|
convertView.setTag(holder);
|
||||||
|
|
||||||
holder.vDelete.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
// remove reference model item from adapter (data and notify about change)
|
|
||||||
SubkeysAddedAdapter.this.remove(holder.mModel);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final ViewHolder holder = (ViewHolder) convertView.getTag();
|
final ViewHolder holder = (ViewHolder) convertView.getTag();
|
||||||
|
|
||||||
// save reference to model item
|
// save reference to model item
|
||||||
@@ -113,8 +108,41 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
|
|||||||
boolean isMasterKey = mNewKeyring && position == 0;
|
boolean isMasterKey = mNewKeyring && position == 0;
|
||||||
if (isMasterKey) {
|
if (isMasterKey) {
|
||||||
holder.vKeyId.setTypeface(null, Typeface.BOLD);
|
holder.vKeyId.setTypeface(null, Typeface.BOLD);
|
||||||
|
holder.vDelete.setImageResource(R.drawable.ic_change_grey_24dp);
|
||||||
|
holder.vDelete.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
// swapping out the old master key with newly set master key
|
||||||
|
AddSubkeyDialogFragment addSubkeyDialogFragment =
|
||||||
|
AddSubkeyDialogFragment.newInstance(true);
|
||||||
|
addSubkeyDialogFragment
|
||||||
|
.setOnAlgorithmSelectedListener(
|
||||||
|
new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey) {
|
||||||
|
// calculate manually as the provided position variable
|
||||||
|
// is not always accurate
|
||||||
|
int pos = SubkeysAddedAdapter.this.getPosition(holder.mModel);
|
||||||
|
SubkeysAddedAdapter.this.remove(holder.mModel);
|
||||||
|
SubkeysAddedAdapter.this.insert(newSubkey, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
addSubkeyDialogFragment.show(
|
||||||
|
((FragmentActivity)mActivity).getSupportFragmentManager()
|
||||||
|
, "addSubkeyDialog");
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
holder.vKeyId.setTypeface(null, Typeface.NORMAL);
|
holder.vKeyId.setTypeface(null, Typeface.NORMAL);
|
||||||
|
holder.vDelete.setImageResource(R.drawable.ic_close_grey_24dp);
|
||||||
|
holder.vDelete.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
// remove reference model item from adapter (data and notify about change)
|
||||||
|
SubkeysAddedAdapter.this.remove(holder.mModel);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.vKeyId.setText(R.string.edit_key_new_subkey);
|
holder.vKeyId.setText(R.string.edit_key_new_subkey);
|
||||||
|
|||||||
+12
-1
@@ -26,6 +26,7 @@ import android.support.v7.app.AppCompatActivity;
|
|||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -45,8 +46,8 @@ public abstract class BaseActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
initTheme();
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
initTheme();
|
||||||
initLayout();
|
initLayout();
|
||||||
initToolbar();
|
initToolbar();
|
||||||
}
|
}
|
||||||
@@ -65,6 +66,16 @@ public abstract class BaseActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home :
|
||||||
|
onBackPressed();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
public static void onResumeChecks(Context context) {
|
public static void onResumeChecks(Context context) {
|
||||||
KeyserverSyncAdapterService.cancelUpdates(context);
|
KeyserverSyncAdapterService.cancelUpdates(context);
|
||||||
// in case user has disabled sync from Android account settings
|
// in case user has disabled sync from Android account settings
|
||||||
|
|||||||
+513
@@ -0,0 +1,513 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||||
|
* Copyright (C) 2013-2014 Signe Rüsch
|
||||||
|
* Copyright (C) 2013-2014 Philipp Jakubeit
|
||||||
|
* Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.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.ui.base;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.hardware.usb.UsbDevice;
|
||||||
|
import android.hardware.usb.UsbManager;
|
||||||
|
import android.nfc.NfcAdapter;
|
||||||
|
import android.nfc.Tag;
|
||||||
|
import android.nfc.TagLostException;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||||
|
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||||
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||||
|
import org.sufficientlysecure.keychain.securitytoken.CardException;
|
||||||
|
import org.sufficientlysecure.keychain.securitytoken.NfcTransport;
|
||||||
|
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper;
|
||||||
|
import org.sufficientlysecure.keychain.securitytoken.Transport;
|
||||||
|
import org.sufficientlysecure.keychain.util.UsbConnectionDispatcher;
|
||||||
|
import org.sufficientlysecure.keychain.securitytoken.UsbTransport;
|
||||||
|
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
||||||
|
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
||||||
|
import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
|
||||||
|
import org.sufficientlysecure.keychain.ui.dialog.FidesmoInstallDialog;
|
||||||
|
import org.sufficientlysecure.keychain.ui.dialog.FidesmoPgpInstallDialog;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import nordpol.android.OnDiscoveredTagListener;
|
||||||
|
import nordpol.android.TagDispatcher;
|
||||||
|
|
||||||
|
public abstract class BaseSecurityTokenActivity extends BaseActivity
|
||||||
|
implements OnDiscoveredTagListener, UsbConnectionDispatcher.OnDiscoveredUsbDeviceListener {
|
||||||
|
public static final int REQUEST_CODE_PIN = 1;
|
||||||
|
|
||||||
|
public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled";
|
||||||
|
|
||||||
|
private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android";
|
||||||
|
|
||||||
|
protected SecurityTokenHelper mSecurityTokenHelper = SecurityTokenHelper.getInstance();
|
||||||
|
protected TagDispatcher mNfcTagDispatcher;
|
||||||
|
protected UsbConnectionDispatcher mUsbDispatcher;
|
||||||
|
private boolean mTagHandlingEnabled;
|
||||||
|
|
||||||
|
private byte[] mSecurityTokenFingerprints;
|
||||||
|
private String mSecurityTokenUserId;
|
||||||
|
private byte[] mSecurityTokenAid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to change UI before SecurityToken handling (UI thread)
|
||||||
|
*/
|
||||||
|
protected void onSecurityTokenPreExecute() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to implement SecurityToken operations (background thread)
|
||||||
|
*/
|
||||||
|
protected void doSecurityTokenInBackground() throws IOException {
|
||||||
|
mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints();
|
||||||
|
mSecurityTokenUserId = mSecurityTokenHelper.getUserId();
|
||||||
|
mSecurityTokenAid = mSecurityTokenHelper.getAid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to handle result of SecurityToken operations (UI thread)
|
||||||
|
*/
|
||||||
|
protected void onSecurityTokenPostExecute() {
|
||||||
|
|
||||||
|
final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints);
|
||||||
|
|
||||||
|
try {
|
||||||
|
CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(
|
||||||
|
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId));
|
||||||
|
long masterKeyId = ring.getMasterKeyId();
|
||||||
|
|
||||||
|
Intent intent = new Intent(this, ViewKeyActivity.class);
|
||||||
|
intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
|
||||||
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
||||||
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
|
||||||
|
intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints);
|
||||||
|
startActivity(intent);
|
||||||
|
} catch (PgpKeyNotFoundException e) {
|
||||||
|
Intent intent = new Intent(this, CreateKeyActivity.class);
|
||||||
|
intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid);
|
||||||
|
intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId);
|
||||||
|
intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_FINGERPRINTS, mSecurityTokenFingerprints);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to use something different than Notify (UI thread)
|
||||||
|
*/
|
||||||
|
protected void onSecurityTokenError(String error) {
|
||||||
|
Notify.create(this, error, Style.WARN).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to do something when PIN is wrong, e.g., clear passphrases (UI thread)
|
||||||
|
*/
|
||||||
|
protected void onSecurityTokenPinError(String error) {
|
||||||
|
onSecurityTokenError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tagDiscovered(final Tag tag) {
|
||||||
|
// Actual NFC operations are executed in doInBackground to not block the UI thread
|
||||||
|
if (!mTagHandlingEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
securityTokenDiscovered(new NfcTransport(tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void usbDeviceDiscovered(final UsbDevice usbDevice) {
|
||||||
|
// Actual USB operations are executed in doInBackground to not block the UI thread
|
||||||
|
if (!mTagHandlingEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
|
||||||
|
securityTokenDiscovered(new UsbTransport(usbDevice, usbManager));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void securityTokenDiscovered(final Transport transport) {
|
||||||
|
// Actual Security Token operations are executed in doInBackground to not block the UI thread
|
||||||
|
if (!mTagHandlingEnabled)
|
||||||
|
return;
|
||||||
|
new AsyncTask<Void, Void, IOException>() {
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
super.onPreExecute();
|
||||||
|
onSecurityTokenPreExecute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IOException doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
handleSecurityToken(transport);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(IOException exception) {
|
||||||
|
super.onPostExecute(exception);
|
||||||
|
|
||||||
|
if (exception != null) {
|
||||||
|
handleSecurityTokenError(exception);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSecurityTokenPostExecute();
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void pauseTagHandling() {
|
||||||
|
mTagHandlingEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void resumeTagHandling() {
|
||||||
|
mTagHandlingEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
mNfcTagDispatcher = TagDispatcher.get(this, this, false, false, true, false);
|
||||||
|
mUsbDispatcher = new UsbConnectionDispatcher(this, this);
|
||||||
|
|
||||||
|
// Check whether we're recreating a previously destroyed instance
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
// Restore value of members from saved state
|
||||||
|
mTagHandlingEnabled = savedInstanceState.getBoolean(EXTRA_TAG_HANDLING_ENABLED);
|
||||||
|
} else {
|
||||||
|
mTagHandlingEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
String action = intent.getAction();
|
||||||
|
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
|
||||||
|
throw new AssertionError("should not happen: NfcOperationActivity.onCreate is called instead of onNewIntent!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
|
||||||
|
outState.putBoolean(EXTRA_TAG_HANDLING_ENABLED, mTagHandlingEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This activity is started as a singleTop activity.
|
||||||
|
* All new NFC Intents which are delivered to this activity are handled here
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onNewIntent(final Intent intent) {
|
||||||
|
mNfcTagDispatcher.interceptIntent(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSecurityTokenError(IOException e) {
|
||||||
|
|
||||||
|
if (e instanceof TagLostException) {
|
||||||
|
onSecurityTokenError(getString(R.string.security_token_error_tag_lost));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e instanceof IsoDepNotSupportedException) {
|
||||||
|
onSecurityTokenError(getString(R.string.security_token_error_iso_dep_not_supported));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
short status;
|
||||||
|
if (e instanceof CardException) {
|
||||||
|
status = ((CardException) e).getResponseCode();
|
||||||
|
} else {
|
||||||
|
status = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrong PIN, a status of 63CX indicates X attempts remaining.
|
||||||
|
// NOTE: Used in ykneo-openpgp version < 1.0.10, changed to 0x6982 in 1.0.11
|
||||||
|
// https://github.com/Yubico/ykneo-openpgp/commit/90c2b91e86fb0e43ee234dd258834e75e3416410
|
||||||
|
if ((status & (short) 0xFFF0) == 0x63C0) {
|
||||||
|
int tries = status & 0x000F;
|
||||||
|
// hook to do something different when PIN is wrong
|
||||||
|
onSecurityTokenPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, all status codes are fixed values.
|
||||||
|
switch (status) {
|
||||||
|
|
||||||
|
// These error conditions are likely to be experienced by an end user.
|
||||||
|
|
||||||
|
/* OpenPGP Card Spec: Security status not satisfied, PW wrong,
|
||||||
|
PW not checked (command not allowed), Secure messaging incorrect (checksum and/or cryptogram) */
|
||||||
|
// NOTE: Used in ykneo-openpgp >= 1.0.11 for wrong PIN
|
||||||
|
case 0x6982: {
|
||||||
|
// hook to do something different when PIN is wrong
|
||||||
|
onSecurityTokenPinError(getString(R.string.security_token_error_security_not_satisfied));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* OpenPGP Card Spec: Selected file in termination state */
|
||||||
|
case 0x6285: {
|
||||||
|
onSecurityTokenError(getString(R.string.security_token_error_terminated));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* OpenPGP Card Spec: Wrong length (Lc and/or Le) */
|
||||||
|
// NOTE: Used in ykneo-openpgp < 1.0.10 for too short PIN, changed in 1.0.11 to 0x6A80 for too short PIN
|
||||||
|
// https://github.com/Yubico/ykneo-openpgp/commit/b49ce8241917e7c087a4dab7b2c755420ff4500f
|
||||||
|
case 0x6700: {
|
||||||
|
// hook to do something different when PIN is wrong
|
||||||
|
onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* OpenPGP Card Spec: Incorrect parameters in the data field */
|
||||||
|
// NOTE: Used in ykneo-openpgp >= 1.0.11 for too short PIN
|
||||||
|
case 0x6A80: {
|
||||||
|
// hook to do something different when PIN is wrong
|
||||||
|
onSecurityTokenPinError(getString(R.string.security_token_error_bad_data));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* OpenPGP Card Spec: Authentication method blocked, PW blocked (error counter zero) */
|
||||||
|
case 0x6983: {
|
||||||
|
onSecurityTokenError(getString(R.string.security_token_error_authentication_blocked));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* OpenPGP Card Spec: Condition of use not satisfied */
|
||||||
|
case 0x6985: {
|
||||||
|
onSecurityTokenError(getString(R.string.security_token_error_conditions_not_satisfied));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* OpenPGP Card Spec: SM data objects incorrect (e.g. wrong TLV-structure in command data) */
|
||||||
|
// NOTE: 6A88 is "Not Found" in the spec, but ykneo-openpgp also returns 6A83 for this in some cases.
|
||||||
|
case 0x6A88:
|
||||||
|
case 0x6A83: {
|
||||||
|
onSecurityTokenError(getString(R.string.security_token_error_data_not_found));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an
|
||||||
|
// unhandled exception on the security token.
|
||||||
|
case 0x6F00: {
|
||||||
|
onSecurityTokenError(getString(R.string.security_token_error_unknown));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 6A82 app not installed on security token!
|
||||||
|
case 0x6A82: {
|
||||||
|
if (mSecurityTokenHelper.isFidesmoToken()) {
|
||||||
|
// Check if the Fidesmo app is installed
|
||||||
|
if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) {
|
||||||
|
promptFidesmoPgpInstall();
|
||||||
|
} else {
|
||||||
|
promptFidesmoAppInstall();
|
||||||
|
}
|
||||||
|
} else { // Other (possibly) compatible hardware
|
||||||
|
onSecurityTokenError(getString(R.string.security_token_error_pgp_app_not_installed));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These errors should not occur in everyday use; if they are returned, it means we
|
||||||
|
// made a mistake sending data to the token, or the token is misbehaving.
|
||||||
|
|
||||||
|
/* OpenPGP Card Spec: Last command of the chain expected */
|
||||||
|
case 0x6883: {
|
||||||
|
onSecurityTokenError(getString(R.string.security_token_error_chaining_error));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* OpenPGP Card Spec: Wrong parameters P1-P2 */
|
||||||
|
case 0x6B00: {
|
||||||
|
onSecurityTokenError(getString(R.string.security_token_error_header, "P1/P2"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* OpenPGP Card Spec: Instruction (INS) not supported */
|
||||||
|
case 0x6D00: {
|
||||||
|
onSecurityTokenError(getString(R.string.security_token_error_header, "INS"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* OpenPGP Card Spec: Class (CLA) not supported */
|
||||||
|
case 0x6E00: {
|
||||||
|
onSecurityTokenError(getString(R.string.security_token_error_header, "CLA"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
onSecurityTokenError(getString(R.string.security_token_error, e.getMessage()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the system is about to start resuming a previous activity,
|
||||||
|
* disables NFC Foreground Dispatch
|
||||||
|
*/
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
Log.d(Constants.TAG, "BaseNfcActivity.onPause");
|
||||||
|
|
||||||
|
mNfcTagDispatcher.disableExclusiveNfc();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the activity will start interacting with the user,
|
||||||
|
* enables NFC Foreground Dispatch
|
||||||
|
*/
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
Log.d(Constants.TAG, "BaseNfcActivity.onResume");
|
||||||
|
mNfcTagDispatcher.enableExclusiveNfc();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void obtainSecurityTokenPin(RequiredInputParcel requiredInput) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this,
|
||||||
|
requiredInput.getMasterKeyId(), requiredInput.getSubKeyId());
|
||||||
|
if (passphrase != null) {
|
||||||
|
mSecurityTokenHelper.setPin(passphrase);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent intent = new Intent(this, PassphraseDialogActivity.class);
|
||||||
|
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT,
|
||||||
|
RequiredInputParcel.createRequiredPassphrase(requiredInput));
|
||||||
|
startActivityForResult(intent, REQUEST_CODE_PIN);
|
||||||
|
} catch (PassphraseCacheService.KeyNotFoundException e) {
|
||||||
|
throw new AssertionError(
|
||||||
|
"tried to find passphrase for non-existing key. this is a programming error!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
switch (requestCode) {
|
||||||
|
case REQUEST_CODE_PIN: {
|
||||||
|
if (resultCode != Activity.RESULT_OK) {
|
||||||
|
setResult(resultCode);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
|
||||||
|
mSecurityTokenHelper.setPin(input.getPassphrase());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void handleSecurityToken(Transport transport) throws IOException {
|
||||||
|
// Don't reconnect if device was already connected
|
||||||
|
if (!(mSecurityTokenHelper.isPersistentConnectionAllowed()
|
||||||
|
&& mSecurityTokenHelper.isConnected()
|
||||||
|
&& mSecurityTokenHelper.getTransport().equals(transport))) {
|
||||||
|
mSecurityTokenHelper.setTransport(transport);
|
||||||
|
mSecurityTokenHelper.connectToDevice();
|
||||||
|
}
|
||||||
|
doSecurityTokenInBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSecurityTokenConnected() {
|
||||||
|
return mSecurityTokenHelper.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class IsoDepNotSupportedException extends IOException {
|
||||||
|
|
||||||
|
public IsoDepNotSupportedException(String detailMessage) {
|
||||||
|
super(detailMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask user if she wants to install PGP onto her Fidesmo token
|
||||||
|
*/
|
||||||
|
private void promptFidesmoPgpInstall() {
|
||||||
|
FidesmoPgpInstallDialog fidesmoPgpInstallDialog = new FidesmoPgpInstallDialog();
|
||||||
|
fidesmoPgpInstallDialog.show(getSupportFragmentManager(), "fidesmoPgpInstallDialog");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a Dialog to the user informing that Fidesmo App must be installed and with option
|
||||||
|
* to launch the Google Play store.
|
||||||
|
*/
|
||||||
|
private void promptFidesmoAppInstall() {
|
||||||
|
FidesmoInstallDialog fidesmoInstallDialog = new FidesmoInstallDialog();
|
||||||
|
fidesmoInstallDialog.show(getSupportFragmentManager(), "fidesmoInstallDialog");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the package manager to detect if an application is installed on the phone
|
||||||
|
*
|
||||||
|
* @param uri an URI identifying the application's package
|
||||||
|
* @return 'true' if the app is installed
|
||||||
|
*/
|
||||||
|
private boolean isAndroidAppInstalled(String uri) {
|
||||||
|
PackageManager mPackageManager = getPackageManager();
|
||||||
|
boolean mAppInstalled;
|
||||||
|
try {
|
||||||
|
mPackageManager.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
|
||||||
|
mAppInstalled = true;
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
Log.e(Constants.TAG, "App not installed on Android device");
|
||||||
|
mAppInstalled = false;
|
||||||
|
}
|
||||||
|
return mAppInstalled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
mUsbDispatcher.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
mUsbDispatcher.onStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecurityTokenHelper getSecurityTokenHelper() {
|
||||||
|
return mSecurityTokenHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run Security Token routines if last used token is connected and supports
|
||||||
|
* persistent connections
|
||||||
|
*/
|
||||||
|
public void checkDeviceConnection() {
|
||||||
|
mUsbDispatcher.rescanDevices();
|
||||||
|
}
|
||||||
|
}
|
||||||
-1057
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -130,9 +130,9 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
|
|||||||
|
|
||||||
switch (requiredInput.mType) {
|
switch (requiredInput.mType) {
|
||||||
// always use CryptoOperationHelper.startActivityForResult!
|
// always use CryptoOperationHelper.startActivityForResult!
|
||||||
case NFC_MOVE_KEY_TO_CARD:
|
case SECURITY_TOKEN_MOVE_KEY_TO_CARD:
|
||||||
case NFC_DECRYPT:
|
case SECURITY_TOKEN_DECRYPT:
|
||||||
case NFC_SIGN: {
|
case SECURITY_TOKEN_SIGN: {
|
||||||
Intent intent = new Intent(activity, SecurityTokenOperationActivity.class);
|
Intent intent = new Intent(activity, SecurityTokenOperationActivity.class);
|
||||||
intent.putExtra(SecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
|
intent.putExtra(SecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
|
||||||
intent.putExtra(SecurityTokenOperationActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel);
|
intent.putExtra(SecurityTokenOperationActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel);
|
||||||
|
|||||||
+6
-11
@@ -50,14 +50,13 @@ import android.widget.EditText;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.TextView.OnEditorActionListener;
|
import android.widget.TextView.OnEditorActionListener;
|
||||||
|
|
||||||
import com.squareup.okhttp.OkHttpClient;
|
|
||||||
import com.squareup.okhttp.Request;
|
|
||||||
|
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
import org.sufficientlysecure.keychain.util.OkHttpClientFactory;
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
import org.sufficientlysecure.keychain.util.TlsHelper;
|
import org.sufficientlysecure.keychain.util.TlsHelper;
|
||||||
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
|
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
|
||||||
@@ -354,19 +353,15 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On
|
|||||||
|
|
||||||
Log.d("Converted URL", newKeyserver.toString());
|
Log.d("Converted URL", newKeyserver.toString());
|
||||||
|
|
||||||
OkHttpClient client = HkpKeyserver.getClient(newKeyserver.toURL(), proxy);
|
|
||||||
|
|
||||||
// don't follow any redirects
|
|
||||||
client.setFollowRedirects(false);
|
|
||||||
client.setFollowSslRedirects(false);
|
|
||||||
|
|
||||||
if (onlyTrustedKeyserver
|
if (onlyTrustedKeyserver
|
||||||
&& !TlsHelper.usePinnedCertificateIfAvailable(client, newKeyserver.toURL())) {
|
&& TlsHelper.getPinnedSslSocketFactory(newKeyserver.toURL()) == null) {
|
||||||
Log.w(Constants.TAG, "No pinned certificate for this host in OpenKeychain's assets.");
|
Log.w(Constants.TAG, "No pinned certificate for this host in OpenKeychain's assets.");
|
||||||
reason = FailureReason.NO_PINNED_CERTIFICATE;
|
reason = FailureReason.NO_PINNED_CERTIFICATE;
|
||||||
return reason;
|
return reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OkHttpClient client = OkHttpClientFactory.getClientPinnedIfAvailable(newKeyserver.toURL(), proxy);
|
||||||
|
|
||||||
client.newCall(new Request.Builder().url(newKeyserver.toURL()).build()).execute();
|
client.newCall(new Request.Builder().url(newKeyserver.toURL()).build()).execute();
|
||||||
} catch (TlsHelper.TlsHelperException e) {
|
} catch (TlsHelper.TlsHelperException e) {
|
||||||
reason = FailureReason.CONNECTION_FAILED;
|
reason = FailureReason.CONNECTION_FAILED;
|
||||||
|
|||||||
+146
-333
@@ -17,25 +17,26 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui.dialog;
|
package org.sufficientlysecure.keychain.ui.dialog;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.os.Build;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.support.v4.app.DialogFragment;
|
||||||
import android.support.v4.app.FragmentActivity;
|
import android.support.v4.app.FragmentActivity;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.text.Editable;
|
import android.text.Html;
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
import android.widget.DatePicker;
|
import android.widget.DatePicker;
|
||||||
import android.widget.EditText;
|
import android.widget.RadioButton;
|
||||||
|
import android.widget.RadioGroup;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.TableRow;
|
import android.widget.TableRow;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -49,14 +50,18 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
|
|||||||
import org.sufficientlysecure.keychain.util.Choice;
|
import org.sufficientlysecure.keychain.util.Choice;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
public class AddSubkeyDialogFragment extends DialogFragment {
|
public class AddSubkeyDialogFragment extends DialogFragment {
|
||||||
|
|
||||||
public interface OnAlgorithmSelectedListener {
|
public interface OnAlgorithmSelectedListener {
|
||||||
public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey);
|
void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SupportedKeyType {
|
||||||
|
RSA_2048, RSA_3072, RSA_4096, ECC_P256, ECC_P521
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String ARG_WILL_BE_MASTER_KEY = "will_be_master_key";
|
private static final String ARG_WILL_BE_MASTER_KEY = "will_be_master_key";
|
||||||
@@ -66,18 +71,12 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
|||||||
private CheckBox mNoExpiryCheckBox;
|
private CheckBox mNoExpiryCheckBox;
|
||||||
private TableRow mExpiryRow;
|
private TableRow mExpiryRow;
|
||||||
private DatePicker mExpiryDatePicker;
|
private DatePicker mExpiryDatePicker;
|
||||||
private Spinner mAlgorithmSpinner;
|
private Spinner mKeyTypeSpinner;
|
||||||
private View mKeySizeRow;
|
private RadioGroup mUsageRadioGroup;
|
||||||
private Spinner mKeySizeSpinner;
|
private RadioButton mUsageNone;
|
||||||
private View mCurveRow;
|
private RadioButton mUsageSign;
|
||||||
private Spinner mCurveSpinner;
|
private RadioButton mUsageEncrypt;
|
||||||
private TextView mCustomKeyTextView;
|
private RadioButton mUsageSignAndEncrypt;
|
||||||
private EditText mCustomKeyEditText;
|
|
||||||
private TextView mCustomKeyInfoTextView;
|
|
||||||
private CheckBox mFlagCertify;
|
|
||||||
private CheckBox mFlagSign;
|
|
||||||
private CheckBox mFlagEncrypt;
|
|
||||||
private CheckBox mFlagAuthenticate;
|
|
||||||
|
|
||||||
private boolean mWillBeMasterKey;
|
private boolean mWillBeMasterKey;
|
||||||
|
|
||||||
@@ -96,6 +95,8 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
|||||||
return frag;
|
return frag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
final FragmentActivity context = getActivity();
|
final FragmentActivity context = getActivity();
|
||||||
@@ -106,25 +107,27 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
|||||||
|
|
||||||
CustomAlertDialogBuilder dialog = new CustomAlertDialogBuilder(context);
|
CustomAlertDialogBuilder dialog = new CustomAlertDialogBuilder(context);
|
||||||
|
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
View view = mInflater.inflate(R.layout.add_subkey_dialog, null);
|
View view = mInflater.inflate(R.layout.add_subkey_dialog, null);
|
||||||
dialog.setView(view);
|
dialog.setView(view);
|
||||||
dialog.setTitle(R.string.title_add_subkey);
|
|
||||||
|
|
||||||
mNoExpiryCheckBox = (CheckBox) view.findViewById(R.id.add_subkey_no_expiry);
|
mNoExpiryCheckBox = (CheckBox) view.findViewById(R.id.add_subkey_no_expiry);
|
||||||
mExpiryRow = (TableRow) view.findViewById(R.id.add_subkey_expiry_row);
|
mExpiryRow = (TableRow) view.findViewById(R.id.add_subkey_expiry_row);
|
||||||
mExpiryDatePicker = (DatePicker) view.findViewById(R.id.add_subkey_expiry_date_picker);
|
mExpiryDatePicker = (DatePicker) view.findViewById(R.id.add_subkey_expiry_date_picker);
|
||||||
mAlgorithmSpinner = (Spinner) view.findViewById(R.id.add_subkey_algorithm);
|
mKeyTypeSpinner = (Spinner) view.findViewById(R.id.add_subkey_type);
|
||||||
mKeySizeSpinner = (Spinner) view.findViewById(R.id.add_subkey_size);
|
mUsageRadioGroup = (RadioGroup) view.findViewById(R.id.add_subkey_usage_group);
|
||||||
mCurveSpinner = (Spinner) view.findViewById(R.id.add_subkey_curve);
|
mUsageNone = (RadioButton) view.findViewById(R.id.add_subkey_usage_none);
|
||||||
mKeySizeRow = view.findViewById(R.id.add_subkey_row_size);
|
mUsageSign = (RadioButton) view.findViewById(R.id.add_subkey_usage_sign);
|
||||||
mCurveRow = view.findViewById(R.id.add_subkey_row_curve);
|
mUsageEncrypt = (RadioButton) view.findViewById(R.id.add_subkey_usage_encrypt);
|
||||||
mCustomKeyTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_label);
|
mUsageSignAndEncrypt = (RadioButton) view.findViewById(R.id.add_subkey_usage_sign_and_encrypt);
|
||||||
mCustomKeyEditText = (EditText) view.findViewById(R.id.add_subkey_custom_key_size_input);
|
|
||||||
mCustomKeyInfoTextView = (TextView) view.findViewById(R.id.add_subkey_custom_key_size_info);
|
if(mWillBeMasterKey) {
|
||||||
mFlagCertify = (CheckBox) view.findViewById(R.id.add_subkey_flag_certify);
|
dialog.setTitle(R.string.title_change_master_key);
|
||||||
mFlagSign = (CheckBox) view.findViewById(R.id.add_subkey_flag_sign);
|
mUsageNone.setVisibility(View.VISIBLE);
|
||||||
mFlagEncrypt = (CheckBox) view.findViewById(R.id.add_subkey_flag_encrypt);
|
mUsageNone.setChecked(true);
|
||||||
mFlagAuthenticate = (CheckBox) view.findViewById(R.id.add_subkey_flag_authenticate);
|
} else {
|
||||||
|
dialog.setTitle(R.string.title_add_subkey);
|
||||||
|
}
|
||||||
|
|
||||||
mNoExpiryCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
mNoExpiryCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -143,65 +146,24 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
|||||||
mExpiryDatePicker.setMinDate(minDateCal.getTime().getTime());
|
mExpiryDatePicker.setMinDate(minDateCal.getTime().getTime());
|
||||||
|
|
||||||
{
|
{
|
||||||
ArrayList<Choice<Algorithm>> choices = new ArrayList<>();
|
ArrayList<Choice<SupportedKeyType>> choices = new ArrayList<>();
|
||||||
choices.add(new Choice<>(Algorithm.DSA, getResources().getString(
|
choices.add(new Choice<>(SupportedKeyType.RSA_2048, getResources().getString(
|
||||||
R.string.dsa)));
|
R.string.rsa_2048), getResources().getString(R.string.rsa_2048_description_html)));
|
||||||
if (!mWillBeMasterKey) {
|
choices.add(new Choice<>(SupportedKeyType.RSA_3072, getResources().getString(
|
||||||
choices.add(new Choice<>(Algorithm.ELGAMAL, getResources().getString(
|
R.string.rsa_3072), getResources().getString(R.string.rsa_3072_description_html)));
|
||||||
R.string.elgamal)));
|
choices.add(new Choice<>(SupportedKeyType.RSA_4096, getResources().getString(
|
||||||
}
|
R.string.rsa_4096), getResources().getString(R.string.rsa_4096_description_html)));
|
||||||
choices.add(new Choice<>(Algorithm.RSA, getResources().getString(
|
choices.add(new Choice<>(SupportedKeyType.ECC_P256, getResources().getString(
|
||||||
R.string.rsa)));
|
R.string.ecc_p256), getResources().getString(R.string.ecc_p256_description_html)));
|
||||||
choices.add(new Choice<>(Algorithm.ECDSA, getResources().getString(
|
choices.add(new Choice<>(SupportedKeyType.ECC_P521, getResources().getString(
|
||||||
R.string.ecdsa)));
|
R.string.ecc_p521), getResources().getString(R.string.ecc_p521_description_html)));
|
||||||
choices.add(new Choice<>(Algorithm.ECDH, getResources().getString(
|
TwoLineArrayAdapter adapter = new TwoLineArrayAdapter(context,
|
||||||
R.string.ecdh)));
|
|
||||||
ArrayAdapter<Choice<Algorithm>> adapter = new ArrayAdapter<>(context,
|
|
||||||
android.R.layout.simple_spinner_item, choices);
|
android.R.layout.simple_spinner_item, choices);
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
mKeyTypeSpinner.setAdapter(adapter);
|
||||||
mAlgorithmSpinner.setAdapter(adapter);
|
// make RSA 3072 the default
|
||||||
// make RSA the default
|
|
||||||
for (int i = 0; i < choices.size(); ++i) {
|
for (int i = 0; i < choices.size(); ++i) {
|
||||||
if (choices.get(i).getId() == Algorithm.RSA) {
|
if (choices.get(i).getId() == SupportedKeyType.RSA_3072) {
|
||||||
mAlgorithmSpinner.setSelection(i);
|
mKeyTypeSpinner.setSelection(i);
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dynamic ArrayAdapter must be created (instead of ArrayAdapter.getFromResource), because it's content may change
|
|
||||||
ArrayAdapter<CharSequence> keySizeAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item,
|
|
||||||
new ArrayList<CharSequence>(Arrays.asList(getResources().getStringArray(R.array.rsa_key_size_spinner_values))));
|
|
||||||
keySizeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
|
||||||
mKeySizeSpinner.setAdapter(keySizeAdapter);
|
|
||||||
mKeySizeSpinner.setSelection(1); // Default to 4096 for the key length
|
|
||||||
|
|
||||||
{
|
|
||||||
ArrayList<Choice<Curve>> choices = new ArrayList<>();
|
|
||||||
|
|
||||||
choices.add(new Choice<>(Curve.NIST_P256, getResources().getString(
|
|
||||||
R.string.key_curve_nist_p256)));
|
|
||||||
choices.add(new Choice<>(Curve.NIST_P384, getResources().getString(
|
|
||||||
R.string.key_curve_nist_p384)));
|
|
||||||
choices.add(new Choice<>(Curve.NIST_P521, getResources().getString(
|
|
||||||
R.string.key_curve_nist_p521)));
|
|
||||||
|
|
||||||
/* @see SaveKeyringParcel
|
|
||||||
choices.add(new Choice<Curve>(Curve.BRAINPOOL_P256, getResources().getString(
|
|
||||||
R.string.key_curve_bp_p256)));
|
|
||||||
choices.add(new Choice<Curve>(Curve.BRAINPOOL_P384, getResources().getString(
|
|
||||||
R.string.key_curve_bp_p384)));
|
|
||||||
choices.add(new Choice<Curve>(Curve.BRAINPOOL_P512, getResources().getString(
|
|
||||||
R.string.key_curve_bp_p512)));
|
|
||||||
*/
|
|
||||||
|
|
||||||
ArrayAdapter<Choice<Curve>> adapter = new ArrayAdapter<>(context,
|
|
||||||
android.R.layout.simple_spinner_item, choices);
|
|
||||||
mCurveSpinner.setAdapter(adapter);
|
|
||||||
// make NIST P-256 the default
|
|
||||||
for (int i = 0; i < choices.size(); ++i) {
|
|
||||||
if (choices.get(i).getId() == Curve.NIST_P256) {
|
|
||||||
mCurveSpinner.setSelection(i);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,45 +177,35 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
|||||||
|
|
||||||
final AlertDialog alertDialog = dialog.show();
|
final AlertDialog alertDialog = dialog.show();
|
||||||
|
|
||||||
mCustomKeyEditText.addTextChangedListener(new TextWatcher() {
|
mKeyTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s) {
|
|
||||||
setOkButtonAvailability(alertDialog);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mKeySizeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
setCustomKeyVisibility();
|
// noinspection unchecked
|
||||||
setOkButtonAvailability(alertDialog);
|
SupportedKeyType keyType = ((Choice<SupportedKeyType>) parent.getSelectedItem()).getId();
|
||||||
|
|
||||||
|
// RadioGroup.getCheckedRadioButtonId() gives the wrong RadioButton checked
|
||||||
|
// when programmatically unchecking children radio buttons. Clearing all is the only option.
|
||||||
|
mUsageRadioGroup.clearCheck();
|
||||||
|
|
||||||
|
if(mWillBeMasterKey) {
|
||||||
|
mUsageNone.setChecked(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyType == SupportedKeyType.ECC_P521 || keyType == SupportedKeyType.ECC_P256) {
|
||||||
|
mUsageSignAndEncrypt.setEnabled(false);
|
||||||
|
if (mWillBeMasterKey) {
|
||||||
|
mUsageEncrypt.setEnabled(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// need to enable if previously disabled for ECC masterkey
|
||||||
|
mUsageEncrypt.setEnabled(true);
|
||||||
|
mUsageSignAndEncrypt.setEnabled(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNothingSelected(AdapterView<?> parent) {
|
public void onNothingSelected(AdapterView<?> parent) {}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mAlgorithmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
|
||||||
@Override
|
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
updateUiForAlgorithm(((Choice<Algorithm>) parent.getSelectedItem()).getId());
|
|
||||||
|
|
||||||
setCustomKeyVisibility();
|
|
||||||
setOkButtonAvailability(alertDialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNothingSelected(AdapterView<?> parent) {
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return alertDialog;
|
return alertDialog;
|
||||||
@@ -269,36 +221,74 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
|||||||
positiveButton.setOnClickListener(new View.OnClickListener() {
|
positiveButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
if (!mFlagCertify.isChecked() && !mFlagSign.isChecked()
|
if (mUsageRadioGroup.getCheckedRadioButtonId() == -1) {
|
||||||
&& !mFlagEncrypt.isChecked() && !mFlagAuthenticate.isChecked()) {
|
Toast.makeText(getActivity(), R.string.edit_key_select_usage, Toast.LENGTH_LONG).show();
|
||||||
Toast.makeText(getActivity(), R.string.edit_key_select_flag, Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Algorithm algorithm = ((Choice<Algorithm>) mAlgorithmSpinner.getSelectedItem()).getId();
|
// noinspection unchecked
|
||||||
|
SupportedKeyType keyType = ((Choice<SupportedKeyType>) mKeyTypeSpinner.getSelectedItem()).getId();
|
||||||
Curve curve = null;
|
Curve curve = null;
|
||||||
Integer keySize = null;
|
Integer keySize = null;
|
||||||
// For EC keys, add a curve
|
Algorithm algorithm = null;
|
||||||
if (algorithm == Algorithm.ECDH || algorithm == Algorithm.ECDSA) {
|
|
||||||
curve = ((Choice<Curve>) mCurveSpinner.getSelectedItem()).getId();
|
// set keysize & curve, for RSA & ECC respectively
|
||||||
// Otherwise, get a keysize
|
switch (keyType) {
|
||||||
} else {
|
case RSA_2048: {
|
||||||
keySize = getProperKeyLength(algorithm, getSelectedKeyLength());
|
keySize = 2048;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RSA_3072: {
|
||||||
|
keySize = 3072;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RSA_4096: {
|
||||||
|
keySize = 4096;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ECC_P256: {
|
||||||
|
curve = Curve.NIST_P256;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ECC_P521: {
|
||||||
|
curve = Curve.NIST_P521;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set algorithm
|
||||||
|
switch (keyType) {
|
||||||
|
case RSA_2048:
|
||||||
|
case RSA_3072:
|
||||||
|
case RSA_4096: {
|
||||||
|
algorithm = Algorithm.RSA;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ECC_P256:
|
||||||
|
case ECC_P521: {
|
||||||
|
if(mUsageEncrypt.isChecked()) {
|
||||||
|
algorithm = Algorithm.ECDH;
|
||||||
|
} else {
|
||||||
|
algorithm = Algorithm.ECDSA;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set flags
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
if (mFlagCertify.isChecked()) {
|
if (mWillBeMasterKey) {
|
||||||
flags |= KeyFlags.CERTIFY_OTHER;
|
flags |= KeyFlags.CERTIFY_OTHER;
|
||||||
}
|
}
|
||||||
if (mFlagSign.isChecked()) {
|
if (mUsageSign.isChecked()) {
|
||||||
flags |= KeyFlags.SIGN_DATA;
|
flags |= KeyFlags.SIGN_DATA;
|
||||||
}
|
} else if (mUsageEncrypt.isChecked()) {
|
||||||
if (mFlagEncrypt.isChecked()) {
|
|
||||||
flags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
|
flags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
|
||||||
|
} else if (mUsageSignAndEncrypt.isChecked()) {
|
||||||
|
flags |= KeyFlags.SIGN_DATA | KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
|
||||||
}
|
}
|
||||||
if (mFlagAuthenticate.isChecked()) {
|
|
||||||
flags |= KeyFlags.AUTHENTICATION;
|
|
||||||
}
|
|
||||||
|
|
||||||
long expiry;
|
long expiry;
|
||||||
if (mNoExpiryCheckBox.isChecked()) {
|
if (mNoExpiryCheckBox.isChecked()) {
|
||||||
@@ -332,206 +322,29 @@ public class AddSubkeyDialogFragment extends DialogFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getSelectedKeyLength() {
|
private class TwoLineArrayAdapter extends ArrayAdapter<Choice<SupportedKeyType>> {
|
||||||
final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem();
|
public TwoLineArrayAdapter(Context context, int resource, List<Choice<SupportedKeyType>> objects) {
|
||||||
final String customLengthString = getResources().getString(R.string.key_size_custom);
|
super(context, resource, objects);
|
||||||
final boolean customSelected = customLengthString.equals(selectedItemString);
|
|
||||||
String keyLengthString = customSelected ? mCustomKeyEditText.getText().toString() : selectedItemString;
|
|
||||||
int keySize;
|
|
||||||
try {
|
|
||||||
keySize = Integer.parseInt(keyLengthString);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
keySize = 0;
|
|
||||||
}
|
}
|
||||||
return keySize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <h3>RSA</h3>
|
@Override
|
||||||
* <p>for RSA algorithm, key length must be greater than 2048. Possibility to generate keys bigger
|
public View getDropDownView(int position, View convertView, ViewGroup parent) {
|
||||||
* than 8192 bits is currently disabled, because it's almost impossible to generate them on a mobile device (check
|
// inflate view if not given one
|
||||||
* <a href="http://www.javamex.com/tutorials/cryptography/rsa_key_length.shtml">RSA key length plot</a> and
|
if (convertView == null) {
|
||||||
* <a href="http://www.keylength.com/">Cryptographic Key Length Recommendation</a>). Also, key length must be a
|
convertView = getActivity().getLayoutInflater()
|
||||||
* multiplicity of 8.</p>
|
.inflate(R.layout.two_line_spinner_dropdown_item, parent, false);
|
||||||
* <h3>ElGamal</h3>
|
|
||||||
* <p>For ElGamal algorithm, supported key lengths are 2048, 3072, 4096 or 8192 bits.</p>
|
|
||||||
* <h3>DSA</h3>
|
|
||||||
* <p>For DSA algorithm key length must be between 2048 and 3072. Also, it must me dividable by 64.</p>
|
|
||||||
*
|
|
||||||
* @return correct key length, according to BouncyCastle specification. Returns <code>-1</code>, if key length is
|
|
||||||
* inappropriate.
|
|
||||||
*/
|
|
||||||
private int getProperKeyLength(Algorithm algorithm, int currentKeyLength) {
|
|
||||||
final int[] elGamalSupportedLengths = {2048, 3072, 4096, 8192};
|
|
||||||
int properKeyLength = -1;
|
|
||||||
switch (algorithm) {
|
|
||||||
case RSA: {
|
|
||||||
if (currentKeyLength >= 2048 && currentKeyLength <= 16384) {
|
|
||||||
properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case ELGAMAL: {
|
|
||||||
int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length];
|
|
||||||
for (int i = 0; i < elGamalSupportedLengths.length; i++) {
|
|
||||||
elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength);
|
|
||||||
}
|
|
||||||
int minimalValue = Integer.MAX_VALUE;
|
|
||||||
int minimalIndex = -1;
|
|
||||||
for (int i = 0; i < elGammalKeyDiff.length; i++) {
|
|
||||||
if (elGammalKeyDiff[i] <= minimalValue) {
|
|
||||||
minimalValue = elGammalKeyDiff[i];
|
|
||||||
minimalIndex = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
properKeyLength = elGamalSupportedLengths[minimalIndex];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DSA: {
|
|
||||||
// Bouncy Castle supports 4096 maximum
|
|
||||||
if (currentKeyLength >= 2048 && currentKeyLength <= 4096) {
|
|
||||||
properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return properKeyLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setOkButtonAvailability(AlertDialog alertDialog) {
|
Choice c = this.getItem(position);
|
||||||
Algorithm algorithm = ((Choice<Algorithm>) mAlgorithmSpinner.getSelectedItem()).getId();
|
|
||||||
boolean enabled = algorithm == Algorithm.ECDSA || algorithm == Algorithm.ECDH
|
|
||||||
|| getProperKeyLength(algorithm, getSelectedKeyLength()) > 0;
|
|
||||||
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setCustomKeyVisibility() {
|
TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
|
||||||
final String selectedItemString = (String) mKeySizeSpinner.getSelectedItem();
|
TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
|
||||||
final String customLengthString = getResources().getString(R.string.key_size_custom);
|
|
||||||
final boolean customSelected = customLengthString.equals(selectedItemString);
|
|
||||||
final int visibility = customSelected ? View.VISIBLE : View.GONE;
|
|
||||||
|
|
||||||
mCustomKeyEditText.setVisibility(visibility);
|
text1.setText(c.getName());
|
||||||
mCustomKeyTextView.setVisibility(visibility);
|
text2.setText(Html.fromHtml(c.getDescription()));
|
||||||
mCustomKeyInfoTextView.setVisibility(visibility);
|
|
||||||
|
|
||||||
// hide keyboard after setting visibility to gone
|
return convertView;
|
||||||
if (visibility == View.GONE) {
|
|
||||||
InputMethodManager imm = (InputMethodManager)
|
|
||||||
getActivity().getSystemService(FragmentActivity.INPUT_METHOD_SERVICE);
|
|
||||||
imm.hideSoftInputFromWindow(mCustomKeyEditText.getWindowToken(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUiForAlgorithm(Algorithm algorithm) {
|
|
||||||
final ArrayAdapter<CharSequence> keySizeAdapter = (ArrayAdapter<CharSequence>) mKeySizeSpinner.getAdapter();
|
|
||||||
keySizeAdapter.clear();
|
|
||||||
switch (algorithm) {
|
|
||||||
case RSA: {
|
|
||||||
replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values);
|
|
||||||
mKeySizeSpinner.setSelection(1);
|
|
||||||
mKeySizeRow.setVisibility(View.VISIBLE);
|
|
||||||
mCurveRow.setVisibility(View.GONE);
|
|
||||||
mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_rsa));
|
|
||||||
// allowed flags:
|
|
||||||
mFlagSign.setEnabled(true);
|
|
||||||
mFlagEncrypt.setEnabled(true);
|
|
||||||
mFlagAuthenticate.setEnabled(true);
|
|
||||||
|
|
||||||
if (mWillBeMasterKey) {
|
|
||||||
mFlagCertify.setEnabled(true);
|
|
||||||
|
|
||||||
mFlagCertify.setChecked(true);
|
|
||||||
mFlagSign.setChecked(false);
|
|
||||||
mFlagEncrypt.setChecked(false);
|
|
||||||
} else {
|
|
||||||
mFlagCertify.setEnabled(false);
|
|
||||||
|
|
||||||
mFlagCertify.setChecked(false);
|
|
||||||
mFlagSign.setChecked(true);
|
|
||||||
mFlagEncrypt.setChecked(true);
|
|
||||||
}
|
|
||||||
mFlagAuthenticate.setChecked(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ELGAMAL: {
|
|
||||||
replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values);
|
|
||||||
mKeySizeSpinner.setSelection(3);
|
|
||||||
mKeySizeRow.setVisibility(View.VISIBLE);
|
|
||||||
mCurveRow.setVisibility(View.GONE);
|
|
||||||
mCustomKeyInfoTextView.setText(""); // ElGamal does not support custom key length
|
|
||||||
// allowed flags:
|
|
||||||
mFlagCertify.setChecked(false);
|
|
||||||
mFlagCertify.setEnabled(false);
|
|
||||||
mFlagSign.setChecked(false);
|
|
||||||
mFlagSign.setEnabled(false);
|
|
||||||
mFlagEncrypt.setChecked(true);
|
|
||||||
mFlagEncrypt.setEnabled(true);
|
|
||||||
mFlagAuthenticate.setChecked(false);
|
|
||||||
mFlagAuthenticate.setEnabled(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DSA: {
|
|
||||||
replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values);
|
|
||||||
mKeySizeSpinner.setSelection(2);
|
|
||||||
mKeySizeRow.setVisibility(View.VISIBLE);
|
|
||||||
mCurveRow.setVisibility(View.GONE);
|
|
||||||
mCustomKeyInfoTextView.setText(getResources().getString(R.string.key_size_custom_info_dsa));
|
|
||||||
// allowed flags:
|
|
||||||
mFlagCertify.setChecked(false);
|
|
||||||
mFlagCertify.setEnabled(false);
|
|
||||||
mFlagSign.setChecked(true);
|
|
||||||
mFlagSign.setEnabled(true);
|
|
||||||
mFlagEncrypt.setChecked(false);
|
|
||||||
mFlagEncrypt.setEnabled(false);
|
|
||||||
mFlagAuthenticate.setChecked(false);
|
|
||||||
mFlagAuthenticate.setEnabled(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ECDSA: {
|
|
||||||
mKeySizeRow.setVisibility(View.GONE);
|
|
||||||
mCurveRow.setVisibility(View.VISIBLE);
|
|
||||||
mCustomKeyInfoTextView.setText("");
|
|
||||||
// allowed flags:
|
|
||||||
mFlagCertify.setEnabled(mWillBeMasterKey);
|
|
||||||
mFlagCertify.setChecked(mWillBeMasterKey);
|
|
||||||
mFlagSign.setEnabled(true);
|
|
||||||
mFlagSign.setChecked(!mWillBeMasterKey);
|
|
||||||
mFlagEncrypt.setEnabled(false);
|
|
||||||
mFlagEncrypt.setChecked(false);
|
|
||||||
mFlagAuthenticate.setEnabled(true);
|
|
||||||
mFlagAuthenticate.setChecked(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ECDH: {
|
|
||||||
mKeySizeRow.setVisibility(View.GONE);
|
|
||||||
mCurveRow.setVisibility(View.VISIBLE);
|
|
||||||
mCustomKeyInfoTextView.setText("");
|
|
||||||
// allowed flags:
|
|
||||||
mFlagCertify.setChecked(false);
|
|
||||||
mFlagCertify.setEnabled(false);
|
|
||||||
mFlagSign.setChecked(false);
|
|
||||||
mFlagSign.setEnabled(false);
|
|
||||||
mFlagEncrypt.setChecked(true);
|
|
||||||
mFlagEncrypt.setEnabled(true);
|
|
||||||
mFlagAuthenticate.setChecked(false);
|
|
||||||
mFlagAuthenticate.setEnabled(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
keySizeAdapter.notifyDataSetChanged();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
|
||||||
private void replaceArrayAdapterContent(ArrayAdapter<CharSequence> arrayAdapter, int stringArrayResourceId) {
|
|
||||||
final String[] spinnerValuesStringArray = getResources().getStringArray(stringArrayResourceId);
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
|
||||||
arrayAdapter.addAll(spinnerValuesStringArray);
|
|
||||||
} else {
|
|
||||||
for (final String value : spinnerValuesStringArray) {
|
|
||||||
arrayAdapter.add(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+30
-7
@@ -35,7 +35,11 @@ public class EditSubkeyDialogFragment extends DialogFragment {
|
|||||||
public static final int MESSAGE_CHANGE_EXPIRY = 1;
|
public static final int MESSAGE_CHANGE_EXPIRY = 1;
|
||||||
public static final int MESSAGE_REVOKE = 2;
|
public static final int MESSAGE_REVOKE = 2;
|
||||||
public static final int MESSAGE_STRIP = 3;
|
public static final int MESSAGE_STRIP = 3;
|
||||||
public static final int MESSAGE_MOVE_KEY_TO_CARD = 4;
|
public static final int MESSAGE_MOVE_KEY_TO_SECURITY_TOKEN = 4;
|
||||||
|
public static final int SUBKEY_MENU_CHANGE_EXPIRY = 0;
|
||||||
|
public static final int SUBKEY_MENU_REVOKE_SUBKEY = 1;
|
||||||
|
public static final int SUBKEY_MENU_STRIP_SUBKEY = 2;
|
||||||
|
public static final int SUBKEY_MENU_MOVE_TO_SECURITY_TOKEN = 3;
|
||||||
|
|
||||||
private Messenger mMessenger;
|
private Messenger mMessenger;
|
||||||
|
|
||||||
@@ -68,17 +72,17 @@ public class EditSubkeyDialogFragment extends DialogFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
switch (which) {
|
switch (which) {
|
||||||
case 0:
|
case SUBKEY_MENU_CHANGE_EXPIRY:
|
||||||
sendMessageToHandler(MESSAGE_CHANGE_EXPIRY, null);
|
sendMessageToHandler(MESSAGE_CHANGE_EXPIRY, null);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case SUBKEY_MENU_REVOKE_SUBKEY:
|
||||||
sendMessageToHandler(MESSAGE_REVOKE, null);
|
sendMessageToHandler(MESSAGE_REVOKE, null);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case SUBKEY_MENU_STRIP_SUBKEY:
|
||||||
sendMessageToHandler(MESSAGE_STRIP, null);
|
showAlertDialog();
|
||||||
break;
|
break;
|
||||||
case 3:
|
case SUBKEY_MENU_MOVE_TO_SECURITY_TOKEN:
|
||||||
sendMessageToHandler(MESSAGE_MOVE_KEY_TO_CARD, null);
|
sendMessageToHandler(MESSAGE_MOVE_KEY_TO_SECURITY_TOKEN, null);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -95,6 +99,25 @@ public class EditSubkeyDialogFragment extends DialogFragment {
|
|||||||
return builder.show();
|
return builder.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showAlertDialog() {
|
||||||
|
CustomAlertDialogBuilder stripAlertDialog = new CustomAlertDialogBuilder(getActivity());
|
||||||
|
stripAlertDialog.setTitle(R.string.title_alert_strip).
|
||||||
|
setMessage(R.string.alert_strip).setCancelable(true);
|
||||||
|
stripAlertDialog.setPositiveButton(R.string.strip, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
sendMessageToHandler(MESSAGE_STRIP, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stripAlertDialog.setNegativeButton(R.string.btn_do_not_save, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stripAlertDialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send message back to handler which is initialized in a activity
|
* Send message back to handler which is initialized in a activity
|
||||||
*
|
*
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user