Compare commits
155 Commits
Author | SHA1 | Date | |
---|---|---|---|
7366f14d32 | |||
|
2b26f293b8 | ||
|
7c3518a011 | ||
|
297f53e171 | ||
|
f5aa36ef9f | ||
|
5895385153 | ||
|
300fd8e0f2 | ||
|
04d2b6a507 | ||
|
9a82b33d16 | ||
|
e3ed2c8e72 | ||
|
cc793c2882 | ||
|
5e77117232 | ||
|
8e60ccb650 | ||
|
0d61221c5f | ||
|
0c20d40863 | ||
|
7b416d7d7d | ||
|
04e9137b66 | ||
|
d2cdfb34fa | ||
|
d12d469714 | ||
|
e85982a419 | ||
|
3755bce9ce | ||
|
7d371d8a39 | ||
|
f677446d51 | ||
|
b87a7372c9 | ||
|
84165c092e | ||
|
2d03965777 | ||
|
312cb38848 | ||
|
71ea521198 | ||
|
b8305d43dc | ||
|
b7834b4326 | ||
|
ae199313ee | ||
|
db0266c0ae | ||
|
908545e521 | ||
|
fe4659f8d6 | ||
|
34c7252048 | ||
|
7998b2a262 | ||
|
6f47c78981 | ||
|
442aed8a2d | ||
|
46e75a085e | ||
|
fb87f2ed2a | ||
|
d599825046 | ||
|
d445b4d2e0 | ||
|
2c47035e02 | ||
|
5e4842ab64 | ||
|
66e7876abd | ||
|
0b3317600b | ||
|
655cdfbbee | ||
|
d57324aa4b | ||
|
4f55b1f5d1 | ||
|
cda8b63bb4 | ||
|
5d652e4c41 | ||
|
244f92ed57 | ||
|
98ba424576 | ||
|
7f67658de9 | ||
|
b856d82ae2 | ||
|
14226461c1 | ||
|
7a5121894e | ||
|
7c32098211 | ||
|
6749b03d9a | ||
|
d16b09b2a6 | ||
|
05fcbcae7b | ||
|
260364e267 | ||
|
aa31abd93f | ||
|
8da88f33bc | ||
|
61dce088c2 | ||
|
9b6416943b | ||
|
e6ea98fabc | ||
|
47f98493e2 | ||
|
28cb70d7dc | ||
|
1045deb0e3 | ||
|
0505af7520 | ||
|
69c8ecd553 | ||
|
074b6633b0 | ||
|
82f3d70224 | ||
|
7cfc0d80d0 | ||
|
ecfbc743f3 | ||
|
bd5a5c0138 | ||
|
b3ebb64666 | ||
|
e2988c2a68 | ||
|
7d29901f64 | ||
|
ebba24cbd8 | ||
|
6bc40d12ad | ||
|
14a08361e5 | ||
|
b356df900f | ||
|
af5fc66229 | ||
|
784dbc087e | ||
|
403f74f558 | ||
|
204893a025 | ||
|
1bc14ab6ae | ||
|
cf5fadae76 | ||
|
3976eadf06 | ||
|
93f3a98eae | ||
|
5d87872245 | ||
|
cfeffef80d | ||
|
3be44898db | ||
|
ef209450c6 | ||
|
99fe806ea3 | ||
|
0d8370be1d | ||
|
36ecd60c1b | ||
|
bde58c6ff1 | ||
|
61a6346f89 | ||
|
313b4ac7d3 | ||
|
b9563ff2ef | ||
|
1406eec2dc | ||
|
8be6450a36 | ||
|
c9f9af6603 | ||
|
dd94c70fbe | ||
|
213798dde1 | ||
|
e174b8af3b | ||
|
25d0325c5f | ||
|
b794719020 | ||
|
38d8f4be52 | ||
|
40703fe961 | ||
|
4ecd4389b3 | ||
|
62e65a8240 | ||
|
724726a4fd | ||
|
a8e95f676e | ||
|
426d17bd0a | ||
|
56a75774d0 | ||
|
8dc9773c1e | ||
|
fed0e7db8d | ||
|
6c17734e73 | ||
|
08e0357471 | ||
|
c4b774f7b8 | ||
|
8a15d28ed9 | ||
|
1651f9fb61 | ||
|
0456e04c1a | ||
|
43d9e2ba76 | ||
|
ab63fa8091 | ||
|
2cdaa75b01 | ||
|
c8266203f8 | ||
|
0f520975e4 | ||
|
4885361cd2 | ||
|
b430ba51eb | ||
|
bd8e45b556 | ||
|
cd0d84d10d | ||
|
064c9d461f | ||
|
7e5e0df0bc | ||
|
48f6e20f6c | ||
|
c1e7fcf024 | ||
|
f554cc9c93 | ||
|
4b2c8a1309 | ||
|
a81474b7a5 | ||
|
022fde29ae | ||
|
8c97bee14c | ||
|
5f6421e82b | ||
|
2d3f745c36 | ||
|
aa75534e5b | ||
|
0504033c6b | ||
|
bc48ce4210 | ||
|
de2006a61f | ||
|
28b9068ae0 | ||
|
d21fb77336 | ||
|
a0107afd3e | ||
|
76241e90ad |
49
.travis.yml
@ -1,19 +1,36 @@
|
|||||||
language: java
|
language: android
|
||||||
jdk: openjdk7
|
jdk: openjdk7
|
||||||
before_install:
|
# container based build, we don't need root anyways
|
||||||
# Install base Android SDK
|
sudo: false
|
||||||
- sudo apt-get update -qq
|
# env:
|
||||||
- if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm lib32z1 lib32stdc++6; fi
|
# global:
|
||||||
- wget http://dl.google.com/android/android-sdk_r24.1.2-linux.tgz
|
# - ANDROID_API_LEVEL=21
|
||||||
- tar xzf android-sdk_r24.1.2-linux.tgz
|
# - ANDROID_ABI=armeabi-v7a
|
||||||
- export ANDROID_HOME=$PWD/android-sdk-linux
|
# - ADB_INSTALL_TIMEOUT=8 # minutes (2 minutes by default)
|
||||||
- export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
|
android:
|
||||||
|
components:
|
||||||
|
- build-tools-22.0.1
|
||||||
|
- build-tools-21.1.2
|
||||||
|
- build-tools-21.1.1
|
||||||
|
- build-tools-19.1.0
|
||||||
|
- android-22
|
||||||
|
- android-21
|
||||||
|
- android-19
|
||||||
|
- platform-tools
|
||||||
|
- extra-android-support
|
||||||
|
- extra-android-m2repository
|
||||||
|
licenses:
|
||||||
|
- 'android-sdk-preview-license-52d11cd2'
|
||||||
|
- 'android-sdk-license-.+'
|
||||||
|
- 'google-gdk-license-.+'
|
||||||
|
|
||||||
|
# doesn't work, travis is just too slow
|
||||||
|
# before_script:
|
||||||
|
# - echo no | android create avd --force -n test -t android-$ANDROID_API_LEVEL --abi $ANDROID_ABI
|
||||||
|
# - emulator -avd test -no-skin -no-audio -no-window &
|
||||||
|
# - ./tools/android-wait-for-emulator
|
||||||
|
# - adb shell input keyevent 82 &
|
||||||
|
|
||||||
# Install required Android components.
|
|
||||||
#- echo "y" | android update sdk -a --filter build-tools-19.1.0,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force
|
|
||||||
- ( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) | android update sdk --no-ui --all --force --filter build-tools-22.0.1,build-tools-21.1.2,build-tools-21.1.1,build-tools-19.1.0,android-22,android-21,android-19,platform-tools,extra-android-support,extra-android-m2repository
|
|
||||||
install: echo "Installation done"
|
|
||||||
script:
|
script:
|
||||||
- ./gradlew assemble -S -q
|
# - ./gradlew connectedAndroidTest
|
||||||
- ./gradlew --info OpenKeychain-Test:testDebug
|
- ./gradlew testDebug jacocoTestReport coveralls
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
Please go to https://github.com/open-keychain/open-keychain/blob/development/OpenKeychain/src/main/res/raw/help_changelog.md
|
Please go to https://github.com/open-keychain/open-keychain/blob/HEAD/OpenKeychain/src/main/res/raw/help_changelog.md
|
51
Graphics/drawables/ic_stat_notify.svg
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
version="1.1"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 95 95"
|
||||||
|
enable-background="new 0 0 100 100"
|
||||||
|
xml:space="preserve"
|
||||||
|
id="svg2"
|
||||||
|
inkscape:version="0.48.4 r9939"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
sodipodi:docname="ic_stat_notify.svg"><metadata
|
||||||
|
id="metadata14"><rdf:RDF><cc:Work
|
||||||
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||||
|
id="defs12" /><sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1145"
|
||||||
|
id="namedview10"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="3.776"
|
||||||
|
inkscape:cx="56.6318"
|
||||||
|
inkscape:cy="73.41883"
|
||||||
|
inkscape:window-x="1917"
|
||||||
|
inkscape:window-y="-3"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg2"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0" /><path
|
||||||
|
d="m 36.917194,57.417227 5.333672,5.267283 9.512321,1.139867 1.441261,9.395693 9.80158,1.42359 0.863749,9.681407 9.944699,1.281231 L 74.966476,95 93.848,93.718769 95,74.642665 57.527222,37.629328 c 0,0 5.836602,-15.873525 -7.855375,-29.398624 C 35.97987,-5.2943953 17.169906,0.04556 8.5233497,8.587099 0.30816341,16.700566 -5.1686273,36.063379 7.5144667,48.589975 22.215327,63.112582 36.917194,57.417227 36.917194,57.417227 z m 53.904158,19.076105 -0.28926,5.411632 -38.338543,-37.869481 2.88353,-2.848176 35.744273,35.306025 z M 13.884235,13.882256 c 5.835594,-5.76305 13.617394,-7.4205864 17.383821,-3.700338 3.76542,3.719253 2.088317,11.406638 -3.747277,17.170684 -5.835595,5.762055 -13.618402,7.418596 -17.383822,3.699343 C 6.3715371,27.332692 8.0486407,19.645306 13.884235,13.882256 z"
|
||||||
|
id="path4"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#ffffff" /></svg>
|
After Width: | Height: | Size: 2.4 KiB |
@ -1,121 +0,0 @@
|
|||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information
|
|
||||||
classpath 'com.novoda:gradle-android-test-plugin:0.10.4'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply plugin: 'java'
|
|
||||||
apply plugin: 'android-test'
|
|
||||||
apply plugin: 'jacoco'
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
testCompile 'junit:junit:4.11'
|
|
||||||
testCompile 'com.google.android:android:4.1.1.4'
|
|
||||||
testCompile('com.squareup:fest-android:1.0.8') { exclude module: 'support-v4' }
|
|
||||||
testCompile 'org.apache.maven:maven-ant-tasks:2.1.3'
|
|
||||||
testCompile ('org.robolectric:robolectric:2.4') {
|
|
||||||
exclude module: 'classworlds'
|
|
||||||
exclude module: 'maven-artifact'
|
|
||||||
exclude module: 'maven-artifact-manager'
|
|
||||||
exclude module: 'maven-error-diagnostics'
|
|
||||||
exclude module: 'maven-model'
|
|
||||||
exclude module: 'maven-plugin-registry'
|
|
||||||
exclude module: 'maven-profile'
|
|
||||||
exclude module: 'maven-project'
|
|
||||||
exclude module: 'maven-settings'
|
|
||||||
exclude module: 'nekohtml'
|
|
||||||
exclude module: 'plexus-container-default'
|
|
||||||
exclude module: 'plexus-interpolation'
|
|
||||||
exclude module: 'plexus-utils'
|
|
||||||
exclude module: 'support-v4' // crazy but my android studio don't like this dependency and to fix it remove .idea and re import project
|
|
||||||
exclude module: 'wagon-file'
|
|
||||||
exclude module: 'wagon-http-lightweight'
|
|
||||||
exclude module: 'wagon-http-shared'
|
|
||||||
exclude module: 'wagon-provider-api'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
exclude '**/*$*'
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
projectUnderTest ':OpenKeychain'
|
|
||||||
}
|
|
||||||
|
|
||||||
jacoco {
|
|
||||||
toolVersion = "0.7.2.201409121644"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def coverageSourceDirs = [
|
|
||||||
'../OpenKeychain/src/main/java',
|
|
||||||
'../OpenKeychain/src/gen',
|
|
||||||
'../OpenKeychain/build/source/apt/debug',
|
|
||||||
'../OpenKeychain/build/source/generated/buildConfig/debug',
|
|
||||||
'../OpenKeychain/build/source/generated/r/debug'
|
|
||||||
]
|
|
||||||
|
|
||||||
jacocoTestReport {
|
|
||||||
reports {
|
|
||||||
xml.enabled = true
|
|
||||||
html.destination "${buildDir}/jacocoHtml"
|
|
||||||
}
|
|
||||||
// class R is used, but usage will not be covered, so ignore this class from report
|
|
||||||
classDirectories = fileTree(dir: '../OpenKeychain/build/intermediates/classes/debug/org/sufficientlysecure/keychain', exclude: [ 'R*.class' ])
|
|
||||||
additionalSourceDirs = files(coverageSourceDirs)
|
|
||||||
executionData = files('build/jacoco/testDebug.exec')
|
|
||||||
}
|
|
||||||
|
|
||||||
// new workaround to force add custom output dirs for android studio
|
|
||||||
task addTest {
|
|
||||||
def file = file(project.name + ".iml")
|
|
||||||
doLast {
|
|
||||||
try {
|
|
||||||
def parsedXml = (new XmlParser()).parse(file)
|
|
||||||
def node = parsedXml.component[1]
|
|
||||||
def outputNode = parsedXml.component[1].output[0]
|
|
||||||
def outputTestNode = parsedXml.component[1].'output-test'[0]
|
|
||||||
def rewrite = false
|
|
||||||
|
|
||||||
new Node(node, 'sourceFolder', ['url': 'file://$MODULE_DIR$/' + "${it}", 'isTestSource': "true"])
|
|
||||||
|
|
||||||
if(outputNode == null) {
|
|
||||||
new Node(node, 'output', ['url': 'file://$MODULE_DIR$/build/resources/testDebug'])
|
|
||||||
} else {
|
|
||||||
if(outputNode.attributes['url'] != 'file://$MODULE_DIR$/build/resources/testDebug') {
|
|
||||||
outputNode.attributes = ['url': 'file://$MODULE_DIR$/build/resources/testDebug']
|
|
||||||
rewrite = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(outputTestNode == null) {
|
|
||||||
new Node(node, 'output-test', ['url': 'file://$MODULE_DIR$/build/test-classes/debug'])
|
|
||||||
} else {
|
|
||||||
if(outputTestNode.attributes['url'] != 'file://$MODULE_DIR$/build/test-classes/debug') {
|
|
||||||
outputTestNode.attributes = ['url': 'file://$MODULE_DIR$/build/test-classes/debug']
|
|
||||||
rewrite = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(rewrite) {
|
|
||||||
def writer = new StringWriter()
|
|
||||||
new XmlNodePrinter(new PrintWriter(writer)).print(parsedXml)
|
|
||||||
file.text = writer.toString()
|
|
||||||
}
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
// iml not found, common on command line only builds
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// always do the addtest on prebuild
|
|
||||||
gradle.projectsEvaluated {
|
|
||||||
testDebugClasses.dependsOn(addTest)
|
|
||||||
}
|
|
@ -1,5 +1,7 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'witness'
|
apply plugin: 'witness'
|
||||||
|
apply plugin: 'jacoco'
|
||||||
|
apply plugin: 'com.github.kt3k.coveralls'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information
|
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information
|
||||||
@ -11,11 +13,22 @@ dependencies {
|
|||||||
compile 'com.android.support:recyclerview-v7:22.1.0'
|
compile 'com.android.support:recyclerview-v7:22.1.0'
|
||||||
compile 'com.android.support:cardview-v7:22.1.0'
|
compile 'com.android.support:cardview-v7:22.1.0'
|
||||||
|
|
||||||
// UI testing libs
|
// Unit tests in the local JVM with Robolectric
|
||||||
androidTestCompile 'com.android.support.test:runner:0.2'
|
// https://developer.android.com/training/testing/unit-testing/local-unit-tests.html
|
||||||
androidTestCompile 'com.android.support.test:rules:0.2'
|
// https://github.com/nenick/AndroidStudioAndRobolectric
|
||||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.1'
|
// http://www.vogella.com/tutorials/Robolectric/article.html
|
||||||
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.1'
|
testCompile 'junit:junit:4.12'
|
||||||
|
testCompile 'org.robolectric:robolectric:3.0-rc3'
|
||||||
|
|
||||||
|
// UI testing with Espresso
|
||||||
|
androidTestCompile 'com.android.support.test:runner:0.3'
|
||||||
|
androidTestCompile 'com.android.support.test:rules:0.3'
|
||||||
|
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
|
||||||
|
androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2') {
|
||||||
|
exclude group: 'com.android.support', module: 'appcompat'
|
||||||
|
exclude group: 'com.android.support', module: 'support-v4'
|
||||||
|
exclude module: 'recyclerview-v7'
|
||||||
|
}
|
||||||
|
|
||||||
// Temporary workaround for bug: https://code.google.com/p/android-test-kit/issues/detail?id=136
|
// 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
|
// from https://github.com/googlesamples/android-testing/blob/master/build.gradle#L21
|
||||||
@ -31,7 +44,7 @@ dependencies {
|
|||||||
compile 'com.jpardogo.materialtabstrip:library:1.0.9'
|
compile 'com.jpardogo.materialtabstrip:library:1.0.9'
|
||||||
compile 'com.getbase:floatingactionbutton:1.9.0'
|
compile 'com.getbase:floatingactionbutton:1.9.0'
|
||||||
compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0'
|
compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0'
|
||||||
compile "com.splitwise:tokenautocomplete:1.3.3@aar"
|
compile 'com.splitwise:tokenautocomplete:1.3.3@aar'
|
||||||
compile 'se.emilsjolander:stickylistheaders:2.6.0'
|
compile 'se.emilsjolander:stickylistheaders:2.6.0'
|
||||||
compile 'org.sufficientlysecure:html-textview:1.1'
|
compile 'org.sufficientlysecure:html-textview:1.1'
|
||||||
compile 'com.mikepenz.materialdrawer:library:2.8.2@aar'
|
compile 'com.mikepenz.materialdrawer:library:2.8.2@aar'
|
||||||
@ -98,8 +111,16 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 15
|
minSdkVersion 15
|
||||||
targetSdkVersion 22
|
targetSdkVersion 22
|
||||||
|
versionCode 32300
|
||||||
|
versionName "3.2.3"
|
||||||
|
applicationId "org.sufficientlysecure.keychain"
|
||||||
|
// the androidjunitrunner is broken regarding coverage, see here:
|
||||||
|
// https://code.google.com/p/android/issues/detail?id=170607
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
// this workaround runner fixes the coverage problem, BUT doesn't work
|
||||||
|
// with android studio single test execution. use it to generate coverage
|
||||||
|
// data, but keep the other one otherwis
|
||||||
|
// testInstrumentationRunner "org.sufficientlysecure.keychain.JacocoWorkaroundJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@ -111,6 +132,25 @@ android {
|
|||||||
release {
|
release {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
|
||||||
|
// Reference them in the java files with e.g. BuildConfig.ACCOUNT_TYPE.
|
||||||
|
buildConfigField "String", "ACCOUNT_TYPE", "\"org.sufficientlysecure.keychain.account\""
|
||||||
|
|
||||||
|
// Reference them in .xml files.
|
||||||
|
resValue "string", "account_type", "org.sufficientlysecure.keychain.account"
|
||||||
|
}
|
||||||
|
|
||||||
|
debug {
|
||||||
|
applicationIdSuffix ".debug"
|
||||||
|
|
||||||
|
// Reference them in the java files with e.g. BuildConfig.ACCOUNT_TYPE.
|
||||||
|
buildConfigField "String", "ACCOUNT_TYPE", "\"org.sufficientlysecure.keychain.debug.account\""
|
||||||
|
|
||||||
|
// Reference them in .xml files.
|
||||||
|
resValue "string", "account_type", "org.sufficientlysecure.keychain.debug.account"
|
||||||
|
|
||||||
|
// Enable code coverage (Jacoco)
|
||||||
|
testCoverageEnabled true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,6 +203,42 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// apply plugin: 'spoon'
|
||||||
|
|
||||||
|
task jacocoTestReport(type:JacocoReport) {
|
||||||
|
group = "Reporting"
|
||||||
|
description = "Generate Jacoco coverage reports"
|
||||||
|
|
||||||
|
classDirectories = fileTree(
|
||||||
|
dir: "${buildDir}/intermediates/classes/debug",
|
||||||
|
excludes: ['**/R.class',
|
||||||
|
'**/R$*.class',
|
||||||
|
'**/*$ViewInjector*.*',
|
||||||
|
'**/BuildConfig.*',
|
||||||
|
'**/Manifest*.*']
|
||||||
|
)
|
||||||
|
|
||||||
|
sourceDirectories = files("${buildDir.parent}/src/main/java")
|
||||||
|
additionalSourceDirs = files([
|
||||||
|
"${buildDir}/generated/source/buildConfig/debug",
|
||||||
|
"${buildDir}/generated/source/r/debug"
|
||||||
|
])
|
||||||
|
executionData = files([
|
||||||
|
"${buildDir}/jacoco/testDebug.exec",
|
||||||
|
"${buildDir}/outputs/code-coverage/connected/coverage.ec"
|
||||||
|
])
|
||||||
|
|
||||||
|
reports {
|
||||||
|
xml.enabled = true
|
||||||
|
html.enabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix for: No report file available: [/home/travis/build/open-keychain/open-keychain/OpenKeychain/build/reports/cobertura/coverage.xml, /home/travis/build/open-keychain/open-keychain/OpenKeychain/build/reports/jacoco/test/jacocoTestReport.xml]
|
||||||
|
coveralls {
|
||||||
|
jacocoReportPath 'build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml'
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: This disables Lint!
|
// NOTE: This disables Lint!
|
||||||
tasks.whenTaskAdded { task ->
|
tasks.whenTaskAdded { task ->
|
||||||
if (task.name.contains('lint')) {
|
if (task.name.contains('lint')) {
|
||||||
|
BIN
OpenKeychain/build/outputs/code-coverage/connected/coverage.ec
Normal file
1032
OpenKeychain/src/androidTest/assets/valodim.pub.asc
Normal file
156
OpenKeychain/src/androidTest/assets/x.sec.asc
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
mQINBFTxJOIBEACpXpXjz5ivyR/uWJexJXZrLHrvBRcivQSxzvy5Owdun8MGOxzo
|
||||||
|
YTPAJzskZH4Gg9MIQj9puUke8A59o9KtNMAd3Szp6aQs3lv/eGOw0DUe6g8a6kJ+
|
||||||
|
oQsHMYYLnyuYrOvQrBewKhh4FynkryvUZCL25dgrMHeTeMAeslCscdT6JYd2gxgi
|
||||||
|
o0+Bn8R6BonS7hTZStfAOYB0SO7X+7R0CpPsMiHtNZASzLRkk53dB9AO6m81CurP
|
||||||
|
qh1oPjc7j1QIiwCszSD8IxuR3fH6nyUlMtqx/mRmKNlbIYnr3sN5VAMp7THq/Qjt
|
||||||
|
9XDs1XHBE+0H+yg7K4fPamSJsxy9zPq/rA9vy4pQIMzxugxeWEn4OPWtguRSYtI1
|
||||||
|
WA+Ki93QJ+DVRlpMvz/MA71ZkvWUHpFnIssU52NdHZrIZ21KCo7PiPRY9tQkVhz5
|
||||||
|
S39a8npSeCIwZ9bH3UGKrRyPkhal59rjPqhZky1X+O2iTfbbFrPE+I4b46pWX9XU
|
||||||
|
UgXDnUh8Qeymhh0f1DTcbquW2nwU/R1quKjw/TCZQxKnkAzCXsVFk2QoIG1qDVrU
|
||||||
|
YlKXTXoYaRfzhMSYSvh7JHqdldB+MyKAbXpLtZsYIlLkuqe/KI0IPfO69ajDxLOr
|
||||||
|
+wTtuA9T1SICGmQkjYkQiqVy4y7vDW4Zw143sPXreZhiBbLBoWAIJ/JcbQARAQAB
|
||||||
|
tAVYIDx4PokCNwQTAQoAIQULCQgHAwYVCgkLCAMEFgIDAQIZAQWCVPEk4wKeAQKb
|
||||||
|
AQAKCRCdYE0vMQcWo/TGD/9SEfE7LxWw4pPNTpVTECDztpLGrj7kCr0anO6XnCqL
|
||||||
|
3OYoZz5vZvKBmunoPT7VqerQIZCnhB0b0ZOI+l51J03Xwvfy1+MIQCnZz++JY94B
|
||||||
|
h5dv3QAj8WUJJ6KfKrAxoXGU67mNhW5oe3mXQd3/a0JjmjV2yzFFOS4edyWPQal9
|
||||||
|
lY6MpOKfEIiD6uRqh8A1A9jAstJ9c5XHVtuBv265DEK5tMyAU3cmtm0pEzgmvPoG
|
||||||
|
kxo1vDAhP9GKb+DcBrB6XuZ7Kahl+kDpEhmuI6drxJBhez169aE43dK7G3X1W78f
|
||||||
|
OyO0C4jWAy1kVj4aYT4qgJj8TwRoAwlzX5RqmK4RW17sX3nOlff/FQclepawOrU+
|
||||||
|
LO5ZgbQ4qG2yDJSJ+tcS2fIO1MoI5vPa7DIVEpMM7DbrPYVy0Ix/xv80MwKQhnWs
|
||||||
|
P5tLRGuJ7JbKPzqvBtG7xWow5isOMkqeBkU0yUr59tAtHwM6c3Vi2at9YBiraqBY
|
||||||
|
3mgEukIuNpSuhFSBhniVUovVGgj8LCLDL+mpuc9+HUzYWJKGks+eGY0Od+dJjgh+
|
||||||
|
wXQk0rTTkY80kKpIjREDOVuPRhsw5OYb63fbiaYwormPx4pXv2mitOYNAXy3YNpR
|
||||||
|
Xl5MvObYLQugpqtyjpijyyANbsHKWwClkL/vxnbcfRXF307NQGSwhs3gKpSuovVE
|
||||||
|
edEkI2QBI0VncGdwaWQ6QGRuczp0ZXN0Lm11Z2VuZ3VpbGQuY29tiQIcBBMBCgAG
|
||||||
|
BYJU9KuzAAoJEJ1gTS8xBxajnPEP+gL3MasL9GcXt0c93QkQsRay8IMCspM+Qt/X
|
||||||
|
rfoUSJb41V4nqeyumX/kRmY7/eMZJnxIJgn8aWmRjiPhasUMBfeXmm4RAmwHEkFP
|
||||||
|
6nw288dHIlKgRaAMwfs44thKxPKTkscLiSqbmrRhLULW840pIZkgtOLmhH2tiaa7
|
||||||
|
w/hgDCMgHfwZaSlQkEDfvAef7i61itNdBlL0e0CelzNL0Uc5P6b/Nvn5uWlrLIHZ
|
||||||
|
evOx8dswhmwz25D7KZQPh91JpAqoLk5hj3/DAbVxeUFK/Zt9o/bu+ij57qSfRif3
|
||||||
|
HlLydH98FFcetqLbGDt1/Eiea3tpqjK5Xgf+HOppQo2MpOCP64xNejGWnKx6M7yF
|
||||||
|
A/yiI4Ahe4uP9+SXar0FPLGXAfyc/pSrRN94dY+iXpWPCSlNRNAOyr1mxPCvw9MM
|
||||||
|
C5wYnfLLSWUMPYv9rQyr1pvYyxe2r0Fla6JAkRy5nMnR1RdorwafeMcyQi3nllBS
|
||||||
|
S0wzmagus1W2qqRbbOR8J5CXkkjbw9vV0vXFnCHq93ms9Ebnhr5rNzVndKnxVCmK
|
||||||
|
hftW7gFz8Qz3tG66eSWA/6VrRvfUNfR5cUBpVOARqxqYz7OPrGg0Lkfw+JPQX8v/
|
||||||
|
WMIfM6Jsj/koJmOb2y/h0rJ+iTBofPCoGONKIzlOK5c8bhSmU/27KX1xBacTgN9Q
|
||||||
|
hlulIbZs0SsqZAAS1odwZ3BpZCtjb29raWU6QGRuczp0ZXN0Lm11Z2VuZ3VpbGQu
|
||||||
|
Y29tiQIcBBMBCgAGBYJU901MAAoJEJ1gTS8xBxajHKAP/3WDeUfBkuq50v3E8SBx
|
||||||
|
qDHXomPncNOccRPS1/DYwxVp5JYE8NsFW2rlmeJiy4Rhm4MnptUFUTPMBfYOcHvT
|
||||||
|
oWr/3Gz0y5ik5oiPC5hAXWCPrNkdtgBugBByL+0+w2PWKQqwBIc9ef+7aIh28dKn
|
||||||
|
6zr71Au+DXJKqYz/jOqY3IJGzBkydxOPSy83WNPm9OR/zfhCOoCTDAy+0TGxYgjW
|
||||||
|
Idi4k4yDRWhAeF+GqJRuDf1RKZxPL1MjwhX0Lv4ndR+V3sECRVh6HRIBGYYti8kU
|
||||||
|
0Q9jdbYjU+guestyyvsWQBOaGRP+gGQY+fYdHb2YUK6jMcndPxbRonjaRY5Bcc0J
|
||||||
|
NAYAqFv/WyR7hd4SWXI8Zg3u+GV7x8ZbnUOwfvXBVvZohbycpoT7WVOPRhQ2CQ5P
|
||||||
|
uS2E5QaoA6fUfH2qUAaxlvnfMRPL9E1svIEFXMtf1GqO6hzzJWV6Qw59HODTy568
|
||||||
|
StloM6IVnlolJfxf7+aEQx+KrmxJqVVWlbZ5u5gzQa+Mak0N5F6m8w4VftyHT0W6
|
||||||
|
RinNh3IGbk64+uXYzAUhJ6badfXrrxTP0wkjPPPjNC/BKfPCzYhZnyGLCuFwlOqE
|
||||||
|
o1Nq6qMzSHOlxR08ycX0MLNEfWsqu2CV8YGLYThejOr8JSjyXM3ANHNB+AZ5/eeY
|
||||||
|
AGUc5XgxTbq/MpnytiBzaZfw0Tw7ZAAS1odwZ3BpZCtjb29raWU6Z2VuZXJpY0Bo
|
||||||
|
dHRwczovL211Z2VuZ3VpbGQuY29tL3BncGtleS50eHSJAhwEEwEIAAYFglT4KOIA
|
||||||
|
CgkQnWBNLzEHFqOZJQ//UL+/ZfikmVA8DDIbbd8beIgJ0uRZgQ4aVuxRNiWhxQbl
|
||||||
|
5KPQaiztrwr3ESgyf3HjjUyGhg5/UP+ObvloCqcwCphSrBNxHiEp31jLin7QfWLp
|
||||||
|
vzPOjtMJNEeq3yeXq+YpHw2VZ+nod+XD785YtLBqq1SipNSZEifRYnsLhqQOj6cB
|
||||||
|
t0F2RLBm5afLbd4KjelT7W7229lYlGJkoQEwrmVYASiMfeBDBnHJ1Xgp1P0dm1gS
|
||||||
|
ERVSzV2qW6w0/psOHq4cnq4XWSn/IRW3UYfHIDDZJG125keYoaJAbBnzC+OO4eV2
|
||||||
|
KheY5dkmaeT0RmgzpEyJq5PRDNpnoXvSYuC9KGiNTbCiQ3liJnUJslbzDwvTyTfa
|
||||||
|
wxUba4dXhpcSE8mjXa/9/vMJER5yW0zzkSa+fjIIZcL0PQqlTeDBzFMSA9Ut9Y+m
|
||||||
|
6vdGwelIeyb38dWvsDC1NUUR2pydTsRtFM1o7jUtGoovQlLh5/9x/YDh8vqpzip3
|
||||||
|
L5SSvN4RdXN4P+EZor4xfi0PVbHoDZusxyPfKEFlCnX6k9qEaWplacLbl8M8mtpv
|
||||||
|
++3vYVW6hA3rK/6XdftFDJpbmj+zTXvrATE/lvPcJHiswiuMLx9BPoEiv1MGXTyE
|
||||||
|
6TxB5H7L1eCQWfbZYnkEWiuXlfDyofvDfufiGTsBAQzN3ePuhYUNznUB+1syD4E=
|
||||||
|
=Sxs8
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
|
lQdGBFTxJOIBEACpXpXjz5ivyR/uWJexJXZrLHrvBRcivQSxzvy5Owdun8MGOxzo
|
||||||
|
YTPAJzskZH4Gg9MIQj9puUke8A59o9KtNMAd3Szp6aQs3lv/eGOw0DUe6g8a6kJ+
|
||||||
|
oQsHMYYLnyuYrOvQrBewKhh4FynkryvUZCL25dgrMHeTeMAeslCscdT6JYd2gxgi
|
||||||
|
o0+Bn8R6BonS7hTZStfAOYB0SO7X+7R0CpPsMiHtNZASzLRkk53dB9AO6m81CurP
|
||||||
|
qh1oPjc7j1QIiwCszSD8IxuR3fH6nyUlMtqx/mRmKNlbIYnr3sN5VAMp7THq/Qjt
|
||||||
|
9XDs1XHBE+0H+yg7K4fPamSJsxy9zPq/rA9vy4pQIMzxugxeWEn4OPWtguRSYtI1
|
||||||
|
WA+Ki93QJ+DVRlpMvz/MA71ZkvWUHpFnIssU52NdHZrIZ21KCo7PiPRY9tQkVhz5
|
||||||
|
S39a8npSeCIwZ9bH3UGKrRyPkhal59rjPqhZky1X+O2iTfbbFrPE+I4b46pWX9XU
|
||||||
|
UgXDnUh8Qeymhh0f1DTcbquW2nwU/R1quKjw/TCZQxKnkAzCXsVFk2QoIG1qDVrU
|
||||||
|
YlKXTXoYaRfzhMSYSvh7JHqdldB+MyKAbXpLtZsYIlLkuqe/KI0IPfO69ajDxLOr
|
||||||
|
+wTtuA9T1SICGmQkjYkQiqVy4y7vDW4Zw143sPXreZhiBbLBoWAIJ/JcbQARAQAB
|
||||||
|
/gkDCMorb5SPPoHekALY3vvpC5rmKLs5ULOzs9BaI0qq/D5kQjSw4WnQIJwLTgI8
|
||||||
|
iwRYL+pUn7t8txDAEWKCSI1kVUaNocWYeUpiEYo9WWWHwGpf2ZZXwS/cQYi1APD5
|
||||||
|
U8uN/fKwFyRzLEXi++b6Lp4GA3l6f3sEwpSu1Wz/jRKDAweBENrlrCPEbn+VNkF8
|
||||||
|
X4aIhFNr95WFmTmjNp1AP+Ons4uny4sBVJqkbzoosLNSBHN05+mWsrWepTVGI0VP
|
||||||
|
SPmm/PY2st+zwwRPjGbWvEcFHUCnZMGrF1bhl7nQ+tZh5KTgCe/gcPVlf3ZQtEqG
|
||||||
|
RdzlWA1jsDF4h1/vpynOyONpZ5Hx0QWe2z1C7VeR4SsgASdbtX4EWj4Squpya0rf
|
||||||
|
X4k/EoY7vYX/OoOfi5aExkM79vHzN2EM89KI9ecoCfHy5x6Ovlhep7mL/p48zJZU
|
||||||
|
Q5pDfT5aLL6XV6T2gOWRFnowdETEBHWt6qYRhW47mvkdAiEBBpPi3sQnYM7M1hvD
|
||||||
|
2/yjogrgCUgAfCvh4vJbu2LPiGHkqFoS2iEEDexAcQ8REw+ePi4cg818R23zAJDV
|
||||||
|
xdLqpKig71YDTDxxkCuNoJeHOdFa2I9J9hjlTgYEBU6oJ+9TlsIueEhDnEGiScmn
|
||||||
|
SrhsFOcNyi+TJQg9KMx1AcX3RsCpOsRMxlqY0+zOh6YCh5MGXk/QQGGLnEFEax/V
|
||||||
|
ia2nCj91xA8BwPwjAOmb1Z9rdAkdLuPybLEAhlfu3ZFTDy8uQnL0LvC4grKUlYcm
|
||||||
|
soeWSaOihxZ6Q6ZxxWOysQX/kYBz5TCn/liSKr2qJZVxur487RYGIrWDKSJtPotc
|
||||||
|
Ke1gdUGG+EZJji6ik8blTfwtbuwCRX1WuiZiJidG5RtD0v84fm0U9uolY5HX4GFO
|
||||||
|
y8LCJVASyOvD0Af1gf6MIwAQTt+X3UIQr7zyN/QN5aBpVnI5wN5FmGoojz+MBDE2
|
||||||
|
/35kLYMQiQ3IspZR5apVpWJ2OPD/i0OunQU6t39zKR+0N5YPGLq2ZcTgRPGkSffz
|
||||||
|
DX0jxta8Gl55ysY9aKp3LvbZqvAyHBttnOuNzkw3KgTl1jAsOj3pjeVF+kPWwvAE
|
||||||
|
fjsSZjkXTFRZC7VG7bawzJK80Ux7EoQEwi7ixup5UK3DFwlp4eK0KGFJKaIKYj7V
|
||||||
|
8CHQE0muDx2s5KmSysYs+r2888r4MEAlEz1WyXdDoyQA0dIGfpvnts5AJCZqceal
|
||||||
|
akI5rQXwyD2K4X0N5IA54E1iJnYRwebXGsn8ldD2w+WEfWq3g87e3enmHSe48DuD
|
||||||
|
k3I0mf7V7AMEmrwuB+xVN035/QwScQeWE+tV6AdHleu4Ceo9b9VlmE/PYE3P+xKM
|
||||||
|
8ZZIe/wAeHhsocSlFL8c8hwXUNQ4TXOrsgfDF+3DYAXvQiwM1WxdPV8zSPU2Mu/2
|
||||||
|
4Q0Ba0ZepcKQNU+CPV88XRbzfb8NZ/mTmnHYX/P7vIWyg8i0w+z2Pm+/GFr1I7Gb
|
||||||
|
ily11beRweFUs97xnz21g+bJ2NrJkAP6ewFLO3FTWJoK4l/wFNRp9PcwvqGsqSxR
|
||||||
|
OdxrYN9RCmhiemACiJbYxoQN7wTCmmaHdtXcfqkYJjNsKydUwPV/Kaaf+PlWBOVp
|
||||||
|
yTOVh7MMPN8fjBPFYcFZ5xs/z/gpI7dFu6sXjEw8F6Pe+hPgp+IeWmQsUTgoQ8bD
|
||||||
|
HoK/MjjsuD88rWdDXdbfc/PpIC/cGqPpu89sOS8hSQJVgKn48TKbkIA+FrWogeuw
|
||||||
|
Ofr/IXYT2qlcwAmlrdRjLswyTsLM5eQU3VH5/IlVilQQjFZBbiqBjOW0BVggPHg+
|
||||||
|
iQI3BBMBCgAhBQsJCAcDBhUKCQsIAwQWAgMBAhkBBYJU8STjAp4BApsBAAoJEJ1g
|
||||||
|
TS8xBxaj9MYP/1IR8TsvFbDik81OlVMQIPO2ksauPuQKvRqc7pecKovc5ihnPm9m
|
||||||
|
8oGa6eg9PtWp6tAhkKeEHRvRk4j6XnUnTdfC9/LX4whAKdnP74lj3gGHl2/dACPx
|
||||||
|
ZQknop8qsDGhcZTruY2Fbmh7eZdB3f9rQmOaNXbLMUU5Lh53JY9BqX2Vjoyk4p8Q
|
||||||
|
iIPq5GqHwDUD2MCy0n1zlcdW24G/brkMQrm0zIBTdya2bSkTOCa8+gaTGjW8MCE/
|
||||||
|
0Ypv4NwGsHpe5nspqGX6QOkSGa4jp2vEkGF7PXr1oTjd0rsbdfVbvx87I7QLiNYD
|
||||||
|
LWRWPhphPiqAmPxPBGgDCXNflGqYrhFbXuxfec6V9/8VByV6lrA6tT4s7lmBtDio
|
||||||
|
bbIMlIn61xLZ8g7Uygjm89rsMhUSkwzsNus9hXLQjH/G/zQzApCGdaw/m0tEa4ns
|
||||||
|
lso/Oq8G0bvFajDmKw4ySp4GRTTJSvn20C0fAzpzdWLZq31gGKtqoFjeaAS6Qi42
|
||||||
|
lK6EVIGGeJVSi9UaCPwsIsMv6am5z34dTNhYkoaSz54ZjQ5350mOCH7BdCTStNOR
|
||||||
|
jzSQqkiNEQM5W49GGzDk5hvrd9uJpjCiuY/Hile/aaK05g0BfLdg2lFeXky85tgt
|
||||||
|
C6Cmq3KOmKPLIA1uwcpbAKWQv+/Gdtx9FcXfTs1AZLCGzeAqlK6i9UR50SQjZAEj
|
||||||
|
RWdwZ3BpZDpAZG5zOnRlc3QubXVnZW5ndWlsZC5jb22JAhwEEwEKAAYFglT0q7MA
|
||||||
|
CgkQnWBNLzEHFqOc8Q/6Avcxqwv0Zxe3Rz3dCRCxFrLwgwKykz5C39et+hRIlvjV
|
||||||
|
Xiep7K6Zf+RGZjv94xkmfEgmCfxpaZGOI+FqxQwF95eabhECbAcSQU/qfDbzx0ci
|
||||||
|
UqBFoAzB+zji2ErE8pOSxwuJKpuatGEtQtbzjSkhmSC04uaEfa2JprvD+GAMIyAd
|
||||||
|
/BlpKVCQQN+8B5/uLrWK010GUvR7QJ6XM0vRRzk/pv82+fm5aWssgdl687Hx2zCG
|
||||||
|
bDPbkPsplA+H3UmkCqguTmGPf8MBtXF5QUr9m32j9u76KPnupJ9GJ/ceUvJ0f3wU
|
||||||
|
Vx62otsYO3X8SJ5re2mqMrleB/4c6mlCjYyk4I/rjE16MZacrHozvIUD/KIjgCF7
|
||||||
|
i4/35JdqvQU8sZcB/Jz+lKtE33h1j6JelY8JKU1E0A7KvWbE8K/D0wwLnBid8stJ
|
||||||
|
ZQw9i/2tDKvWm9jLF7avQWVrokCRHLmcydHVF2ivBp94xzJCLeeWUFJLTDOZqC6z
|
||||||
|
VbaqpFts5HwnkJeSSNvD29XS9cWcIer3eaz0RueGvms3NWd0qfFUKYqF+1buAXPx
|
||||||
|
DPe0brp5JYD/pWtG99Q19HlxQGlU4BGrGpjPs4+saDQuR/D4k9Bfy/9Ywh8zomyP
|
||||||
|
+SgmY5vbL+HSsn6JMGh88KgY40ojOU4rlzxuFKZT/bspfXEFpxOA31CGW6UhtmzR
|
||||||
|
KypkABLWh3BncGlkK2Nvb2tpZTpAZG5zOnRlc3QubXVnZW5ndWlsZC5jb22JAhwE
|
||||||
|
EwEKAAYFglT3TUwACgkQnWBNLzEHFqMcoA//dYN5R8GS6rnS/cTxIHGoMdeiY+dw
|
||||||
|
05xxE9LX8NjDFWnklgTw2wVbauWZ4mLLhGGbgyem1QVRM8wF9g5we9Ohav/cbPTL
|
||||||
|
mKTmiI8LmEBdYI+s2R22AG6AEHIv7T7DY9YpCrAEhz15/7toiHbx0qfrOvvUC74N
|
||||||
|
ckqpjP+M6pjcgkbMGTJ3E49LLzdY0+b05H/N+EI6gJMMDL7RMbFiCNYh2LiTjINF
|
||||||
|
aEB4X4aolG4N/VEpnE8vUyPCFfQu/id1H5XewQJFWHodEgEZhi2LyRTRD2N1tiNT
|
||||||
|
6C56y3LK+xZAE5oZE/6AZBj59h0dvZhQrqMxyd0/FtGieNpFjkFxzQk0BgCoW/9b
|
||||||
|
JHuF3hJZcjxmDe74ZXvHxludQ7B+9cFW9miFvJymhPtZU49GFDYJDk+5LYTlBqgD
|
||||||
|
p9R8fapQBrGW+d8xE8v0TWy8gQVcy1/Uao7qHPMlZXpDDn0c4NPLnrxK2WgzohWe
|
||||||
|
WiUl/F/v5oRDH4qubEmpVVaVtnm7mDNBr4xqTQ3kXqbzDhV+3IdPRbpGKc2HcgZu
|
||||||
|
Trj65djMBSEnptp19euvFM/TCSM88+M0L8Ep88LNiFmfIYsK4XCU6oSjU2rqozNI
|
||||||
|
c6XFHTzJxfQws0R9ayq7YJXxgYthOF6M6vwlKPJczcA0c0H4Bnn955gAZRzleDFN
|
||||||
|
ur8ymfK2IHNpl/DRPDtkABLWh3BncGlkK2Nvb2tpZTpnZW5lcmljQGh0dHBzOi8v
|
||||||
|
bXVnZW5ndWlsZC5jb20vcGdwa2V5LnR4dIkCHAQTAQgABgWCVPgo4gAKCRCdYE0v
|
||||||
|
MQcWo5klD/9Qv79l+KSZUDwMMhtt3xt4iAnS5FmBDhpW7FE2JaHFBuXko9BqLO2v
|
||||||
|
CvcRKDJ/ceONTIaGDn9Q/45u+WgKpzAKmFKsE3EeISnfWMuKftB9Yum/M86O0wk0
|
||||||
|
R6rfJ5er5ikfDZVn6eh35cPvzli0sGqrVKKk1JkSJ9FiewuGpA6PpwG3QXZEsGbl
|
||||||
|
p8tt3gqN6VPtbvbb2ViUYmShATCuZVgBKIx94EMGccnVeCnU/R2bWBIRFVLNXapb
|
||||||
|
rDT+mw4erhyerhdZKf8hFbdRh8cgMNkkbXbmR5ihokBsGfML447h5XYqF5jl2SZp
|
||||||
|
5PRGaDOkTImrk9EM2mehe9Ji4L0oaI1NsKJDeWImdQmyVvMPC9PJN9rDFRtrh1eG
|
||||||
|
lxITyaNdr/3+8wkRHnJbTPORJr5+MghlwvQ9CqVN4MHMUxID1S31j6bq90bB6Uh7
|
||||||
|
Jvfx1a+wMLU1RRHanJ1OxG0UzWjuNS0aii9CUuHn/3H9gOHy+qnOKncvlJK83hF1
|
||||||
|
c3g/4RmivjF+LQ9VsegNm6zHI98oQWUKdfqT2oRpamVpwtuXwzya2m/77e9hVbqE
|
||||||
|
Desr/pd1+0UMmluaP7NNe+sBMT+W89wkeKzCK4wvH0E+gSK/UwZdPITpPEHkfsvV
|
||||||
|
4JBZ9tlieQRaK5eV8PKh+8N+5+IZOwEBDM3d4+6FhQ3OdQH7WzIPgQ==
|
||||||
|
=AYKY
|
||||||
|
-----END PGP PRIVATE KEY BLOCK-----
|
@ -0,0 +1,262 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain;
|
||||||
|
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.test.rule.ActivityTestRule;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.test.suitebuilder.annotation.LargeTest;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
|
||||||
|
import org.hamcrest.CoreMatchers;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||||
|
import org.sufficientlysecure.keychain.ui.MainActivity;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
|
|
||||||
|
import static android.support.test.espresso.Espresso.onData;
|
||||||
|
import static android.support.test.espresso.Espresso.onView;
|
||||||
|
import static android.support.test.espresso.Espresso.pressBack;
|
||||||
|
import static android.support.test.espresso.action.ViewActions.click;
|
||||||
|
import static android.support.test.espresso.action.ViewActions.typeText;
|
||||||
|
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static android.support.test.espresso.contrib.DrawerActions.openDrawer;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||||
|
import static org.hamcrest.CoreMatchers.allOf;
|
||||||
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.randomString;
|
||||||
|
import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken;
|
||||||
|
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId;
|
||||||
|
import static org.sufficientlysecure.keychain.matcher.DrawableMatcher.withDrawable;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@LargeTest
|
||||||
|
public class AsymmetricOperationTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ActivityTestRule<MainActivity> mActivity
|
||||||
|
= new ActivityTestRule<MainActivity>(MainActivity.class) {
|
||||||
|
@Override
|
||||||
|
protected Intent getActivityIntent() {
|
||||||
|
Intent intent = super.getActivityIntent();
|
||||||
|
intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
Activity activity = mActivity.getActivity();
|
||||||
|
|
||||||
|
// import these two, make sure they're there
|
||||||
|
importKeysFromResource(activity, "x.sec.asc");
|
||||||
|
|
||||||
|
// make sure no passphrases are cached
|
||||||
|
PassphraseCacheService.clearCachedPassphrases(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTextEncryptDecryptFromToken() throws Exception {
|
||||||
|
|
||||||
|
// navigate to 'encrypt text'
|
||||||
|
openDrawer(R.id.drawer_layout);
|
||||||
|
onView(withText(R.string.nav_encrypt_decrypt)).perform(click());
|
||||||
|
onView(withId(R.id.encrypt_text)).perform(click());
|
||||||
|
|
||||||
|
String cleartext = randomString(10, 30);
|
||||||
|
|
||||||
|
{ // encrypt
|
||||||
|
|
||||||
|
// the EncryptKeyCompletionView is tested individually
|
||||||
|
onView(withId(R.id.recipient_list)).perform(tokenEncryptViewAddToken(0x9D604D2F310716A3L));
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_text_text)).perform(typeText(cleartext));
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_copy)).perform(click());
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to decrypt from clipboard view
|
||||||
|
pressBack();
|
||||||
|
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
|
||||||
|
|
||||||
|
{ // decrypt
|
||||||
|
onView(withId(R.id.passphrase_passphrase)).perform(typeText("x"));
|
||||||
|
onView(withText(R.string.btn_unlock)).perform(click());
|
||||||
|
|
||||||
|
onView(withId(R.id.decrypt_text_plaintext)).check(matches(
|
||||||
|
withText(cleartext)));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_encrypted)));
|
||||||
|
onView(withId(R.id.result_signature_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_no_signature)));
|
||||||
|
onView(withId(R.id.result_signature_layout)).check(matches(
|
||||||
|
not(isDisplayed())));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_lock_closed_24dp)));
|
||||||
|
onView(withId(R.id.result_signature_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_signature_unknown_cutout_24dp)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTextEncryptDecryptFromKeyView() throws Exception {
|
||||||
|
|
||||||
|
String cleartext = randomString(10, 30);
|
||||||
|
|
||||||
|
{ // encrypt
|
||||||
|
|
||||||
|
// navigate to edit key dialog
|
||||||
|
onData(withKeyItemId(0x9D604D2F310716A3L))
|
||||||
|
.inAdapterView(allOf(isAssignableFrom(AdapterView.class),
|
||||||
|
isDescendantOfA(withId(R.id.key_list_list))))
|
||||||
|
.perform(click());
|
||||||
|
onView(withId(R.id.view_key_action_encrypt_text)).perform(click());
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_text_text)).perform(typeText(cleartext));
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_copy)).perform(click());
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to decrypt from clipboard view
|
||||||
|
pressBack();
|
||||||
|
pressBack();
|
||||||
|
|
||||||
|
openDrawer(R.id.drawer_layout);
|
||||||
|
onView(withText(R.string.nav_encrypt_decrypt)).perform(click());
|
||||||
|
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
|
||||||
|
|
||||||
|
{ // decrypt
|
||||||
|
|
||||||
|
onView(withId(R.id.passphrase_passphrase)).perform(typeText("x"));
|
||||||
|
onView(withText(R.string.btn_unlock)).perform(click());
|
||||||
|
|
||||||
|
onView(withId(R.id.decrypt_text_plaintext)).check(matches(
|
||||||
|
withText(cleartext)));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_encrypted)));
|
||||||
|
onView(withId(R.id.result_signature_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_no_signature)));
|
||||||
|
onView(withId(R.id.result_signature_layout)).check(matches(
|
||||||
|
not(isDisplayed())));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_lock_closed_24dp)));
|
||||||
|
onView(withId(R.id.result_signature_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_signature_unknown_cutout_24dp)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pressBack();
|
||||||
|
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
|
||||||
|
|
||||||
|
{ // decrypt again, passphrase should be cached
|
||||||
|
|
||||||
|
onView(withId(R.id.decrypt_text_plaintext)).check(matches(
|
||||||
|
withText(cleartext)));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_encrypted)));
|
||||||
|
onView(withId(R.id.result_signature_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_no_signature)));
|
||||||
|
onView(withId(R.id.result_signature_layout)).check(matches(
|
||||||
|
not(isDisplayed())));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_lock_closed_24dp)));
|
||||||
|
onView(withId(R.id.result_signature_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_signature_unknown_cutout_24dp)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSignVerify() throws Exception {
|
||||||
|
|
||||||
|
String cleartext = randomString(10, 30);
|
||||||
|
|
||||||
|
// navigate to 'encrypt text'
|
||||||
|
openDrawer(R.id.drawer_layout);
|
||||||
|
onView(withText(R.string.nav_encrypt_decrypt)).perform(click());
|
||||||
|
onView(withId(R.id.encrypt_text)).perform(click());
|
||||||
|
|
||||||
|
{ // sign
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_copy)).perform(click());
|
||||||
|
checkSnackbar(Style.ERROR, R.string.error_empty_text);
|
||||||
|
|
||||||
|
// navigate to edit key dialog
|
||||||
|
onView(withId(R.id.sign)).perform(click());
|
||||||
|
onData(withKeyItemId(0x9D604D2F310716A3L))
|
||||||
|
.inAdapterView(isAssignableFrom(AdapterView.class))
|
||||||
|
.perform(click());
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_text_text)).perform(typeText(cleartext));
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_copy)).perform(click());
|
||||||
|
|
||||||
|
onView(withId(R.id.passphrase_passphrase)).perform(typeText("x"));
|
||||||
|
onView(withText(R.string.btn_unlock)).perform(click());
|
||||||
|
|
||||||
|
checkSnackbar(Style.OK, R.string.msg_se_success);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to decrypt from clipboard view
|
||||||
|
pressBack();
|
||||||
|
|
||||||
|
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
|
||||||
|
|
||||||
|
{ // decrypt
|
||||||
|
|
||||||
|
onView(withId(R.id.decrypt_text_plaintext)).check(matches(
|
||||||
|
// startsWith because there may be extra newlines
|
||||||
|
withText(CoreMatchers.startsWith(cleartext))));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_not_encrypted)));
|
||||||
|
onView(withId(R.id.result_signature_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_signature_secret)));
|
||||||
|
onView(withId(R.id.result_signature_layout)).check(matches(
|
||||||
|
isDisplayed()));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_lock_open_24dp)));
|
||||||
|
onView(withId(R.id.result_signature_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_signature_verified_cutout_24dp)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,15 +17,18 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain;
|
package org.sufficientlysecure.keychain;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
import android.support.test.rule.ActivityTestRule;
|
import android.support.test.rule.ActivityTestRule;
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.test.suitebuilder.annotation.LargeTest;
|
||||||
import android.text.method.HideReturnsTransformationMethod;
|
import android.text.method.HideReturnsTransformationMethod;
|
||||||
import android.text.method.PasswordTransformationMethod;
|
import android.text.method.PasswordTransformationMethod;
|
||||||
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
import org.sufficientlysecure.keychain.ui.MainActivity;
|
||||||
|
|
||||||
import static android.support.test.espresso.Espresso.onView;
|
import static android.support.test.espresso.Espresso.onView;
|
||||||
import static android.support.test.espresso.action.ViewActions.click;
|
import static android.support.test.espresso.action.ViewActions.click;
|
||||||
@ -44,6 +47,7 @@ import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withError
|
|||||||
import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withTransformationMethod;
|
import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withTransformationMethod;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@LargeTest
|
||||||
public class CreateKeyActivityTest {
|
public class CreateKeyActivityTest {
|
||||||
|
|
||||||
public static final String SAMPLE_NAME = "Sample Name";
|
public static final String SAMPLE_NAME = "Sample Name";
|
||||||
@ -52,10 +56,21 @@ public class CreateKeyActivityTest {
|
|||||||
public static final String SAMPLE_PASSWORD = "sample_password";
|
public static final String SAMPLE_PASSWORD = "sample_password";
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public ActivityTestRule<CreateKeyActivity> mActivityRule = new ActivityTestRule<>(CreateKeyActivity.class);
|
public final ActivityTestRule<MainActivity> mActivity
|
||||||
|
= new ActivityTestRule<MainActivity>(MainActivity.class) {
|
||||||
|
@Override
|
||||||
|
protected Intent getActivityIntent() {
|
||||||
|
Intent intent = super.getActivityIntent();
|
||||||
|
intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateMyKey() {
|
public void testCreateMyKey() {
|
||||||
|
|
||||||
|
mActivity.getActivity();
|
||||||
|
|
||||||
// Clicks create my key
|
// Clicks create my key
|
||||||
onView(withId(R.id.create_key_create_key_button))
|
onView(withId(R.id.create_key_create_key_button))
|
||||||
.perform(click());
|
.perform(click());
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain;
|
||||||
|
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.test.rule.ActivityTestRule;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.test.suitebuilder.annotation.LargeTest;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||||
|
import org.sufficientlysecure.keychain.ui.MainActivity;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
|
|
||||||
|
import static android.support.test.espresso.Espresso.onData;
|
||||||
|
import static android.support.test.espresso.Espresso.onView;
|
||||||
|
import static android.support.test.espresso.action.ViewActions.click;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||||
|
import static org.hamcrest.CoreMatchers.allOf;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource;
|
||||||
|
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId;
|
||||||
|
|
||||||
|
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@LargeTest
|
||||||
|
public class EditKeyTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ActivityTestRule<MainActivity> mActivity
|
||||||
|
= new ActivityTestRule<MainActivity>(MainActivity.class) {
|
||||||
|
@Override
|
||||||
|
protected Intent getActivityIntent() {
|
||||||
|
Intent intent = super.getActivityIntent();
|
||||||
|
intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test01Edit() throws Exception {
|
||||||
|
Activity activity = mActivity.getActivity();
|
||||||
|
|
||||||
|
new KeychainDatabase(activity).clearDatabase();
|
||||||
|
|
||||||
|
// import key for testing, get a stable initial state
|
||||||
|
importKeysFromResource(activity, "x.sec.asc");
|
||||||
|
|
||||||
|
// navigate to edit key dialog
|
||||||
|
onData(withKeyItemId(0x9D604D2F310716A3L))
|
||||||
|
.inAdapterView(allOf(isAssignableFrom(AdapterView.class),
|
||||||
|
isDescendantOfA(withId(R.id.key_list_list))))
|
||||||
|
.perform(click());
|
||||||
|
onView(withId(R.id.menu_key_view_edit)).perform(click());
|
||||||
|
|
||||||
|
// no-op should yield snackbar
|
||||||
|
onView(withText(R.string.btn_save)).perform(click());
|
||||||
|
checkSnackbar(Style.ERROR, R.string.msg_mf_error_noop);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.test.espresso.matcher.ViewMatchers;
|
||||||
|
import android.support.test.rule.ActivityTestRule;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.test.suitebuilder.annotation.LargeTest;
|
||||||
|
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
import org.sufficientlysecure.keychain.ui.MainActivity;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
|
|
||||||
|
import static android.support.test.InstrumentationRegistry.getInstrumentation;
|
||||||
|
import static android.support.test.espresso.Espresso.onView;
|
||||||
|
import static android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
|
||||||
|
import static android.support.test.espresso.Espresso.pressBack;
|
||||||
|
import static android.support.test.espresso.action.ViewActions.click;
|
||||||
|
import static android.support.test.espresso.action.ViewActions.typeText;
|
||||||
|
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static android.support.test.espresso.contrib.DrawerActions.openDrawer;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||||
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.checkSnackbar;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.randomString;
|
||||||
|
import static org.sufficientlysecure.keychain.matcher.DrawableMatcher.withDrawable;
|
||||||
|
|
||||||
|
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@LargeTest
|
||||||
|
public class EncryptDecryptSymmetricTests {
|
||||||
|
|
||||||
|
public static final String PASSPHRASE = randomString(5, 20);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ActivityTestRule<MainActivity> mActivity
|
||||||
|
= new ActivityTestRule<MainActivity>(MainActivity.class) {
|
||||||
|
@Override
|
||||||
|
protected Intent getActivityIntent() {
|
||||||
|
Intent intent = super.getActivityIntent();
|
||||||
|
intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSymmetricTextEncryptDecrypt() throws Exception {
|
||||||
|
|
||||||
|
MainActivity activity = mActivity.getActivity();
|
||||||
|
|
||||||
|
String text = randomString(10, 30);
|
||||||
|
|
||||||
|
// navigate to encrypt/decrypt
|
||||||
|
openDrawer(R.id.drawer_layout);
|
||||||
|
onView(ViewMatchers.withText(R.string.nav_encrypt_decrypt)).perform(click());
|
||||||
|
onView(withId(R.id.encrypt_text)).perform(click());
|
||||||
|
|
||||||
|
{
|
||||||
|
onView(withId(R.id.encrypt_text_text)).perform(typeText(text));
|
||||||
|
|
||||||
|
openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext());
|
||||||
|
onView(withText(R.string.label_symmetric)).perform(click());
|
||||||
|
|
||||||
|
onView(withId(R.id.passphrase)).perform(typeText(PASSPHRASE));
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_copy)).perform(click());
|
||||||
|
|
||||||
|
checkSnackbar(Style.ERROR, R.string.passphrases_do_not_match);
|
||||||
|
|
||||||
|
onView(withId(R.id.passphraseAgain)).perform(typeText(PASSPHRASE));
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_text_text)).check(matches(withText(text)));
|
||||||
|
|
||||||
|
onView(withId(R.id.encrypt_copy)).perform(click());
|
||||||
|
|
||||||
|
checkSnackbar(Style.OK, R.string.msg_se_success);
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to decrypt from clipboard view
|
||||||
|
pressBack();
|
||||||
|
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
|
||||||
|
|
||||||
|
{
|
||||||
|
onView(withId(R.id.passphrase_passphrase)).perform(typeText(PASSPHRASE));
|
||||||
|
onView(withText(R.string.btn_unlock)).perform(click());
|
||||||
|
|
||||||
|
onView(withId(R.id.decrypt_text_plaintext)).check(matches(
|
||||||
|
withText(text)));
|
||||||
|
|
||||||
|
// TODO write generic status verifier
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_encrypted)));
|
||||||
|
onView(withId(R.id.result_signature_text)).check(matches(
|
||||||
|
withText(R.string.decrypt_result_no_signature)));
|
||||||
|
onView(withId(R.id.result_signature_layout)).check(matches(
|
||||||
|
not(isDisplayed())));
|
||||||
|
|
||||||
|
onView(withId(R.id.result_encryption_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_lock_closed_24dp)));
|
||||||
|
onView(withId(R.id.result_signature_icon)).check(matches(
|
||||||
|
withDrawable(R.drawable.status_signature_unknown_cutout_24dp)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain;
|
||||||
|
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.test.espresso.action.ViewActions;
|
||||||
|
import android.support.test.espresso.matcher.RootMatchers;
|
||||||
|
import android.support.test.rule.ActivityTestRule;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.test.suitebuilder.annotation.LargeTest;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.sufficientlysecure.keychain.ui.EncryptTextActivity;
|
||||||
|
|
||||||
|
import static android.support.test.espresso.Espresso.onData;
|
||||||
|
import static android.support.test.espresso.Espresso.onView;
|
||||||
|
import static android.support.test.espresso.action.ViewActions.click;
|
||||||
|
import static android.support.test.espresso.action.ViewActions.typeText;
|
||||||
|
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static org.hamcrest.CoreMatchers.allOf;
|
||||||
|
import static org.sufficientlysecure.keychain.TestHelpers.importKeysFromResource;
|
||||||
|
import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken;
|
||||||
|
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId;
|
||||||
|
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyToken;
|
||||||
|
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@LargeTest
|
||||||
|
public class EncryptKeyCompletionViewTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final ActivityTestRule<EncryptTextActivity> mActivity
|
||||||
|
= new ActivityTestRule<>(EncryptTextActivity.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTextEncryptDecryptFromToken() throws Exception {
|
||||||
|
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.putExtra(EncryptTextActivity.EXTRA_ENCRYPTION_KEY_IDS, new long[] { 0x9D604D2F310716A3L });
|
||||||
|
Activity activity = mActivity.launchActivity(intent);
|
||||||
|
|
||||||
|
// import these two, make sure they're there
|
||||||
|
importKeysFromResource(activity, "x.sec.asc");
|
||||||
|
|
||||||
|
// check if the element passed in from intent
|
||||||
|
onView(withId(R.id.recipient_list)).check(matches(withKeyToken(0x9D604D2F310716A3L)));
|
||||||
|
onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL));
|
||||||
|
|
||||||
|
// type X, select from list, check if it's there
|
||||||
|
onView(withId(R.id.recipient_list)).perform(typeText("x"));
|
||||||
|
onData(withKeyItemId(0x9D604D2F310716A3L)).inRoot(RootMatchers.isPlatformPopup())
|
||||||
|
.inAdapterView(allOf(isAssignableFrom(AdapterView.class),
|
||||||
|
hasDescendant(withId(R.id.key_list_item_name)))).perform(click());
|
||||||
|
onView(withId(R.id.recipient_list)).check(matches(withKeyToken(0x9D604D2F310716A3L)));
|
||||||
|
onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL));
|
||||||
|
onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL));
|
||||||
|
|
||||||
|
// add directly, check if it's there
|
||||||
|
onView(withId(R.id.recipient_list)).perform(tokenEncryptViewAddToken(0x9D604D2F310716A3L));
|
||||||
|
onView(withId(R.id.recipient_list)).check(matches(withKeyToken(0x9D604D2F310716A3L)));
|
||||||
|
onView(withId(R.id.recipient_list)).perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package org.sufficientlysecure.keychain;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.test.runner.AndroidJUnitRunner;
|
||||||
|
|
||||||
|
|
||||||
|
public class JacocoWorkaroundJUnitRunner extends AndroidJUnitRunner {
|
||||||
|
static {
|
||||||
|
System.setProperty("jacoco-agent.destfile", "/data/data/"
|
||||||
|
+ BuildConfig.APPLICATION_ID + "/coverage.ec");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish(int resultCode, Bundle results) {
|
||||||
|
try {
|
||||||
|
Class rt = Class.forName("org.jacoco.agent.rt.RT");
|
||||||
|
Method getAgent = rt.getMethod("getAgent");
|
||||||
|
Method dump = getAgent.getReturnType().getMethod("dump", boolean.class);
|
||||||
|
Object agent = getAgent.invoke(null);
|
||||||
|
dump.invoke(agent, false);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
super.finish(resultCode, results);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
|
||||||
|
import org.hamcrest.CoreMatchers;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
|
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||||
|
|
||||||
|
import static android.support.test.InstrumentationRegistry.getInstrumentation;
|
||||||
|
import static android.support.test.espresso.Espresso.onView;
|
||||||
|
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||||
|
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSnackbarLineColor;
|
||||||
|
|
||||||
|
|
||||||
|
public class TestHelpers {
|
||||||
|
|
||||||
|
|
||||||
|
public static void checkSnackbar(Style style, @StringRes Integer text) {
|
||||||
|
|
||||||
|
onView(withClassName(CoreMatchers.endsWith("Snackbar")))
|
||||||
|
.check(matches(withSnackbarLineColor(style.mLineColor)));
|
||||||
|
|
||||||
|
if (text != null) {
|
||||||
|
onView(withClassName(CoreMatchers.endsWith("Snackbar")))
|
||||||
|
.check(matches(hasDescendant(withText(text))));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void importKeysFromResource(Context context, String name) throws Exception {
|
||||||
|
IteratorWithIOThrow<UncachedKeyRing> stream = UncachedKeyRing.fromStream(
|
||||||
|
getInstrumentation().getContext().getAssets().open(name));
|
||||||
|
|
||||||
|
ProviderHelper helper = new ProviderHelper(context);
|
||||||
|
while(stream.hasNext()) {
|
||||||
|
UncachedKeyRing ring = stream.next();
|
||||||
|
if (ring.isSecret()) {
|
||||||
|
helper.saveSecretKeyRing(ring, new ProgressScaler());
|
||||||
|
} else {
|
||||||
|
helper.savePublicKeyRing(ring, new ProgressScaler());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String randomString(int min, int max) {
|
||||||
|
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789!@#$%^&*()-_=";
|
||||||
|
Random r = new Random();
|
||||||
|
StringBuilder passbuilder = new StringBuilder();
|
||||||
|
// 5% chance for an empty string
|
||||||
|
for(int i = 0, j = r.nextInt(max)+min; i < j; i++) {
|
||||||
|
passbuilder.append(chars.charAt(r.nextInt(chars.length())));
|
||||||
|
}
|
||||||
|
return passbuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.actions;
|
||||||
|
|
||||||
|
|
||||||
|
import android.support.test.espresso.UiController;
|
||||||
|
import android.support.test.espresso.ViewAction;
|
||||||
|
import android.support.test.espresso.matcher.ViewMatchers;
|
||||||
|
import android.support.v4.view.GravityCompat;
|
||||||
|
import android.support.v4.widget.DrawerLayout;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.tokenautocomplete.TokenCompleteTextView;
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||||
|
|
||||||
|
import static android.support.test.InstrumentationRegistry.getTargetContext;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class CustomActions {
|
||||||
|
|
||||||
|
public static ViewAction tokenEncryptViewAddToken(long keyId) throws Exception {
|
||||||
|
CanonicalizedPublicKeyRing ring =
|
||||||
|
new ProviderHelper(getTargetContext()).getCanonicalizedPublicKeyRing(keyId);
|
||||||
|
final Object item = new KeyAdapter.KeyItem(ring);
|
||||||
|
|
||||||
|
return new ViewAction() {
|
||||||
|
@Override
|
||||||
|
public Matcher<View> getConstraints() {
|
||||||
|
return ViewMatchers.isAssignableFrom(TokenCompleteTextView.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "add completion token";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void perform(UiController uiController, View view) {
|
||||||
|
((TokenCompleteTextView) view).addObject(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ViewAction tokenViewAddToken(final Object item) {
|
||||||
|
return new ViewAction() {
|
||||||
|
@Override
|
||||||
|
public Matcher<View> getConstraints() {
|
||||||
|
return ViewMatchers.isAssignableFrom(TokenCompleteTextView.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "add completion token";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void perform(UiController uiController, View view) {
|
||||||
|
((TokenCompleteTextView) view).addObject(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.matcher;
|
||||||
|
|
||||||
|
|
||||||
|
import android.support.annotation.ColorRes;
|
||||||
|
import android.support.test.espresso.matcher.BoundedMatcher;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.nispok.snackbar.Snackbar;
|
||||||
|
import org.hamcrest.Description;
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
|
import org.sufficientlysecure.keychain.EncryptKeyCompletionViewTest;
|
||||||
|
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView;
|
||||||
|
|
||||||
|
import static android.support.test.internal.util.Checks.checkNotNull;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class CustomMatchers {
|
||||||
|
|
||||||
|
public static Matcher<View> withSnackbarLineColor(@ColorRes final int colorRes) {
|
||||||
|
return new BoundedMatcher<View, Snackbar>(Snackbar.class) {
|
||||||
|
public void describeTo(Description description) {
|
||||||
|
description.appendText("with color resource id: " + colorRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matchesSafely(Snackbar snackbar) {
|
||||||
|
return snackbar.getResources().getColor(colorRes) == snackbar.getLineColor();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matcher<Object> withKeyItemId(final long keyId) {
|
||||||
|
return new BoundedMatcher<Object, KeyItem>(KeyItem.class) {
|
||||||
|
@Override
|
||||||
|
public boolean matchesSafely(KeyItem item) {
|
||||||
|
return item.mKeyId == keyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void describeTo(Description description) {
|
||||||
|
description.appendText("with key id: " + keyId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matcher<View> withKeyToken(@ColorRes final long keyId) {
|
||||||
|
return new BoundedMatcher<View, EncryptKeyCompletionView>(EncryptKeyCompletionView.class) {
|
||||||
|
public void describeTo(Description description) {
|
||||||
|
description.appendText("with key id token: " + keyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matchesSafely(EncryptKeyCompletionView tokenView) {
|
||||||
|
for (Object object : tokenView.getObjects()) {
|
||||||
|
if (object instanceof KeyItem && ((KeyItem) object).mKeyId == keyId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Xavi Rigau <xrigau@gmail.com>
|
||||||
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* From the droidcon anroid espresso repository.
|
||||||
|
* https://github.com/xrigau/droidcon-android-espresso/
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.matcher;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.hamcrest.Description;
|
||||||
|
import org.hamcrest.TypeSafeMatcher;
|
||||||
|
|
||||||
|
|
||||||
|
public class DrawableMatcher extends TypeSafeMatcher<View> {
|
||||||
|
|
||||||
|
private final int mResourceId;
|
||||||
|
private final boolean mIgnoreFilters;
|
||||||
|
|
||||||
|
public DrawableMatcher(int resourceId, boolean ignoreFilters) {
|
||||||
|
super(View.class);
|
||||||
|
mResourceId = resourceId;
|
||||||
|
mIgnoreFilters = ignoreFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resourceName = null;
|
||||||
|
private Drawable expectedDrawable = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matchesSafely(View target) {
|
||||||
|
if (expectedDrawable == null) {
|
||||||
|
loadDrawableFromResources(target.getResources());
|
||||||
|
}
|
||||||
|
if (invalidExpectedDrawable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target instanceof ImageView) {
|
||||||
|
return hasImage((ImageView) target) || hasBackground(target);
|
||||||
|
}
|
||||||
|
if (target instanceof TextView) {
|
||||||
|
return hasCompoundDrawable((TextView) target) || hasBackground(target);
|
||||||
|
}
|
||||||
|
return hasBackground(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadDrawableFromResources(Resources resources) {
|
||||||
|
try {
|
||||||
|
expectedDrawable = resources.getDrawable(mResourceId);
|
||||||
|
resourceName = resources.getResourceEntryName(mResourceId);
|
||||||
|
} catch (Resources.NotFoundException ignored) {
|
||||||
|
// view could be from a context unaware of the resource id.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean invalidExpectedDrawable() {
|
||||||
|
return expectedDrawable == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasImage(ImageView target) {
|
||||||
|
return isSameDrawable(target.getDrawable());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasCompoundDrawable(TextView target) {
|
||||||
|
for (Drawable drawable : target.getCompoundDrawables()) {
|
||||||
|
if (isSameDrawable(drawable)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasBackground(View target) {
|
||||||
|
return isSameDrawable(target.getBackground());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSameDrawable(Drawable drawable) {
|
||||||
|
if (drawable == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// if those are both bitmap drawables, compare their bitmaps (ignores color filters, which is what we want!)
|
||||||
|
if (mIgnoreFilters && drawable instanceof BitmapDrawable && expectedDrawable instanceof BitmapDrawable) {
|
||||||
|
return ((BitmapDrawable) drawable).getBitmap().equals(((BitmapDrawable) expectedDrawable).getBitmap());
|
||||||
|
}
|
||||||
|
return expectedDrawable.getConstantState().equals(drawable.getConstantState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void describeTo(Description description) {
|
||||||
|
description.appendText("with drawable from resource id: ");
|
||||||
|
description.appendValue(mResourceId);
|
||||||
|
if (resourceName != null) {
|
||||||
|
description.appendText("[");
|
||||||
|
description.appendText(resourceName);
|
||||||
|
description.appendText("]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DrawableMatcher withDrawable(int resourceId, boolean ignoreFilters) {
|
||||||
|
return new DrawableMatcher(resourceId, ignoreFilters);
|
||||||
|
}
|
||||||
|
public static DrawableMatcher withDrawable(int resourceId) {
|
||||||
|
return new DrawableMatcher(resourceId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
* Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
||||||
|
BIN
OpenKeychain/src/debug/res/drawable-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
OpenKeychain/src/debug/res/drawable-hdpi/ic_stat_notify.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
OpenKeychain/src/debug/res/drawable-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
OpenKeychain/src/debug/res/drawable-mdpi/ic_stat_notify.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
OpenKeychain/src/debug/res/drawable-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
OpenKeychain/src/debug/res/drawable-xhdpi/ic_stat_notify.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
OpenKeychain/src/debug/res/drawable-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
OpenKeychain/src/debug/res/drawable-xxhdpi/ic_stat_notify.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
OpenKeychain/src/debug/res/drawable-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
OpenKeychain/src/debug/res/drawable-xxxhdpi/ic_stat_notify.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
@ -2,9 +2,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="org.sufficientlysecure.keychain"
|
package="org.sufficientlysecure.keychain"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto">
|
||||||
android:versionCode="32300"
|
|
||||||
android:versionName="3.2.3">
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
General remarks
|
General remarks
|
||||||
@ -50,9 +48,9 @@
|
|||||||
android:name="android.hardware.screen.portrait"
|
android:name="android.hardware.screen.portrait"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
|
||||||
<permission android:name="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE" />
|
<permission android:name="${applicationId}.WRITE_TEMPORARY_STORAGE" />
|
||||||
|
|
||||||
<uses-permission android:name="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE" />
|
<uses-permission android:name="${applicationId}.WRITE_TEMPORARY_STORAGE" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
@ -719,15 +717,15 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:process=":remote_api" />
|
android:process=":remote_api" />
|
||||||
<service
|
<service
|
||||||
android:name=".service.KeychainIntentService"
|
android:name=".service.KeychainNewService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
<service
|
<service
|
||||||
android:name=".service.CloudImportService"
|
android:name=".service.KeychainService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".provider.KeychainProvider"
|
android:name=".provider.KeychainProvider"
|
||||||
android:authorities="org.sufficientlysecure.keychain.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<!-- Internal classes of the remote APIs (not exported) -->
|
<!-- Internal classes of the remote APIs (not exported) -->
|
||||||
@ -805,9 +803,9 @@
|
|||||||
<!-- Storage Provider for temporary decrypted files -->
|
<!-- Storage Provider for temporary decrypted files -->
|
||||||
<provider
|
<provider
|
||||||
android:name=".provider.TemporaryStorageProvider"
|
android:name=".provider.TemporaryStorageProvider"
|
||||||
android:authorities="org.sufficientlysecure.keychain.tempstorage"
|
android:authorities="${applicationId}.tempstorage"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:writePermission="org.sufficientlysecure.keychain.WRITE_TEMPORARY_STORAGE" />
|
android:writePermission="${applicationId}.WRITE_TEMPORARY_STORAGE" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
@ -31,14 +31,17 @@ public final class Constants {
|
|||||||
public static final boolean DEBUG_LOG_DB_QUERIES = false;
|
public static final boolean DEBUG_LOG_DB_QUERIES = false;
|
||||||
public static final boolean DEBUG_SYNC_REMOVE_CONTACTS = false;
|
public static final boolean DEBUG_SYNC_REMOVE_CONTACTS = false;
|
||||||
|
|
||||||
public static final String TAG = "Keychain";
|
public static final String TAG = DEBUG ? "Keychain D" : "Keychain";
|
||||||
|
|
||||||
public static final String PACKAGE_NAME = "org.sufficientlysecure.keychain";
|
public static final String PACKAGE_NAME = "org.sufficientlysecure.keychain";
|
||||||
|
|
||||||
public static final String ACCOUNT_NAME = "OpenKeychain";
|
public static final String ACCOUNT_NAME = DEBUG ? "OpenKeychain D" : "OpenKeychain";
|
||||||
public static final String ACCOUNT_TYPE = PACKAGE_NAME + ".account";
|
public static final String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE;
|
||||||
public static final String CUSTOM_CONTACT_DATA_MIME_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key";
|
public static final String CUSTOM_CONTACT_DATA_MIME_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key";
|
||||||
|
|
||||||
|
public static final String PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";
|
||||||
|
public static final String TEMPSTORAGE_AUTHORITY = BuildConfig.APPLICATION_ID + ".tempstorage";
|
||||||
|
|
||||||
// as defined in http://tools.ietf.org/html/rfc3156, section 7
|
// as defined in http://tools.ietf.org/html/rfc3156, section 7
|
||||||
public static final String NFC_MIME = "application/pgp-keys";
|
public static final String NFC_MIME = "application/pgp-keys";
|
||||||
|
|
||||||
@ -84,6 +87,10 @@ public final class Constants {
|
|||||||
public static final String SEARCH_KEYBASE = "search_keybase_pref";
|
public static final String SEARCH_KEYBASE = "search_keybase_pref";
|
||||||
public static final String USE_DEFAULT_YUBIKEY_PIN = "useDefaultYubikeyPin";
|
public static final String USE_DEFAULT_YUBIKEY_PIN = "useDefaultYubikeyPin";
|
||||||
public static final String USE_NUMKEYPAD_FOR_YUBIKEY_PIN = "useNumKeypadForYubikeyPin";
|
public static final String USE_NUMKEYPAD_FOR_YUBIKEY_PIN = "useNumKeypadForYubikeyPin";
|
||||||
|
public static final String ENCRYPT_FILENAMES = "encryptFilenames";
|
||||||
|
public static final String FILE_USE_COMPRESSION = "useFileCompression";
|
||||||
|
public static final String TEXT_USE_COMPRESSION = "useTextCompression";
|
||||||
|
public static final String USE_ARMOR = "useArmor";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Defaults {
|
public static final class Defaults {
|
||||||
|
@ -128,8 +128,6 @@ public class KeychainApplication extends Application {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add OpenKeychain account to Android to link contacts with keys
|
* Add OpenKeychain account to Android to link contacts with keys
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
*/
|
*/
|
||||||
public static void setupAccountAsNeeded(Context context) {
|
public static void setupAccountAsNeeded(Context context) {
|
||||||
try {
|
try {
|
||||||
@ -165,7 +163,7 @@ public class KeychainApplication extends Application {
|
|||||||
int edgeDrawableId = context.getResources().getIdentifier("overscroll_edge", "drawable", "android");
|
int edgeDrawableId = context.getResources().getIdentifier("overscroll_edge", "drawable", "android");
|
||||||
Drawable androidEdge = context.getResources().getDrawable(edgeDrawableId);
|
Drawable androidEdge = context.getResources().getDrawable(edgeDrawableId);
|
||||||
androidEdge.setColorFilter(brandColor, PorterDuff.Mode.SRC_IN);
|
androidEdge.setColorFilter(brandColor, PorterDuff.Mode.SRC_IN);
|
||||||
} catch (Resources.NotFoundException e) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,10 @@ public class ClipboardReflection {
|
|||||||
Method methodGetPrimaryClip = clipboard.getClass().getMethod("getPrimaryClip");
|
Method methodGetPrimaryClip = clipboard.getClass().getMethod("getPrimaryClip");
|
||||||
Object clipData = methodGetPrimaryClip.invoke(clipboard);
|
Object clipData = methodGetPrimaryClip.invoke(clipboard);
|
||||||
|
|
||||||
|
if (clipData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// ClipData.Item clipDataItem = clipData.getItemAt(0);
|
// ClipData.Item clipDataItem = clipData.getItemAt(0);
|
||||||
Method methodGetItemAt = clipData.getClass().getMethod("getItemAt", int.class);
|
Method methodGetItemAt = clipData.getClass().getMethod("getItemAt", int.class);
|
||||||
Object clipDataItem = methodGetItemAt.invoke(clipData, 0);
|
Object clipDataItem = methodGetItemAt.invoke(clipData, 0);
|
||||||
|
@ -18,17 +18,20 @@
|
|||||||
package org.sufficientlysecure.keychain.operations;
|
package org.sufficientlysecure.keychain.operations;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface;
|
import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface;
|
||||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||||
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.PassphraseCacheService;
|
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||||
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public abstract class BaseOperation implements PassphraseCacheInterface {
|
public abstract class BaseOperation <T extends Parcelable> implements PassphraseCacheInterface {
|
||||||
|
|
||||||
final public Context mContext;
|
final public Context mContext;
|
||||||
final public Progressable mProgressable;
|
final public Progressable mProgressable;
|
||||||
@ -40,7 +43,7 @@ public abstract class BaseOperation implements PassphraseCacheInterface {
|
|||||||
* of common methods for progress, cancellation and passphrase cache handling.
|
* of common methods for progress, cancellation and passphrase cache handling.
|
||||||
*
|
*
|
||||||
* An "operation" in this sense is a high level operation which is called
|
* An "operation" in this sense is a high level operation which is called
|
||||||
* by the KeychainIntentService or OpenPgpService services. Concrete
|
* by the KeychainService or OpenPgpService services. Concrete
|
||||||
* subclasses of this class should implement either a single or a group of
|
* subclasses of this class should implement either a single or a group of
|
||||||
* related operations. An operation must rely solely on its input
|
* related operations. An operation must rely solely on its input
|
||||||
* parameters for operation specifics. It should also write a log of its
|
* parameters for operation specifics. It should also write a log of its
|
||||||
@ -49,7 +52,7 @@ public abstract class BaseOperation implements PassphraseCacheInterface {
|
|||||||
*
|
*
|
||||||
* An operation must *not* throw exceptions of any kind, errors should be
|
* An operation must *not* throw exceptions of any kind, errors should be
|
||||||
* handled as part of the OperationResult! Consequently, all handling of
|
* handled as part of the OperationResult! Consequently, all handling of
|
||||||
* errors in KeychainIntentService and OpenPgpService should consist of
|
* errors in KeychainService and OpenPgpService should consist of
|
||||||
* informational rather than operational means.
|
* informational rather than operational means.
|
||||||
*
|
*
|
||||||
* Note that subclasses of this class should be either Android- or
|
* Note that subclasses of this class should be either Android- or
|
||||||
@ -73,6 +76,10 @@ public abstract class BaseOperation implements PassphraseCacheInterface {
|
|||||||
mCancelled = cancelled;
|
mCancelled = cancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OperationResult execute(T input, CryptoInputParcel cryptoInput) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public void updateProgress(int message, int current, int total) {
|
public void updateProgress(int message, int current, int total) {
|
||||||
if (mProgressable != null) {
|
if (mProgressable != null) {
|
||||||
mProgressable.setProgress(message, current, total);
|
mProgressable.setProgress(message, current, total);
|
||||||
|
@ -64,7 +64,7 @@ public class CertifyOperation extends BaseOperation {
|
|||||||
super(context, providerHelper, progressable, cancelled);
|
super(context, providerHelper, progressable, cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CertifyResult certify(CertifyActionsParcel parcel, CryptoInputParcel cryptoInput, String keyServerUri) {
|
public CertifyResult execute(CertifyActionsParcel parcel, CryptoInputParcel cryptoInput) {
|
||||||
|
|
||||||
OperationLog log = new OperationLog();
|
OperationLog log = new OperationLog();
|
||||||
log.add(LogType.MSG_CRT, 0);
|
log.add(LogType.MSG_CRT, 0);
|
||||||
@ -79,13 +79,32 @@ public class CertifyOperation extends BaseOperation {
|
|||||||
log.add(LogType.MSG_CRT_UNLOCK, 1);
|
log.add(LogType.MSG_CRT_UNLOCK, 1);
|
||||||
certificationKey = secretKeyRing.getSecretKey();
|
certificationKey = secretKeyRing.getSecretKey();
|
||||||
|
|
||||||
|
Passphrase passphrase;
|
||||||
|
|
||||||
|
switch (certificationKey.getSecretKeyType()) {
|
||||||
|
case PIN:
|
||||||
|
case PATTERN:
|
||||||
|
case PASSPHRASE:
|
||||||
if (!cryptoInput.hasPassphrase()) {
|
if (!cryptoInput.hasPassphrase()) {
|
||||||
return new CertifyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
|
return new CertifyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
|
||||||
certificationKey.getKeyId(), certificationKey.getKeyId(), null));
|
certificationKey.getKeyId(), certificationKey.getKeyId(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
// certification is always with the master key id, so use that one
|
// certification is always with the master key id, so use that one
|
||||||
Passphrase passphrase = cryptoInput.getPassphrase();
|
passphrase = cryptoInput.getPassphrase();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PASSPHRASE_EMPTY:
|
||||||
|
passphrase = new Passphrase("");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DIVERT_TO_CARD:
|
||||||
|
passphrase = null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2);
|
||||||
|
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
|
||||||
|
}
|
||||||
|
|
||||||
if (!certificationKey.unlock(passphrase)) {
|
if (!certificationKey.unlock(passphrase)) {
|
||||||
log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2);
|
log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2);
|
||||||
@ -167,8 +186,8 @@ public class CertifyOperation extends BaseOperation {
|
|||||||
|
|
||||||
HkpKeyserver keyServer = null;
|
HkpKeyserver keyServer = null;
|
||||||
ImportExportOperation importExportOperation = null;
|
ImportExportOperation importExportOperation = null;
|
||||||
if (keyServerUri != null) {
|
if (parcel.keyServerUri != null) {
|
||||||
keyServer = new HkpKeyserver(keyServerUri);
|
keyServer = new HkpKeyserver(parcel.keyServerUri);
|
||||||
importExportOperation = new ImportExportOperation(mContext, mProviderHelper, mProgressable);
|
importExportOperation = new ImportExportOperation(mContext, mProviderHelper, mProgressable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,6 @@ import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
|||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
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.ProgressScaler;
|
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
@ -51,7 +50,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
* @see SaveKeyringParcel
|
* @see SaveKeyringParcel
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class EditKeyOperation extends BaseOperation {
|
public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
|
||||||
|
|
||||||
public EditKeyOperation(Context context, ProviderHelper providerHelper,
|
public EditKeyOperation(Context context, ProviderHelper providerHelper,
|
||||||
Progressable progressable, AtomicBoolean cancelled) {
|
Progressable progressable, AtomicBoolean cancelled) {
|
||||||
|
@ -38,7 +38,6 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
|
|||||||
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
|
||||||
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.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
@ -157,6 +156,15 @@ public class ImportExportOperation extends BaseOperation {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since the introduction of multithreaded import, we expect calling functions to handle the key sync i,e
|
||||||
|
* ContactSyncAdapterService.requestSync()
|
||||||
|
*
|
||||||
|
* @param entries keys to import
|
||||||
|
* @param num number of keys to import
|
||||||
|
* @param keyServerUri contains uri of keyserver to import from, if it is an import from cloud
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public ImportKeyResult importKeyRings(Iterator<ParcelableKeyRing> entries, int num, String keyServerUri) {
|
public ImportKeyResult importKeyRings(Iterator<ParcelableKeyRing> entries, int num, String keyServerUri) {
|
||||||
updateProgress(R.string.progress_importing, 0, 100);
|
updateProgress(R.string.progress_importing, 0, 100);
|
||||||
|
|
||||||
@ -244,25 +252,25 @@ public class ImportExportOperation extends BaseOperation {
|
|||||||
try {
|
try {
|
||||||
log.add(LogType.MSG_IMPORT_FETCH_KEYBASE, 2, entry.mKeybaseName);
|
log.add(LogType.MSG_IMPORT_FETCH_KEYBASE, 2, entry.mKeybaseName);
|
||||||
byte[] data = keybaseServer.get(entry.mKeybaseName).getBytes();
|
byte[] data = keybaseServer.get(entry.mKeybaseName).getBytes();
|
||||||
key = UncachedKeyRing.decodeFromData(data);
|
UncachedKeyRing keybaseKey = UncachedKeyRing.decodeFromData(data);
|
||||||
|
|
||||||
// If there already is a key (of keybase origin), merge the two
|
// If there already is a key, merge the two
|
||||||
if (key != null) {
|
if (key != null && keybaseKey != null) {
|
||||||
log.add(LogType.MSG_IMPORT_MERGE, 3);
|
log.add(LogType.MSG_IMPORT_MERGE, 3);
|
||||||
UncachedKeyRing merged = UncachedKeyRing.decodeFromData(data);
|
keybaseKey = key.merge(keybaseKey, log, 4);
|
||||||
merged = key.merge(merged, log, 4);
|
|
||||||
// If the merge didn't fail, use the new merged key
|
// If the merge didn't fail, use the new merged key
|
||||||
if (merged != null) {
|
if (keybaseKey != null) {
|
||||||
key = merged;
|
key = keybaseKey;
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log.add(LogType.MSG_IMPORT_FETCH_ERROR_DECODE, 3);
|
log.add(LogType.MSG_IMPORT_MERGE_ERROR, 4);
|
||||||
key = UncachedKeyRing.decodeFromData(data);
|
}
|
||||||
|
} else if (keybaseKey != null) {
|
||||||
|
key = keybaseKey;
|
||||||
}
|
}
|
||||||
} catch (Keyserver.QueryFailedException e) {
|
} catch (Keyserver.QueryFailedException e) {
|
||||||
// download failed, too bad. just proceed
|
// download failed, too bad. just proceed
|
||||||
Log.e(Constants.TAG, "query failed", e);
|
Log.e(Constants.TAG, "query failed", e);
|
||||||
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3);
|
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -331,8 +339,8 @@ public class ImportExportOperation extends BaseOperation {
|
|||||||
|
|
||||||
// Special: make sure new data is synced into contacts
|
// Special: make sure new data is synced into contacts
|
||||||
// disabling sync right now since it reduces speed while multi-threading
|
// disabling sync right now since it reduces speed while multi-threading
|
||||||
// so, we expect calling functions to take care of it. KeychainIntentService handles this
|
// so, we expect calling functions to take care of it. KeychainService handles this
|
||||||
//ContactSyncAdapterService.requestSync();
|
// ContactSyncAdapterService.requestSync();
|
||||||
|
|
||||||
// convert to long array
|
// convert to long array
|
||||||
long[] importedMasterKeyIdsArray = new long[importedMasterKeyIds.size()];
|
long[] importedMasterKeyIdsArray = new long[importedMasterKeyIds.size()];
|
||||||
@ -376,8 +384,6 @@ public class ImportExportOperation extends BaseOperation {
|
|||||||
log.add(LogType.MSG_IMPORT_ERROR, 1);
|
log.add(LogType.MSG_IMPORT_ERROR, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
ContactSyncAdapterService.requestSync();
|
|
||||||
|
|
||||||
return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret,
|
return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret,
|
||||||
importedMasterKeyIdsArray);
|
importedMasterKeyIdsArray);
|
||||||
}
|
}
|
||||||
@ -405,13 +411,17 @@ public class ImportExportOperation extends BaseOperation {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
OutputStream outStream = new FileOutputStream(outputFile);
|
OutputStream outStream = new FileOutputStream(outputFile);
|
||||||
|
try {
|
||||||
ExportResult result = exportKeyRings(log, masterKeyIds, exportSecret, outStream);
|
ExportResult result = exportKeyRings(log, masterKeyIds, exportSecret, outStream);
|
||||||
if (result.cancelled()) {
|
if (result.cancelled()) {
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
new File(outputFile).delete();
|
new File(outputFile).delete();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} catch (FileNotFoundException e) {
|
} finally {
|
||||||
|
outStream.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
log.add(LogType.MSG_EXPORT_ERROR_FOPEN, 1);
|
log.add(LogType.MSG_EXPORT_ERROR_FOPEN, 1);
|
||||||
return new ExportResult(ExportResult.RESULT_ERROR, log);
|
return new ExportResult(ExportResult.RESULT_ERROR, log);
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
* a pending result, it will terminate.
|
* a pending result, it will terminate.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class SignEncryptOperation extends BaseOperation {
|
public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
|
||||||
|
|
||||||
public SignEncryptOperation(Context context, ProviderHelper providerHelper,
|
public SignEncryptOperation(Context context, ProviderHelper providerHelper,
|
||||||
Progressable progressable, AtomicBoolean cancelled) {
|
Progressable progressable, AtomicBoolean cancelled) {
|
||||||
|
@ -36,6 +36,23 @@ public class DecryptVerifyResult extends InputPendingResult {
|
|||||||
// https://tools.ietf.org/html/rfc4880#page56
|
// https://tools.ietf.org/html/rfc4880#page56
|
||||||
String mCharset;
|
String mCharset;
|
||||||
|
|
||||||
|
byte[] mOutputBytes;
|
||||||
|
|
||||||
|
public DecryptVerifyResult(int result, OperationLog log) {
|
||||||
|
super(result, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DecryptVerifyResult(OperationLog log, RequiredInputParcel requiredInput) {
|
||||||
|
super(log, requiredInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DecryptVerifyResult(Parcel source) {
|
||||||
|
super(source);
|
||||||
|
mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
|
||||||
|
mDecryptMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean isKeysDisallowed () {
|
public boolean isKeysDisallowed () {
|
||||||
return (mResult & RESULT_KEY_DISALLOWED) == RESULT_KEY_DISALLOWED;
|
return (mResult & RESULT_KEY_DISALLOWED) == RESULT_KEY_DISALLOWED;
|
||||||
}
|
}
|
||||||
@ -64,18 +81,12 @@ public class DecryptVerifyResult extends InputPendingResult {
|
|||||||
mCharset = charset;
|
mCharset = charset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DecryptVerifyResult(int result, OperationLog log) {
|
public void setOutputBytes(byte[] outputBytes) {
|
||||||
super(result, log);
|
mOutputBytes = outputBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DecryptVerifyResult(OperationLog log, RequiredInputParcel requiredInput) {
|
public byte[] getOutputBytes() {
|
||||||
super(log, requiredInput);
|
return mOutputBytes;
|
||||||
}
|
|
||||||
|
|
||||||
public DecryptVerifyResult(Parcel source) {
|
|
||||||
super(source);
|
|
||||||
mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
|
|
||||||
mDecryptMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int describeContents() {
|
public int describeContents() {
|
||||||
|
@ -22,6 +22,7 @@ import android.app.Activity;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
@ -101,9 +102,9 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public OperationLog getLog() {
|
public OperationLog getLog() {
|
||||||
// If there is only a single entry, and it's a compound one, return that log
|
SubLogEntryParcel singleSubLog = mLog.getSubResultIfSingle();
|
||||||
if (mLog.isSingleCompound()) {
|
if (singleSubLog != null) {
|
||||||
return ((SubLogEntryParcel) mLog.getFirst()).getSubResult().getLog();
|
return singleSubLog.getSubResult().getLog();
|
||||||
}
|
}
|
||||||
// Otherwse, return our regular log
|
// Otherwse, return our regular log
|
||||||
return mLog;
|
return mLog;
|
||||||
@ -169,9 +170,9 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
|
|
||||||
public static class SubLogEntryParcel extends LogEntryParcel {
|
public static class SubLogEntryParcel extends LogEntryParcel {
|
||||||
|
|
||||||
OperationResult mSubResult;
|
@NonNull OperationResult mSubResult;
|
||||||
|
|
||||||
public SubLogEntryParcel(OperationResult subResult, LogType type, int indent, Object... parameters) {
|
public SubLogEntryParcel(@NonNull OperationResult subResult, LogType type, int indent, Object... parameters) {
|
||||||
super(type, indent, parameters);
|
super(type, indent, parameters);
|
||||||
mSubResult = subResult;
|
mSubResult = subResult;
|
||||||
|
|
||||||
@ -209,6 +210,10 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
String logText;
|
String logText;
|
||||||
|
|
||||||
LogEntryParcel entryParcel = mLog.getLast();
|
LogEntryParcel entryParcel = mLog.getLast();
|
||||||
|
if (entryParcel == null) {
|
||||||
|
Log.e(Constants.TAG, "Tried to show empty log!");
|
||||||
|
return Notify.create(activity, R.string.error_empty_log, Style.ERROR);
|
||||||
|
}
|
||||||
// special case: first parameter may be a quantity
|
// special case: first parameter may be a quantity
|
||||||
if (entryParcel.mParameters != null && entryParcel.mParameters.length > 0
|
if (entryParcel.mParameters != null && entryParcel.mParameters.length > 0
|
||||||
&& entryParcel.mParameters[0] instanceof Integer) {
|
&& entryParcel.mParameters[0] instanceof Integer) {
|
||||||
@ -269,7 +274,7 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
* mark.
|
* mark.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public static enum LogType {
|
public enum LogType {
|
||||||
|
|
||||||
MSG_INTERNAL_ERROR (LogLevel.ERROR, R.string.msg_internal_error),
|
MSG_INTERNAL_ERROR (LogLevel.ERROR, R.string.msg_internal_error),
|
||||||
MSG_OPERATION_CANCELLED (LogLevel.CANCELLED, R.string.msg_cancelled),
|
MSG_OPERATION_CANCELLED (LogLevel.CANCELLED, R.string.msg_cancelled),
|
||||||
@ -495,6 +500,12 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
MSG_MF_ERROR_REVOKED_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_revoked_primary),
|
MSG_MF_ERROR_REVOKED_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_revoked_primary),
|
||||||
MSG_MF_ERROR_SIG (LogLevel.ERROR, R.string.msg_mf_error_sig),
|
MSG_MF_ERROR_SIG (LogLevel.ERROR, R.string.msg_mf_error_sig),
|
||||||
MSG_MF_ERROR_SUBKEY_MISSING(LogLevel.ERROR, R.string.msg_mf_error_subkey_missing),
|
MSG_MF_ERROR_SUBKEY_MISSING(LogLevel.ERROR, R.string.msg_mf_error_subkey_missing),
|
||||||
|
MSG_MF_ERROR_CONFLICTING_NFC_COMMANDS(LogLevel.ERROR, R.string.msg_mf_error_conflicting_nfc_commands),
|
||||||
|
MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT(LogLevel.ERROR, R.string.msg_mf_error_duplicate_keytocard_for_slot),
|
||||||
|
MSG_MF_ERROR_INVALID_FLAGS_FOR_KEYTOCARD(LogLevel.ERROR, R.string.msg_mf_error_invalid_flags_for_keytocard),
|
||||||
|
MSG_MF_ERROR_BAD_NFC_ALGO(LogLevel.ERROR, R.string.edit_key_error_bad_nfc_algo),
|
||||||
|
MSG_MF_ERROR_BAD_NFC_SIZE(LogLevel.ERROR, R.string.edit_key_error_bad_nfc_size),
|
||||||
|
MSG_MF_ERROR_BAD_NFC_STRIPPED(LogLevel.ERROR, R.string.edit_key_error_bad_nfc_stripped),
|
||||||
MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master),
|
MSG_MF_MASTER (LogLevel.DEBUG, R.string.msg_mf_master),
|
||||||
MSG_MF_NOTATION_PIN (LogLevel.DEBUG, R.string.msg_mf_notation_pin),
|
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_NOTATION_EMPTY (LogLevel.DEBUG, R.string.msg_mf_notation_empty),
|
||||||
@ -512,6 +523,8 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
MSG_MF_SUBKEY_NEW (LogLevel.INFO, R.string.msg_mf_subkey_new),
|
MSG_MF_SUBKEY_NEW (LogLevel.INFO, R.string.msg_mf_subkey_new),
|
||||||
MSG_MF_SUBKEY_REVOKE (LogLevel.INFO, R.string.msg_mf_subkey_revoke),
|
MSG_MF_SUBKEY_REVOKE (LogLevel.INFO, R.string.msg_mf_subkey_revoke),
|
||||||
MSG_MF_SUBKEY_STRIP (LogLevel.INFO, R.string.msg_mf_subkey_strip),
|
MSG_MF_SUBKEY_STRIP (LogLevel.INFO, R.string.msg_mf_subkey_strip),
|
||||||
|
MSG_MF_KEYTOCARD_START (LogLevel.INFO, R.string.msg_mf_keytocard_start),
|
||||||
|
MSG_MF_KEYTOCARD_FINISH (LogLevel.OK, R.string.msg_mf_keytocard_finish),
|
||||||
MSG_MF_SUCCESS (LogLevel.OK, R.string.msg_mf_success),
|
MSG_MF_SUCCESS (LogLevel.OK, R.string.msg_mf_success),
|
||||||
MSG_MF_UID_ADD (LogLevel.INFO, R.string.msg_mf_uid_add),
|
MSG_MF_UID_ADD (LogLevel.INFO, R.string.msg_mf_uid_add),
|
||||||
MSG_MF_UID_PRIMARY (LogLevel.INFO, R.string.msg_mf_uid_primary),
|
MSG_MF_UID_PRIMARY (LogLevel.INFO, R.string.msg_mf_uid_primary),
|
||||||
@ -588,6 +601,7 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
MSG_DC_CLEAR_SIGNATURE_OK (LogLevel.OK, R.string.msg_dc_clear_signature_ok),
|
MSG_DC_CLEAR_SIGNATURE_OK (LogLevel.OK, R.string.msg_dc_clear_signature_ok),
|
||||||
MSG_DC_CLEAR_SIGNATURE (LogLevel.DEBUG, R.string.msg_dc_clear_signature),
|
MSG_DC_CLEAR_SIGNATURE (LogLevel.DEBUG, R.string.msg_dc_clear_signature),
|
||||||
MSG_DC_ERROR_BAD_PASSPHRASE (LogLevel.ERROR, R.string.msg_dc_error_bad_passphrase),
|
MSG_DC_ERROR_BAD_PASSPHRASE (LogLevel.ERROR, R.string.msg_dc_error_bad_passphrase),
|
||||||
|
MSG_DC_ERROR_CORRUPT_DATA (LogLevel.ERROR, R.string.msg_dc_error_corrupt_data),
|
||||||
MSG_DC_ERROR_EXTRACT_KEY (LogLevel.ERROR, R.string.msg_dc_error_extract_key),
|
MSG_DC_ERROR_EXTRACT_KEY (LogLevel.ERROR, R.string.msg_dc_error_extract_key),
|
||||||
MSG_DC_ERROR_INTEGRITY_CHECK (LogLevel.ERROR, R.string.msg_dc_error_integrity_check),
|
MSG_DC_ERROR_INTEGRITY_CHECK (LogLevel.ERROR, R.string.msg_dc_error_integrity_check),
|
||||||
MSG_DC_ERROR_INTEGRITY_MISSING (LogLevel.ERROR, R.string.msg_dc_error_integrity_missing),
|
MSG_DC_ERROR_INTEGRITY_MISSING (LogLevel.ERROR, R.string.msg_dc_error_integrity_missing),
|
||||||
@ -687,6 +701,7 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
MSG_IMPORT_FETCH_KEYBASE (LogLevel.INFO, R.string.msg_import_fetch_keybase),
|
MSG_IMPORT_FETCH_KEYBASE (LogLevel.INFO, R.string.msg_import_fetch_keybase),
|
||||||
MSG_IMPORT_KEYSERVER (LogLevel.DEBUG, R.string.msg_import_keyserver),
|
MSG_IMPORT_KEYSERVER (LogLevel.DEBUG, R.string.msg_import_keyserver),
|
||||||
MSG_IMPORT_MERGE (LogLevel.DEBUG, R.string.msg_import_merge),
|
MSG_IMPORT_MERGE (LogLevel.DEBUG, R.string.msg_import_merge),
|
||||||
|
MSG_IMPORT_MERGE_ERROR (LogLevel.ERROR, R.string.msg_import_merge_error),
|
||||||
MSG_IMPORT_FINGERPRINT_ERROR (LogLevel.ERROR, R.string.msg_import_fingerprint_error),
|
MSG_IMPORT_FINGERPRINT_ERROR (LogLevel.ERROR, R.string.msg_import_fingerprint_error),
|
||||||
MSG_IMPORT_FINGERPRINT_OK (LogLevel.DEBUG, R.string.msg_import_fingerprint_ok),
|
MSG_IMPORT_FINGERPRINT_OK (LogLevel.DEBUG, R.string.msg_import_fingerprint_ok),
|
||||||
MSG_IMPORT_ERROR (LogLevel.ERROR, R.string.msg_import_error),
|
MSG_IMPORT_ERROR (LogLevel.ERROR, R.string.msg_import_error),
|
||||||
@ -755,7 +770,7 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Enumeration of possible log levels. */
|
/** Enumeration of possible log levels. */
|
||||||
public static enum LogLevel {
|
public enum LogLevel {
|
||||||
DEBUG,
|
DEBUG,
|
||||||
INFO,
|
INFO,
|
||||||
WARN,
|
WARN,
|
||||||
@ -795,8 +810,15 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
mParcels.add(new SubLogEntryParcel(subResult, subLog.getFirst().mType, indent, subLog.getFirst().mParameters));
|
mParcels.add(new SubLogEntryParcel(subResult, subLog.getFirst().mType, indent, subLog.getFirst().mParameters));
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isSingleCompound() {
|
public SubLogEntryParcel getSubResultIfSingle() {
|
||||||
return mParcels.size() == 1 && getFirst() instanceof SubLogEntryParcel;
|
if (mParcels.size() != 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LogEntryParcel first = getFirst();
|
||||||
|
if (first instanceof SubLogEntryParcel) {
|
||||||
|
return (SubLogEntryParcel) first;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
@ -844,7 +866,11 @@ public abstract class OperationResult implements Parcelable {
|
|||||||
if (mParcels.isEmpty()) {
|
if (mParcels.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return mParcels.get(mParcels.size() -1);
|
LogEntryParcel last = mParcels.get(mParcels.size() -1);
|
||||||
|
if (last instanceof SubLogEntryParcel) {
|
||||||
|
return ((SubLogEntryParcel) last).getSubResult().getLog().getLast();
|
||||||
|
}
|
||||||
|
return last;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.pgp;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.S2K;
|
|
||||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||||
import org.spongycastle.openpgp.PGPPublicKey;
|
import org.spongycastle.openpgp.PGPPublicKey;
|
||||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.spongycastle.openpgp.PGPSecretKey;
|
||||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||||
@ -96,12 +96,6 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a dummy secret ring from this key */
|
|
||||||
public UncachedKeyRing createDummySecretRing () {
|
|
||||||
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), null);
|
|
||||||
return new UncachedKeyRing(secRing);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a dummy secret ring from this key */
|
/** Create a dummy secret ring from this key */
|
||||||
public UncachedKeyRing createDivertSecretRing (byte[] cardAid, long[] subKeyIds) {
|
public UncachedKeyRing createDivertSecretRing (byte[] cardAid, long[] subKeyIds) {
|
||||||
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), cardAid);
|
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), cardAid);
|
||||||
@ -114,7 +108,10 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
|
|||||||
// stripped dummy, then move divert-to-card keys over
|
// stripped dummy, then move divert-to-card keys over
|
||||||
PGPSecretKeyRing newRing = PGPSecretKeyRing.constructDummyFromPublic(getRing());
|
PGPSecretKeyRing newRing = PGPSecretKeyRing.constructDummyFromPublic(getRing());
|
||||||
for (long subKeyId : subKeyIds) {
|
for (long subKeyId : subKeyIds) {
|
||||||
newRing = PGPSecretKeyRing.insertSecretKey(newRing, secRing.getSecretKey(subKeyId));
|
PGPSecretKey key = secRing.getSecretKey(subKeyId);
|
||||||
|
if (key != null) {
|
||||||
|
newRing = PGPSecretKeyRing.insertSecretKey(newRing, key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new UncachedKeyRing(newRing);
|
return new UncachedKeyRing(newRing);
|
||||||
|
@ -33,6 +33,7 @@ import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
|||||||
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
|
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
|
||||||
import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
|
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
|
||||||
|
import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;
|
||||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||||
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
|
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
|
||||||
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
|
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
|
||||||
@ -45,6 +46,8 @@ import org.sufficientlysecure.keychain.util.Log;
|
|||||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.interfaces.RSAPrivateCrtKey;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -283,6 +286,27 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For use only in card export; returns the secret key in Chinese Remainder Theorem format.
|
||||||
|
public RSAPrivateCrtKey getCrtSecretKey() throws PgpGeneralException {
|
||||||
|
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
|
||||||
|
throw new PgpGeneralException("Cannot get secret key attributes while key is locked.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
|
||||||
|
throw new PgpGeneralException("Cannot get secret key attributes of divert-to-card key.");
|
||||||
|
}
|
||||||
|
|
||||||
|
JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
|
||||||
|
PrivateKey retVal;
|
||||||
|
try {
|
||||||
|
retVal = keyConverter.getPrivateKey(mPrivateKey);
|
||||||
|
} catch (PGPException e) {
|
||||||
|
throw new PgpGeneralException("Error converting private key!", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (RSAPrivateCrtKey)retVal;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getIv() {
|
public byte[] getIv() {
|
||||||
return mSecretKey.getIV();
|
return mSecretKey.getIV();
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import android.text.TextUtils;
|
|||||||
|
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ public abstract class KeyRing {
|
|||||||
return userIdString;
|
return userIdString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class UserId {
|
public static class UserId implements Serializable {
|
||||||
public final String name;
|
public final String name;
|
||||||
public final String email;
|
public final String email;
|
||||||
public final String comment;
|
public final String comment;
|
||||||
|
@ -28,6 +28,7 @@ import org.spongycastle.openpgp.PGPCompressedData;
|
|||||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||||
import org.spongycastle.openpgp.PGPEncryptedDataList;
|
import org.spongycastle.openpgp.PGPEncryptedDataList;
|
||||||
import org.spongycastle.openpgp.PGPException;
|
import org.spongycastle.openpgp.PGPException;
|
||||||
|
import org.spongycastle.openpgp.PGPKeyValidationException;
|
||||||
import org.spongycastle.openpgp.PGPLiteralData;
|
import org.spongycastle.openpgp.PGPLiteralData;
|
||||||
import org.spongycastle.openpgp.PGPOnePassSignature;
|
import org.spongycastle.openpgp.PGPOnePassSignature;
|
||||||
import org.spongycastle.openpgp.PGPOnePassSignatureList;
|
import org.spongycastle.openpgp.PGPOnePassSignatureList;
|
||||||
@ -57,6 +58,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
|
|||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.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.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;
|
||||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
@ -65,6 +67,7 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
|
|||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
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;
|
||||||
@ -72,147 +75,87 @@ import java.net.URLConnection;
|
|||||||
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.Set;
|
|
||||||
|
|
||||||
/**
|
public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> {
|
||||||
* This class uses a Builder pattern!
|
|
||||||
*/
|
|
||||||
public class PgpDecryptVerify extends BaseOperation {
|
|
||||||
|
|
||||||
private InputData mData;
|
public PgpDecryptVerify(Context context, ProviderHelper providerHelper, Progressable progressable) {
|
||||||
private OutputStream mOutStream;
|
super(context, providerHelper, progressable);
|
||||||
|
|
||||||
private boolean mAllowSymmetricDecryption;
|
|
||||||
private Set<Long> mAllowedKeyIds;
|
|
||||||
private boolean mDecryptMetadataOnly;
|
|
||||||
private byte[] mDetachedSignature;
|
|
||||||
private String mRequiredSignerFingerprint;
|
|
||||||
private boolean mSignedLiteralData;
|
|
||||||
|
|
||||||
protected PgpDecryptVerify(Builder builder) {
|
|
||||||
super(builder.mContext, builder.mProviderHelper, builder.mProgressable);
|
|
||||||
|
|
||||||
// private Constructor can only be called from Builder
|
|
||||||
this.mData = builder.mData;
|
|
||||||
this.mOutStream = builder.mOutStream;
|
|
||||||
|
|
||||||
this.mAllowSymmetricDecryption = builder.mAllowSymmetricDecryption;
|
|
||||||
this.mAllowedKeyIds = builder.mAllowedKeyIds;
|
|
||||||
this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly;
|
|
||||||
this.mDetachedSignature = builder.mDetachedSignature;
|
|
||||||
this.mSignedLiteralData = builder.mSignedLiteralData;
|
|
||||||
this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
// mandatory parameter
|
|
||||||
private Context mContext;
|
|
||||||
private ProviderHelper mProviderHelper;
|
|
||||||
private InputData mData;
|
|
||||||
|
|
||||||
// optional
|
|
||||||
private OutputStream mOutStream = null;
|
|
||||||
private Progressable mProgressable = null;
|
|
||||||
private boolean mAllowSymmetricDecryption = true;
|
|
||||||
private Set<Long> mAllowedKeyIds = null;
|
|
||||||
private boolean mDecryptMetadataOnly = false;
|
|
||||||
private byte[] mDetachedSignature = null;
|
|
||||||
private String mRequiredSignerFingerprint = null;
|
|
||||||
private boolean mSignedLiteralData = false;
|
|
||||||
|
|
||||||
public Builder(Context context, ProviderHelper providerHelper,
|
|
||||||
Progressable progressable,
|
|
||||||
InputData data, OutputStream outStream) {
|
|
||||||
mContext = context;
|
|
||||||
mProviderHelper = providerHelper;
|
|
||||||
mProgressable = progressable;
|
|
||||||
mData = data;
|
|
||||||
mOutStream = outStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is used when verifying signed literals to check that they are signed with
|
|
||||||
* the required key
|
|
||||||
*/
|
|
||||||
public Builder setRequiredSignerFingerprint(String fingerprint) {
|
|
||||||
mRequiredSignerFingerprint = fingerprint;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is to force a mode where the message is just the signature key id and
|
|
||||||
* then a literal data packet; used in Keybase.io proofs
|
|
||||||
*/
|
|
||||||
public Builder setSignedLiteralData(boolean signedLiteralData) {
|
|
||||||
mSignedLiteralData = signedLiteralData;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setAllowSymmetricDecryption(boolean allowSymmetricDecryption) {
|
|
||||||
mAllowSymmetricDecryption = allowSymmetricDecryption;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allow these key ids alone for decryption.
|
|
||||||
* This means only ciphertexts encrypted for one of these private key can be decrypted.
|
|
||||||
*/
|
|
||||||
public Builder setAllowedKeyIds(Set<Long> allowedKeyIds) {
|
|
||||||
mAllowedKeyIds = allowedKeyIds;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If enabled, the actual decryption/verification of the content will not be executed.
|
|
||||||
* The metadata only will be decrypted and returned.
|
|
||||||
*/
|
|
||||||
public Builder setDecryptMetadataOnly(boolean decryptMetadataOnly) {
|
|
||||||
mDecryptMetadataOnly = decryptMetadataOnly;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If detachedSignature != null, it will be used exclusively to verify the signature
|
|
||||||
*/
|
|
||||||
public Builder setDetachedSignature(byte[] detachedSignature) {
|
|
||||||
mDetachedSignature = detachedSignature;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PgpDecryptVerify build() {
|
|
||||||
return new PgpDecryptVerify(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypts and/or verifies data based on parameters of class
|
* Decrypts and/or verifies data based on parameters of class
|
||||||
*/
|
*/
|
||||||
public DecryptVerifyResult execute(CryptoInputParcel cryptoInput) {
|
public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput) {
|
||||||
|
InputData inputData;
|
||||||
|
OutputStream outputStream;
|
||||||
|
|
||||||
|
if (input.getInputBytes() != null) {
|
||||||
|
byte[] inputBytes = input.getInputBytes();
|
||||||
|
inputData = new InputData(new ByteArrayInputStream(inputBytes), inputBytes.length);
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
if (mDetachedSignature != null) {
|
InputStream inputStream = mContext.getContentResolver().openInputStream(input.getInputUri());
|
||||||
|
long inputSize = FileHelper.getFileSize(mContext, input.getInputUri(), 0);
|
||||||
|
inputData = new InputData(inputStream, inputSize);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.getOutputUri() == null) {
|
||||||
|
outputStream = new ByteArrayOutputStream();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
outputStream = mContext.getContentResolver().openOutputStream(input.getOutputUri());
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DecryptVerifyResult result = executeInternal(input, cryptoInput, inputData, outputStream);
|
||||||
|
if (outputStream instanceof ByteArrayOutputStream) {
|
||||||
|
byte[] outputData = ((ByteArrayOutputStream) outputStream).toByteArray();
|
||||||
|
result.setOutputBytes(outputData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
|
||||||
|
InputData inputData, OutputStream outputStream) {
|
||||||
|
return executeInternal(input, cryptoInput, inputData, outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DecryptVerifyResult executeInternal(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
|
||||||
|
InputData inputData, OutputStream outputStream) {
|
||||||
|
try {
|
||||||
|
if (input.getDetachedSignature() != null) {
|
||||||
Log.d(Constants.TAG, "Detached signature present, verifying with this signature only");
|
Log.d(Constants.TAG, "Detached signature present, verifying with this signature only");
|
||||||
|
|
||||||
return verifyDetachedSignature(mData.getInputStream(), 0);
|
return verifyDetachedSignature(input, inputData, outputStream, 0);
|
||||||
} else {
|
} else {
|
||||||
// automatically works with PGP ascii armor and PGP binary
|
// automatically works with PGP ascii armor and PGP binary
|
||||||
InputStream in = PGPUtil.getDecoderStream(mData.getInputStream());
|
InputStream in = PGPUtil.getDecoderStream(inputData.getInputStream());
|
||||||
|
|
||||||
if (in instanceof ArmoredInputStream) {
|
if (in instanceof ArmoredInputStream) {
|
||||||
ArmoredInputStream aIn = (ArmoredInputStream) in;
|
ArmoredInputStream aIn = (ArmoredInputStream) in;
|
||||||
// it is ascii armored
|
// it is ascii armored
|
||||||
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
|
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
|
||||||
|
|
||||||
if (mSignedLiteralData) {
|
if (input.isSignedLiteralData()) {
|
||||||
return verifySignedLiteralData(aIn, 0);
|
return verifySignedLiteralData(input, aIn, outputStream, 0);
|
||||||
} else if (aIn.isClearText()) {
|
} else if (aIn.isClearText()) {
|
||||||
// a cleartext signature, verify it with the other method
|
// a cleartext signature, verify it with the other method
|
||||||
return verifyCleartextSignature(aIn, 0);
|
return verifyCleartextSignature(aIn, outputStream, 0);
|
||||||
} else {
|
} else {
|
||||||
// else: ascii armored encryption! go on...
|
// else: ascii armored encryption! go on...
|
||||||
return decryptVerify(cryptoInput, in, 0);
|
return decryptVerify(input, cryptoInput, in, outputStream, 0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return decryptVerify(cryptoInput, in, 0);
|
return decryptVerify(input, cryptoInput, in, outputStream, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (PGPException e) {
|
} catch (PGPException e) {
|
||||||
@ -231,7 +174,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
/**
|
/**
|
||||||
* Verify Keybase.io style signed literal data
|
* Verify Keybase.io style signed literal data
|
||||||
*/
|
*/
|
||||||
private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent)
|
private DecryptVerifyResult verifySignedLiteralData(
|
||||||
|
PgpDecryptVerifyInputParcel input, InputStream in, OutputStream out, int indent)
|
||||||
throws IOException, PGPException {
|
throws IOException, PGPException {
|
||||||
OperationLog log = new OperationLog();
|
OperationLog log = new OperationLog();
|
||||||
log.add(LogType.MSG_VL, indent);
|
log.add(LogType.MSG_VL, indent);
|
||||||
@ -282,9 +226,9 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(signingRing.getFingerprint());
|
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(signingRing.getFingerprint());
|
||||||
if (!(mRequiredSignerFingerprint.equals(fingerprint))) {
|
if (!(input.getRequiredSignerFingerprint().equals(fingerprint))) {
|
||||||
log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
|
log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
|
||||||
Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + mRequiredSignerFingerprint +
|
Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + input.getRequiredSignerFingerprint() +
|
||||||
" got " + fingerprint + "!");
|
" got " + fingerprint + "!");
|
||||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||||
}
|
}
|
||||||
@ -316,7 +260,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
int length;
|
int length;
|
||||||
byte[] buffer = new byte[1 << 16];
|
byte[] buffer = new byte[1 << 16];
|
||||||
while ((length = dataIn.read(buffer)) > 0) {
|
while ((length = dataIn.read(buffer)) > 0) {
|
||||||
mOutStream.write(buffer, 0, length);
|
out.write(buffer, 0, length);
|
||||||
signature.update(buffer, 0, length);
|
signature.update(buffer, 0, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,8 +306,9 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
/**
|
/**
|
||||||
* Decrypt and/or verifies binary or ascii armored pgp
|
* Decrypt and/or verifies binary or ascii armored pgp
|
||||||
*/
|
*/
|
||||||
private DecryptVerifyResult decryptVerify(CryptoInputParcel cryptoInput,
|
private DecryptVerifyResult decryptVerify(
|
||||||
InputStream in, int indent) throws IOException, PGPException {
|
PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
|
||||||
|
InputStream in, OutputStream out, int indent) throws IOException, PGPException {
|
||||||
|
|
||||||
OperationLog log = new OperationLog();
|
OperationLog log = new OperationLog();
|
||||||
|
|
||||||
@ -454,13 +399,13 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// allow only specific keys for decryption?
|
// allow only specific keys for decryption?
|
||||||
if (mAllowedKeyIds != null) {
|
if (input.getAllowedKeyIds() != null) {
|
||||||
long masterKeyId = secretKeyRing.getMasterKeyId();
|
long masterKeyId = secretKeyRing.getMasterKeyId();
|
||||||
Log.d(Constants.TAG, "encData.getKeyID(): " + subKeyId);
|
Log.d(Constants.TAG, "encData.getKeyID(): " + subKeyId);
|
||||||
Log.d(Constants.TAG, "mAllowedKeyIds: " + mAllowedKeyIds);
|
Log.d(Constants.TAG, "mAllowedKeyIds: " + input.getAllowedKeyIds());
|
||||||
Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
|
Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
|
||||||
|
|
||||||
if (!mAllowedKeyIds.contains(masterKeyId)) {
|
if (!input.getAllowedKeyIds().contains(masterKeyId)) {
|
||||||
// this key is in our db, but NOT allowed!
|
// this key is in our db, but NOT allowed!
|
||||||
// continue with the next packet in the while loop
|
// continue with the next packet in the while loop
|
||||||
skippedDisallowedKey = true;
|
skippedDisallowedKey = true;
|
||||||
@ -514,7 +459,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
|
|
||||||
log.add(LogType.MSG_DC_SYM, indent);
|
log.add(LogType.MSG_DC_SYM, indent);
|
||||||
|
|
||||||
if (!mAllowSymmetricDecryption) {
|
if (!input.isAllowSymmetricDecryption()) {
|
||||||
log.add(LogType.MSG_DC_SYM_SKIP, indent + 1);
|
log.add(LogType.MSG_DC_SYM_SKIP, indent + 1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -596,7 +541,12 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
try {
|
try {
|
||||||
PublicKeyDataDecryptorFactory decryptorFactory
|
PublicKeyDataDecryptorFactory decryptorFactory
|
||||||
= secretEncryptionKey.getDecryptorFactory(cryptoInput);
|
= secretEncryptionKey.getDecryptorFactory(cryptoInput);
|
||||||
|
try {
|
||||||
clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
|
clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
|
||||||
|
} catch (PGPKeyValidationException | ArrayIndexOutOfBoundsException e) {
|
||||||
|
log.add(LogType.MSG_DC_ERROR_CORRUPT_DATA, indent + 1);
|
||||||
|
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||||
|
}
|
||||||
|
|
||||||
symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
|
symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
|
||||||
} catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) {
|
} catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) {
|
||||||
@ -758,7 +708,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return here if we want to decrypt the metadata only
|
// return here if we want to decrypt the metadata only
|
||||||
if (mDecryptMetadataOnly) {
|
if (input.isDecryptMetadataOnly()) {
|
||||||
log.add(LogType.MSG_DC_OK_META_ONLY, indent);
|
log.add(LogType.MSG_DC_OK_META_ONLY, indent);
|
||||||
DecryptVerifyResult result =
|
DecryptVerifyResult result =
|
||||||
new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
||||||
@ -781,13 +731,13 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
InputStream dataIn = literalData.getInputStream();
|
InputStream dataIn = literalData.getInputStream();
|
||||||
|
|
||||||
long alreadyWritten = 0;
|
long alreadyWritten = 0;
|
||||||
long wholeSize = mData.getSize() - mData.getStreamPosition();
|
long wholeSize = 0; // TODO inputData.getSize() - inputData.getStreamPosition();
|
||||||
int length;
|
int length;
|
||||||
byte[] buffer = new byte[1 << 16];
|
byte[] buffer = new byte[1 << 16];
|
||||||
while ((length = dataIn.read(buffer)) > 0) {
|
while ((length = dataIn.read(buffer)) > 0) {
|
||||||
Log.d(Constants.TAG, "read bytes: " + length);
|
// Log.d(Constants.TAG, "read bytes: " + length);
|
||||||
if (mOutStream != null) {
|
if (out != null) {
|
||||||
mOutStream.write(buffer, 0, length);
|
out.write(buffer, 0, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update signature buffer if signature is also present
|
// update signature buffer if signature is also present
|
||||||
@ -807,6 +757,12 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
// TODO: slow annealing to fake a progress?
|
// TODO: slow annealing to fake a progress?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
metadata = new OpenPgpMetadata(
|
||||||
|
originalFilename,
|
||||||
|
mimeType,
|
||||||
|
literalData.getModificationTime().getTime(),
|
||||||
|
alreadyWritten);
|
||||||
|
|
||||||
if (signature != null) {
|
if (signature != null) {
|
||||||
updateProgress(R.string.progress_verifying_signature, 90, 100);
|
updateProgress(R.string.progress_verifying_signature, 90, 100);
|
||||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
|
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
|
||||||
@ -883,7 +839,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
|
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
|
||||||
*/
|
*/
|
||||||
private DecryptVerifyResult verifyCleartextSignature(
|
private DecryptVerifyResult verifyCleartextSignature(
|
||||||
ArmoredInputStream aIn, int indent) throws IOException, PGPException {
|
ArmoredInputStream aIn, OutputStream outputStream, int indent) throws IOException, PGPException {
|
||||||
|
|
||||||
OperationLog log = new OperationLog();
|
OperationLog log = new OperationLog();
|
||||||
|
|
||||||
@ -913,8 +869,9 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
out.close();
|
out.close();
|
||||||
|
|
||||||
byte[] clearText = out.toByteArray();
|
byte[] clearText = out.toByteArray();
|
||||||
if (mOutStream != null) {
|
if (outputStream != null) {
|
||||||
mOutStream.write(clearText);
|
outputStream.write(clearText);
|
||||||
|
outputStream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProgress(R.string.progress_processing_signature, 60, 100);
|
updateProgress(R.string.progress_processing_signature, 60, 100);
|
||||||
@ -981,7 +938,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DecryptVerifyResult verifyDetachedSignature(InputStream in, int indent)
|
private DecryptVerifyResult verifyDetachedSignature(
|
||||||
|
PgpDecryptVerifyInputParcel input, InputData inputData, OutputStream out, int indent)
|
||||||
throws IOException, PGPException {
|
throws IOException, PGPException {
|
||||||
|
|
||||||
OperationLog log = new OperationLog();
|
OperationLog log = new OperationLog();
|
||||||
@ -991,7 +949,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
signatureResultBuilder.setSignatureOnly(true);
|
signatureResultBuilder.setSignatureOnly(true);
|
||||||
|
|
||||||
updateProgress(R.string.progress_processing_signature, 0, 100);
|
updateProgress(R.string.progress_processing_signature, 0, 100);
|
||||||
InputStream detachedSigIn = new ByteArrayInputStream(mDetachedSignature);
|
InputStream detachedSigIn = new ByteArrayInputStream(input.getDetachedSignature());
|
||||||
detachedSigIn = PGPUtil.getDecoderStream(detachedSigIn);
|
detachedSigIn = PGPUtil.getDecoderStream(detachedSigIn);
|
||||||
|
|
||||||
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(detachedSigIn);
|
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(detachedSigIn);
|
||||||
@ -1016,12 +974,13 @@ public class PgpDecryptVerify extends BaseOperation {
|
|||||||
|
|
||||||
ProgressScaler progressScaler = new ProgressScaler(mProgressable, 60, 90, 100);
|
ProgressScaler progressScaler = new ProgressScaler(mProgressable, 60, 90, 100);
|
||||||
long alreadyWritten = 0;
|
long alreadyWritten = 0;
|
||||||
long wholeSize = mData.getSize() - mData.getStreamPosition();
|
long wholeSize = inputData.getSize() - inputData.getStreamPosition();
|
||||||
int length;
|
int length;
|
||||||
byte[] buffer = new byte[1 << 16];
|
byte[] buffer = new byte[1 << 16];
|
||||||
|
InputStream in = inputData.getInputStream();
|
||||||
while ((length = in.read(buffer)) > 0) {
|
while ((length = in.read(buffer)) > 0) {
|
||||||
if (mOutStream != null) {
|
if (out != null) {
|
||||||
mOutStream.write(buffer, 0, length);
|
out.write(buffer, 0, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update signature buffer if signature is also present
|
// update signature buffer if signature is also present
|
||||||
|
@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
public class PgpDecryptVerifyInputParcel implements Parcelable {
|
||||||
|
|
||||||
|
private Uri mInputUri;
|
||||||
|
private Uri mOutputUri;
|
||||||
|
private byte[] mInputBytes;
|
||||||
|
|
||||||
|
private boolean mAllowSymmetricDecryption;
|
||||||
|
private HashSet<Long> mAllowedKeyIds;
|
||||||
|
private boolean mDecryptMetadataOnly;
|
||||||
|
private byte[] mDetachedSignature;
|
||||||
|
private String mRequiredSignerFingerprint;
|
||||||
|
private boolean mSignedLiteralData;
|
||||||
|
|
||||||
|
public PgpDecryptVerifyInputParcel() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public PgpDecryptVerifyInputParcel(Uri inputUri, Uri outputUri) {
|
||||||
|
mInputUri = inputUri;
|
||||||
|
mOutputUri = outputUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PgpDecryptVerifyInputParcel(byte[] inputBytes) {
|
||||||
|
mInputBytes = inputBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
PgpDecryptVerifyInputParcel(Parcel source) {
|
||||||
|
// we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
|
||||||
|
mInputUri = source.readParcelable(getClass().getClassLoader());
|
||||||
|
mOutputUri = source.readParcelable(getClass().getClassLoader());
|
||||||
|
mInputBytes = source.createByteArray();
|
||||||
|
|
||||||
|
mAllowSymmetricDecryption = source.readInt() != 0;
|
||||||
|
mAllowedKeyIds = (HashSet<Long>) source.readSerializable();
|
||||||
|
mDecryptMetadataOnly = source.readInt() != 0;
|
||||||
|
mDetachedSignature = source.createByteArray();
|
||||||
|
mRequiredSignerFingerprint = source.readString();
|
||||||
|
mSignedLiteralData = source.readInt() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeParcelable(mInputUri, 0);
|
||||||
|
dest.writeParcelable(mOutputUri, 0);
|
||||||
|
dest.writeByteArray(mInputBytes);
|
||||||
|
|
||||||
|
dest.writeInt(mAllowSymmetricDecryption ? 1 : 0);
|
||||||
|
dest.writeSerializable(mAllowedKeyIds);
|
||||||
|
dest.writeInt(mDecryptMetadataOnly ? 1 : 0);
|
||||||
|
dest.writeByteArray(mDetachedSignature);
|
||||||
|
dest.writeString(mRequiredSignerFingerprint);
|
||||||
|
dest.writeInt(mSignedLiteralData ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] getInputBytes() {
|
||||||
|
return mInputBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri getInputUri() {
|
||||||
|
return mInputUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri getOutputUri() {
|
||||||
|
return mOutputUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isAllowSymmetricDecryption() {
|
||||||
|
return mAllowSymmetricDecryption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PgpDecryptVerifyInputParcel setAllowSymmetricDecryption(boolean allowSymmetricDecryption) {
|
||||||
|
mAllowSymmetricDecryption = allowSymmetricDecryption;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet<Long> getAllowedKeyIds() {
|
||||||
|
return mAllowedKeyIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PgpDecryptVerifyInputParcel setAllowedKeyIds(HashSet<Long> allowedKeyIds) {
|
||||||
|
mAllowedKeyIds = allowedKeyIds;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isDecryptMetadataOnly() {
|
||||||
|
return mDecryptMetadataOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PgpDecryptVerifyInputParcel setDecryptMetadataOnly(boolean decryptMetadataOnly) {
|
||||||
|
mDecryptMetadataOnly = decryptMetadataOnly;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] getDetachedSignature() {
|
||||||
|
return mDetachedSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PgpDecryptVerifyInputParcel setDetachedSignature(byte[] detachedSignature) {
|
||||||
|
mDetachedSignature = detachedSignature;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getRequiredSignerFingerprint() {
|
||||||
|
return mRequiredSignerFingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PgpDecryptVerifyInputParcel setRequiredSignerFingerprint(String requiredSignerFingerprint) {
|
||||||
|
mRequiredSignerFingerprint = requiredSignerFingerprint;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isSignedLiteralData() {
|
||||||
|
return mSignedLiteralData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PgpDecryptVerifyInputParcel setSignedLiteralData(boolean signedLiteralData) {
|
||||||
|
mSignedLiteralData = signedLiteralData;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<PgpDecryptVerifyInputParcel> CREATOR = new Creator<PgpDecryptVerifyInputParcel>() {
|
||||||
|
public PgpDecryptVerifyInputParcel createFromParcel(final Parcel source) {
|
||||||
|
return new PgpDecryptVerifyInputParcel(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PgpDecryptVerifyInputParcel[] newArray(final int size) {
|
||||||
|
return new PgpDecryptVerifyInputParcel[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.pgp;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
|
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
|
||||||
import org.spongycastle.bcpg.S2K;
|
import org.spongycastle.bcpg.S2K;
|
||||||
import org.spongycastle.bcpg.sig.Features;
|
import org.spongycastle.bcpg.sig.Features;
|
||||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||||
@ -45,8 +46,10 @@ import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
|||||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
|
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
|
||||||
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
|
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
|
||||||
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded;
|
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded;
|
||||||
|
import org.spongycastle.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.EditKeyResult;
|
||||||
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.OperationResult.OperationLog;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
@ -59,6 +62,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
|
|||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.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.NfcSignOperationsBuilder;
|
||||||
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcKeyToCardOperationsBuilder;
|
||||||
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;
|
||||||
@ -68,6 +72,7 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
@ -402,9 +407,61 @@ public class PgpKeyOperation {
|
|||||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure we don't have multiple keys for the same slot.
|
||||||
|
boolean hasSign = false;
|
||||||
|
boolean hasEncrypt = false;
|
||||||
|
boolean hasAuth = false;
|
||||||
|
for(SaveKeyringParcel.SubkeyChange change : saveParcel.mChangeSubKeys) {
|
||||||
|
if (change.mMoveKeyToCard) {
|
||||||
|
// If this is a keytocard operation, see if it was completed: look for a hash
|
||||||
|
// matching the given subkey ID in cryptoData.
|
||||||
|
byte[] subKeyId = new byte[8];
|
||||||
|
ByteBuffer buf = ByteBuffer.wrap(subKeyId);
|
||||||
|
buf.putLong(change.mKeyId).rewind();
|
||||||
|
|
||||||
|
byte[] serialNumber = cryptoInput.getCryptoData().get(buf);
|
||||||
|
if (serialNumber != null) {
|
||||||
|
change.mMoveKeyToCard = false;
|
||||||
|
change.mDummyDivert = serialNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (change.mMoveKeyToCard) {
|
||||||
|
// Pending keytocard operation. Need to make sure that we don't have multiple
|
||||||
|
// subkeys pending for the same slot.
|
||||||
|
CanonicalizedSecretKey wsK = wsKR.getSecretKey(change.mKeyId);
|
||||||
|
|
||||||
|
if ((wsK.canSign() || wsK.canCertify())) {
|
||||||
|
if (hasSign) {
|
||||||
|
log.add(LogType.MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT, indent + 1);
|
||||||
|
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||||
|
} else {
|
||||||
|
hasSign = true;
|
||||||
|
}
|
||||||
|
} else if ((wsK.canEncrypt())) {
|
||||||
|
if (hasEncrypt) {
|
||||||
|
log.add(LogType.MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT, indent + 1);
|
||||||
|
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||||
|
} else {
|
||||||
|
hasEncrypt = true;
|
||||||
|
}
|
||||||
|
} else if ((wsK.canAuthenticate())) {
|
||||||
|
if (hasAuth) {
|
||||||
|
log.add(LogType.MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT, indent + 1);
|
||||||
|
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||||
|
} else {
|
||||||
|
hasAuth = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.add(LogType.MSG_MF_ERROR_INVALID_FLAGS_FOR_KEYTOCARD, indent + 1);
|
||||||
|
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isDummy(masterSecretKey) || saveParcel.isRestrictedOnly()) {
|
if (isDummy(masterSecretKey) || saveParcel.isRestrictedOnly()) {
|
||||||
log.add(LogType.MSG_MF_RESTRICTED_MODE, indent);
|
log.add(LogType.MSG_MF_RESTRICTED_MODE, indent);
|
||||||
return internalRestricted(sKR, saveParcel, log);
|
return internalRestricted(sKR, saveParcel, log, indent + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do we require a passphrase? If so, pass it along
|
// Do we require a passphrase? If so, pass it along
|
||||||
@ -430,11 +487,14 @@ public class PgpKeyOperation {
|
|||||||
int masterKeyFlags, long masterKeyExpiry,
|
int masterKeyFlags, long masterKeyExpiry,
|
||||||
CryptoInputParcel cryptoInput,
|
CryptoInputParcel cryptoInput,
|
||||||
SaveKeyringParcel saveParcel,
|
SaveKeyringParcel saveParcel,
|
||||||
OperationLog log, int indent) {
|
OperationLog log,
|
||||||
|
int indent) {
|
||||||
|
|
||||||
NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder(
|
NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder(
|
||||||
cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(),
|
cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(),
|
||||||
masterSecretKey.getKeyID());
|
masterSecretKey.getKeyID());
|
||||||
|
NfcKeyToCardOperationsBuilder nfcKeyToCardOps = new NfcKeyToCardOperationsBuilder(
|
||||||
|
masterSecretKey.getKeyID());
|
||||||
|
|
||||||
progress(R.string.progress_modify, 0);
|
progress(R.string.progress_modify, 0);
|
||||||
|
|
||||||
@ -744,22 +804,36 @@ public class PgpKeyOperation {
|
|||||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (change.mDummyStrip || change.mDummyDivert != null) {
|
if (change.mDummyStrip) {
|
||||||
// IT'S DANGEROUS~
|
// IT'S DANGEROUS~
|
||||||
// no really, it is. this operation irrevocably removes the private key data from the key
|
// no really, it is. this operation irrevocably removes the private key data from the key
|
||||||
if (change.mDummyStrip) {
|
|
||||||
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
|
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
|
||||||
|
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
||||||
|
} else if (change.mMoveKeyToCard) {
|
||||||
|
if (checkSmartCardCompatibility(sKey, log, indent + 1)) {
|
||||||
|
log.add(LogType.MSG_MF_KEYTOCARD_START, indent + 1,
|
||||||
|
KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
|
||||||
|
nfcKeyToCardOps.addSubkey(change.mKeyId);
|
||||||
} else {
|
} else {
|
||||||
// the serial number must be 16 bytes in length
|
// Appropriate log message already set by checkSmartCardCompatibility
|
||||||
|
return new PgpEditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
|
||||||
|
}
|
||||||
|
} else if (change.mDummyDivert != null) {
|
||||||
|
// NOTE: Does this code get executed? Or always handled in internalRestricted?
|
||||||
if (change.mDummyDivert.length != 16) {
|
if (change.mDummyDivert.length != 16) {
|
||||||
log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
|
log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
|
||||||
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
|
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
|
||||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||||
}
|
}
|
||||||
}
|
log.add(LogType.MSG_MF_KEYTOCARD_FINISH, indent + 1,
|
||||||
|
KeyFormattingUtils.convertKeyIdToHex(change.mKeyId),
|
||||||
|
Hex.toHexString(change.mDummyDivert, 8, 6));
|
||||||
|
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert);
|
||||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// This doesn't concern us any further
|
// This doesn't concern us any further
|
||||||
if (!change.mRecertify && (change.mExpiry == null && change.mFlags == null)) {
|
if (!change.mRecertify && (change.mExpiry == null && change.mFlags == null)) {
|
||||||
continue;
|
continue;
|
||||||
@ -981,11 +1055,21 @@ public class PgpKeyOperation {
|
|||||||
|
|
||||||
progress(R.string.progress_done, 100);
|
progress(R.string.progress_done, 100);
|
||||||
|
|
||||||
|
if (!nfcSignOps.isEmpty() && !nfcKeyToCardOps.isEmpty()) {
|
||||||
|
log.add(LogType.MSG_MF_ERROR_CONFLICTING_NFC_COMMANDS, indent+1);
|
||||||
|
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||||
|
}
|
||||||
|
|
||||||
if (!nfcSignOps.isEmpty()) {
|
if (!nfcSignOps.isEmpty()) {
|
||||||
log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent);
|
log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent);
|
||||||
return new PgpEditKeyResult(log, nfcSignOps.build());
|
return new PgpEditKeyResult(log, nfcSignOps.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!nfcKeyToCardOps.isEmpty()) {
|
||||||
|
log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent);
|
||||||
|
return new PgpEditKeyResult(log, nfcKeyToCardOps.build());
|
||||||
|
}
|
||||||
|
|
||||||
log.add(LogType.MSG_MF_SUCCESS, indent);
|
log.add(LogType.MSG_MF_SUCCESS, indent);
|
||||||
return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR));
|
return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR));
|
||||||
|
|
||||||
@ -996,9 +1080,7 @@ public class PgpKeyOperation {
|
|||||||
* otherwise.
|
* otherwise.
|
||||||
*/
|
*/
|
||||||
private PgpEditKeyResult internalRestricted(PGPSecretKeyRing sKR, SaveKeyringParcel saveParcel,
|
private PgpEditKeyResult internalRestricted(PGPSecretKeyRing sKR, SaveKeyringParcel saveParcel,
|
||||||
OperationLog log) {
|
OperationLog log, int indent) {
|
||||||
|
|
||||||
int indent = 1;
|
|
||||||
|
|
||||||
progress(R.string.progress_modify, 0);
|
progress(R.string.progress_modify, 0);
|
||||||
|
|
||||||
@ -1043,6 +1125,9 @@ public class PgpKeyOperation {
|
|||||||
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
|
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
|
||||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||||
}
|
}
|
||||||
|
log.add(LogType.MSG_MF_KEYTOCARD_FINISH, indent + 1,
|
||||||
|
KeyFormattingUtils.convertKeyIdToHex(change.mKeyId),
|
||||||
|
Hex.toHexString(change.mDummyDivert, 8, 6));
|
||||||
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert);
|
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert);
|
||||||
}
|
}
|
||||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
||||||
@ -1488,4 +1573,29 @@ public class PgpKeyOperation {
|
|||||||
&& s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD;
|
&& s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean checkSmartCardCompatibility(PGPSecretKey key, OperationLog log, int indent) {
|
||||||
|
PGPPublicKey publicKey = key.getPublicKey();
|
||||||
|
int algorithm = publicKey.getAlgorithm();
|
||||||
|
if (algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT &&
|
||||||
|
algorithm != PublicKeyAlgorithmTags.RSA_SIGN &&
|
||||||
|
algorithm != PublicKeyAlgorithmTags.RSA_GENERAL) {
|
||||||
|
log.add(LogType.MSG_MF_ERROR_BAD_NFC_ALGO, indent + 1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key size must be 2048
|
||||||
|
int keySize = publicKey.getBitStrength();
|
||||||
|
if (keySize != 2048) {
|
||||||
|
log.add(LogType.MSG_MF_ERROR_BAD_NFC_SIZE, indent + 1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secret key parts must be available
|
||||||
|
if (isDivertToCard(key) || isDummy(key)) {
|
||||||
|
log.add(LogType.MSG_MF_ERROR_BAD_NFC_STRIPPED, indent + 1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,8 @@ package org.sufficientlysecure.keychain.pgp;
|
|||||||
|
|
||||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
|
||||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
@ -57,6 +57,10 @@ public class SignEncryptParcel extends PgpSignEncryptInputParcel {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isIncomplete() {
|
||||||
|
return mInputUris.size() > mOutputUris.size();
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getBytes() {
|
public byte[] getBytes() {
|
||||||
return mBytes;
|
return mBytes;
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ public class KeychainContract {
|
|||||||
String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name
|
String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String CONTENT_AUTHORITY = Constants.PACKAGE_NAME + ".provider";
|
public static final String CONTENT_AUTHORITY = Constants.PROVIDER_AUTHORITY;
|
||||||
|
|
||||||
private static final Uri BASE_CONTENT_URI_INTERNAL = Uri
|
private static final Uri BASE_CONTENT_URI_INTERNAL = Uri
|
||||||
.parse("content://" + CONTENT_AUTHORITY);
|
.parse("content://" + CONTENT_AUTHORITY);
|
||||||
|
@ -179,7 +179,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
+ Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
|
+ Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
|
||||||
+ ")";
|
+ ")";
|
||||||
|
|
||||||
KeychainDatabase(Context context) {
|
public KeychainDatabase(Context context) {
|
||||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
mContext = context;
|
mContext = context;
|
||||||
|
|
||||||
@ -391,11 +391,16 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
private static void copy(File in, File out) throws IOException {
|
private static void copy(File in, File out) throws IOException {
|
||||||
FileInputStream is = new FileInputStream(in);
|
FileInputStream is = new FileInputStream(in);
|
||||||
FileOutputStream os = new FileOutputStream(out);
|
FileOutputStream os = new FileOutputStream(out);
|
||||||
|
try {
|
||||||
byte[] buf = new byte[512];
|
byte[] buf = new byte[512];
|
||||||
while (is.available() > 0) {
|
while (is.available() > 0) {
|
||||||
int count = is.read(buf, 0, 512);
|
int count = is.read(buf, 0, 512);
|
||||||
os.write(buf, 0, count);
|
os.write(buf, 0, count);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
is.close();
|
||||||
|
os.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void debugBackup(Context context, boolean restore) throws IOException {
|
public static void debugBackup(Context context, boolean restore) throws IOException {
|
||||||
|
@ -1514,8 +1514,8 @@ public class ProviderHelper {
|
|||||||
return keyIds;
|
return keyIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<Long> getAllowedKeyIdsForApp(Uri uri) {
|
public HashSet<Long> getAllowedKeyIdsForApp(Uri uri) {
|
||||||
Set<Long> keyIds = new HashSet<>();
|
HashSet<Long> keyIds = new HashSet<>();
|
||||||
|
|
||||||
Cursor cursor = mContentResolver.query(uri, null, null, null, null);
|
Cursor cursor = mContentResolver.query(uri, null, null, null, null);
|
||||||
try {
|
try {
|
||||||
|
@ -45,7 +45,8 @@ public class TemporaryStorageProvider extends ContentProvider {
|
|||||||
private static final String COLUMN_ID = "id";
|
private static final String COLUMN_ID = "id";
|
||||||
private static final String COLUMN_NAME = "name";
|
private static final String COLUMN_NAME = "name";
|
||||||
private static final String COLUMN_TIME = "time";
|
private static final String COLUMN_TIME = "time";
|
||||||
private static final Uri BASE_URI = Uri.parse("content://org.sufficientlysecure.keychain.tempstorage/");
|
public static final String CONTENT_AUTHORITY = Constants.TEMPSTORAGE_AUTHORITY;
|
||||||
|
private static final Uri BASE_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
|
||||||
private static final int DB_VERSION = 2;
|
private static final int DB_VERSION = 2;
|
||||||
|
|
||||||
private static File cacheDir;
|
private static File cacheDir;
|
||||||
|
@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEnt
|
|||||||
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
||||||
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;
|
||||||
@ -63,7 +64,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Set;
|
import java.util.HashSet;
|
||||||
|
|
||||||
public class OpenPgpService extends RemoteService {
|
public class OpenPgpService extends RemoteService {
|
||||||
|
|
||||||
@ -166,6 +167,7 @@ public class OpenPgpService extends RemoteService {
|
|||||||
Intent data, RequiredInputParcel requiredInput) {
|
Intent data, RequiredInputParcel requiredInput) {
|
||||||
|
|
||||||
switch (requiredInput.mType) {
|
switch (requiredInput.mType) {
|
||||||
|
case NFC_KEYTOCARD:
|
||||||
case NFC_DECRYPT:
|
case NFC_DECRYPT:
|
||||||
case NFC_SIGN: {
|
case NFC_SIGN: {
|
||||||
// build PendingIntent for YubiKey NFC operations
|
// build PendingIntent for YubiKey NFC operations
|
||||||
@ -487,23 +489,23 @@ public class OpenPgpService extends RemoteService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor input,
|
private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor inputDescriptor,
|
||||||
ParcelFileDescriptor output, boolean decryptMetadataOnly) {
|
ParcelFileDescriptor output, boolean decryptMetadataOnly) {
|
||||||
InputStream is = null;
|
InputStream inputStream = null;
|
||||||
OutputStream os = null;
|
OutputStream outputStream = null;
|
||||||
try {
|
try {
|
||||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||||
is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
inputStream = new ParcelFileDescriptor.AutoCloseInputStream(inputDescriptor);
|
||||||
|
|
||||||
// output is optional, e.g., for verifying detached signatures
|
// output is optional, e.g., for verifying detached signatures
|
||||||
if (decryptMetadataOnly || output == null) {
|
if (decryptMetadataOnly || output == null) {
|
||||||
os = null;
|
outputStream = null;
|
||||||
} else {
|
} else {
|
||||||
os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
|
outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
String currentPkg = getCurrentCallingPackage();
|
String currentPkg = getCurrentCallingPackage();
|
||||||
Set<Long> allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp(
|
HashSet<Long> allowedKeyIds = mProviderHelper.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) {
|
||||||
@ -511,33 +513,32 @@ public class OpenPgpService extends RemoteService {
|
|||||||
ApiAccounts.buildBaseUri(currentPkg)));
|
ApiAccounts.buildBaseUri(currentPkg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
long inputLength = is.available();
|
CryptoInputParcel cryptoInput = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
|
||||||
InputData inputData = new InputData(is, inputLength);
|
if (cryptoInput == null) {
|
||||||
|
cryptoInput = new CryptoInputParcel();
|
||||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
|
|
||||||
this, new ProviderHelper(getContext()), null, inputData, os
|
|
||||||
);
|
|
||||||
|
|
||||||
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
|
|
||||||
if (inputParcel == null) {
|
|
||||||
inputParcel = new CryptoInputParcel();
|
|
||||||
}
|
}
|
||||||
// override passphrase in input parcel if given by API call
|
// override passphrase in input parcel if given by API call
|
||||||
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
||||||
inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(),
|
cryptoInput = new CryptoInputParcel(cryptoInput.getSignatureTime(),
|
||||||
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
|
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
|
byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
|
||||||
|
|
||||||
|
PgpDecryptVerify op = new PgpDecryptVerify(this, mProviderHelper, null);
|
||||||
|
|
||||||
|
long inputLength = inputStream.available();
|
||||||
|
InputData inputData = new InputData(inputStream, inputLength);
|
||||||
|
|
||||||
// allow only private keys associated with accounts of this app
|
// allow only private keys associated with accounts of this app
|
||||||
// no support for symmetric encryption
|
// no support for symmetric encryption
|
||||||
builder.setAllowSymmetricDecryption(false)
|
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel()
|
||||||
|
.setAllowSymmetricDecryption(false)
|
||||||
.setAllowedKeyIds(allowedKeyIds)
|
.setAllowedKeyIds(allowedKeyIds)
|
||||||
.setDecryptMetadataOnly(decryptMetadataOnly)
|
.setDecryptMetadataOnly(decryptMetadataOnly)
|
||||||
.setDetachedSignature(detachedSignature);
|
.setDetachedSignature(detachedSignature);
|
||||||
|
|
||||||
DecryptVerifyResult pgpResult = builder.build().execute(inputParcel);
|
DecryptVerifyResult pgpResult = op.execute(input, cryptoInput, inputData, outputStream);
|
||||||
|
|
||||||
if (pgpResult.isPending()) {
|
if (pgpResult.isPending()) {
|
||||||
// prepare and return PendingIntent to be executed by client
|
// prepare and return PendingIntent to be executed by client
|
||||||
@ -623,16 +624,16 @@ public class OpenPgpService extends RemoteService {
|
|||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||||
return result;
|
return result;
|
||||||
} finally {
|
} finally {
|
||||||
if (is != null) {
|
if (inputStream != null) {
|
||||||
try {
|
try {
|
||||||
is.close();
|
inputStream.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(Constants.TAG, "IOException when closing InputStream", e);
|
Log.e(Constants.TAG, "IOException when closing InputStream", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (os != null) {
|
if (outputStream != null) {
|
||||||
try {
|
try {
|
||||||
os.close();
|
outputStream.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(Constants.TAG, "IOException when closing OutputStream", e);
|
Log.e(Constants.TAG, "IOException when closing OutputStream", e);
|
||||||
}
|
}
|
||||||
|
@ -217,13 +217,15 @@ public class AppSettingsActivity extends BaseActivity {
|
|||||||
|
|
||||||
// show accounts only if available (deprecated API)
|
// show accounts only if available (deprecated API)
|
||||||
Cursor cursor = getContentResolver().query(accountsUri, null, null, null, null);
|
Cursor cursor = getContentResolver().query(accountsUri, null, null, null, null);
|
||||||
if (cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) try {
|
||||||
mAccountsLabel.setVisibility(View.VISIBLE);
|
mAccountsLabel.setVisibility(View.VISIBLE);
|
||||||
mAccountsListFragment = AccountsListFragment.newInstance(accountsUri);
|
mAccountsListFragment = AccountsListFragment.newInstance(accountsUri);
|
||||||
// Create an instance of the fragments
|
// Create an instance of the fragments
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.replace(R.id.api_accounts_list_fragment, mAccountsListFragment)
|
.replace(R.id.api_accounts_list_fragment, mAccountsListFragment)
|
||||||
.commitAllowingStateLoss();
|
.commitAllowingStateLoss();
|
||||||
|
} finally {
|
||||||
|
cursor.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an instance of the fragments
|
// Create an instance of the fragments
|
||||||
|
@ -43,6 +43,8 @@ public class CertifyActionsParcel implements Parcelable {
|
|||||||
|
|
||||||
public ArrayList<CertifyAction> mCertifyActions = new ArrayList<>();
|
public ArrayList<CertifyAction> mCertifyActions = new ArrayList<>();
|
||||||
|
|
||||||
|
public String keyServerUri;
|
||||||
|
|
||||||
public CertifyActionsParcel(long masterKeyId) {
|
public CertifyActionsParcel(long masterKeyId) {
|
||||||
mMasterKeyId = masterKeyId;
|
mMasterKeyId = masterKeyId;
|
||||||
mLevel = CertifyLevel.DEFAULT;
|
mLevel = CertifyLevel.DEFAULT;
|
||||||
|
@ -1,384 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.sufficientlysecure.keychain.service;
|
|
||||||
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.Message;
|
|
||||||
import android.os.Messenger;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
|
||||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
|
||||||
import org.sufficientlysecure.keychain.operations.ImportExportOperation;
|
|
||||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
|
||||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.SynchronousQueue;
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When this service is started it will initiate a multi-threaded key import and when done it will
|
|
||||||
* shut itself down.
|
|
||||||
*/
|
|
||||||
public class CloudImportService extends Service implements Progressable {
|
|
||||||
|
|
||||||
// required as extras from intent
|
|
||||||
public static final String EXTRA_MESSENGER = "messenger";
|
|
||||||
public static final String EXTRA_DATA = "data";
|
|
||||||
|
|
||||||
// required by data bundle
|
|
||||||
public static final String IMPORT_KEY_LIST = "import_key_list";
|
|
||||||
public static final String IMPORT_KEY_SERVER = "import_key_server";
|
|
||||||
|
|
||||||
// indicates a request to cancel the import
|
|
||||||
public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL";
|
|
||||||
|
|
||||||
// tells the spawned threads whether the user has requested a cancel
|
|
||||||
private static AtomicBoolean mActionCancelled = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to accumulate the results of individual key imports
|
|
||||||
*/
|
|
||||||
private class KeyImportAccumulator {
|
|
||||||
private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog();
|
|
||||||
private int mTotalKeys;
|
|
||||||
private int mImportedKeys = 0;
|
|
||||||
private Progressable mImportProgressable;
|
|
||||||
ArrayList<Long> mImportedMasterKeyIds = new ArrayList<Long>();
|
|
||||||
private int mBadKeys = 0;
|
|
||||||
private int mNewKeys = 0;
|
|
||||||
private int mUpdatedKeys = 0;
|
|
||||||
private int mSecret = 0;
|
|
||||||
private int mResultType = 0;
|
|
||||||
|
|
||||||
public KeyImportAccumulator(int totalKeys) {
|
|
||||||
mTotalKeys = totalKeys;
|
|
||||||
// ignore updates from ImportExportOperation for now
|
|
||||||
mImportProgressable = new Progressable() {
|
|
||||||
@Override
|
|
||||||
public void setProgress(String message, int current, int total) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setProgress(int resourceId, int current, int total) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setProgress(int current, int total) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPreventCancel() {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public Progressable getImportProgressable() {
|
|
||||||
return mImportProgressable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTotalKeys() {
|
|
||||||
return mTotalKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getImportedKeys() {
|
|
||||||
return mImportedKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void accumulateKeyImport(ImportKeyResult result) {
|
|
||||||
mImportedKeys++;
|
|
||||||
mImportLog.addAll(result.getLog().toList());//accumulates log
|
|
||||||
mBadKeys += result.mBadKeys;
|
|
||||||
mNewKeys += result.mNewKeys;
|
|
||||||
mUpdatedKeys += result.mUpdatedKeys;
|
|
||||||
mSecret += result.mSecret;
|
|
||||||
|
|
||||||
long[] masterKeyIds = result.getImportedMasterKeyIds();
|
|
||||||
for (long masterKeyId : masterKeyIds) {
|
|
||||||
mImportedMasterKeyIds.add(masterKeyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if any key import has been cancelled, set result type to cancelled
|
|
||||||
// resultType is added to in getConsolidatedKayImport to account for remaining factors
|
|
||||||
mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns accumulated result of all imports so far
|
|
||||||
*/
|
|
||||||
public ImportKeyResult getConsolidatedImportKeyResult() {
|
|
||||||
|
|
||||||
// adding required information to mResultType
|
|
||||||
// special case,no keys requested for import
|
|
||||||
if (mBadKeys == 0 && mNewKeys == 0 && mUpdatedKeys == 0) {
|
|
||||||
mResultType = ImportKeyResult.RESULT_FAIL_NOTHING;
|
|
||||||
} else {
|
|
||||||
if (mNewKeys > 0) {
|
|
||||||
mResultType |= ImportKeyResult.RESULT_OK_NEWKEYS;
|
|
||||||
}
|
|
||||||
if (mUpdatedKeys > 0) {
|
|
||||||
mResultType |= ImportKeyResult.RESULT_OK_UPDATED;
|
|
||||||
}
|
|
||||||
if (mBadKeys > 0) {
|
|
||||||
mResultType |= ImportKeyResult.RESULT_WITH_ERRORS;
|
|
||||||
if (mNewKeys == 0 && mUpdatedKeys == 0) {
|
|
||||||
mResultType |= ImportKeyResult.RESULT_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mImportLog.containsWarnings()) {
|
|
||||||
mResultType |= ImportKeyResult.RESULT_WARNINGS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
long masterKeyIds[] = new long[mImportedMasterKeyIds.size()];
|
|
||||||
for (int i = 0; i < masterKeyIds.length; i++) {
|
|
||||||
masterKeyIds[i] = mImportedMasterKeyIds.get(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ImportKeyResult(mResultType, mImportLog, mNewKeys, mUpdatedKeys, mBadKeys,
|
|
||||||
mSecret, masterKeyIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isImportFinished() {
|
|
||||||
return mTotalKeys == mImportedKeys;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeyImportAccumulator mKeyImportAccumulator;
|
|
||||||
|
|
||||||
Messenger mMessenger;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
||||||
|
|
||||||
if (ACTION_CANCEL.equals(intent.getAction())) {
|
|
||||||
mActionCancelled.set(true);
|
|
||||||
return Service.START_NOT_STICKY;
|
|
||||||
}
|
|
||||||
|
|
||||||
mActionCancelled.set(false);//we haven't been cancelled, yet
|
|
||||||
|
|
||||||
Bundle extras = intent.getExtras();
|
|
||||||
|
|
||||||
mMessenger = (Messenger) extras.get(EXTRA_MESSENGER);
|
|
||||||
|
|
||||||
Bundle data = extras.getBundle(EXTRA_DATA);
|
|
||||||
|
|
||||||
final String keyServer = data.getString(IMPORT_KEY_SERVER);
|
|
||||||
// keyList being null (in case key list to be reaad from cache) is checked by importKeys
|
|
||||||
final ArrayList<ParcelableKeyRing> keyList = data.getParcelableArrayList(IMPORT_KEY_LIST);
|
|
||||||
|
|
||||||
// Adding keys to the ThreadPoolExecutor takes time, we don't want to block the main thread
|
|
||||||
Thread baseImportThread = new Thread(new Runnable() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
importKeys(keyList, keyServer);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
baseImportThread.start();
|
|
||||||
return Service.START_NOT_STICKY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void importKeys(ArrayList<ParcelableKeyRing> keyList, final String keyServer) {
|
|
||||||
ParcelableFileCache<ParcelableKeyRing> cache =
|
|
||||||
new ParcelableFileCache<>(this, "key_import.pcl");
|
|
||||||
int totKeys = 0;
|
|
||||||
Iterator<ParcelableKeyRing> keyListIterator = null;
|
|
||||||
// either keyList or cache must be null, no guarantees otherwise
|
|
||||||
if (keyList == null) {//export from cache, copied from ImportExportOperation.importKeyRings
|
|
||||||
|
|
||||||
try {
|
|
||||||
ParcelableFileCache.IteratorWithSize<ParcelableKeyRing> it = cache.readCache();
|
|
||||||
keyListIterator = it;
|
|
||||||
totKeys = it.getSize();
|
|
||||||
} catch (IOException e) {
|
|
||||||
|
|
||||||
// Special treatment here, we need a lot
|
|
||||||
OperationResult.OperationLog log = new OperationResult.OperationLog();
|
|
||||||
log.add(OperationResult.LogType.MSG_IMPORT, 0, 0);
|
|
||||||
log.add(OperationResult.LogType.MSG_IMPORT_ERROR_IO, 0, 0);
|
|
||||||
|
|
||||||
keyImportFailed(new ImportKeyResult(ImportKeyResult.RESULT_ERROR, log));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
keyListIterator = keyList.iterator();
|
|
||||||
totKeys = keyList.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (keyListIterator != null) {
|
|
||||||
mKeyImportAccumulator = new KeyImportAccumulator(totKeys);
|
|
||||||
setProgress(0, totKeys);
|
|
||||||
|
|
||||||
final int maxThreads = 200;
|
|
||||||
ExecutorService importExecutor = new ThreadPoolExecutor(0, maxThreads,
|
|
||||||
30L, TimeUnit.SECONDS,
|
|
||||||
new SynchronousQueue<Runnable>());
|
|
||||||
|
|
||||||
while (keyListIterator.hasNext()) {
|
|
||||||
|
|
||||||
final ParcelableKeyRing pkRing = keyListIterator.next();
|
|
||||||
|
|
||||||
Runnable importOperationRunnable = new Runnable() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
ImportKeyResult result = null;
|
|
||||||
try {
|
|
||||||
ImportExportOperation importExportOperation = new ImportExportOperation(
|
|
||||||
CloudImportService.this,
|
|
||||||
new ProviderHelper(CloudImportService.this),
|
|
||||||
mKeyImportAccumulator.getImportProgressable(),
|
|
||||||
mActionCancelled);
|
|
||||||
|
|
||||||
ArrayList<ParcelableKeyRing> list = new ArrayList<>();
|
|
||||||
list.add(pkRing);
|
|
||||||
result = importExportOperation.importKeyRings(list,
|
|
||||||
keyServer);
|
|
||||||
} finally {
|
|
||||||
// in the off-chance that importKeyRings does something to crash the
|
|
||||||
// thread before it can call singleKeyRingImportCompleted, our imported
|
|
||||||
// key count will go wrong. This will cause the service to never die,
|
|
||||||
// and the progress dialog to stay displayed. The finally block was
|
|
||||||
// originally meant to ensure singleKeyRingImportCompleted was called,
|
|
||||||
// and checks for null were to be introduced, but in such a scenario,
|
|
||||||
// knowing an uncaught error exists in importKeyRings is more important.
|
|
||||||
|
|
||||||
// if a null gets passed, something wrong is happening. We want a crash.
|
|
||||||
|
|
||||||
singleKeyRingImportCompleted(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
importExecutor.execute(importOperationRunnable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void singleKeyRingImportCompleted(ImportKeyResult result) {
|
|
||||||
// increase imported key count and accumulate log and bad, new etc. key counts from result
|
|
||||||
mKeyImportAccumulator.accumulateKeyImport(result);
|
|
||||||
|
|
||||||
setProgress(mKeyImportAccumulator.getImportedKeys(), mKeyImportAccumulator.getTotalKeys());
|
|
||||||
|
|
||||||
if (mKeyImportAccumulator.isImportFinished()) {
|
|
||||||
ContactSyncAdapterService.requestSync();
|
|
||||||
|
|
||||||
sendMessageToHandler(ServiceProgressHandler.MessageStatus.OKAY,
|
|
||||||
mKeyImportAccumulator.getConsolidatedImportKeyResult());
|
|
||||||
|
|
||||||
stopSelf();//we're done here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void keyImportFailed(ImportKeyResult result) {
|
|
||||||
sendMessageToHandler(ServiceProgressHandler.MessageStatus.OKAY, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status, Integer arg2, Bundle data) {
|
|
||||||
|
|
||||||
Message msg = Message.obtain();
|
|
||||||
assert msg != null;
|
|
||||||
msg.arg1 = status.ordinal();
|
|
||||||
if (arg2 != null) {
|
|
||||||
msg.arg2 = arg2;
|
|
||||||
}
|
|
||||||
if (data != null) {
|
|
||||||
msg.setData(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
mMessenger.send(msg);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
|
||||||
} catch (NullPointerException e) {
|
|
||||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status, OperationResult data) {
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putParcelable(OperationResult.EXTRA_RESULT, data);
|
|
||||||
sendMessageToHandler(status, null, bundle);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status, Bundle data) {
|
|
||||||
sendMessageToHandler(status, null, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status) {
|
|
||||||
sendMessageToHandler(status, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set progress of ProgressDialog by sending message to handler on UI thread
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public synchronized void setProgress(String message, int progress, int max) {
|
|
||||||
Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
|
|
||||||
+ max);
|
|
||||||
|
|
||||||
Bundle data = new Bundle();
|
|
||||||
if (message != null) {
|
|
||||||
data.putString(ServiceProgressHandler.DATA_MESSAGE, message);
|
|
||||||
}
|
|
||||||
data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress);
|
|
||||||
data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max);
|
|
||||||
|
|
||||||
sendMessageToHandler(ServiceProgressHandler.MessageStatus.UPDATE_PROGRESS, null, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setProgress(int resourceId, int progress, int max) {
|
|
||||||
setProgress(getString(resourceId), progress, max);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setProgress(int progress, int max) {
|
|
||||||
setProgress(null, progress, max);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setPreventCancel() {
|
|
||||||
sendMessageToHandler(ServiceProgressHandler.MessageStatus.PREVENT_CANCEL);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,746 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
|
||||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.sufficientlysecure.keychain.service;
|
|
||||||
|
|
||||||
import android.app.IntentService;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Message;
|
|
||||||
import android.os.Messenger;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
|
|
||||||
import com.textuality.keybase.lib.Proof;
|
|
||||||
import com.textuality.keybase.lib.prover.Prover;
|
|
||||||
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.spongycastle.openpgp.PGPUtil;
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
|
||||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
|
||||||
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
|
||||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
|
||||||
import org.sufficientlysecure.keychain.operations.CertifyOperation;
|
|
||||||
import org.sufficientlysecure.keychain.operations.DeleteOperation;
|
|
||||||
import org.sufficientlysecure.keychain.operations.EditKeyOperation;
|
|
||||||
import org.sufficientlysecure.keychain.operations.ImportExportOperation;
|
|
||||||
import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
|
|
||||||
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
|
|
||||||
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
|
|
||||||
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
|
||||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
|
||||||
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
|
|
||||||
import org.sufficientlysecure.keychain.operations.results.ExportResult;
|
|
||||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
|
||||||
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
|
|
||||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
|
|
||||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
|
||||||
import org.sufficientlysecure.keychain.util.InputData;
|
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
|
||||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import de.measite.minidns.Client;
|
|
||||||
import de.measite.minidns.DNSMessage;
|
|
||||||
import de.measite.minidns.Question;
|
|
||||||
import de.measite.minidns.Record;
|
|
||||||
import de.measite.minidns.record.Data;
|
|
||||||
import de.measite.minidns.record.TXT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This Service contains all important long lasting operations for OpenKeychain. It receives Intents with
|
|
||||||
* data from the activities or other apps, queues these intents, executes them, and stops itself
|
|
||||||
* after doing them.
|
|
||||||
*/
|
|
||||||
public class KeychainIntentService extends IntentService implements Progressable {
|
|
||||||
|
|
||||||
/* extras that can be given by intent */
|
|
||||||
public static final String EXTRA_MESSENGER = "messenger";
|
|
||||||
public static final String EXTRA_DATA = "data";
|
|
||||||
|
|
||||||
/* possible actions */
|
|
||||||
public static final String ACTION_SIGN_ENCRYPT = Constants.INTENT_PREFIX + "SIGN_ENCRYPT";
|
|
||||||
|
|
||||||
public static final String ACTION_DECRYPT_VERIFY = Constants.INTENT_PREFIX + "DECRYPT_VERIFY";
|
|
||||||
|
|
||||||
public static final String ACTION_VERIFY_KEYBASE_PROOF = Constants.INTENT_PREFIX + "VERIFY_KEYBASE_PROOF";
|
|
||||||
|
|
||||||
public static final String ACTION_DECRYPT_METADATA = Constants.INTENT_PREFIX + "DECRYPT_METADATA";
|
|
||||||
|
|
||||||
public static final String ACTION_EDIT_KEYRING = Constants.INTENT_PREFIX + "EDIT_KEYRING";
|
|
||||||
|
|
||||||
public static final String ACTION_PROMOTE_KEYRING = Constants.INTENT_PREFIX + "PROMOTE_KEYRING";
|
|
||||||
|
|
||||||
public static final String ACTION_IMPORT_KEYRING = Constants.INTENT_PREFIX + "IMPORT_KEYRING";
|
|
||||||
public static final String ACTION_EXPORT_KEYRING = Constants.INTENT_PREFIX + "EXPORT_KEYRING";
|
|
||||||
|
|
||||||
public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING";
|
|
||||||
|
|
||||||
public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING";
|
|
||||||
|
|
||||||
public static final String ACTION_DELETE = Constants.INTENT_PREFIX + "DELETE";
|
|
||||||
|
|
||||||
public static final String ACTION_CONSOLIDATE = Constants.INTENT_PREFIX + "CONSOLIDATE";
|
|
||||||
|
|
||||||
public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL";
|
|
||||||
|
|
||||||
/* keys for data bundle */
|
|
||||||
|
|
||||||
// encrypt, decrypt, import export
|
|
||||||
public static final String TARGET = "target";
|
|
||||||
public static final String SOURCE = "source";
|
|
||||||
|
|
||||||
// possible targets:
|
|
||||||
public static enum IOType {
|
|
||||||
UNKNOWN,
|
|
||||||
BYTES,
|
|
||||||
URI;
|
|
||||||
|
|
||||||
private static final IOType[] values = values();
|
|
||||||
|
|
||||||
public static IOType fromInt(int n) {
|
|
||||||
if (n < 0 || n >= values.length) {
|
|
||||||
return UNKNOWN;
|
|
||||||
} else {
|
|
||||||
return values[n];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// encrypt
|
|
||||||
public static final String ENCRYPT_DECRYPT_INPUT_URI = "input_uri";
|
|
||||||
public static final String ENCRYPT_DECRYPT_OUTPUT_URI = "output_uri";
|
|
||||||
public static final String SIGN_ENCRYPT_PARCEL = "sign_encrypt_parcel";
|
|
||||||
|
|
||||||
// decrypt/verify
|
|
||||||
public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes";
|
|
||||||
|
|
||||||
// keybase proof
|
|
||||||
public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint";
|
|
||||||
public static final String KEYBASE_PROOF = "keybase_proof";
|
|
||||||
|
|
||||||
// save keyring
|
|
||||||
public static final String EDIT_KEYRING_PARCEL = "save_parcel";
|
|
||||||
public static final String EDIT_KEYRING_PASSPHRASE = "passphrase";
|
|
||||||
public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
|
|
||||||
|
|
||||||
// delete keyring(s)
|
|
||||||
public static final String DELETE_KEY_LIST = "delete_list";
|
|
||||||
public static final String DELETE_IS_SECRET = "delete_is_secret";
|
|
||||||
|
|
||||||
// import key
|
|
||||||
public static final String IMPORT_KEY_LIST = "import_key_list";
|
|
||||||
public static final String IMPORT_KEY_SERVER = "import_key_server";
|
|
||||||
|
|
||||||
// export key
|
|
||||||
public static final String EXPORT_FILENAME = "export_filename";
|
|
||||||
public static final String EXPORT_URI = "export_uri";
|
|
||||||
public static final String EXPORT_SECRET = "export_secret";
|
|
||||||
public static final String EXPORT_ALL = "export_all";
|
|
||||||
public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id";
|
|
||||||
|
|
||||||
// upload key
|
|
||||||
public static final String UPLOAD_KEY_SERVER = "upload_key_server";
|
|
||||||
|
|
||||||
// certify key
|
|
||||||
public static final String CERTIFY_PARCEL = "certify_parcel";
|
|
||||||
|
|
||||||
// promote key
|
|
||||||
public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id";
|
|
||||||
public static final String PROMOTE_CARD_AID = "promote_card_aid";
|
|
||||||
public static final String PROMOTE_SUBKEY_IDS = "promote_fingerprints";
|
|
||||||
|
|
||||||
// consolidate
|
|
||||||
public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery";
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* possible data keys as result send over messenger
|
|
||||||
*/
|
|
||||||
|
|
||||||
// decrypt/verify
|
|
||||||
public static final String RESULT_DECRYPTED_BYTES = "decrypted_data";
|
|
||||||
|
|
||||||
Messenger mMessenger;
|
|
||||||
|
|
||||||
// this attribute can possibly merged with the one above? not sure...
|
|
||||||
private AtomicBoolean mActionCanceled = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
public KeychainIntentService() {
|
|
||||||
super("KeychainIntentService");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The IntentService calls this method from the default worker thread with the intent that
|
|
||||||
* started the service. When this method returns, IntentService stops the service, as
|
|
||||||
* appropriate.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void onHandleIntent(Intent intent) {
|
|
||||||
|
|
||||||
// We have not been cancelled! (yet)
|
|
||||||
mActionCanceled.set(false);
|
|
||||||
|
|
||||||
Bundle extras = intent.getExtras();
|
|
||||||
if (extras == null) {
|
|
||||||
Log.e(Constants.TAG, "Extras bundle is null!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(extras.containsKey(EXTRA_MESSENGER) || extras.containsKey(EXTRA_DATA) || (intent
|
|
||||||
.getAction() == null))) {
|
|
||||||
Log.e(Constants.TAG,
|
|
||||||
"Extra bundle must contain a messenger, a data bundle, and an action!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Uri dataUri = intent.getData();
|
|
||||||
|
|
||||||
mMessenger = (Messenger) extras.get(EXTRA_MESSENGER);
|
|
||||||
Bundle data = extras.getBundle(EXTRA_DATA);
|
|
||||||
if (data == null) {
|
|
||||||
Log.e(Constants.TAG, "data extra is null!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.logDebugBundle(data, "EXTRA_DATA");
|
|
||||||
|
|
||||||
ProviderHelper providerHelper = new ProviderHelper(this);
|
|
||||||
|
|
||||||
String action = intent.getAction();
|
|
||||||
|
|
||||||
// executeServiceMethod action from extra bundle
|
|
||||||
switch (action) {
|
|
||||||
case ACTION_CERTIFY_KEYRING: {
|
|
||||||
|
|
||||||
// Input
|
|
||||||
CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL);
|
|
||||||
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
|
||||||
String keyServerUri = data.getString(UPLOAD_KEY_SERVER);
|
|
||||||
|
|
||||||
// Operation
|
|
||||||
CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled);
|
|
||||||
CertifyResult result = op.certify(parcel, cryptoInput, keyServerUri);
|
|
||||||
|
|
||||||
// Result
|
|
||||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ACTION_CONSOLIDATE: {
|
|
||||||
|
|
||||||
// Operation
|
|
||||||
ConsolidateResult result;
|
|
||||||
if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) {
|
|
||||||
result = new ProviderHelper(this).consolidateDatabaseStep2(this);
|
|
||||||
} else {
|
|
||||||
result = new ProviderHelper(this).consolidateDatabaseStep1(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Result
|
|
||||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ACTION_DECRYPT_METADATA: {
|
|
||||||
|
|
||||||
try {
|
|
||||||
/* Input */
|
|
||||||
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
|
||||||
|
|
||||||
InputData inputData = createDecryptInputData(data);
|
|
||||||
|
|
||||||
// verifyText and decrypt returning additional resultData values for the
|
|
||||||
// verification of signatures
|
|
||||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
|
|
||||||
this, new ProviderHelper(this), this, inputData, null
|
|
||||||
);
|
|
||||||
builder.setAllowSymmetricDecryption(true)
|
|
||||||
.setDecryptMetadataOnly(true);
|
|
||||||
|
|
||||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput);
|
|
||||||
|
|
||||||
sendMessageToHandler(MessageStatus.OKAY, decryptVerifyResult);
|
|
||||||
} catch (Exception e) {
|
|
||||||
sendErrorToHandler(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ACTION_VERIFY_KEYBASE_PROOF: {
|
|
||||||
|
|
||||||
try {
|
|
||||||
Proof proof = new Proof(new JSONObject(data.getString(KEYBASE_PROOF)));
|
|
||||||
setProgress(R.string.keybase_message_fetching_data, 0, 100);
|
|
||||||
|
|
||||||
Prover prover = Prover.findProverFor(proof);
|
|
||||||
|
|
||||||
if (prover == null) {
|
|
||||||
sendProofError(getString(R.string.keybase_no_prover_found) + ": " + proof.getPrettyName());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!prover.fetchProofData()) {
|
|
||||||
sendProofError(prover.getLog(), getString(R.string.keybase_problem_fetching_evidence));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String requiredFingerprint = data.getString(KEYBASE_REQUIRED_FINGERPRINT);
|
|
||||||
if (!prover.checkFingerprint(requiredFingerprint)) {
|
|
||||||
sendProofError(getString(R.string.keybase_key_mismatch));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String domain = prover.dnsTxtCheckRequired();
|
|
||||||
if (domain != null) {
|
|
||||||
DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT));
|
|
||||||
if (dnsQuery == null) {
|
|
||||||
sendProofError(prover.getLog(), getString(R.string.keybase_dns_query_failure));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Record[] records = dnsQuery.getAnswers();
|
|
||||||
List<List<byte[]>> extents = new ArrayList<List<byte[]>>();
|
|
||||||
for (Record r : records) {
|
|
||||||
Data d = r.getPayload();
|
|
||||||
if (d instanceof TXT) {
|
|
||||||
extents.add(((TXT) d).getExtents());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!prover.checkDnsTxt(extents)) {
|
|
||||||
sendProofError(prover.getLog(), null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] messageBytes = prover.getPgpMessage().getBytes();
|
|
||||||
if (prover.rawMessageCheckRequired()) {
|
|
||||||
InputStream messageByteStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(messageBytes));
|
|
||||||
if (!prover.checkRawMessageBytes(messageByteStream)) {
|
|
||||||
sendProofError(prover.getLog(), null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// kind of awkward, but this whole class wants to pull bytes out of “data”
|
|
||||||
data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal());
|
|
||||||
data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, messageBytes);
|
|
||||||
|
|
||||||
InputData inputData = createDecryptInputData(data);
|
|
||||||
OutputStream outStream = createCryptOutputStream(data);
|
|
||||||
|
|
||||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
|
|
||||||
this, new ProviderHelper(this), this,
|
|
||||||
inputData, outStream
|
|
||||||
);
|
|
||||||
builder.setSignedLiteralData(true).setRequiredSignerFingerprint(requiredFingerprint);
|
|
||||||
|
|
||||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute(
|
|
||||||
new CryptoInputParcel());
|
|
||||||
outStream.close();
|
|
||||||
|
|
||||||
if (!decryptVerifyResult.success()) {
|
|
||||||
OperationLog log = decryptVerifyResult.getLog();
|
|
||||||
OperationResult.LogEntryParcel lastEntry = null;
|
|
||||||
for (OperationResult.LogEntryParcel entry : log) {
|
|
||||||
lastEntry = entry;
|
|
||||||
}
|
|
||||||
sendProofError(getString(lastEntry.mType.getMsgId()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!prover.validate(outStream.toString())) {
|
|
||||||
sendProofError(getString(R.string.keybase_message_payload_mismatch));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bundle resultData = new Bundle();
|
|
||||||
resultData.putString(ServiceProgressHandler.DATA_MESSAGE, "OK");
|
|
||||||
|
|
||||||
// these help the handler construct a useful human-readable message
|
|
||||||
resultData.putString(ServiceProgressHandler.KEYBASE_PROOF_URL, prover.getProofUrl());
|
|
||||||
resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_URL, prover.getPresenceUrl());
|
|
||||||
resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_LABEL, prover.getPresenceLabel());
|
|
||||||
sendMessageToHandler(MessageStatus.OKAY, resultData);
|
|
||||||
} catch (Exception e) {
|
|
||||||
sendErrorToHandler(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ACTION_DECRYPT_VERIFY: {
|
|
||||||
|
|
||||||
try {
|
|
||||||
/* Input */
|
|
||||||
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
|
||||||
|
|
||||||
InputData inputData = createDecryptInputData(data);
|
|
||||||
OutputStream outStream = createCryptOutputStream(data);
|
|
||||||
|
|
||||||
/* Operation */
|
|
||||||
Bundle resultData = new Bundle();
|
|
||||||
|
|
||||||
// verifyText and decrypt returning additional resultData values for the
|
|
||||||
// verification of signatures
|
|
||||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
|
|
||||||
this, new ProviderHelper(this), this,
|
|
||||||
inputData, outStream
|
|
||||||
);
|
|
||||||
builder.setAllowSymmetricDecryption(true);
|
|
||||||
|
|
||||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput);
|
|
||||||
|
|
||||||
outStream.close();
|
|
||||||
|
|
||||||
resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult);
|
|
||||||
|
|
||||||
/* Output */
|
|
||||||
finalizeDecryptOutputStream(data, resultData, outStream);
|
|
||||||
Log.logDebugBundle(resultData, "resultData");
|
|
||||||
|
|
||||||
sendMessageToHandler(MessageStatus.OKAY, resultData);
|
|
||||||
|
|
||||||
} catch (IOException | PgpGeneralException e) {
|
|
||||||
// TODO get rid of this!
|
|
||||||
sendErrorToHandler(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ACTION_DELETE: {
|
|
||||||
|
|
||||||
// Input
|
|
||||||
long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST);
|
|
||||||
boolean isSecret = data.getBoolean(DELETE_IS_SECRET);
|
|
||||||
|
|
||||||
// Operation
|
|
||||||
DeleteOperation op = new DeleteOperation(this, new ProviderHelper(this), this);
|
|
||||||
DeleteResult result = op.execute(masterKeyIds, isSecret);
|
|
||||||
|
|
||||||
// Result
|
|
||||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ACTION_EDIT_KEYRING: {
|
|
||||||
|
|
||||||
// Input
|
|
||||||
SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL);
|
|
||||||
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
|
||||||
|
|
||||||
// Operation
|
|
||||||
EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled);
|
|
||||||
OperationResult result = op.execute(saveParcel, cryptoInput);
|
|
||||||
|
|
||||||
// Result
|
|
||||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ACTION_PROMOTE_KEYRING: {
|
|
||||||
|
|
||||||
// Input
|
|
||||||
long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID);
|
|
||||||
byte[] cardAid = data.getByteArray(PROMOTE_CARD_AID);
|
|
||||||
long[] subKeyIds = data.getLongArray(PROMOTE_SUBKEY_IDS);
|
|
||||||
|
|
||||||
// Operation
|
|
||||||
PromoteKeyOperation op = new PromoteKeyOperation(
|
|
||||||
this, providerHelper, this, mActionCanceled);
|
|
||||||
PromoteKeyResult result = op.execute(keyRingId, cardAid, subKeyIds);
|
|
||||||
|
|
||||||
// Result
|
|
||||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ACTION_EXPORT_KEYRING: {
|
|
||||||
|
|
||||||
// Input
|
|
||||||
boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
|
|
||||||
String outputFile = data.getString(EXPORT_FILENAME);
|
|
||||||
Uri outputUri = data.getParcelable(EXPORT_URI);
|
|
||||||
|
|
||||||
boolean exportAll = data.getBoolean(EXPORT_ALL);
|
|
||||||
long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
|
|
||||||
|
|
||||||
// Operation
|
|
||||||
ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this);
|
|
||||||
ExportResult result;
|
|
||||||
if (outputFile != null) {
|
|
||||||
result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile);
|
|
||||||
} else {
|
|
||||||
result = importExportOperation.exportToUri(masterKeyIds, exportSecret, outputUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Result
|
|
||||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ACTION_IMPORT_KEYRING: {
|
|
||||||
|
|
||||||
// Input
|
|
||||||
String keyServer = data.getString(IMPORT_KEY_SERVER);
|
|
||||||
ArrayList<ParcelableKeyRing> list = data.getParcelableArrayList(IMPORT_KEY_LIST);
|
|
||||||
ParcelableFileCache<ParcelableKeyRing> cache =
|
|
||||||
new ParcelableFileCache<>(this, "key_import.pcl");
|
|
||||||
|
|
||||||
// Operation
|
|
||||||
ImportExportOperation importExportOperation = new ImportExportOperation(
|
|
||||||
this, providerHelper, this, mActionCanceled);
|
|
||||||
// Either list or cache must be null, no guarantees otherwise.
|
|
||||||
ImportKeyResult result = list != null
|
|
||||||
? importExportOperation.importKeyRings(list, keyServer)
|
|
||||||
: importExportOperation.importKeyRings(cache, keyServer);
|
|
||||||
|
|
||||||
// Result
|
|
||||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
case ACTION_SIGN_ENCRYPT: {
|
|
||||||
|
|
||||||
// Input
|
|
||||||
SignEncryptParcel inputParcel = data.getParcelable(SIGN_ENCRYPT_PARCEL);
|
|
||||||
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
|
||||||
|
|
||||||
// Operation
|
|
||||||
SignEncryptOperation op = new SignEncryptOperation(
|
|
||||||
this, new ProviderHelper(this), this, mActionCanceled);
|
|
||||||
SignEncryptResult result = op.execute(inputParcel, cryptoInput);
|
|
||||||
|
|
||||||
// Result
|
|
||||||
sendMessageToHandler(MessageStatus.OKAY, result);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ACTION_UPLOAD_KEYRING: {
|
|
||||||
try {
|
|
||||||
|
|
||||||
/* Input */
|
|
||||||
String keyServer = data.getString(UPLOAD_KEY_SERVER);
|
|
||||||
// and dataUri!
|
|
||||||
|
|
||||||
/* Operation */
|
|
||||||
HkpKeyserver server = new HkpKeyserver(keyServer);
|
|
||||||
|
|
||||||
CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
|
|
||||||
ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this);
|
|
||||||
|
|
||||||
try {
|
|
||||||
importExportOperation.uploadKeyRingToServer(server, keyring);
|
|
||||||
} catch (Keyserver.AddKeyException e) {
|
|
||||||
throw new PgpGeneralException("Unable to export key to selected server");
|
|
||||||
}
|
|
||||||
|
|
||||||
sendMessageToHandler(MessageStatus.OKAY);
|
|
||||||
} catch (Exception e) {
|
|
||||||
sendErrorToHandler(e);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendProofError(List<String> log, String label) {
|
|
||||||
String msg = null;
|
|
||||||
label = (label == null) ? "" : label + ": ";
|
|
||||||
for (String m : log) {
|
|
||||||
Log.e(Constants.TAG, label + m);
|
|
||||||
msg = m;
|
|
||||||
}
|
|
||||||
sendProofError(label + msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendProofError(String msg) {
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putString(ServiceProgressHandler.DATA_ERROR, msg);
|
|
||||||
sendMessageToHandler(MessageStatus.OKAY, bundle);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendErrorToHandler(Exception e) {
|
|
||||||
// TODO: Implement a better exception handling here
|
|
||||||
// contextualize the exception, if necessary
|
|
||||||
String message;
|
|
||||||
if (e instanceof PgpGeneralMsgIdException) {
|
|
||||||
e = ((PgpGeneralMsgIdException) e).getContextualized(this);
|
|
||||||
message = e.getMessage();
|
|
||||||
} else {
|
|
||||||
message = e.getMessage();
|
|
||||||
}
|
|
||||||
Log.d(Constants.TAG, "KeychainIntentService Exception: ", e);
|
|
||||||
|
|
||||||
Bundle data = new Bundle();
|
|
||||||
data.putString(ServiceProgressHandler.DATA_ERROR, message);
|
|
||||||
sendMessageToHandler(MessageStatus.EXCEPTION, null, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendMessageToHandler(MessageStatus status, Integer arg2, Bundle data) {
|
|
||||||
|
|
||||||
Message msg = Message.obtain();
|
|
||||||
assert msg != null;
|
|
||||||
msg.arg1 = status.ordinal();
|
|
||||||
if (arg2 != null) {
|
|
||||||
msg.arg2 = arg2;
|
|
||||||
}
|
|
||||||
if (data != null) {
|
|
||||||
msg.setData(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
mMessenger.send(msg);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
|
||||||
} catch (NullPointerException e) {
|
|
||||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendMessageToHandler(MessageStatus status, OperationResult data) {
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putParcelable(OperationResult.EXTRA_RESULT, data);
|
|
||||||
sendMessageToHandler(status, null, bundle);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendMessageToHandler(MessageStatus status, Bundle data) {
|
|
||||||
sendMessageToHandler(status, null, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendMessageToHandler(MessageStatus status) {
|
|
||||||
sendMessageToHandler(status, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set progress of ProgressDialog by sending message to handler on UI thread
|
|
||||||
*/
|
|
||||||
public void setProgress(String message, int progress, int max) {
|
|
||||||
Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
|
|
||||||
+ max);
|
|
||||||
|
|
||||||
Bundle data = new Bundle();
|
|
||||||
if (message != null) {
|
|
||||||
data.putString(ServiceProgressHandler.DATA_MESSAGE, message);
|
|
||||||
}
|
|
||||||
data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress);
|
|
||||||
data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max);
|
|
||||||
|
|
||||||
sendMessageToHandler(MessageStatus.UPDATE_PROGRESS, null, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProgress(int resourceId, int progress, int max) {
|
|
||||||
setProgress(getString(resourceId), progress, max);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProgress(int progress, int max) {
|
|
||||||
setProgress(null, progress, max);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPreventCancel() {
|
|
||||||
sendMessageToHandler(MessageStatus.PREVENT_CANCEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
private InputData createDecryptInputData(Bundle data) throws IOException, PgpGeneralException {
|
|
||||||
return createCryptInputData(data, DECRYPT_CIPHERTEXT_BYTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
private InputData createCryptInputData(Bundle data, String bytesName) throws PgpGeneralException, IOException {
|
|
||||||
int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET);
|
|
||||||
IOType type = IOType.fromInt(source);
|
|
||||||
switch (type) {
|
|
||||||
case BYTES: /* encrypting bytes directly */
|
|
||||||
byte[] bytes = data.getByteArray(bytesName);
|
|
||||||
return new InputData(new ByteArrayInputStream(bytes), bytes.length);
|
|
||||||
|
|
||||||
case URI: /* encrypting content uri */
|
|
||||||
Uri providerUri = data.getParcelable(ENCRYPT_DECRYPT_INPUT_URI);
|
|
||||||
|
|
||||||
// InputStream
|
|
||||||
return new InputData(getContentResolver().openInputStream(providerUri), FileHelper.getFileSize(this, providerUri, 0));
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new PgpGeneralException("No target chosen!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private OutputStream createCryptOutputStream(Bundle data) throws PgpGeneralException, FileNotFoundException {
|
|
||||||
int target = data.getInt(TARGET);
|
|
||||||
IOType type = IOType.fromInt(target);
|
|
||||||
switch (type) {
|
|
||||||
case BYTES:
|
|
||||||
return new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
case URI:
|
|
||||||
Uri providerUri = data.getParcelable(ENCRYPT_DECRYPT_OUTPUT_URI);
|
|
||||||
|
|
||||||
return getContentResolver().openOutputStream(providerUri);
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new PgpGeneralException("No target chosen!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void finalizeDecryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream) {
|
|
||||||
finalizeCryptOutputStream(data, resultData, outStream, RESULT_DECRYPTED_BYTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void finalizeCryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream, String bytesName) {
|
|
||||||
int target = data.getInt(TARGET);
|
|
||||||
IOType type = IOType.fromInt(target);
|
|
||||||
switch (type) {
|
|
||||||
case BYTES:
|
|
||||||
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
|
|
||||||
resultData.putByteArray(bytesName, output);
|
|
||||||
break;
|
|
||||||
case URI:
|
|
||||||
// nothing, output was written, just send okay and verification bundle
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
||||||
if (ACTION_CANCEL.equals(intent.getAction())) {
|
|
||||||
mActionCanceled.set(true);
|
|
||||||
return START_NOT_STICKY;
|
|
||||||
}
|
|
||||||
return super.onStartCommand(intent, flags, startId);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.service;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.Messenger;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.operations.BaseOperation;
|
||||||
|
import org.sufficientlysecure.keychain.operations.CertifyOperation;
|
||||||
|
import org.sufficientlysecure.keychain.operations.EditKeyOperation;
|
||||||
|
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
||||||
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
|
||||||
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This Service contains all important long lasting operations for OpenKeychain. It receives Intents with
|
||||||
|
* data from the activities or other apps, executes them, and stops itself after doing them.
|
||||||
|
*/
|
||||||
|
public class KeychainNewService extends Service implements Progressable {
|
||||||
|
|
||||||
|
// messenger for communication (hack)
|
||||||
|
public static final String EXTRA_MESSENGER = "messenger";
|
||||||
|
|
||||||
|
// extras for operation
|
||||||
|
public static final String EXTRA_OPERATION_INPUT = "op_input";
|
||||||
|
public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
|
||||||
|
|
||||||
|
// this attribute can possibly merged with the one above? not sure...
|
||||||
|
private AtomicBoolean mActionCanceled = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
ThreadLocal<Messenger> mMessenger = new ThreadLocal<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is run on the main thread, we need to spawn a runnable which runs on another thread for the actual operation
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(final Intent intent, int flags, int startId) {
|
||||||
|
|
||||||
|
Runnable actionRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// We have not been cancelled! (yet)
|
||||||
|
mActionCanceled.set(false);
|
||||||
|
|
||||||
|
Bundle extras = intent.getExtras();
|
||||||
|
|
||||||
|
// Set messenger for communication (for this particular thread)
|
||||||
|
mMessenger.set(extras.<Messenger>getParcelable(EXTRA_MESSENGER));
|
||||||
|
|
||||||
|
// Input
|
||||||
|
Parcelable inputParcel = extras.getParcelable(EXTRA_OPERATION_INPUT);
|
||||||
|
CryptoInputParcel cryptoInput = extras.getParcelable(EXTRA_CRYPTO_INPUT);
|
||||||
|
|
||||||
|
// Operation
|
||||||
|
BaseOperation op;
|
||||||
|
|
||||||
|
// just for brevity
|
||||||
|
KeychainNewService outerThis = KeychainNewService.this;
|
||||||
|
if (inputParcel instanceof SignEncryptParcel) {
|
||||||
|
op = new SignEncryptOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
|
||||||
|
} else if (inputParcel instanceof PgpDecryptVerifyInputParcel) {
|
||||||
|
op = new PgpDecryptVerify(outerThis, new ProviderHelper(outerThis), outerThis);
|
||||||
|
} else if (inputParcel instanceof SaveKeyringParcel) {
|
||||||
|
op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
|
||||||
|
} else if (inputParcel instanceof CertifyAction) {
|
||||||
|
op = new CertifyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") // this is unchecked, we make sure it's the correct op above!
|
||||||
|
OperationResult result = op.execute(inputParcel, cryptoInput);
|
||||||
|
|
||||||
|
sendMessageToHandler(MessageStatus.OKAY, result);
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Thread actionThread = new Thread(actionRunnable);
|
||||||
|
actionThread.start();
|
||||||
|
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessageToHandler(MessageStatus status, Integer arg2, Bundle data) {
|
||||||
|
|
||||||
|
Message msg = Message.obtain();
|
||||||
|
assert msg != null;
|
||||||
|
msg.arg1 = status.ordinal();
|
||||||
|
if (arg2 != null) {
|
||||||
|
msg.arg2 = arg2;
|
||||||
|
}
|
||||||
|
if (data != null) {
|
||||||
|
msg.setData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mMessenger.get().send(msg);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessageToHandler(MessageStatus status, OperationResult data) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(OperationResult.EXTRA_RESULT, data);
|
||||||
|
sendMessageToHandler(status, null, bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessageToHandler(MessageStatus status) {
|
||||||
|
sendMessageToHandler(status, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set progress of ProgressDialog by sending message to handler on UI thread
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setProgress(String message, int progress, int max) {
|
||||||
|
Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
|
||||||
|
+ max);
|
||||||
|
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
if (message != null) {
|
||||||
|
data.putString(ServiceProgressHandler.DATA_MESSAGE, message);
|
||||||
|
}
|
||||||
|
data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress);
|
||||||
|
data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max);
|
||||||
|
|
||||||
|
sendMessageToHandler(MessageStatus.UPDATE_PROGRESS, null, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgress(int resourceId, int progress, int max) {
|
||||||
|
setProgress(getString(resourceId), progress, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgress(int progress, int max) {
|
||||||
|
setProgress(null, progress, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPreventCancel() {
|
||||||
|
sendMessageToHandler(MessageStatus.PREVENT_CANCEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,777 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.sufficientlysecure.keychain.service;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.Messenger;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
|
||||||
|
import com.textuality.keybase.lib.Proof;
|
||||||
|
import com.textuality.keybase.lib.prover.Prover;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.spongycastle.openpgp.PGPUtil;
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
||||||
|
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||||
|
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.operations.CertifyOperation;
|
||||||
|
import org.sufficientlysecure.keychain.operations.DeleteOperation;
|
||||||
|
import org.sufficientlysecure.keychain.operations.EditKeyOperation;
|
||||||
|
import org.sufficientlysecure.keychain.operations.ImportExportOperation;
|
||||||
|
import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
|
||||||
|
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.ExportResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.SynchronousQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import de.measite.minidns.Client;
|
||||||
|
import de.measite.minidns.DNSMessage;
|
||||||
|
import de.measite.minidns.Question;
|
||||||
|
import de.measite.minidns.Record;
|
||||||
|
import de.measite.minidns.record.Data;
|
||||||
|
import de.measite.minidns.record.TXT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This Service contains all important long lasting operations for OpenKeychain. It receives Intents with
|
||||||
|
* data from the activities or other apps, executes them, and stops itself after doing them.
|
||||||
|
*/
|
||||||
|
public class KeychainService extends Service implements Progressable {
|
||||||
|
|
||||||
|
/* extras that can be given by intent */
|
||||||
|
public static final String EXTRA_MESSENGER = "messenger";
|
||||||
|
public static final String EXTRA_DATA = "data";
|
||||||
|
|
||||||
|
/* possible actions */
|
||||||
|
|
||||||
|
public static final String ACTION_VERIFY_KEYBASE_PROOF = Constants.INTENT_PREFIX + "VERIFY_KEYBASE_PROOF";
|
||||||
|
|
||||||
|
public static final String ACTION_EDIT_KEYRING = Constants.INTENT_PREFIX + "EDIT_KEYRING";
|
||||||
|
|
||||||
|
public static final String ACTION_PROMOTE_KEYRING = Constants.INTENT_PREFIX + "PROMOTE_KEYRING";
|
||||||
|
|
||||||
|
public static final String ACTION_IMPORT_KEYRING = Constants.INTENT_PREFIX + "IMPORT_KEYRING";
|
||||||
|
public static final String ACTION_EXPORT_KEYRING = Constants.INTENT_PREFIX + "EXPORT_KEYRING";
|
||||||
|
|
||||||
|
public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING";
|
||||||
|
|
||||||
|
public static final String ACTION_DELETE = Constants.INTENT_PREFIX + "DELETE";
|
||||||
|
|
||||||
|
public static final String ACTION_CONSOLIDATE = Constants.INTENT_PREFIX + "CONSOLIDATE";
|
||||||
|
|
||||||
|
public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL";
|
||||||
|
|
||||||
|
/* keys for data bundle */
|
||||||
|
|
||||||
|
// keybase proof
|
||||||
|
public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint";
|
||||||
|
public static final String KEYBASE_PROOF = "keybase_proof";
|
||||||
|
|
||||||
|
// save keyring
|
||||||
|
public static final String EDIT_KEYRING_PARCEL = "save_parcel";
|
||||||
|
public static final String EDIT_KEYRING_PASSPHRASE = "passphrase";
|
||||||
|
public static final String EXTRA_CRYPTO_INPUT = "crypto_input";
|
||||||
|
|
||||||
|
// delete keyring(s)
|
||||||
|
public static final String DELETE_KEY_LIST = "delete_list";
|
||||||
|
public static final String DELETE_IS_SECRET = "delete_is_secret";
|
||||||
|
|
||||||
|
// import key
|
||||||
|
public static final String IMPORT_KEY_LIST = "import_key_list";
|
||||||
|
public static final String IMPORT_KEY_SERVER = "import_key_server";
|
||||||
|
|
||||||
|
// export key
|
||||||
|
public static final String EXPORT_FILENAME = "export_filename";
|
||||||
|
public static final String EXPORT_URI = "export_uri";
|
||||||
|
public static final String EXPORT_SECRET = "export_secret";
|
||||||
|
public static final String EXPORT_ALL = "export_all";
|
||||||
|
public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id";
|
||||||
|
|
||||||
|
// upload key
|
||||||
|
public static final String UPLOAD_KEY_SERVER = "upload_key_server";
|
||||||
|
|
||||||
|
// promote key
|
||||||
|
public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id";
|
||||||
|
public static final String PROMOTE_CARD_AID = "promote_card_aid";
|
||||||
|
public static final String PROMOTE_SUBKEY_IDS = "promote_fingerprints";
|
||||||
|
|
||||||
|
// consolidate
|
||||||
|
public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery";
|
||||||
|
|
||||||
|
Messenger mMessenger;
|
||||||
|
|
||||||
|
// this attribute can possibly merged with the one above? not sure...
|
||||||
|
private AtomicBoolean mActionCanceled = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
|
||||||
|
private KeyImportAccumulator mKeyImportAccumulator;
|
||||||
|
|
||||||
|
private KeychainService mKeychainService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is run on the main thread, we need to spawn a runnable which runs on another thread for the actual operation
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(final Intent intent, int flags, int startId) {
|
||||||
|
mKeychainService = this;
|
||||||
|
|
||||||
|
if (ACTION_CANCEL.equals(intent.getAction())) {
|
||||||
|
mActionCanceled.set(true);
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
Runnable actionRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// We have not been cancelled! (yet)
|
||||||
|
mActionCanceled.set(false);
|
||||||
|
|
||||||
|
Bundle extras = intent.getExtras();
|
||||||
|
if (extras == null) {
|
||||||
|
Log.e(Constants.TAG, "Extras bundle is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(extras.containsKey(EXTRA_MESSENGER) || extras.containsKey(EXTRA_DATA) || (intent
|
||||||
|
.getAction() == null))) {
|
||||||
|
Log.e(Constants.TAG,
|
||||||
|
"Extra bundle must contain a messenger, a data bundle, and an action!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri dataUri = intent.getData();
|
||||||
|
|
||||||
|
mMessenger = (Messenger) extras.get(EXTRA_MESSENGER);
|
||||||
|
Bundle data = extras.getBundle(EXTRA_DATA);
|
||||||
|
if (data == null) {
|
||||||
|
Log.e(Constants.TAG, "data extra is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.logDebugBundle(data, "EXTRA_DATA");
|
||||||
|
|
||||||
|
ProviderHelper providerHelper = new ProviderHelper(mKeychainService);
|
||||||
|
|
||||||
|
String action = intent.getAction();
|
||||||
|
|
||||||
|
// executeServiceMethod action from extra bundle
|
||||||
|
switch (action) {
|
||||||
|
case ACTION_CONSOLIDATE: {
|
||||||
|
|
||||||
|
// Operation
|
||||||
|
ConsolidateResult result;
|
||||||
|
if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) {
|
||||||
|
result = providerHelper.consolidateDatabaseStep2(mKeychainService);
|
||||||
|
} else {
|
||||||
|
result = providerHelper.consolidateDatabaseStep1(mKeychainService);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result
|
||||||
|
sendMessageToHandler(MessageStatus.OKAY, result);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACTION_VERIFY_KEYBASE_PROOF: {
|
||||||
|
|
||||||
|
try {
|
||||||
|
Proof proof = new Proof(new JSONObject(data.getString(KEYBASE_PROOF)));
|
||||||
|
setProgress(R.string.keybase_message_fetching_data, 0, 100);
|
||||||
|
|
||||||
|
Prover prover = Prover.findProverFor(proof);
|
||||||
|
|
||||||
|
if (prover == null) {
|
||||||
|
sendProofError(getString(R.string.keybase_no_prover_found) + ": " + proof
|
||||||
|
.getPrettyName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prover.fetchProofData()) {
|
||||||
|
sendProofError(prover.getLog(), getString(R.string.keybase_problem_fetching_evidence));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String requiredFingerprint = data.getString(KEYBASE_REQUIRED_FINGERPRINT);
|
||||||
|
if (!prover.checkFingerprint(requiredFingerprint)) {
|
||||||
|
sendProofError(getString(R.string.keybase_key_mismatch));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String domain = prover.dnsTxtCheckRequired();
|
||||||
|
if (domain != null) {
|
||||||
|
DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT));
|
||||||
|
if (dnsQuery == null) {
|
||||||
|
sendProofError(prover.getLog(), getString(R.string.keybase_dns_query_failure));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Record[] records = dnsQuery.getAnswers();
|
||||||
|
List<List<byte[]>> extents = new ArrayList<List<byte[]>>();
|
||||||
|
for (Record r : records) {
|
||||||
|
Data d = r.getPayload();
|
||||||
|
if (d instanceof TXT) {
|
||||||
|
extents.add(((TXT) d).getExtents());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!prover.checkDnsTxt(extents)) {
|
||||||
|
sendProofError(prover.getLog(), null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] messageBytes = prover.getPgpMessage().getBytes();
|
||||||
|
if (prover.rawMessageCheckRequired()) {
|
||||||
|
InputStream messageByteStream = PGPUtil.getDecoderStream(new ByteArrayInputStream
|
||||||
|
(messageBytes));
|
||||||
|
if (!prover.checkRawMessageBytes(messageByteStream)) {
|
||||||
|
sendProofError(prover.getLog(), null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PgpDecryptVerify op = new PgpDecryptVerify(mKeychainService, providerHelper,
|
||||||
|
mKeychainService);
|
||||||
|
|
||||||
|
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(messageBytes)
|
||||||
|
.setSignedLiteralData(true)
|
||||||
|
.setRequiredSignerFingerprint(requiredFingerprint);
|
||||||
|
|
||||||
|
DecryptVerifyResult decryptVerifyResult = op.execute(input, new CryptoInputParcel());
|
||||||
|
|
||||||
|
if (!decryptVerifyResult.success()) {
|
||||||
|
OperationLog log = decryptVerifyResult.getLog();
|
||||||
|
OperationResult.LogEntryParcel lastEntry = null;
|
||||||
|
for (OperationResult.LogEntryParcel entry : log) {
|
||||||
|
lastEntry = entry;
|
||||||
|
}
|
||||||
|
sendProofError(getString(lastEntry.mType.getMsgId()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prover.validate(new String(decryptVerifyResult.getOutputBytes()))) {
|
||||||
|
sendProofError(getString(R.string.keybase_message_payload_mismatch));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bundle resultData = new Bundle();
|
||||||
|
resultData.putString(ServiceProgressHandler.DATA_MESSAGE, "OK");
|
||||||
|
|
||||||
|
// these help the handler construct a useful human-readable message
|
||||||
|
resultData.putString(ServiceProgressHandler.KEYBASE_PROOF_URL, prover.getProofUrl());
|
||||||
|
resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_URL, prover.getPresenceUrl());
|
||||||
|
resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_LABEL, prover
|
||||||
|
.getPresenceLabel());
|
||||||
|
sendMessageToHandler(MessageStatus.OKAY, resultData);
|
||||||
|
} catch (Exception e) {
|
||||||
|
sendErrorToHandler(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACTION_DELETE: {
|
||||||
|
|
||||||
|
// Input
|
||||||
|
long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST);
|
||||||
|
boolean isSecret = data.getBoolean(DELETE_IS_SECRET);
|
||||||
|
|
||||||
|
// Operation
|
||||||
|
DeleteOperation op = new DeleteOperation(mKeychainService, providerHelper, mKeychainService);
|
||||||
|
DeleteResult result = op.execute(masterKeyIds, isSecret);
|
||||||
|
|
||||||
|
// Result
|
||||||
|
sendMessageToHandler(MessageStatus.OKAY, result);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACTION_EDIT_KEYRING: {
|
||||||
|
|
||||||
|
// Input
|
||||||
|
SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL);
|
||||||
|
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
|
||||||
|
|
||||||
|
// Operation
|
||||||
|
EditKeyOperation op = new EditKeyOperation(mKeychainService, providerHelper,
|
||||||
|
mKeychainService, mActionCanceled);
|
||||||
|
OperationResult result = op.execute(saveParcel, cryptoInput);
|
||||||
|
|
||||||
|
// Result
|
||||||
|
sendMessageToHandler(MessageStatus.OKAY, result);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACTION_PROMOTE_KEYRING: {
|
||||||
|
|
||||||
|
// Input
|
||||||
|
long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID);
|
||||||
|
byte[] cardAid = data.getByteArray(PROMOTE_CARD_AID);
|
||||||
|
long[] subKeyIds = data.getLongArray(PROMOTE_SUBKEY_IDS);
|
||||||
|
|
||||||
|
// Operation
|
||||||
|
PromoteKeyOperation op = new PromoteKeyOperation(
|
||||||
|
mKeychainService, providerHelper, mKeychainService,
|
||||||
|
mActionCanceled);
|
||||||
|
PromoteKeyResult result = op.execute(keyRingId, cardAid, subKeyIds);
|
||||||
|
|
||||||
|
// Result
|
||||||
|
sendMessageToHandler(MessageStatus.OKAY, result);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACTION_EXPORT_KEYRING: {
|
||||||
|
|
||||||
|
// Input
|
||||||
|
boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
|
||||||
|
String outputFile = data.getString(EXPORT_FILENAME);
|
||||||
|
Uri outputUri = data.getParcelable(EXPORT_URI);
|
||||||
|
|
||||||
|
boolean exportAll = data.getBoolean(EXPORT_ALL);
|
||||||
|
long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
|
||||||
|
|
||||||
|
// Operation
|
||||||
|
ImportExportOperation importExportOperation = new ImportExportOperation(
|
||||||
|
mKeychainService, providerHelper, mKeychainService);
|
||||||
|
ExportResult result;
|
||||||
|
if (outputFile != null) {
|
||||||
|
result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile);
|
||||||
|
} else {
|
||||||
|
result = importExportOperation.exportToUri(masterKeyIds, exportSecret, outputUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result
|
||||||
|
sendMessageToHandler(MessageStatus.OKAY, result);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACTION_IMPORT_KEYRING: {
|
||||||
|
|
||||||
|
// Input
|
||||||
|
String keyServer = data.getString(IMPORT_KEY_SERVER);
|
||||||
|
ArrayList<ParcelableKeyRing> keyList = data.getParcelableArrayList(IMPORT_KEY_LIST);
|
||||||
|
|
||||||
|
// either keyList or cache must be null, no guarantees otherwise
|
||||||
|
if (keyList == null) {// import from file, do serially
|
||||||
|
serialKeyImport(null, keyServer, providerHelper);
|
||||||
|
} else {
|
||||||
|
// if there is more than one key with the same fingerprint, we do a serial import to prevent
|
||||||
|
// https://github.com/open-keychain/open-keychain/issues/1221
|
||||||
|
HashSet<String> keyFingerprintSet = new HashSet<>();
|
||||||
|
for (int i = 0; i < keyList.size(); i++) {
|
||||||
|
keyFingerprintSet.add(keyList.get(i).mExpectedFingerprint);
|
||||||
|
}
|
||||||
|
if (keyFingerprintSet.size() == keyList.size()) {
|
||||||
|
// all keys have unique fingerprints
|
||||||
|
multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer);
|
||||||
|
} else {
|
||||||
|
serialKeyImport(keyList, keyServer, providerHelper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACTION_UPLOAD_KEYRING: {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Input
|
||||||
|
String keyServer = data.getString(UPLOAD_KEY_SERVER);
|
||||||
|
// and dataUri!
|
||||||
|
|
||||||
|
// Operation
|
||||||
|
HkpKeyserver server = new HkpKeyserver(keyServer);
|
||||||
|
|
||||||
|
CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
|
||||||
|
ImportExportOperation importExportOperation = new ImportExportOperation(mKeychainService,
|
||||||
|
providerHelper, mKeychainService);
|
||||||
|
|
||||||
|
try {
|
||||||
|
importExportOperation.uploadKeyRingToServer(server, keyring);
|
||||||
|
} catch (Keyserver.AddKeyException e) {
|
||||||
|
throw new PgpGeneralException("Unable to export key to selected server");
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessageToHandler(MessageStatus.OKAY);
|
||||||
|
} catch (Exception e) {
|
||||||
|
sendErrorToHandler(e);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!intent.getAction().equals(ACTION_IMPORT_KEYRING)) {
|
||||||
|
// import keyring handles stopping service on its own
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Thread actionThread = new Thread(actionRunnable);
|
||||||
|
actionThread.start();
|
||||||
|
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendProofError(List<String> log, String label) {
|
||||||
|
String msg = null;
|
||||||
|
label = (label == null) ? "" : label + ": ";
|
||||||
|
for (String m : log) {
|
||||||
|
Log.e(Constants.TAG, label + m);
|
||||||
|
msg = m;
|
||||||
|
}
|
||||||
|
sendProofError(label + msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendProofError(String msg) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putString(ServiceProgressHandler.DATA_ERROR, msg);
|
||||||
|
sendMessageToHandler(MessageStatus.OKAY, bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendErrorToHandler(Exception e) {
|
||||||
|
// TODO: Implement a better exception handling here
|
||||||
|
// contextualize the exception, if necessary
|
||||||
|
String message;
|
||||||
|
if (e instanceof PgpGeneralMsgIdException) {
|
||||||
|
e = ((PgpGeneralMsgIdException) e).getContextualized(mKeychainService);
|
||||||
|
message = e.getMessage();
|
||||||
|
} else {
|
||||||
|
message = e.getMessage();
|
||||||
|
}
|
||||||
|
Log.d(Constants.TAG, "KeychainService Exception: ", e);
|
||||||
|
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
data.putString(ServiceProgressHandler.DATA_ERROR, message);
|
||||||
|
sendMessageToHandler(MessageStatus.EXCEPTION, null, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessageToHandler(MessageStatus status, Integer arg2, Bundle data) {
|
||||||
|
|
||||||
|
Message msg = Message.obtain();
|
||||||
|
assert msg != null;
|
||||||
|
msg.arg1 = status.ordinal();
|
||||||
|
if (arg2 != null) {
|
||||||
|
msg.arg2 = arg2;
|
||||||
|
}
|
||||||
|
if (data != null) {
|
||||||
|
msg.setData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mMessenger.send(msg);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessageToHandler(MessageStatus status, OperationResult data) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(OperationResult.EXTRA_RESULT, data);
|
||||||
|
sendMessageToHandler(status, null, bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessageToHandler(MessageStatus status, Bundle data) {
|
||||||
|
sendMessageToHandler(status, null, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendMessageToHandler(MessageStatus status) {
|
||||||
|
sendMessageToHandler(status, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set progress of ProgressDialog by sending message to handler on UI thread
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setProgress(String message, int progress, int max) {
|
||||||
|
Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
|
||||||
|
+ max);
|
||||||
|
|
||||||
|
Bundle data = new Bundle();
|
||||||
|
if (message != null) {
|
||||||
|
data.putString(ServiceProgressHandler.DATA_MESSAGE, message);
|
||||||
|
}
|
||||||
|
data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress);
|
||||||
|
data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max);
|
||||||
|
|
||||||
|
sendMessageToHandler(MessageStatus.UPDATE_PROGRESS, null, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgress(int resourceId, int progress, int max) {
|
||||||
|
setProgress(getString(resourceId), progress, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgress(int progress, int max) {
|
||||||
|
setProgress(null, progress, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPreventCancel() {
|
||||||
|
sendMessageToHandler(MessageStatus.PREVENT_CANCEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void serialKeyImport(ArrayList<ParcelableKeyRing> keyList, final String keyServer,
|
||||||
|
ProviderHelper providerHelper) {
|
||||||
|
Log.d(Constants.TAG, "serial key import starting");
|
||||||
|
ParcelableFileCache<ParcelableKeyRing> cache =
|
||||||
|
new ParcelableFileCache<>(mKeychainService, "key_import.pcl");
|
||||||
|
|
||||||
|
// Operation
|
||||||
|
ImportExportOperation importExportOperation = new ImportExportOperation(
|
||||||
|
mKeychainService, providerHelper, mKeychainService,
|
||||||
|
mActionCanceled);
|
||||||
|
// Either list or cache must be null, no guarantees otherwise.
|
||||||
|
ImportKeyResult result = keyList != null
|
||||||
|
? importExportOperation.importKeyRings(keyList, keyServer)
|
||||||
|
: importExportOperation.importKeyRings(cache, keyServer);
|
||||||
|
|
||||||
|
ContactSyncAdapterService.requestSync();
|
||||||
|
// Result
|
||||||
|
sendMessageToHandler(MessageStatus.OKAY, result);
|
||||||
|
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void multiThreadedKeyImport(Iterator<ParcelableKeyRing> keyListIterator, int totKeys, final String
|
||||||
|
keyServer) {
|
||||||
|
Log.d(Constants.TAG, "Multi-threaded key import starting");
|
||||||
|
if (keyListIterator != null) {
|
||||||
|
mKeyImportAccumulator = new KeyImportAccumulator(totKeys, mKeychainService);
|
||||||
|
setProgress(0, totKeys);
|
||||||
|
|
||||||
|
final int maxThreads = 200;
|
||||||
|
ExecutorService importExecutor = new ThreadPoolExecutor(0, maxThreads,
|
||||||
|
30L, TimeUnit.SECONDS,
|
||||||
|
new SynchronousQueue<Runnable>());
|
||||||
|
|
||||||
|
while (keyListIterator.hasNext()) {
|
||||||
|
|
||||||
|
final ParcelableKeyRing pkRing = keyListIterator.next();
|
||||||
|
|
||||||
|
Runnable importOperationRunnable = new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ImportKeyResult result = null;
|
||||||
|
try {
|
||||||
|
ImportExportOperation importExportOperation = new ImportExportOperation(
|
||||||
|
mKeychainService,
|
||||||
|
new ProviderHelper(mKeychainService),
|
||||||
|
mKeyImportAccumulator.getImportProgressable(),
|
||||||
|
mActionCanceled);
|
||||||
|
|
||||||
|
ArrayList<ParcelableKeyRing> list = new ArrayList<>();
|
||||||
|
list.add(pkRing);
|
||||||
|
|
||||||
|
result = importExportOperation.importKeyRings(list,
|
||||||
|
keyServer);
|
||||||
|
} finally {
|
||||||
|
// in the off-chance that importKeyRings does something to crash the
|
||||||
|
// thread before it can call singleKeyRingImportCompleted, our imported
|
||||||
|
// key count will go wrong. This will cause the service to never die,
|
||||||
|
// and the progress dialog to stay displayed. The finally block was
|
||||||
|
// originally meant to ensure singleKeyRingImportCompleted was called,
|
||||||
|
// and checks for null were to be introduced, but in such a scenario,
|
||||||
|
// knowing an uncaught error exists in importKeyRings is more important.
|
||||||
|
|
||||||
|
// if a null gets passed, something wrong is happening. We want a crash.
|
||||||
|
|
||||||
|
mKeyImportAccumulator.singleKeyRingImportCompleted(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
importExecutor.execute(importOperationRunnable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to accumulate the results of individual key imports
|
||||||
|
*/
|
||||||
|
private class KeyImportAccumulator {
|
||||||
|
private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog();
|
||||||
|
private int mTotalKeys;
|
||||||
|
private int mImportedKeys = 0;
|
||||||
|
private Progressable mInternalProgressable;
|
||||||
|
ArrayList<Long> mImportedMasterKeyIds = new ArrayList<Long>();
|
||||||
|
private int mBadKeys = 0;
|
||||||
|
private int mNewKeys = 0;
|
||||||
|
private int mUpdatedKeys = 0;
|
||||||
|
private int mSecret = 0;
|
||||||
|
private int mResultType = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* meant to be used with a service due to stopSelf() in singleKeyRingImportCompleted. Remove this if
|
||||||
|
* generalising.
|
||||||
|
*
|
||||||
|
* @param totalKeys total number of keys to be imported
|
||||||
|
* @param externalProgressable the external progressable to be updated every time a key is imported
|
||||||
|
*/
|
||||||
|
public KeyImportAccumulator(int totalKeys, Progressable externalProgressable) {
|
||||||
|
mTotalKeys = totalKeys;
|
||||||
|
// ignore updates from ImportExportOperation for now
|
||||||
|
mInternalProgressable = new Progressable() {
|
||||||
|
@Override
|
||||||
|
public void setProgress(String message, int current, int total) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgress(int resourceId, int current, int total) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgress(int current, int total) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPreventCancel() {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void singleKeyRingImportCompleted(ImportKeyResult result) {
|
||||||
|
// increase imported key count and accumulate log and bad, new etc. key counts from result
|
||||||
|
mKeyImportAccumulator.accumulateKeyImport(result);
|
||||||
|
|
||||||
|
setProgress(mKeyImportAccumulator.getImportedKeys(), mKeyImportAccumulator.getTotalKeys());
|
||||||
|
|
||||||
|
if (mKeyImportAccumulator.isImportFinished()) {
|
||||||
|
ContactSyncAdapterService.requestSync();
|
||||||
|
|
||||||
|
sendMessageToHandler(ServiceProgressHandler.MessageStatus.OKAY,
|
||||||
|
mKeyImportAccumulator.getConsolidatedImportKeyResult());
|
||||||
|
|
||||||
|
stopSelf();//we're done here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Progressable getImportProgressable() {
|
||||||
|
return mInternalProgressable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalKeys() {
|
||||||
|
return mTotalKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getImportedKeys() {
|
||||||
|
return mImportedKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void accumulateKeyImport(ImportKeyResult result) {
|
||||||
|
mImportedKeys++;
|
||||||
|
mImportLog.addAll(result.getLog().toList());//accumulates log
|
||||||
|
mBadKeys += result.mBadKeys;
|
||||||
|
mNewKeys += result.mNewKeys;
|
||||||
|
mUpdatedKeys += result.mUpdatedKeys;
|
||||||
|
mSecret += result.mSecret;
|
||||||
|
|
||||||
|
long[] masterKeyIds = result.getImportedMasterKeyIds();
|
||||||
|
for (long masterKeyId : masterKeyIds) {
|
||||||
|
mImportedMasterKeyIds.add(masterKeyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if any key import has been cancelled, set result type to cancelled
|
||||||
|
// resultType is added to in getConsolidatedKayImport to account for remaining factors
|
||||||
|
mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns accumulated result of all imports so far
|
||||||
|
*/
|
||||||
|
public ImportKeyResult getConsolidatedImportKeyResult() {
|
||||||
|
|
||||||
|
// adding required information to mResultType
|
||||||
|
// special case,no keys requested for import
|
||||||
|
if (mBadKeys == 0 && mNewKeys == 0 && mUpdatedKeys == 0) {
|
||||||
|
mResultType = ImportKeyResult.RESULT_FAIL_NOTHING;
|
||||||
|
} else {
|
||||||
|
if (mNewKeys > 0) {
|
||||||
|
mResultType |= ImportKeyResult.RESULT_OK_NEWKEYS;
|
||||||
|
}
|
||||||
|
if (mUpdatedKeys > 0) {
|
||||||
|
mResultType |= ImportKeyResult.RESULT_OK_UPDATED;
|
||||||
|
}
|
||||||
|
if (mBadKeys > 0) {
|
||||||
|
mResultType |= ImportKeyResult.RESULT_WITH_ERRORS;
|
||||||
|
if (mNewKeys == 0 && mUpdatedKeys == 0) {
|
||||||
|
mResultType |= ImportKeyResult.RESULT_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mImportLog.containsWarnings()) {
|
||||||
|
mResultType |= ImportKeyResult.RESULT_WARNINGS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long masterKeyIds[] = new long[mImportedMasterKeyIds.size()];
|
||||||
|
for (int i = 0; i < masterKeyIds.length; i++) {
|
||||||
|
masterKeyIds[i] = mImportedMasterKeyIds.get(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ImportKeyResult(mResultType, mImportLog, mNewKeys, mUpdatedKeys, mBadKeys,
|
||||||
|
mSecret, masterKeyIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isImportFinished() {
|
||||||
|
return mTotalKeys == mImportedKeys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -36,6 +36,9 @@ import android.os.Messenger;
|
|||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.support.v4.util.LongSparseArray;
|
import android.support.v4.util.LongSparseArray;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
@ -149,6 +152,14 @@ public class PassphraseCacheService extends Service {
|
|||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void clearCachedPassphrases(Context context) {
|
||||||
|
Log.d(Constants.TAG, "PassphraseCacheService.clearCachedPassphrase()");
|
||||||
|
|
||||||
|
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||||
|
intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
|
||||||
|
|
||||||
|
context.startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a cached passphrase from memory by sending an intent to the service. This method is
|
* Gets a cached passphrase from memory by sending an intent to the service. This method is
|
||||||
@ -411,9 +422,9 @@ public class PassphraseCacheService extends Service {
|
|||||||
|
|
||||||
long referenceKeyId;
|
long referenceKeyId;
|
||||||
if (Preferences.getPreferences(mContext).getPassphraseCacheSubs()) {
|
if (Preferences.getPreferences(mContext).getPassphraseCacheSubs()) {
|
||||||
referenceKeyId = intent.getLongExtra(EXTRA_KEY_ID, 0L);
|
|
||||||
} else {
|
|
||||||
referenceKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, 0L);
|
referenceKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, 0L);
|
||||||
|
} else {
|
||||||
|
referenceKeyId = intent.getLongExtra(EXTRA_KEY_ID, 0L);
|
||||||
}
|
}
|
||||||
// Stop specific ttl alarm and
|
// Stop specific ttl alarm and
|
||||||
am.cancel(buildIntent(this, referenceKeyId));
|
am.cancel(buildIntent(this, referenceKeyId));
|
||||||
@ -466,11 +477,26 @@ public class PassphraseCacheService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// from de.azapps.mirakel.helper.Helpers from https://github.com/MirakelX/mirakel-android
|
||||||
|
private static Bitmap getBitmap(int resId, Context ctx) {
|
||||||
|
final int mLargeIconWidth = (int) ctx.getResources().getDimension(
|
||||||
|
android.R.dimen.notification_large_icon_width);
|
||||||
|
final int mLargeIconHeight = (int) ctx.getResources().getDimension(
|
||||||
|
android.R.dimen.notification_large_icon_height);
|
||||||
|
final Drawable d = ctx.getResources().getDrawable(resId);
|
||||||
|
final Bitmap b = Bitmap.createBitmap(mLargeIconWidth, mLargeIconHeight, Bitmap.Config.ARGB_8888);
|
||||||
|
final Canvas c = new Canvas(b);
|
||||||
|
d.setBounds(0, 0, mLargeIconWidth, mLargeIconHeight);
|
||||||
|
d.draw(c);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
private Notification getNotification() {
|
private Notification getNotification() {
|
||||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
builder.setSmallIcon(R.drawable.ic_launcher)
|
builder.setSmallIcon(R.drawable.ic_stat_notify)
|
||||||
|
.setLargeIcon(getBitmap(R.drawable.ic_launcher, getBaseContext()))
|
||||||
.setContentTitle(getString(R.string.app_name))
|
.setContentTitle(getString(R.string.app_name))
|
||||||
.setContentText(String.format(getString(R.string.passp_cache_notif_n_keys),
|
.setContentText(String.format(getString(R.string.passp_cache_notif_n_keys),
|
||||||
mPassphraseCache.size()));
|
mPassphraseCache.size()));
|
||||||
@ -502,7 +528,8 @@ public class PassphraseCacheService extends Service {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Fallback, since expandable notifications weren't available back then
|
// Fallback, since expandable notifications weren't available back then
|
||||||
builder.setSmallIcon(R.drawable.ic_launcher)
|
builder.setSmallIcon(R.drawable.ic_stat_notify)
|
||||||
|
.setLargeIcon(getBitmap(R.drawable.ic_launcher, getBaseContext()))
|
||||||
.setContentTitle(String.format(getString(R.string.passp_cache_notif_n_keys),
|
.setContentTitle(String.format(getString(R.string.passp_cache_notif_n_keys),
|
||||||
mPassphraseCache.size()))
|
mPassphraseCache.size()))
|
||||||
.setContentText(getString(R.string.passp_cache_notif_click_to_clear));
|
.setContentText(getString(R.string.passp_cache_notif_click_to_clear));
|
||||||
|
@ -95,7 +95,8 @@ public class SaveKeyringParcel implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (SubkeyChange change : mChangeSubKeys) {
|
for (SubkeyChange change : mChangeSubKeys) {
|
||||||
if (change.mRecertify || change.mFlags != null || change.mExpiry != null) {
|
if (change.mRecertify || change.mFlags != null || change.mExpiry != null
|
||||||
|
|| change.mMoveKeyToCard) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,6 +143,8 @@ public class SaveKeyringParcel implements Parcelable {
|
|||||||
public boolean mRecertify;
|
public boolean mRecertify;
|
||||||
// if this flag is true, the subkey should be changed to a stripped key
|
// if this flag is true, the subkey should be changed to a stripped key
|
||||||
public boolean mDummyStrip;
|
public boolean mDummyStrip;
|
||||||
|
// if this flag is true, the subkey should be moved to a card
|
||||||
|
public boolean mMoveKeyToCard;
|
||||||
// if this is non-null, the subkey will be changed to a divert-to-card
|
// if this is non-null, the subkey will be changed to a divert-to-card
|
||||||
// key for the given serial number
|
// key for the given serial number
|
||||||
public byte[] mDummyDivert;
|
public byte[] mDummyDivert;
|
||||||
@ -161,16 +164,16 @@ public class SaveKeyringParcel implements Parcelable {
|
|||||||
mExpiry = expiry;
|
mExpiry = expiry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SubkeyChange(long keyId, boolean dummyStrip, byte[] dummyDivert) {
|
public SubkeyChange(long keyId, boolean dummyStrip, boolean moveKeyToCard) {
|
||||||
this(keyId, null, null);
|
this(keyId, null, null);
|
||||||
|
|
||||||
// these flags are mutually exclusive!
|
// these flags are mutually exclusive!
|
||||||
if (dummyStrip && dummyDivert != null) {
|
if (dummyStrip && moveKeyToCard) {
|
||||||
throw new AssertionError(
|
throw new AssertionError(
|
||||||
"cannot set strip and divert flags at the same time - this is a bug!");
|
"cannot set strip and keytocard flags at the same time - this is a bug!");
|
||||||
}
|
}
|
||||||
mDummyStrip = dummyStrip;
|
mDummyStrip = dummyStrip;
|
||||||
mDummyDivert = dummyDivert;
|
mMoveKeyToCard = moveKeyToCard;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -179,6 +182,7 @@ public class SaveKeyringParcel implements Parcelable {
|
|||||||
out += "mFlags: " + mFlags + ", ";
|
out += "mFlags: " + mFlags + ", ";
|
||||||
out += "mExpiry: " + mExpiry + ", ";
|
out += "mExpiry: " + mExpiry + ", ";
|
||||||
out += "mDummyStrip: " + mDummyStrip + ", ";
|
out += "mDummyStrip: " + mDummyStrip + ", ";
|
||||||
|
out += "mMoveKeyToCard: " + mMoveKeyToCard + ", ";
|
||||||
out += "mDummyDivert: [" + (mDummyDivert == null ? 0 : mDummyDivert.length) + " bytes]";
|
out += "mDummyDivert: [" + (mDummyDivert == null ? 0 : mDummyDivert.length) + " bytes]";
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
|
@ -17,8 +17,7 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.service;
|
package org.sufficientlysecure.keychain.service;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
@ -27,7 +26,6 @@ import android.support.v4.app.FragmentManager;
|
|||||||
|
|
||||||
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.CertifyResult;
|
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@ -35,7 +33,7 @@ import org.sufficientlysecure.keychain.util.Log;
|
|||||||
public class ServiceProgressHandler extends Handler {
|
public class ServiceProgressHandler extends Handler {
|
||||||
|
|
||||||
// possible messages sent from this service to handler on ui
|
// possible messages sent from this service to handler on ui
|
||||||
public static enum MessageStatus{
|
public enum MessageStatus {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
OKAY,
|
OKAY,
|
||||||
EXCEPTION,
|
EXCEPTION,
|
||||||
@ -44,9 +42,8 @@ public class ServiceProgressHandler extends Handler {
|
|||||||
|
|
||||||
private static final MessageStatus[] values = values();
|
private static final MessageStatus[] values = values();
|
||||||
|
|
||||||
public static MessageStatus fromInt(int n)
|
public static MessageStatus fromInt(int n) {
|
||||||
{
|
if (n < 0 || n >= values.length) {
|
||||||
if(n < 0 || n >= values.length) {
|
|
||||||
return UNKNOWN;
|
return UNKNOWN;
|
||||||
} else {
|
} else {
|
||||||
return values[n];
|
return values[n];
|
||||||
@ -66,74 +63,58 @@ public class ServiceProgressHandler extends Handler {
|
|||||||
public static final String KEYBASE_PRESENCE_URL = "keybase_presence_url";
|
public static final String KEYBASE_PRESENCE_URL = "keybase_presence_url";
|
||||||
public static final String KEYBASE_PRESENCE_LABEL = "keybase_presence_label";
|
public static final String KEYBASE_PRESENCE_LABEL = "keybase_presence_label";
|
||||||
|
|
||||||
Activity mActivity;
|
FragmentActivity mActivity;
|
||||||
ProgressDialogFragment mProgressDialogFragment;
|
|
||||||
|
|
||||||
public ServiceProgressHandler(Activity activity) {
|
public ServiceProgressHandler(FragmentActivity activity) {
|
||||||
this.mActivity = activity;
|
mActivity = activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceProgressHandler(Activity activity,
|
public void showProgressDialog() {
|
||||||
ProgressDialogFragment progressDialogFragment) {
|
showProgressDialog("", ProgressDialog.STYLE_SPINNER, false);
|
||||||
this.mActivity = activity;
|
|
||||||
this.mProgressDialogFragment = progressDialogFragment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceProgressHandler(Activity activity,
|
public void showProgressDialog(
|
||||||
String progressDialogMessage,
|
String progressDialogMessage, int progressDialogStyle, boolean cancelable) {
|
||||||
int progressDialogStyle,
|
|
||||||
ProgressDialogFragment.ServiceType serviceType) {
|
|
||||||
this(activity, progressDialogMessage, progressDialogStyle, false, serviceType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServiceProgressHandler(Activity activity,
|
final ProgressDialogFragment frag = ProgressDialogFragment.newInstance(
|
||||||
String progressDialogMessage,
|
|
||||||
int progressDialogStyle,
|
|
||||||
boolean cancelable,
|
|
||||||
ProgressDialogFragment.ServiceType serviceType) {
|
|
||||||
this.mActivity = activity;
|
|
||||||
this.mProgressDialogFragment = ProgressDialogFragment.newInstance(
|
|
||||||
progressDialogMessage,
|
progressDialogMessage,
|
||||||
progressDialogStyle,
|
progressDialogStyle,
|
||||||
cancelable,
|
cancelable);
|
||||||
serviceType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showProgressDialog(FragmentActivity activity) {
|
|
||||||
if (mProgressDialogFragment == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This is a hack!, see
|
// TODO: This is a hack!, see
|
||||||
// http://stackoverflow.com/questions/10114324/show-dialogfragment-from-onactivityresult
|
// http://stackoverflow.com/questions/10114324/show-dialogfragment-from-onactivityresult
|
||||||
final FragmentManager manager = activity.getSupportFragmentManager();
|
final FragmentManager manager = mActivity.getSupportFragmentManager();
|
||||||
Handler handler = new Handler();
|
Handler handler = new Handler();
|
||||||
handler.post(new Runnable() {
|
handler.post(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
mProgressDialogFragment.show(manager, "progressDialog");
|
frag.show(manager, "progressDialog");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
Bundle data = message.getData();
|
Bundle data = message.getData();
|
||||||
|
|
||||||
if (mProgressDialogFragment == null) {
|
ProgressDialogFragment progressDialogFragment =
|
||||||
// Log.e(Constants.TAG,
|
(ProgressDialogFragment) mActivity.getSupportFragmentManager()
|
||||||
// "Progress has not been updated because mProgressDialogFragment was null!");
|
.findFragmentByTag("progressDialog");
|
||||||
|
|
||||||
|
if (progressDialogFragment == null) {
|
||||||
|
Log.e(Constants.TAG, "Progress has not been updated because mProgressDialogFragment was null!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageStatus status = MessageStatus.fromInt(message.arg1);
|
MessageStatus status = MessageStatus.fromInt(message.arg1);
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case OKAY:
|
case OKAY:
|
||||||
mProgressDialogFragment.dismissAllowingStateLoss();
|
progressDialogFragment.dismissAllowingStateLoss();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EXCEPTION:
|
case EXCEPTION:
|
||||||
mProgressDialogFragment.dismissAllowingStateLoss();
|
progressDialogFragment.dismissAllowingStateLoss();
|
||||||
|
|
||||||
// show error from service
|
// show error from service
|
||||||
if (data.containsKey(DATA_ERROR)) {
|
if (data.containsKey(DATA_ERROR)) {
|
||||||
@ -149,13 +130,13 @@ public class ServiceProgressHandler extends Handler {
|
|||||||
|
|
||||||
// update progress from service
|
// update progress from service
|
||||||
if (data.containsKey(DATA_MESSAGE)) {
|
if (data.containsKey(DATA_MESSAGE)) {
|
||||||
mProgressDialogFragment.setProgress(data.getString(DATA_MESSAGE),
|
progressDialogFragment.setProgress(data.getString(DATA_MESSAGE),
|
||||||
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
|
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
|
||||||
} else if (data.containsKey(DATA_MESSAGE_ID)) {
|
} else if (data.containsKey(DATA_MESSAGE_ID)) {
|
||||||
mProgressDialogFragment.setProgress(data.getInt(DATA_MESSAGE_ID),
|
progressDialogFragment.setProgress(data.getInt(DATA_MESSAGE_ID),
|
||||||
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
|
data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX));
|
||||||
} else {
|
} else {
|
||||||
mProgressDialogFragment.setProgress(data.getInt(DATA_PROGRESS),
|
progressDialogFragment.setProgress(data.getInt(DATA_PROGRESS),
|
||||||
data.getInt(DATA_PROGRESS_MAX));
|
data.getInt(DATA_PROGRESS_MAX));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,7 +144,7 @@ public class ServiceProgressHandler extends Handler {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case PREVENT_CANCEL:
|
case PREVENT_CANCEL:
|
||||||
mProgressDialogFragment.setPreventCancel(true);
|
progressDialogFragment.setPreventCancel(true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.sufficientlysecure.keychain.service.input;
|
package org.sufficientlysecure.keychain.service.input;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -11,7 +12,7 @@ import android.os.Parcelable;
|
|||||||
public class RequiredInputParcel implements Parcelable {
|
public class RequiredInputParcel implements Parcelable {
|
||||||
|
|
||||||
public enum RequiredInputType {
|
public enum RequiredInputType {
|
||||||
PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT
|
PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_KEYTOCARD
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date mSignatureTime;
|
public Date mSignatureTime;
|
||||||
@ -211,4 +212,46 @@ public class RequiredInputParcel implements Parcelable {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class NfcKeyToCardOperationsBuilder {
|
||||||
|
ArrayList<byte[]> mSubkeysToExport = new ArrayList<>();
|
||||||
|
Long mMasterKeyId;
|
||||||
|
|
||||||
|
public NfcKeyToCardOperationsBuilder(Long masterKeyId) {
|
||||||
|
mMasterKeyId = masterKeyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RequiredInputParcel build() {
|
||||||
|
byte[][] inputHashes = new byte[mSubkeysToExport.size()][];
|
||||||
|
mSubkeysToExport.toArray(inputHashes);
|
||||||
|
ByteBuffer buf = ByteBuffer.wrap(mSubkeysToExport.get(0));
|
||||||
|
|
||||||
|
// We need to pass in a subkey here...
|
||||||
|
return new RequiredInputParcel(RequiredInputType.NFC_KEYTOCARD,
|
||||||
|
inputHashes, null, null, mMasterKeyId, buf.getLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSubkey(long subkeyId) {
|
||||||
|
byte[] subKeyId = new byte[8];
|
||||||
|
ByteBuffer buf = ByteBuffer.wrap(subKeyId);
|
||||||
|
buf.putLong(subkeyId).rewind();
|
||||||
|
mSubkeysToExport.add(subKeyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAll(RequiredInputParcel input) {
|
||||||
|
if (!mMasterKeyId.equals(input.mMasterKeyId)) {
|
||||||
|
throw new AssertionError("Master keys must match, this is a programming error!");
|
||||||
|
}
|
||||||
|
if (input.mType != RequiredInputType.NFC_KEYTOCARD) {
|
||||||
|
throw new AssertionError("Operation types must match, this is a programming error!");
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.addAll(mSubkeysToExport, input.mInputHashes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return mSubkeysToExport.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -52,12 +52,11 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
|||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
|
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
|
||||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
|
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@ -65,10 +64,12 @@ import org.sufficientlysecure.keychain.util.Preferences;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class CertifyKeyFragment
|
||||||
public class CertifyKeyFragment extends CryptoOperationFragment
|
extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult>
|
||||||
implements LoaderManager.LoaderCallbacks<Cursor> {
|
implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
|
||||||
|
public static final String ARG_CHECK_STATES = "check_states";
|
||||||
|
|
||||||
private CheckBox mUploadKeyCheckbox;
|
private CheckBox mUploadKeyCheckbox;
|
||||||
ListView mUserIds;
|
ListView mUserIds;
|
||||||
|
|
||||||
@ -89,7 +90,6 @@ public class CertifyKeyFragment extends CryptoOperationFragment
|
|||||||
private static final int INDEX_IS_REVOKED = 4;
|
private static final int INDEX_IS_REVOKED = 4;
|
||||||
|
|
||||||
private MultiUserIdsAdapter mUserIdsAdapter;
|
private MultiUserIdsAdapter mUserIdsAdapter;
|
||||||
private Messenger mPassthroughMessenger;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
@ -102,12 +102,16 @@ public class CertifyKeyFragment extends CryptoOperationFragment
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mPassthroughMessenger = getActivity().getIntent().getParcelableExtra(
|
ArrayList<Boolean> checkedStates;
|
||||||
KeychainIntentService.EXTRA_MESSENGER);
|
if (savedInstanceState != null) {
|
||||||
mPassthroughMessenger = null; // TODO remove, development hack
|
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().getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none);
|
long certifyKeyId = getActivity().getIntent()
|
||||||
|
.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);
|
||||||
@ -119,7 +123,9 @@ public class CertifyKeyFragment extends CryptoOperationFragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0);
|
}
|
||||||
|
|
||||||
|
mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates);
|
||||||
mUserIds.setAdapter(mUserIdsAdapter);
|
mUserIds.setAdapter(mUserIdsAdapter);
|
||||||
mUserIds.setDividerHeight(0);
|
mUserIds.setDividerHeight(0);
|
||||||
|
|
||||||
@ -132,6 +138,15 @@ public class CertifyKeyFragment extends CryptoOperationFragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
|
||||||
|
ArrayList<Boolean> states = mUserIdsAdapter.getCheckStates();
|
||||||
|
// no proper parceling method available :(
|
||||||
|
outState.putSerializable(ARG_CHECK_STATES, states);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
||||||
@ -156,7 +171,7 @@ public class CertifyKeyFragment extends CryptoOperationFragment
|
|||||||
Notify.create(getActivity(), getString(R.string.select_key_to_certify),
|
Notify.create(getActivity(), getString(R.string.select_key_to_certify),
|
||||||
Notify.Style.ERROR).show();
|
Notify.Style.ERROR).show();
|
||||||
} else {
|
} else {
|
||||||
cryptoOperation(new CryptoInputParcel());
|
cryptoOperation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -287,85 +302,39 @@ public class CertifyKeyFragment extends CryptoOperationFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
protected 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 = mUserIdsAdapter.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();
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bundle data = new Bundle();
|
|
||||||
{
|
|
||||||
long selectedKeyId = mCertifyKeySpinner.getSelectedKeyId();
|
long selectedKeyId = mCertifyKeySpinner.getSelectedKeyId();
|
||||||
|
|
||||||
// fill values for this action
|
// fill values for this action
|
||||||
CertifyActionsParcel parcel = new CertifyActionsParcel(selectedKeyId);
|
CertifyActionsParcel actionsParcel = new CertifyActionsParcel(selectedKeyId);
|
||||||
parcel.mCertifyActions.addAll(certifyActions);
|
actionsParcel.mCertifyActions.addAll(certifyActions);
|
||||||
|
|
||||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
// cached for next cryptoOperation loop
|
||||||
data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel);
|
cacheActionsParcel(actionsParcel);
|
||||||
if (mUploadKeyCheckbox.isChecked()) {
|
|
||||||
String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
|
return actionsParcel;
|
||||||
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send all information needed to service to sign key in other thread
|
@Override
|
||||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
protected void onCryptoOperationSuccess(CertifyResult result) {
|
||||||
intent.setAction(KeychainIntentService.ACTION_CERTIFY_KEYRING);
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
|
||||||
|
|
||||||
if (mPassthroughMessenger != null) {
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, mPassthroughMessenger);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Message is received after signing is done in KeychainIntentService
|
|
||||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
|
||||||
getActivity(),
|
|
||||||
getString(R.string.progress_certifying),
|
|
||||||
ProgressDialog.STYLE_SPINNER,
|
|
||||||
true,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
public void handleMessage(Message message) {
|
|
||||||
// handle messages by KeychainIntentCryptoServiceHandler first
|
|
||||||
super.handleMessage(message);
|
|
||||||
|
|
||||||
// handle pending messages
|
|
||||||
if (handlePendingMessage(message)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
|
||||||
Bundle data = message.getData();
|
|
||||||
|
|
||||||
CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT);
|
|
||||||
|
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
intent.putExtra(CertifyResult.EXTRA_RESULT, result);
|
intent.putExtra(CertifyResult.EXTRA_RESULT, result);
|
||||||
getActivity().setResult(Activity.RESULT_OK, intent);
|
getActivity().setResult(Activity.RESULT_OK, intent);
|
||||||
getActivity().finish();
|
getActivity().finish();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
|
||||||
Messenger messenger = new Messenger(saveHandler);
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
|
||||||
|
|
||||||
// show progress dialog
|
|
||||||
saveHandler.showProgressDialog(getActivity());
|
|
||||||
}
|
|
||||||
|
|
||||||
// start service with intent
|
|
||||||
getActivity().startService(intent);
|
|
||||||
|
|
||||||
if (mPassthroughMessenger != null) {
|
|
||||||
getActivity().setResult(Activity.RESULT_OK);
|
|
||||||
getActivity().finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCryptoOperationCancelled() {
|
||||||
|
super.onCryptoOperationCancelled();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,9 +25,8 @@ import android.os.Messenger;
|
|||||||
import android.support.v4.app.FragmentActivity;
|
import android.support.v4.app.FragmentActivity;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We can not directly create a dialog on the application context.
|
* We can not directly create a dialog on the application context.
|
||||||
@ -49,12 +48,9 @@ public class ConsolidateDialogActivity extends FragmentActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void consolidateRecovery(boolean recovery) {
|
private void consolidateRecovery(boolean recovery) {
|
||||||
// Message is received after importing is done in KeychainIntentService
|
// Message is received after importing is done in KeychainService
|
||||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
ServiceProgressHandler saveHandler = new ServiceProgressHandler(this) {
|
||||||
this,
|
@Override
|
||||||
getString(R.string.progress_importing),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -68,7 +64,7 @@ public class ConsolidateDialogActivity extends FragmentActivity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final ConsolidateResult result =
|
final ConsolidateResult result =
|
||||||
returnData.getParcelable(KeychainIntentService.RESULT_CONSOLIDATE);
|
returnData.getParcelable(KeychainService.RESULT_CONSOLIDATE);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -81,20 +77,23 @@ public class ConsolidateDialogActivity extends FragmentActivity {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Send all information needed to service to import key in other thread
|
// Send all information needed to service to import key in other thread
|
||||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
Intent intent = new Intent(this, KeychainService.class);
|
||||||
intent.setAction(KeychainIntentService.ACTION_CONSOLIDATE);
|
intent.setAction(KeychainService.ACTION_CONSOLIDATE);
|
||||||
|
|
||||||
// fill values for this action
|
// fill values for this action
|
||||||
Bundle data = new Bundle();
|
Bundle data = new Bundle();
|
||||||
data.putBoolean(KeychainIntentService.CONSOLIDATE_RECOVERY, recovery);
|
data.putBoolean(KeychainService.CONSOLIDATE_RECOVERY, recovery);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
intent.putExtra(KeychainService.EXTRA_DATA, data);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(saveHandler);
|
Messenger messenger = new Messenger(saveHandler);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
// show progress dialog
|
// show progress dialog
|
||||||
saveHandler.showProgressDialog(this);
|
saveHandler.showProgressDialog(
|
||||||
|
getString(R.string.progress_importing),
|
||||||
|
ProgressDialog.STYLE_HORIZONTAL, false
|
||||||
|
);
|
||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
startService(intent);
|
startService(intent);
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
@ -41,6 +43,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
|
|||||||
public static final String EXTRA_FIRST_TIME = "first_time";
|
public static final String EXTRA_FIRST_TIME = "first_time";
|
||||||
public static final String EXTRA_ADDITIONAL_EMAILS = "additional_emails";
|
public static final String EXTRA_ADDITIONAL_EMAILS = "additional_emails";
|
||||||
public static final String EXTRA_PASSPHRASE = "passphrase";
|
public static final String EXTRA_PASSPHRASE = "passphrase";
|
||||||
|
public static final String EXTRA_USE_SMART_CARD_SETTINGS = "use_smart_card_settings";
|
||||||
|
|
||||||
public static final String EXTRA_NFC_USER_ID = "nfc_user_id";
|
public static final String EXTRA_NFC_USER_ID = "nfc_user_id";
|
||||||
public static final String EXTRA_NFC_AID = "nfc_aid";
|
public static final String EXTRA_NFC_AID = "nfc_aid";
|
||||||
@ -53,6 +56,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
|
|||||||
ArrayList<String> mAdditionalEmails;
|
ArrayList<String> mAdditionalEmails;
|
||||||
Passphrase mPassphrase;
|
Passphrase mPassphrase;
|
||||||
boolean mFirstTime;
|
boolean mFirstTime;
|
||||||
|
boolean mUseSmartCardSettings;
|
||||||
|
|
||||||
Fragment mCurrentFragment;
|
Fragment mCurrentFragment;
|
||||||
|
|
||||||
@ -68,6 +72,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
|
|||||||
mAdditionalEmails = savedInstanceState.getStringArrayList(EXTRA_ADDITIONAL_EMAILS);
|
mAdditionalEmails = savedInstanceState.getStringArrayList(EXTRA_ADDITIONAL_EMAILS);
|
||||||
mPassphrase = savedInstanceState.getParcelable(EXTRA_PASSPHRASE);
|
mPassphrase = savedInstanceState.getParcelable(EXTRA_PASSPHRASE);
|
||||||
mFirstTime = savedInstanceState.getBoolean(EXTRA_FIRST_TIME);
|
mFirstTime = savedInstanceState.getBoolean(EXTRA_FIRST_TIME);
|
||||||
|
mUseSmartCardSettings = savedInstanceState.getBoolean(EXTRA_USE_SMART_CARD_SETTINGS);
|
||||||
|
|
||||||
mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
|
mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
|
||||||
} else {
|
} else {
|
||||||
@ -77,6 +82,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
|
|||||||
mName = intent.getStringExtra(EXTRA_NAME);
|
mName = intent.getStringExtra(EXTRA_NAME);
|
||||||
mEmail = intent.getStringExtra(EXTRA_EMAIL);
|
mEmail = intent.getStringExtra(EXTRA_EMAIL);
|
||||||
mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false);
|
mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false);
|
||||||
|
mUseSmartCardSettings = intent.getBooleanExtra(EXTRA_USE_SMART_CARD_SETTINGS, false);
|
||||||
|
|
||||||
if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) {
|
if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) {
|
||||||
byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS);
|
byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS);
|
||||||
@ -116,6 +122,16 @@ public class CreateKeyActivity extends BaseNfcActivity {
|
|||||||
byte[] nfcAid = nfcGetAid();
|
byte[] nfcAid = nfcGetAid();
|
||||||
String userId = nfcGetUserId();
|
String userId = nfcGetUserId();
|
||||||
|
|
||||||
|
// If all fingerprint bytes are 0, the card contains no keys.
|
||||||
|
boolean cardContainsKeys = false;
|
||||||
|
for (byte b : scannedFingerprints) {
|
||||||
|
if (b != 0) {
|
||||||
|
cardContainsKeys = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cardContainsKeys) {
|
||||||
try {
|
try {
|
||||||
long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints);
|
long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints);
|
||||||
CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId);
|
CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId);
|
||||||
@ -134,6 +150,18 @@ public class CreateKeyActivity extends BaseNfcActivity {
|
|||||||
scannedFingerprints, nfcAid, userId);
|
scannedFingerprints, nfcAid, userId);
|
||||||
loadFragment(frag, FragAction.TO_RIGHT);
|
loadFragment(frag, FragAction.TO_RIGHT);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setTitle(R.string.first_time_blank_smartcard_title)
|
||||||
|
.setMessage(R.string.first_time_blank_smartcard_message)
|
||||||
|
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int button) {
|
||||||
|
CreateKeyActivity.this.mUseSmartCardSettings = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.no, null).show();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,6 +174,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
|
|||||||
outState.putStringArrayList(EXTRA_ADDITIONAL_EMAILS, mAdditionalEmails);
|
outState.putStringArrayList(EXTRA_ADDITIONAL_EMAILS, mAdditionalEmails);
|
||||||
outState.putParcelable(EXTRA_PASSPHRASE, mPassphrase);
|
outState.putParcelable(EXTRA_PASSPHRASE, mPassphrase);
|
||||||
outState.putBoolean(EXTRA_FIRST_TIME, mFirstTime);
|
outState.putBoolean(EXTRA_FIRST_TIME, mFirstTime);
|
||||||
|
outState.putBoolean(EXTRA_USE_SMART_CARD_SETTINGS, mUseSmartCardSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -38,13 +38,12 @@ import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
|
|||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
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.ChangeUnlockParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
|
|
||||||
@ -157,12 +156,22 @@ public class CreateKeyFinalFragment extends Fragment {
|
|||||||
|
|
||||||
if (mSaveKeyringParcel == null) {
|
if (mSaveKeyringParcel == null) {
|
||||||
mSaveKeyringParcel = new SaveKeyringParcel();
|
mSaveKeyringParcel = new SaveKeyringParcel();
|
||||||
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
if (createKeyActivity.mUseSmartCardSettings) {
|
||||||
Algorithm.RSA, 4096, null, KeyFlags.CERTIFY_OTHER, 0L));
|
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
|
||||||
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
2048, null, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER, 0L));
|
||||||
Algorithm.RSA, 4096, null, KeyFlags.SIGN_DATA, 0L));
|
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
|
||||||
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(
|
2048, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L));
|
||||||
Algorithm.RSA, 4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L));
|
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
|
||||||
|
2048, null, KeyFlags.AUTHENTICATION, 0L));
|
||||||
|
mEditText.setText(R.string.create_key_custom);
|
||||||
|
} else {
|
||||||
|
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
|
||||||
|
4096, null, KeyFlags.CERTIFY_OTHER, 0L));
|
||||||
|
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
|
||||||
|
4096, null, KeyFlags.SIGN_DATA, 0L));
|
||||||
|
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
|
||||||
|
4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L));
|
||||||
|
}
|
||||||
String userId = KeyRing.createUserId(
|
String userId = KeyRing.createUserId(
|
||||||
new KeyRing.UserId(createKeyActivity.mName, createKeyActivity.mEmail, null)
|
new KeyRing.UserId(createKeyActivity.mName, createKeyActivity.mEmail, null)
|
||||||
);
|
);
|
||||||
@ -185,14 +194,11 @@ public class CreateKeyFinalFragment extends Fragment {
|
|||||||
|
|
||||||
|
|
||||||
private void createKey() {
|
private void createKey() {
|
||||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
Intent intent = new Intent(getActivity(), KeychainService.class);
|
||||||
intent.setAction(KeychainIntentService.ACTION_EDIT_KEYRING);
|
intent.setAction(KeychainService.ACTION_EDIT_KEYRING);
|
||||||
|
|
||||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
|
||||||
getActivity(),
|
@Override
|
||||||
getString(R.string.progress_building_key),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -227,15 +233,16 @@ public class CreateKeyFinalFragment extends Fragment {
|
|||||||
Bundle data = new Bundle();
|
Bundle data = new Bundle();
|
||||||
|
|
||||||
// get selected key entries
|
// get selected key entries
|
||||||
data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, mSaveKeyringParcel);
|
data.putParcelable(KeychainService.EDIT_KEYRING_PARCEL, mSaveKeyringParcel);
|
||||||
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
intent.putExtra(KeychainService.EXTRA_DATA, data);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(saveHandler);
|
Messenger messenger = new Messenger(saveHandler);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
saveHandler.showProgressDialog(getActivity());
|
saveHandler.showProgressDialog(getString(R.string.progress_building_key),
|
||||||
|
ProgressDialog.STYLE_HORIZONTAL, false);
|
||||||
|
|
||||||
getActivity().startService(intent);
|
getActivity().startService(intent);
|
||||||
}
|
}
|
||||||
@ -243,9 +250,9 @@ public class CreateKeyFinalFragment extends Fragment {
|
|||||||
// TODO move into EditKeyOperation
|
// TODO move into EditKeyOperation
|
||||||
private void uploadKey(final EditKeyResult saveKeyResult) {
|
private void uploadKey(final EditKeyResult saveKeyResult) {
|
||||||
// Send all information needed to service to upload key in other thread
|
// Send all information needed to service to upload key in other thread
|
||||||
final Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
final Intent intent = new Intent(getActivity(), KeychainService.class);
|
||||||
|
|
||||||
intent.setAction(KeychainIntentService.ACTION_UPLOAD_KEYRING);
|
intent.setAction(KeychainService.ACTION_UPLOAD_KEYRING);
|
||||||
|
|
||||||
// set data uri as path to keyring
|
// set data uri as path to keyring
|
||||||
Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(
|
Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(
|
||||||
@ -257,15 +264,12 @@ public class CreateKeyFinalFragment extends Fragment {
|
|||||||
|
|
||||||
// upload to favorite keyserver
|
// upload to favorite keyserver
|
||||||
String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
|
String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
|
||||||
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver);
|
data.putString(KeychainService.UPLOAD_KEY_SERVER, keyserver);
|
||||||
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
intent.putExtra(KeychainService.EXTRA_DATA, data);
|
||||||
|
|
||||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
|
||||||
getActivity(),
|
@Override
|
||||||
getString(R.string.progress_uploading),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -287,13 +291,16 @@ public class CreateKeyFinalFragment extends Fragment {
|
|||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(saveHandler);
|
Messenger messenger = new Messenger(saveHandler);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
// show progress dialog
|
// show progress dialog
|
||||||
saveHandler.showProgressDialog(getActivity());
|
saveHandler.showProgressDialog(
|
||||||
|
getString(R.string.progress_uploading),
|
||||||
|
ProgressDialog.STYLE_HORIZONTAL, false);
|
||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
getActivity().startService(intent);
|
getActivity().startService(intent);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -40,11 +40,10 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
|||||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
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.NfcListenerFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
|
||||||
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;
|
||||||
|
|
||||||
@ -176,13 +175,9 @@ public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListe
|
|||||||
|
|
||||||
public void importKey() {
|
public void importKey() {
|
||||||
|
|
||||||
// Message is received after decrypting is done in KeychainIntentService
|
// Message is received after decrypting is done in KeychainService
|
||||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
|
||||||
getActivity(),
|
@Override
|
||||||
getString(R.string.progress_importing),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT
|
|
||||||
) {
|
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -220,31 +215,34 @@ public class CreateKeyYubiKeyImportFragment extends Fragment implements NfcListe
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Send all information needed to service to decrypt in other thread
|
// Send all information needed to service to decrypt in other thread
|
||||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
Intent intent = new Intent(getActivity(), KeychainService.class);
|
||||||
|
|
||||||
// fill values for this action
|
// fill values for this action
|
||||||
Bundle data = new Bundle();
|
Bundle data = new Bundle();
|
||||||
|
|
||||||
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
|
intent.setAction(KeychainService.ACTION_IMPORT_KEYRING);
|
||||||
|
|
||||||
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
|
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
|
||||||
keyList.add(new ParcelableKeyRing(mNfcFingerprint, null, null));
|
keyList.add(new ParcelableKeyRing(mNfcFingerprint, null, null));
|
||||||
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyList);
|
data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, keyList);
|
||||||
|
|
||||||
{
|
{
|
||||||
Preferences prefs = Preferences.getPreferences(getActivity());
|
Preferences prefs = Preferences.getPreferences(getActivity());
|
||||||
Preferences.CloudSearchPrefs cloudPrefs =
|
Preferences.CloudSearchPrefs cloudPrefs =
|
||||||
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
|
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
|
||||||
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
data.putString(KeychainService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
||||||
}
|
}
|
||||||
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
intent.putExtra(KeychainService.EXTRA_DATA, data);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(saveHandler);
|
Messenger messenger = new Messenger(saveHandler);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
saveHandler.showProgressDialog(getActivity());
|
saveHandler.showProgressDialog(
|
||||||
|
getString(R.string.progress_importing),
|
||||||
|
ProgressDialog.STYLE_HORIZONTAL, false
|
||||||
|
);
|
||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
getActivity().startService(intent);
|
getActivity().startService(intent);
|
||||||
|
@ -21,7 +21,9 @@ import android.app.Activity;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
@ -37,8 +39,6 @@ public class DecryptFilesActivity extends BaseActivity {
|
|||||||
// intern
|
// intern
|
||||||
public static final String ACTION_DECRYPT_DATA_OPEN = Constants.INTENT_PREFIX + "DECRYPT_DATA_OPEN";
|
public static final String ACTION_DECRYPT_DATA_OPEN = Constants.INTENT_PREFIX + "DECRYPT_DATA_OPEN";
|
||||||
|
|
||||||
DecryptFilesFragment mFragment;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@ -62,19 +62,12 @@ public class DecryptFilesActivity extends BaseActivity {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles all actions with this intent
|
* Handles all actions with this intent
|
||||||
*
|
|
||||||
* @param intent
|
|
||||||
*/
|
*/
|
||||||
private void handleActions(Bundle savedInstanceState, Intent intent) {
|
private void handleActions(Bundle savedInstanceState, Intent intent) {
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
String type = intent.getType();
|
String type = intent.getType();
|
||||||
Uri uri = intent.getData();
|
Uri uri = intent.getData();
|
||||||
|
|
||||||
Bundle mFileFragmentBundle = new Bundle();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Android's Action
|
|
||||||
*/
|
|
||||||
if (Intent.ACTION_SEND.equals(action) && type != null) {
|
if (Intent.ACTION_SEND.equals(action) && type != null) {
|
||||||
// When sending to Keychain Decrypt via share menu
|
// When sending to Keychain Decrypt via share menu
|
||||||
// Binary via content provider (could also be files)
|
// Binary via content provider (could also be files)
|
||||||
@ -88,39 +81,27 @@ public class DecryptFilesActivity extends BaseActivity {
|
|||||||
action = ACTION_DECRYPT_DATA;
|
action = ACTION_DECRYPT_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// No need to initialize fragments if we are being restored
|
||||||
* Main Actions
|
|
||||||
*/
|
|
||||||
if (ACTION_DECRYPT_DATA.equals(action) && uri != null) {
|
|
||||||
mFileFragmentBundle.putParcelable(DecryptFilesFragment.ARG_URI, uri);
|
|
||||||
|
|
||||||
loadFragment(savedInstanceState, uri, false);
|
|
||||||
} else if (ACTION_DECRYPT_DATA_OPEN.equals(action)) {
|
|
||||||
loadFragment(savedInstanceState, null, true);
|
|
||||||
} else if (ACTION_DECRYPT_DATA.equals(action)) {
|
|
||||||
Log.e(Constants.TAG,
|
|
||||||
"Include an Uri with setInputData() in your Intent!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadFragment(Bundle savedInstanceState, Uri uri, boolean openDialog) {
|
|
||||||
// However, if we're being restored from a previous state,
|
|
||||||
// then we don't need to do anything and should return or else
|
|
||||||
// we could end up with overlapping fragments.
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an instance of the fragment
|
// Definitely need a data uri with the decrypt_data intent
|
||||||
mFragment = DecryptFilesFragment.newInstance(uri, openDialog);
|
if (ACTION_DECRYPT_DATA.equals(action) && uri == null) {
|
||||||
|
Toast.makeText(this, "No data to decrypt!", Toast.LENGTH_LONG).show();
|
||||||
|
setResult(Activity.RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean showOpenDialog = ACTION_DECRYPT_DATA_OPEN.equals(action);
|
||||||
|
DecryptFilesFragment frag = DecryptFilesFragment.newInstance(uri, showOpenDialog);
|
||||||
|
|
||||||
// Add the fragment to the 'fragment_container' FrameLayout
|
// Add the fragment to the 'fragment_container' FrameLayout
|
||||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.replace(R.id.decrypt_files_fragment_container, mFragment)
|
.replace(R.id.decrypt_files_fragment_container, frag)
|
||||||
.commitAllowingStateLoss();
|
.commitAllowingStateLoss();
|
||||||
// do it immediately!
|
|
||||||
getSupportFragmentManager().executePendingTransactions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -36,12 +36,11 @@ 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.operations.results.DecryptVerifyResult;
|
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@ -64,8 +63,6 @@ public class DecryptFilesFragment extends DecryptFragment {
|
|||||||
private Uri mInputUri = null;
|
private Uri mInputUri = null;
|
||||||
private Uri mOutputUri = null;
|
private Uri mOutputUri = null;
|
||||||
|
|
||||||
private String mCurrentCryptoOperation;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new instance of this fragment
|
* Creates new instance of this fragment
|
||||||
*/
|
*/
|
||||||
@ -111,18 +108,26 @@ public class DecryptFilesFragment extends DecryptFragment {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
|
||||||
|
outState.putParcelable(ARG_URI, mInputUri);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
setInputUri(getArguments().<Uri>getParcelable(ARG_URI));
|
Bundle state = savedInstanceState != null ? savedInstanceState : getArguments();
|
||||||
|
setInputUri(state.<Uri>getParcelable(ARG_URI));
|
||||||
|
|
||||||
if (getArguments().getBoolean(ARG_OPEN_DIRECTLY, false)) {
|
// should only come from args
|
||||||
|
if (state.getBoolean(ARG_OPEN_DIRECTLY, false)) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT);
|
FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT);
|
||||||
} else {
|
} else {
|
||||||
FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*",
|
FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*", REQUEST_CODE_INPUT);
|
||||||
REQUEST_CODE_INPUT);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,7 +149,7 @@ public class DecryptFilesFragment extends DecryptFragment {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
startDecryptFilenames();
|
cryptoOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String removeEncryptedAppend(String name) {
|
private String removeEncryptedAppend(String name) {
|
||||||
@ -172,115 +177,6 @@ public class DecryptFilesFragment extends DecryptFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startDecrypt() {
|
|
||||||
mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_VERIFY;
|
|
||||||
cryptoOperation(new CryptoInputParcel());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startDecryptFilenames() {
|
|
||||||
mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_METADATA;
|
|
||||||
cryptoOperation(new CryptoInputParcel());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressLint("HandlerLeak")
|
|
||||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
|
||||||
// Send all information needed to service to decrypt in other thread
|
|
||||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
|
||||||
|
|
||||||
// fill values for this action
|
|
||||||
Bundle data = new Bundle();
|
|
||||||
// use current operation, either decrypt metadata or decrypt payload
|
|
||||||
intent.setAction(mCurrentCryptoOperation);
|
|
||||||
|
|
||||||
// data
|
|
||||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
|
||||||
|
|
||||||
Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri);
|
|
||||||
|
|
||||||
data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal());
|
|
||||||
data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_INPUT_URI, mInputUri);
|
|
||||||
|
|
||||||
data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal());
|
|
||||||
data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri);
|
|
||||||
|
|
||||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
|
||||||
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
|
||||||
|
|
||||||
// Message is received after decrypting is done in KeychainIntentService
|
|
||||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
|
||||||
getActivity(),
|
|
||||||
getString(R.string.progress_decrypting),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
@Override
|
|
||||||
public void handleMessage(Message message) {
|
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
|
||||||
super.handleMessage(message);
|
|
||||||
|
|
||||||
// handle pending messages
|
|
||||||
if (handlePendingMessage(message)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
|
||||||
// get returned data bundle
|
|
||||||
Bundle returnData = message.getData();
|
|
||||||
|
|
||||||
DecryptVerifyResult pgpResult =
|
|
||||||
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
|
|
||||||
|
|
||||||
if (pgpResult.success()) {
|
|
||||||
switch (mCurrentCryptoOperation) {
|
|
||||||
case KeychainIntentService.ACTION_DECRYPT_METADATA: {
|
|
||||||
askForOutputFilename(pgpResult.getDecryptMetadata().getFilename());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case KeychainIntentService.ACTION_DECRYPT_VERIFY: {
|
|
||||||
// display signature result in activity
|
|
||||||
loadVerifyResult(pgpResult);
|
|
||||||
|
|
||||||
if (mDeleteAfter.isChecked()) {
|
|
||||||
// Create and show dialog to delete original file
|
|
||||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri);
|
|
||||||
deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
|
|
||||||
setInputUri(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// A future open after decryption feature
|
|
||||||
if () {
|
|
||||||
Intent viewFile = new Intent(Intent.ACTION_VIEW);
|
|
||||||
viewFile.setInputData(mOutputUri);
|
|
||||||
startActivity(viewFile);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
Log.e(Constants.TAG, "Bug: not supported operation!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pgpResult.createNotify(getActivity()).show(DecryptFilesFragment.this);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
|
||||||
Messenger messenger = new Messenger(saveHandler);
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
|
||||||
|
|
||||||
// show progress dialog
|
|
||||||
saveHandler.showProgressDialog(getActivity());
|
|
||||||
|
|
||||||
// start service with intent
|
|
||||||
getActivity().startService(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
@ -295,7 +191,7 @@ public class DecryptFilesFragment extends DecryptFragment {
|
|||||||
// This happens after output file was selected, so start our operation
|
// This happens after output file was selected, so start our operation
|
||||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||||
mOutputUri = data.getData();
|
mOutputUri = data.getData();
|
||||||
startDecrypt();
|
cryptoOperation();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -310,4 +206,20 @@ public class DecryptFilesFragment extends DecryptFragment {
|
|||||||
protected void onVerifyLoaded(boolean hideErrorOverlay) {
|
protected void onVerifyLoaded(boolean hideErrorOverlay) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PgpDecryptVerifyInputParcel createOperationInput() {
|
||||||
|
return new PgpDecryptVerifyInputParcel(mInputUri, mOutputUri).setAllowSymmetricDecryption(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCryptoOperationSuccess(DecryptVerifyResult result) {
|
||||||
|
|
||||||
|
// display signature result in activity
|
||||||
|
loadVerifyResult(result);
|
||||||
|
|
||||||
|
// TODO delete after decrypt not implemented!
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -43,12 +43,14 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
|||||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
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.pgp.KeyRing;
|
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
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.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
|
import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||||
@ -56,8 +58,9 @@ import org.sufficientlysecure.keychain.ui.util.Notify;
|
|||||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
|
|
||||||
public abstract class DecryptFragment extends CryptoOperationFragment implements
|
public abstract class DecryptFragment
|
||||||
LoaderManager.LoaderCallbacks<Cursor> {
|
extends CachingCryptoOperationFragment<PgpDecryptVerifyInputParcel, DecryptVerifyResult>
|
||||||
|
implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
|
||||||
public static final int LOADER_ID_UNIFIED = 0;
|
public static final int LOADER_ID_UNIFIED = 0;
|
||||||
public static final String ARG_DECRYPT_VERIFY_RESULT = "decrypt_verify_result";
|
public static final String ARG_DECRYPT_VERIFY_RESULT = "decrypt_verify_result";
|
||||||
@ -130,8 +133,9 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
|||||||
|
|
||||||
private void lookupUnknownKey(long unknownKeyId) {
|
private void lookupUnknownKey(long unknownKeyId) {
|
||||||
|
|
||||||
// Message is received after importing is done in KeychainIntentService
|
// Message is received after importing is done in KeychainService
|
||||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(getActivity()) {
|
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(getActivity()) {
|
||||||
|
@Override
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -162,7 +166,7 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
|||||||
Preferences prefs = Preferences.getPreferences(getActivity());
|
Preferences prefs = Preferences.getPreferences(getActivity());
|
||||||
Preferences.CloudSearchPrefs cloudPrefs =
|
Preferences.CloudSearchPrefs cloudPrefs =
|
||||||
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
|
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
|
||||||
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
data.putString(KeychainService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -171,17 +175,17 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
|
|||||||
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
|
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
|
||||||
selectedEntries.add(keyEntry);
|
selectedEntries.add(keyEntry);
|
||||||
|
|
||||||
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, selectedEntries);
|
data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, selectedEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send all information needed to service to query keys in other thread
|
// Send all information needed to service to query keys in other thread
|
||||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
Intent intent = new Intent(getActivity(), KeychainService.class);
|
||||||
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
|
intent.setAction(KeychainService.ACTION_IMPORT_KEYRING);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
intent.putExtra(KeychainService.EXTRA_DATA, data);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(serviceHandler);
|
Messenger messenger = new Messenger(serviceHandler);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
getActivity().startService(intent);
|
getActivity().startService(intent);
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,8 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Message;
|
|
||||||
import android.os.Messenger;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@ -34,11 +31,7 @@ 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.operations.results.DecryptVerifyResult;
|
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;
|
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||||
|
|
||||||
@ -116,7 +109,7 @@ public class DecryptTextFragment extends DecryptFragment {
|
|||||||
mShowMenuOptions = args.getBoolean(ARG_SHOW_MENU, false);
|
mShowMenuOptions = args.getBoolean(ARG_SHOW_MENU, false);
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
cryptoOperation(new CryptoInputParcel());
|
cryptoOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -159,52 +152,26 @@ public class DecryptTextFragment extends DecryptFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
protected PgpDecryptVerifyInputParcel createOperationInput() {
|
||||||
// Send all information needed to service to decrypt in other thread
|
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mCiphertext.getBytes());
|
||||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
input.setAllowSymmetricDecryption(true);
|
||||||
|
return input;
|
||||||
// fill values for this action
|
|
||||||
Bundle data = new Bundle();
|
|
||||||
|
|
||||||
intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
|
|
||||||
|
|
||||||
// data
|
|
||||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
|
||||||
data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal());
|
|
||||||
data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, mCiphertext.getBytes());
|
|
||||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
|
||||||
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
|
||||||
|
|
||||||
// Message is received after encrypting is done in KeychainIntentService
|
|
||||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
|
||||||
getActivity(),
|
|
||||||
getString(R.string.progress_decrypting),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
public void handleMessage(Message message) {
|
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
|
||||||
super.handleMessage(message);
|
|
||||||
|
|
||||||
// handle pending messages
|
|
||||||
if (handlePendingMessage(message)) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
@Override
|
||||||
// get returned data bundle
|
protected void onVerifyLoaded(boolean hideErrorOverlay) {
|
||||||
Bundle returnData = message.getData();
|
mShowMenuOptions = hideErrorOverlay;
|
||||||
|
getActivity().supportInvalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
DecryptVerifyResult pgpResult =
|
@Override
|
||||||
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
|
protected void onCryptoOperationSuccess(DecryptVerifyResult result) {
|
||||||
|
|
||||||
if (pgpResult.success()) {
|
byte[] decryptedMessage = result.getOutputBytes();
|
||||||
byte[] decryptedMessage = returnData
|
|
||||||
.getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES);
|
|
||||||
String displayMessage;
|
String displayMessage;
|
||||||
if (pgpResult.getCharset() != null) {
|
if (result.getCharset() != null) {
|
||||||
try {
|
try {
|
||||||
displayMessage = new String(decryptedMessage, pgpResult.getCharset());
|
displayMessage = new String(decryptedMessage, result.getCharset());
|
||||||
} catch (UnsupportedEncodingException e) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
// if we can't decode properly, just fall back to utf-8
|
// if we can't decode properly, just fall back to utf-8
|
||||||
displayMessage = new String(decryptedMessage);
|
displayMessage = new String(decryptedMessage);
|
||||||
@ -215,29 +182,8 @@ public class DecryptTextFragment extends DecryptFragment {
|
|||||||
mText.setText(displayMessage);
|
mText.setText(displayMessage);
|
||||||
|
|
||||||
// display signature result in activity
|
// display signature result in activity
|
||||||
loadVerifyResult(pgpResult);
|
loadVerifyResult(result);
|
||||||
} else {
|
|
||||||
// TODO: show also invalid layout with different text?
|
|
||||||
}
|
|
||||||
pgpResult.createNotify(getActivity()).show(DecryptTextFragment.this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
|
||||||
Messenger messenger = new Messenger(saveHandler);
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
|
||||||
|
|
||||||
// show progress dialog
|
|
||||||
saveHandler.showProgressDialog(getActivity());
|
|
||||||
|
|
||||||
// start service with intent
|
|
||||||
getActivity().startService(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onVerifyLoaded(boolean hideErrorOverlay) {
|
|
||||||
mShowMenuOptions = hideErrorOverlay;
|
|
||||||
getActivity().supportInvalidateOptionsMenu();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import android.os.Bundle;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
|
import android.os.Parcelable;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v4.content.CursorLoader;
|
import android.support.v4.content.CursorLoader;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
@ -39,9 +40,13 @@ 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.DialogFragmentWorkaround;
|
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
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.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||||
@ -49,7 +54,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
|||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
import org.sufficientlysecure.keychain.provider.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.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
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.ChangeUnlockParcel;
|
||||||
@ -65,9 +70,8 @@ import org.sufficientlysecure.keychain.ui.util.Notify;
|
|||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
|
|
||||||
|
public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel, OperationResult>
|
||||||
public class EditKeyFragment extends CryptoOperationFragment implements
|
implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
LoaderManager.LoaderCallbacks<Cursor> {
|
|
||||||
|
|
||||||
public static final String ARG_DATA_URI = "uri";
|
public static final String ARG_DATA_URI = "uri";
|
||||||
public static final String ARG_SAVE_KEYRING_PARCEL = "save_keyring_parcel";
|
public static final String ARG_SAVE_KEYRING_PARCEL = "save_keyring_parcel";
|
||||||
@ -415,16 +419,66 @@ public class EditKeyFragment extends CryptoOperationFragment implements
|
|||||||
mSaveKeyringParcel.mRevokeSubKeys.add(keyId);
|
mSaveKeyringParcel.mRevokeSubKeys.add(keyId);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EditSubkeyDialogFragment.MESSAGE_STRIP:
|
case EditSubkeyDialogFragment.MESSAGE_STRIP: {
|
||||||
|
SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position);
|
||||||
|
if (secretKeyType == SecretKeyType.GNU_DUMMY) {
|
||||||
|
// Key is already stripped; this is a no-op.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
SubkeyChange change = mSaveKeyringParcel.getSubkeyChange(keyId);
|
SubkeyChange change = mSaveKeyringParcel.getSubkeyChange(keyId);
|
||||||
if (change == null) {
|
if (change == null) {
|
||||||
mSaveKeyringParcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, null));
|
mSaveKeyringParcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, false));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// toggle
|
// toggle
|
||||||
change.mDummyStrip = !change.mDummyStrip;
|
change.mDummyStrip = !change.mDummyStrip;
|
||||||
|
if (change.mDummyStrip && change.mMoveKeyToCard) {
|
||||||
|
// User had chosen to divert key, but now wants to strip it instead.
|
||||||
|
change.mMoveKeyToCard = false;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case EditSubkeyDialogFragment.MESSAGE_KEYTOCARD: {
|
||||||
|
Activity activity = EditKeyFragment.this.getActivity();
|
||||||
|
SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position);
|
||||||
|
if (secretKeyType == SecretKeyType.DIVERT_TO_CARD ||
|
||||||
|
secretKeyType == SecretKeyType.GNU_DUMMY) {
|
||||||
|
Notify.create(activity, R.string.edit_key_error_bad_nfc_stripped, Notify.Style.ERROR)
|
||||||
|
.show((ViewGroup) activity.findViewById(R.id.import_snackbar));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int algorithm = mSubkeysAdapter.getAlgorithm(position);
|
||||||
|
// these are the PGP constants for RSA_GENERAL, RSA_ENCRYPT and RSA_SIGN
|
||||||
|
if (algorithm != 1 && algorithm != 2 && algorithm != 3) {
|
||||||
|
Notify.create(activity, R.string.edit_key_error_bad_nfc_algo, Notify.Style.ERROR)
|
||||||
|
.show((ViewGroup) activity.findViewById(R.id.import_snackbar));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (mSubkeysAdapter.getKeySize(position) != 2048) {
|
||||||
|
Notify.create(activity, R.string.edit_key_error_bad_nfc_size, Notify.Style.ERROR)
|
||||||
|
.show((ViewGroup) activity.findViewById(R.id.import_snackbar));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SubkeyChange change;
|
||||||
|
change = mSaveKeyringParcel.getSubkeyChange(keyId);
|
||||||
|
if (change == null) {
|
||||||
|
mSaveKeyringParcel.mChangeSubKeys.add(
|
||||||
|
new SubkeyChange(keyId, false, true)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// toggle
|
||||||
|
change.mMoveKeyToCard = !change.mMoveKeyToCard;
|
||||||
|
if (change.mMoveKeyToCard && change.mDummyStrip) {
|
||||||
|
// User had chosen to strip key, but now wants to divert it.
|
||||||
|
change.mDummyStrip = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
|
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -521,7 +575,7 @@ public class EditKeyFragment extends CryptoOperationFragment implements
|
|||||||
addSubkeyDialogFragment.show(getActivity().getSupportFragmentManager(), "addSubkeyDialog");
|
addSubkeyDialogFragment.show(getActivity().getSupportFragmentManager(), "addSubkeyDialog");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void returnKeyringParcel() {
|
protected void returnKeyringParcel() {
|
||||||
if (mSaveKeyringParcel.mAddUserIds.size() == 0) {
|
if (mSaveKeyringParcel.mAddUserIds.size() == 0) {
|
||||||
Notify.create(getActivity(), R.string.edit_key_error_add_identity, Notify.Style.ERROR).show();
|
Notify.create(getActivity(), R.string.edit_key_error_add_identity, Notify.Style.ERROR).show();
|
||||||
return;
|
return;
|
||||||
@ -540,76 +594,6 @@ public class EditKeyFragment extends CryptoOperationFragment implements
|
|||||||
getActivity().finish();
|
getActivity().finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
|
||||||
|
|
||||||
Log.d(Constants.TAG, "cryptoInput:\n" + cryptoInput);
|
|
||||||
Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel);
|
|
||||||
|
|
||||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
|
||||||
getActivity(),
|
|
||||||
getString(R.string.progress_saving),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
true,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
public void handleMessage(Message message) {
|
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
|
||||||
super.handleMessage(message);
|
|
||||||
|
|
||||||
if (handlePendingMessage(message)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
|
||||||
|
|
||||||
// get returned data bundle
|
|
||||||
Bundle returnData = message.getData();
|
|
||||||
if (returnData == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final OperationResult result =
|
|
||||||
returnData.getParcelable(OperationResult.EXTRA_RESULT);
|
|
||||||
if (result == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if bad -> display here!
|
|
||||||
if (!result.success()) {
|
|
||||||
result.createNotify(getActivity()).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if good -> finish, return result to showkey and display there!
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.putExtra(OperationResult.EXTRA_RESULT, result);
|
|
||||||
getActivity().setResult(EditKeyActivity.RESULT_OK, intent);
|
|
||||||
getActivity().finish();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send all information needed to service to import key in other thread
|
|
||||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
|
||||||
intent.setAction(KeychainIntentService.ACTION_EDIT_KEYRING);
|
|
||||||
|
|
||||||
// fill values for this action
|
|
||||||
Bundle data = new Bundle();
|
|
||||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
|
||||||
data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, mSaveKeyringParcel);
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
|
||||||
Messenger messenger = new Messenger(saveHandler);
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
|
||||||
|
|
||||||
// show progress dialog
|
|
||||||
saveHandler.showProgressDialog(getActivity());
|
|
||||||
|
|
||||||
// start service with intent
|
|
||||||
getActivity().startService(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes this activity, returning a result parcel with a single error log entry.
|
* Closes this activity, returning a result parcel with a single error log entry.
|
||||||
*/
|
*/
|
||||||
@ -624,4 +608,20 @@ public class EditKeyFragment extends CryptoOperationFragment implements
|
|||||||
getActivity().finish();
|
getActivity().finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SaveKeyringParcel createOperationInput() {
|
||||||
|
return mSaveKeyringParcel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCryptoOperationSuccess(OperationResult result) {
|
||||||
|
|
||||||
|
// if good -> finish, return result to showkey and display there!
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.putExtra(OperationResult.EXTRA_RESULT, result);
|
||||||
|
getActivity().setResult(EditKeyActivity.RESULT_OK, intent);
|
||||||
|
getActivity().finish();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -73,12 +73,10 @@ public class EncryptFilesActivity extends EncryptActivity {
|
|||||||
uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean useArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, false);
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||||
|
|
||||||
EncryptFilesFragment encryptFragment = EncryptFilesFragment.newInstance(uris, useArmor);
|
EncryptFilesFragment encryptFragment = EncryptFilesFragment.newInstance(uris);
|
||||||
transaction.replace(R.id.encrypt_file_container, encryptFragment);
|
transaction.replace(R.id.encrypt_file_container, encryptFragment);
|
||||||
transaction.commit();
|
transaction.commit();
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
@ -26,8 +25,6 @@ import android.graphics.Point;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Message;
|
|
||||||
import android.os.Messenger;
|
|
||||||
import android.support.v7.widget.DefaultItemAnimator;
|
import android.support.v7.widget.DefaultItemAnimator;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
@ -49,18 +46,17 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
|
|||||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration;
|
import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration;
|
||||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
|
||||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||||
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 org.sufficientlysecure.keychain.util.Preferences;
|
||||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -70,7 +66,8 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class EncryptFilesFragment extends CryptoOperationFragment {
|
public class EncryptFilesFragment
|
||||||
|
extends CachingCryptoOperationFragment<SignEncryptParcel, SignEncryptResult> {
|
||||||
|
|
||||||
public static final String ARG_DELETE_AFTER_ENCRYPT = "delete_after_encrypt";
|
public static final String ARG_DELETE_AFTER_ENCRYPT = "delete_after_encrypt";
|
||||||
public static final String ARG_ENCRYPT_FILENAMES = "encrypt_filenames";
|
public static final String ARG_ENCRYPT_FILENAMES = "encrypt_filenames";
|
||||||
@ -89,7 +86,7 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
|
|||||||
|
|
||||||
private boolean mShareAfterEncrypt;
|
private boolean mShareAfterEncrypt;
|
||||||
|
|
||||||
private ArrayList<Uri> mOutputUris = new ArrayList<>();
|
private ArrayList<Uri> mOutputUris;
|
||||||
|
|
||||||
private RecyclerView mSelectedFiles;
|
private RecyclerView mSelectedFiles;
|
||||||
|
|
||||||
@ -99,11 +96,10 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
|
|||||||
/**
|
/**
|
||||||
* Creates new instance of this fragment
|
* Creates new instance of this fragment
|
||||||
*/
|
*/
|
||||||
public static EncryptFilesFragment newInstance(ArrayList<Uri> uris, boolean useArmor) {
|
public static EncryptFilesFragment newInstance(ArrayList<Uri> uris) {
|
||||||
EncryptFilesFragment frag = new EncryptFilesFragment();
|
EncryptFilesFragment frag = new EncryptFilesFragment();
|
||||||
|
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putBoolean(ARG_USE_ASCII_ARMOR, useArmor);
|
|
||||||
args.putParcelableArrayList(ARG_URIS, uris);
|
args.putParcelableArrayList(ARG_URIS, uris);
|
||||||
frag.setArguments(args);
|
frag.setArguments(args);
|
||||||
|
|
||||||
@ -167,11 +163,28 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
Preferences prefs = Preferences.getPreferences(getActivity());
|
||||||
|
|
||||||
Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState;
|
Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState;
|
||||||
mDeleteAfterEncrypt = args.getBoolean(ARG_DELETE_AFTER_ENCRYPT, false);
|
mDeleteAfterEncrypt = args.getBoolean(ARG_DELETE_AFTER_ENCRYPT, false);
|
||||||
|
|
||||||
|
if (args.containsKey(ARG_USE_ASCII_ARMOR)) {
|
||||||
mUseArmor = args.getBoolean(ARG_USE_ASCII_ARMOR, false);
|
mUseArmor = args.getBoolean(ARG_USE_ASCII_ARMOR, false);
|
||||||
|
} else {
|
||||||
|
mUseArmor = prefs.getUseArmor();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.containsKey(ARG_USE_COMPRESSION)) {
|
||||||
mUseCompression = args.getBoolean(ARG_USE_COMPRESSION, true);
|
mUseCompression = args.getBoolean(ARG_USE_COMPRESSION, true);
|
||||||
|
} else {
|
||||||
|
mUseCompression = prefs.getFilesUseCompression();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.containsKey(ARG_ENCRYPT_FILENAMES)) {
|
||||||
mEncryptFilenames = args.getBoolean(ARG_ENCRYPT_FILENAMES, true);
|
mEncryptFilenames = args.getBoolean(ARG_ENCRYPT_FILENAMES, true);
|
||||||
|
} else {
|
||||||
|
mEncryptFilenames = prefs.getEncryptFilenames();
|
||||||
|
}
|
||||||
|
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
@ -221,33 +234,6 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void encryptClicked(boolean share) {
|
|
||||||
if (mFilesModels.isEmpty()) {
|
|
||||||
Notify.create(getActivity(), R.string.error_no_file_selected,
|
|
||||||
Notify.Style.ERROR).show(this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (share) {
|
|
||||||
mOutputUris.clear();
|
|
||||||
int filenameCounter = 1;
|
|
||||||
for (FilesAdapter.ViewModel model : mFilesModels) {
|
|
||||||
String targetName =
|
|
||||||
(mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri))
|
|
||||||
+ (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
|
|
||||||
mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName));
|
|
||||||
filenameCounter++;
|
|
||||||
}
|
|
||||||
startEncrypt(true);
|
|
||||||
} else {
|
|
||||||
if (mFilesModels.size() > 1) {
|
|
||||||
Notify.create(getActivity(), R.string.error_multi_not_supported,
|
|
||||||
Notify.Style.ERROR).show(this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showOutputFileDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addFile(Intent data) {
|
public void addFile(Intent data) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||||
addInputUri(data.getData());
|
addInputUri(data.getData());
|
||||||
@ -281,17 +267,17 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
|
|||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.encrypt_save: {
|
case R.id.encrypt_save: {
|
||||||
encryptClicked(false);
|
mShareAfterEncrypt = false;
|
||||||
|
cryptoOperation();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case R.id.encrypt_share: {
|
case R.id.encrypt_share: {
|
||||||
encryptClicked(true);
|
mShareAfterEncrypt = true;
|
||||||
|
cryptoOperation();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case R.id.check_use_armor: {
|
case R.id.check_use_armor: {
|
||||||
// we can NOT do this for every item, others might care!
|
toggleUseArmor(item, !item.isChecked());
|
||||||
item.setChecked(!item.isChecked());
|
|
||||||
mUseArmor = item.isChecked();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case R.id.check_delete_after_encrypt: {
|
case R.id.check_delete_after_encrypt: {
|
||||||
@ -300,13 +286,11 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case R.id.check_enable_compression: {
|
case R.id.check_enable_compression: {
|
||||||
item.setChecked(!item.isChecked());
|
toggleEnableCompression(item, !item.isChecked());
|
||||||
mUseCompression = item.isChecked();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case R.id.check_encrypt_filenames: {
|
case R.id.check_encrypt_filenames: {
|
||||||
item.setChecked(!item.isChecked());
|
toggleEncryptFilenamesCheck(item, !item.isChecked());
|
||||||
mEncryptFilenames = item.isChecked();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// case R.id.check_hidden_recipients: {
|
// case R.id.check_hidden_recipients: {
|
||||||
@ -321,7 +305,75 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onEncryptSuccess(final SignEncryptResult result) {
|
public void toggleUseArmor(MenuItem item, final boolean useArmor) {
|
||||||
|
|
||||||
|
mUseArmor = useArmor;
|
||||||
|
item.setChecked(useArmor);
|
||||||
|
|
||||||
|
Notify.create(getActivity(), useArmor
|
||||||
|
? R.string.snack_armor_on
|
||||||
|
: R.string.snack_armor_off,
|
||||||
|
Notify.LENGTH_LONG, Style.OK, new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void onAction() {
|
||||||
|
Preferences.getPreferences(getActivity()).setUseArmor(useArmor);
|
||||||
|
Notify.create(getActivity(), useArmor
|
||||||
|
? R.string.snack_armor_on
|
||||||
|
: R.string.snack_armor_off,
|
||||||
|
Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved)
|
||||||
|
.show(EncryptFilesFragment.this, false);
|
||||||
|
}
|
||||||
|
}, R.string.btn_save_default).show(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggleEnableCompression(MenuItem item, final boolean compress) {
|
||||||
|
|
||||||
|
mUseCompression = compress;
|
||||||
|
item.setChecked(compress);
|
||||||
|
|
||||||
|
Notify.create(getActivity(), compress
|
||||||
|
? R.string.snack_compression_on
|
||||||
|
: R.string.snack_compression_off,
|
||||||
|
Notify.LENGTH_LONG, Style.OK, new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void onAction() {
|
||||||
|
Preferences.getPreferences(getActivity()).setFilesUseCompression(compress);
|
||||||
|
Notify.create(getActivity(), compress
|
||||||
|
? R.string.snack_compression_on
|
||||||
|
: R.string.snack_compression_off,
|
||||||
|
Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved)
|
||||||
|
.show(EncryptFilesFragment.this, false);
|
||||||
|
}
|
||||||
|
}, R.string.btn_save_default).show(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggleEncryptFilenamesCheck(MenuItem item, final boolean encryptFilenames) {
|
||||||
|
|
||||||
|
mEncryptFilenames = encryptFilenames;
|
||||||
|
item.setChecked(encryptFilenames);
|
||||||
|
|
||||||
|
Notify.create(getActivity(), encryptFilenames
|
||||||
|
? R.string.snack_encrypt_filenames_on
|
||||||
|
: R.string.snack_encrypt_filenames_off,
|
||||||
|
Notify.LENGTH_LONG, Style.OK, new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void onAction() {
|
||||||
|
Preferences.getPreferences(getActivity()).setEncryptFilenames(encryptFilenames);
|
||||||
|
Notify.create(getActivity(), encryptFilenames
|
||||||
|
? R.string.snack_encrypt_filenames_on
|
||||||
|
: R.string.snack_encrypt_filenames_off,
|
||||||
|
Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved)
|
||||||
|
.show(EncryptFilesFragment.this, false);
|
||||||
|
}
|
||||||
|
}, R.string.btn_save_default).show(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCryptoOperationSuccess(final SignEncryptResult result) {
|
||||||
|
|
||||||
if (mDeleteAfterEncrypt) {
|
if (mDeleteAfterEncrypt) {
|
||||||
DeleteFileDialogFragment deleteFileDialog =
|
DeleteFileDialogFragment deleteFileDialog =
|
||||||
DeleteFileDialogFragment.newInstance(mFilesAdapter.getAsArrayList());
|
DeleteFileDialogFragment.newInstance(mFilesAdapter.getAsArrayList());
|
||||||
@ -349,29 +401,91 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
|
|||||||
result.createNotify(getActivity()).show();
|
result.createNotify(getActivity()).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SignEncryptParcel createEncryptBundle() {
|
// prepares mOutputUris, either directly and returns false, or indirectly
|
||||||
|
// which returns true and will call cryptoOperation after mOutputUris has
|
||||||
|
// been set at a later point.
|
||||||
|
private boolean prepareOutputStreams(boolean share) {
|
||||||
|
|
||||||
if (mFilesModels.isEmpty()) {
|
if (mFilesModels.isEmpty()) {
|
||||||
Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR)
|
Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR)
|
||||||
.show(this);
|
.show(this);
|
||||||
return null;
|
return true;
|
||||||
} else if (mFilesModels.size() > 1 && !mShareAfterEncrypt) {
|
} else if (mFilesModels.size() > 1 && !mShareAfterEncrypt) {
|
||||||
Log.e(Constants.TAG, "Aborting: mInputUris.size() > 1 && !mShareAfterEncrypt");
|
Log.e(Constants.TAG, "Aborting: mInputUris.size() > 1 && !mShareAfterEncrypt");
|
||||||
// This should be impossible...
|
// This should be impossible...
|
||||||
return null;
|
return true;
|
||||||
} else if (mFilesModels.size() != mOutputUris.size()) {
|
}
|
||||||
Log.e(Constants.TAG, "Aborting: mInputUris.size() != mOutputUris.size()");
|
|
||||||
// This as well
|
if (share) {
|
||||||
|
mOutputUris = new ArrayList<>();
|
||||||
|
int filenameCounter = 1;
|
||||||
|
for (FilesAdapter.ViewModel model : mFilesModels) {
|
||||||
|
String targetName =
|
||||||
|
(mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri))
|
||||||
|
+ (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
|
||||||
|
mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName));
|
||||||
|
filenameCounter++;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if (mFilesModels.size() > 1) {
|
||||||
|
Notify.create(getActivity(), R.string.error_multi_not_supported,
|
||||||
|
Notify.Style.ERROR).show(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
showOutputFileDialog();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SignEncryptParcel createOperationInput() {
|
||||||
|
|
||||||
|
SignEncryptParcel actionsParcel = getCachedActionsParcel();
|
||||||
|
|
||||||
|
// we have three cases here: nothing cached, cached except output, fully cached
|
||||||
|
if (actionsParcel == null) {
|
||||||
|
|
||||||
|
// clear output uris for now, they will be created by prepareOutputStreams later
|
||||||
|
mOutputUris = null;
|
||||||
|
|
||||||
|
actionsParcel = createIncompleteCryptoInput();
|
||||||
|
// this is null if invalid, just return in that case
|
||||||
|
if (actionsParcel == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheActionsParcel(actionsParcel);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it's incomplete, prepare output streams
|
||||||
|
if (actionsParcel.isIncomplete()) {
|
||||||
|
// if this is still null, prepare output streams again
|
||||||
|
if (mOutputUris == null) {
|
||||||
|
// this may interrupt the flow, and call us again from onActivityResult
|
||||||
|
if (prepareOutputStreams(mShareAfterEncrypt)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actionsParcel.addOutputUris(mOutputUris);
|
||||||
|
cacheActionsParcel(actionsParcel);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return actionsParcel;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SignEncryptParcel createIncompleteCryptoInput() {
|
||||||
|
|
||||||
// fill values for this action
|
// fill values for this action
|
||||||
SignEncryptParcel data = new SignEncryptParcel();
|
SignEncryptParcel data = new SignEncryptParcel();
|
||||||
|
|
||||||
data.addInputUris(mFilesAdapter.getAsArrayList());
|
data.addInputUris(mFilesAdapter.getAsArrayList());
|
||||||
data.addOutputUris(mOutputUris);
|
|
||||||
|
|
||||||
if (mUseCompression) {
|
if (mUseCompression) {
|
||||||
data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
|
data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
|
||||||
@ -471,68 +585,6 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
|
|||||||
return sendIntent;
|
return sendIntent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startEncrypt(boolean share) {
|
|
||||||
mShareAfterEncrypt = share;
|
|
||||||
cryptoOperation();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
|
||||||
|
|
||||||
final SignEncryptParcel input = createEncryptBundle();
|
|
||||||
// this is null if invalid, just return in that case
|
|
||||||
if (input == null) {
|
|
||||||
// Notify was created by inputIsValid.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send all information needed to service to edit key in other thread
|
|
||||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
|
||||||
intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
|
|
||||||
|
|
||||||
Bundle data = new Bundle();
|
|
||||||
data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input);
|
|
||||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
|
||||||
|
|
||||||
// Message is received after encrypting is done in KeychainIntentService
|
|
||||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
|
|
||||||
getActivity(),
|
|
||||||
getString(R.string.progress_encrypting),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
true,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
public void handleMessage(Message message) {
|
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
|
||||||
super.handleMessage(message);
|
|
||||||
|
|
||||||
// handle pending messages
|
|
||||||
if (handlePendingMessage(message)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
|
||||||
SignEncryptResult result =
|
|
||||||
message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
|
|
||||||
if (result.success()) {
|
|
||||||
onEncryptSuccess(result);
|
|
||||||
} else {
|
|
||||||
result.createNotify(getActivity()).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Create a new Messenger for the communication back
|
|
||||||
Messenger messenger = new Messenger(serviceHandler);
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
|
||||||
|
|
||||||
// show progress dialog
|
|
||||||
serviceHandler.showProgressDialog(getActivity());
|
|
||||||
|
|
||||||
// start service with intent
|
|
||||||
getActivity().startService(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
@ -545,9 +597,10 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
|
|||||||
case REQUEST_CODE_OUTPUT: {
|
case REQUEST_CODE_OUTPUT: {
|
||||||
// This happens after output file was selected, so start our operation
|
// This happens after output file was selected, so start our operation
|
||||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||||
mOutputUris.clear();
|
mOutputUris = new ArrayList<>(1);
|
||||||
mOutputUris.add(data.getData());
|
mOutputUris.add(data.getData());
|
||||||
startEncrypt(false);
|
mShareAfterEncrypt = false;
|
||||||
|
cryptoOperation();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,9 @@ 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.ViewAnimator;
|
||||||
|
|
||||||
|
import com.tokenautocomplete.TokenCompleteTextView.TokenListener;
|
||||||
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.CanonicalizedPublicKeyRing;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||||
@ -33,6 +35,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException
|
|||||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
|
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
|
||||||
import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView;
|
import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView;
|
||||||
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
|
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
|
||||||
|
import org.sufficientlysecure.keychain.ui.widget.KeySpinner.OnKeyChangedListener;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
|
|
||||||
@ -76,6 +79,35 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
|
|||||||
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
|
||||||
|
|
||||||
|
final ViewAnimator vSignatureIcon = (ViewAnimator) view.findViewById(R.id.result_signature_icon);
|
||||||
|
mSignKeySpinner.setOnKeyChangedListener(new OnKeyChangedListener() {
|
||||||
|
@Override
|
||||||
|
public void onKeyChanged(long masterKeyId) {
|
||||||
|
int child = masterKeyId != Constants.key.none ? 1 : 0;
|
||||||
|
if (vSignatureIcon.getDisplayedChild() != child) {
|
||||||
|
vSignatureIcon.setDisplayedChild(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final ViewAnimator vEncryptionIcon = (ViewAnimator) view.findViewById(R.id.result_encryption_icon);
|
||||||
|
mEncryptKeyView.setTokenListener(new TokenListener() {
|
||||||
|
@Override
|
||||||
|
public void onTokenAdded(Object o) {
|
||||||
|
if (vEncryptionIcon.getDisplayedChild() != 1) {
|
||||||
|
vEncryptionIcon.setDisplayedChild(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTokenRemoved(Object o) {
|
||||||
|
int child = mEncryptKeyView.getObjects().isEmpty() ? 0 : 1;
|
||||||
|
if (vEncryptionIcon.getDisplayedChild() != child) {
|
||||||
|
vEncryptionIcon.setDisplayedChild(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,11 +117,12 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
|
|||||||
mProviderHelper = new ProviderHelper(getActivity());
|
mProviderHelper = new ProviderHelper(getActivity());
|
||||||
|
|
||||||
// preselect keys given, from state or arguments
|
// preselect keys given, from state or arguments
|
||||||
long signatureKeyId, encryptionKeyIds[];
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
signatureKeyId = getArguments().getLong(ARG_SINGATURE_KEY_ID);
|
Long signatureKeyId = getArguments().getLong(ARG_SINGATURE_KEY_ID);
|
||||||
encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS);
|
if (signatureKeyId == Constants.key.none) {
|
||||||
|
signatureKeyId = null;
|
||||||
|
}
|
||||||
|
long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS);
|
||||||
preselectKeys(signatureKeyId, encryptionKeyIds);
|
preselectKeys(signatureKeyId, encryptionKeyIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,8 +131,8 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
|
|||||||
/**
|
/**
|
||||||
* If an Intent gives a signatureMasterKeyId and/or encryptionMasterKeyIds, preselect those!
|
* If an Intent gives a signatureMasterKeyId and/or encryptionMasterKeyIds, preselect those!
|
||||||
*/
|
*/
|
||||||
private void preselectKeys(long signatureKeyId, long[] encryptionKeyIds) {
|
private void preselectKeys(Long signatureKeyId, long[] encryptionKeyIds) {
|
||||||
if (signatureKeyId != Constants.key.none) {
|
if (signatureKeyId != null) {
|
||||||
try {
|
try {
|
||||||
CachedPublicKeyRing keyring = mProviderHelper.getCachedPublicKeyRing(
|
CachedPublicKeyRing keyring = mProviderHelper.getCachedPublicKeyRing(
|
||||||
KeyRings.buildUnifiedKeyRingUri(signatureKeyId));
|
KeyRings.buildUnifiedKeyRingUri(signatureKeyId));
|
||||||
@ -134,7 +167,7 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getAsymmetricSigningKeyId() {
|
public long getAsymmetricSigningKeyId() {
|
||||||
return mSignKeySpinner.getSelectedItemId();
|
return mSignKeySpinner.getSelectedKeyId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -18,11 +18,8 @@
|
|||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Message;
|
|
||||||
import android.os.Messenger;
|
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -41,19 +38,19 @@ import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
|||||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
|
||||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class EncryptTextFragment extends CryptoOperationFragment {
|
public class EncryptTextFragment
|
||||||
|
extends CachingCryptoOperationFragment<SignEncryptParcel, SignEncryptResult> {
|
||||||
|
|
||||||
public static final String ARG_TEXT = "text";
|
public static final String ARG_TEXT = "text";
|
||||||
public static final String ARG_USE_COMPRESSION = "use_compression";
|
public static final String ARG_USE_COMPRESSION = "use_compression";
|
||||||
@ -131,10 +128,19 @@ public class EncryptTextFragment extends CryptoOperationFragment {
|
|||||||
mMessage = getArguments().getString(ARG_TEXT);
|
mMessage = getArguments().getString(ARG_TEXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Preferences prefs = Preferences.getPreferences(getActivity());
|
||||||
|
|
||||||
Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState;
|
Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState;
|
||||||
|
|
||||||
mUseCompression = args.getBoolean(ARG_USE_COMPRESSION, true);
|
mUseCompression = args.getBoolean(ARG_USE_COMPRESSION, true);
|
||||||
|
if (args.containsKey(ARG_USE_COMPRESSION)) {
|
||||||
|
mUseCompression = args.getBoolean(ARG_USE_COMPRESSION, true);
|
||||||
|
} else {
|
||||||
|
mUseCompression = prefs.getTextUseCompression();
|
||||||
|
}
|
||||||
|
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -147,12 +153,9 @@ public class EncryptTextFragment extends CryptoOperationFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
if (item.isCheckable()) {
|
|
||||||
item.setChecked(!item.isChecked());
|
|
||||||
}
|
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.check_enable_compression: {
|
case R.id.check_enable_compression: {
|
||||||
mUseCompression = item.isChecked();
|
toggleEnableCompression(item, !item.isChecked());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// case R.id.check_hidden_recipients: {
|
// case R.id.check_hidden_recipients: {
|
||||||
@ -161,11 +164,13 @@ public class EncryptTextFragment extends CryptoOperationFragment {
|
|||||||
// break;
|
// break;
|
||||||
// }
|
// }
|
||||||
case R.id.encrypt_copy: {
|
case R.id.encrypt_copy: {
|
||||||
cryptoOperation(false);
|
mShareAfterEncrypt = false;
|
||||||
|
cryptoOperation();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case R.id.encrypt_share: {
|
case R.id.encrypt_share: {
|
||||||
cryptoOperation(true);
|
mShareAfterEncrypt = true;
|
||||||
|
cryptoOperation();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@ -175,21 +180,29 @@ public class EncryptTextFragment extends CryptoOperationFragment {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onEncryptSuccess(SignEncryptResult result) {
|
public void toggleEnableCompression(MenuItem item, final boolean compress) {
|
||||||
if (mShareAfterEncrypt) {
|
|
||||||
// Share encrypted message/file
|
mUseCompression = compress;
|
||||||
startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
|
item.setChecked(compress);
|
||||||
} else {
|
|
||||||
// Copy to clipboard
|
Notify.create(getActivity(), compress
|
||||||
copyToClipboard(result.getResultBytes());
|
? R.string.snack_compression_on
|
||||||
result.createNotify(getActivity()).show();
|
: R.string.snack_compression_off,
|
||||||
// Notify.create(EncryptTextActivity.this,
|
Notify.LENGTH_LONG, Style.OK, new ActionListener() {
|
||||||
// R.string.encrypt_sign_clipboard_successful, Notify.Style.OK)
|
@Override
|
||||||
// .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
|
public void onAction() {
|
||||||
|
Preferences.getPreferences(getActivity()).setTextUseCompression(compress);
|
||||||
|
Notify.create(getActivity(), compress
|
||||||
|
? R.string.snack_compression_on
|
||||||
|
: R.string.snack_compression_off,
|
||||||
|
Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved)
|
||||||
|
.show(EncryptTextFragment.this, false);
|
||||||
}
|
}
|
||||||
|
}, R.string.btn_save_default).show(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SignEncryptParcel createEncryptBundle() {
|
protected SignEncryptParcel createOperationInput() {
|
||||||
|
|
||||||
if (mMessage == null || mMessage.isEmpty()) {
|
if (mMessage == null || mMessage.isEmpty()) {
|
||||||
Notify.create(getActivity(), R.string.error_empty_text, Notify.Style.ERROR)
|
Notify.create(getActivity(), R.string.error_empty_text, Notify.Style.ERROR)
|
||||||
@ -227,7 +240,7 @@ public class EncryptTextFragment extends CryptoOperationFragment {
|
|||||||
boolean gotEncryptionKeys = (encryptionKeyIds != null
|
boolean gotEncryptionKeys = (encryptionKeyIds != null
|
||||||
&& encryptionKeyIds.length > 0);
|
&& encryptionKeyIds.length > 0);
|
||||||
|
|
||||||
if (!gotEncryptionKeys && signingKeyId == 0L) {
|
if (!gotEncryptionKeys && signingKeyId == Constants.key.none) {
|
||||||
Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
|
Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
|
||||||
.show(this);
|
.show(this);
|
||||||
return null;
|
return null;
|
||||||
@ -302,65 +315,21 @@ public class EncryptTextFragment extends CryptoOperationFragment {
|
|||||||
return sendIntent;
|
return sendIntent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cryptoOperation(boolean share) {
|
|
||||||
mShareAfterEncrypt = share;
|
|
||||||
cryptoOperation();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
protected void onCryptoOperationSuccess(SignEncryptResult result) {
|
||||||
|
|
||||||
final SignEncryptParcel input = createEncryptBundle();
|
if (mShareAfterEncrypt) {
|
||||||
// this is null if invalid, just return in that case
|
// Share encrypted message/file
|
||||||
if (input == null) {
|
startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
|
||||||
// Notify was created by inputIsValid.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send all information needed to service to edit key in other thread
|
|
||||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
|
||||||
intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
|
|
||||||
|
|
||||||
final Bundle data = new Bundle();
|
|
||||||
data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input);
|
|
||||||
data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
|
||||||
|
|
||||||
// Message is received after encrypting is done in KeychainIntentService
|
|
||||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
|
|
||||||
getActivity(),
|
|
||||||
getString(R.string.progress_encrypting),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
public void handleMessage(Message message) {
|
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
|
||||||
super.handleMessage(message);
|
|
||||||
|
|
||||||
if (handlePendingMessage(message)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
|
||||||
SignEncryptResult result =
|
|
||||||
message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
|
|
||||||
|
|
||||||
if (result.success()) {
|
|
||||||
onEncryptSuccess(result);
|
|
||||||
} else {
|
} else {
|
||||||
|
// Copy to clipboard
|
||||||
|
copyToClipboard(result.getResultBytes());
|
||||||
result.createNotify(getActivity()).show();
|
result.createNotify(getActivity()).show();
|
||||||
|
// Notify.create(EncryptTextActivity.this,
|
||||||
|
// R.string.encrypt_sign_clipboard_successful, Notify.Style.OK)
|
||||||
|
// .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Create a new Messenger for the communication back
|
|
||||||
Messenger messenger = new Messenger(serviceHandler);
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
|
||||||
|
|
||||||
// show progress dialog
|
|
||||||
serviceHandler.showProgressDialog(getActivity());
|
|
||||||
|
|
||||||
// start service with intent
|
|
||||||
getActivity().startService(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -35,11 +35,9 @@ import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
|||||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
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.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
||||||
import org.sufficientlysecure.keychain.service.CloudImportService;
|
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@ -265,7 +263,7 @@ public class ImportKeysActivity extends BaseNfcActivity {
|
|||||||
// However, if we're being restored from a previous state,
|
// However, if we're being restored from a previous state,
|
||||||
// then we don't need to do anything and should return or else
|
// then we don't need to do anything and should return or else
|
||||||
// we could end up with overlapping fragments.
|
// we could end up with overlapping fragments.
|
||||||
if (savedInstanceState != null) {
|
if (mListFragment != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,7 +283,7 @@ public class ImportKeysActivity extends BaseNfcActivity {
|
|||||||
// However, if we're being restored from a previous state,
|
// However, if we're being restored from a previous state,
|
||||||
// then we don't need to do anything and should return or else
|
// then we don't need to do anything and should return or else
|
||||||
// we could end up with overlapping fragments.
|
// we could end up with overlapping fragments.
|
||||||
if (savedInstanceState != null) {
|
if (mTopFragment != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,7 +314,7 @@ public class ImportKeysActivity extends BaseNfcActivity {
|
|||||||
// However, if we're being restored from a previous state,
|
// However, if we're being restored from a previous state,
|
||||||
// then we don't need to do anything and should return or else
|
// then we don't need to do anything and should return or else
|
||||||
// we could end up with overlapping fragments.
|
// we could end up with overlapping fragments.
|
||||||
if (savedInstanceState != null) {
|
if (mTopFragment != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,16 +381,15 @@ public class ImportKeysActivity extends BaseNfcActivity {
|
|||||||
* Import keys with mImportData
|
* Import keys with mImportData
|
||||||
*/
|
*/
|
||||||
public void importKeys() {
|
public void importKeys() {
|
||||||
ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState();
|
|
||||||
if (ls instanceof ImportKeysListFragment.BytesLoaderState) {
|
|
||||||
Log.d(Constants.TAG, "importKeys started");
|
|
||||||
|
|
||||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
|
if (mListFragment.getSelectedEntries().size() == 0) {
|
||||||
this,
|
Notify.create(this, R.string.error_nothing_import_selected, Notify.Style.ERROR)
|
||||||
getString(R.string.progress_importing),
|
.show((ViewGroup) findViewById(R.id.import_snackbar));
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
return;
|
||||||
true,
|
}
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
|
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(this) {
|
||||||
|
@Override
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -401,15 +398,18 @@ public class ImportKeysActivity extends BaseNfcActivity {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Currently not using CloudImport here due to https://github.com/open-keychain/open-keychain/issues/1221
|
|
||||||
// Send all information needed to service to import key in other thread
|
// Send all information needed to service to import key in other thread
|
||||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
Intent intent = new Intent(this, KeychainService.class);
|
||||||
|
|
||||||
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
|
intent.setAction(KeychainService.ACTION_IMPORT_KEYRING);
|
||||||
|
|
||||||
// fill values for this action
|
// fill values for this action
|
||||||
Bundle data = new Bundle();
|
Bundle data = new Bundle();
|
||||||
|
|
||||||
|
ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState();
|
||||||
|
if (ls instanceof ImportKeysListFragment.BytesLoaderState) {
|
||||||
|
Log.d(Constants.TAG, "importKeys started");
|
||||||
|
|
||||||
// get DATA from selected key entries
|
// get DATA from selected key entries
|
||||||
IteratorWithSize<ParcelableKeyRing> selectedEntries = mListFragment.getSelectedData();
|
IteratorWithSize<ParcelableKeyRing> selectedEntries = mListFragment.getSelectedData();
|
||||||
|
|
||||||
@ -423,14 +423,18 @@ public class ImportKeysActivity extends BaseNfcActivity {
|
|||||||
new ParcelableFileCache<>(this, "key_import.pcl");
|
new ParcelableFileCache<>(this, "key_import.pcl");
|
||||||
cache.writeCache(selectedEntries);
|
cache.writeCache(selectedEntries);
|
||||||
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
intent.putExtra(KeychainService.EXTRA_DATA, data);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(serviceHandler);
|
Messenger messenger = new Messenger(serviceHandler);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
// show progress dialog
|
// show progress dialog
|
||||||
serviceHandler.showProgressDialog(this);
|
serviceHandler.showProgressDialog(
|
||||||
|
getString(R.string.progress_importing),
|
||||||
|
ProgressDialog.STYLE_HORIZONTAL,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
startService(intent);
|
startService(intent);
|
||||||
@ -442,27 +446,7 @@ public class ImportKeysActivity extends BaseNfcActivity {
|
|||||||
} else if (ls instanceof ImportKeysListFragment.CloudLoaderState) {
|
} else if (ls instanceof ImportKeysListFragment.CloudLoaderState) {
|
||||||
ImportKeysListFragment.CloudLoaderState sls = (ImportKeysListFragment.CloudLoaderState) ls;
|
ImportKeysListFragment.CloudLoaderState sls = (ImportKeysListFragment.CloudLoaderState) ls;
|
||||||
|
|
||||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
|
data.putString(KeychainService.IMPORT_KEY_SERVER, sls.mCloudPrefs.keyserver);
|
||||||
this,
|
|
||||||
getString(R.string.progress_importing),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
true,
|
|
||||||
ProgressDialogFragment.ServiceType.CLOUD_IMPORT) {
|
|
||||||
public void handleMessage(Message message) {
|
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
|
||||||
super.handleMessage(message);
|
|
||||||
|
|
||||||
ImportKeysActivity.this.handleMessage(message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send all information needed to service to query keys in other thread
|
|
||||||
Intent intent = new Intent(this, CloudImportService.class);
|
|
||||||
|
|
||||||
// fill values for this action
|
|
||||||
Bundle data = new Bundle();
|
|
||||||
|
|
||||||
data.putString(CloudImportService.IMPORT_KEY_SERVER, sls.mCloudPrefs.keyserver);
|
|
||||||
|
|
||||||
// get selected key entries
|
// get selected key entries
|
||||||
ArrayList<ParcelableKeyRing> keys = new ArrayList<>();
|
ArrayList<ParcelableKeyRing> keys = new ArrayList<>();
|
||||||
@ -475,22 +459,22 @@ public class ImportKeysActivity extends BaseNfcActivity {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data.putParcelableArrayList(CloudImportService.IMPORT_KEY_LIST, keys);
|
data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, keys);
|
||||||
|
|
||||||
intent.putExtra(CloudImportService.EXTRA_DATA, data);
|
intent.putExtra(KeychainService.EXTRA_DATA, data);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(serviceHandler);
|
Messenger messenger = new Messenger(serviceHandler);
|
||||||
intent.putExtra(CloudImportService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
// show progress dialog
|
// show progress dialog
|
||||||
serviceHandler.showProgressDialog(this);
|
serviceHandler.showProgressDialog(
|
||||||
|
getString(R.string.progress_importing),
|
||||||
|
ProgressDialog.STYLE_HORIZONTAL, true
|
||||||
|
);
|
||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
startService(intent);
|
startService(intent);
|
||||||
} else {
|
|
||||||
Notify.create(this, R.string.error_nothing_import, Notify.Style.ERROR)
|
|
||||||
.show((ViewGroup) findViewById(R.id.import_snackbar));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,9 +43,8 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
|||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
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.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
|
||||||
import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4;
|
import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
@ -206,13 +205,9 @@ public class ImportKeysProxyActivity extends FragmentActivity {
|
|||||||
|
|
||||||
private void startImportService(ArrayList<ParcelableKeyRing> keyRings) {
|
private void startImportService(ArrayList<ParcelableKeyRing> keyRings) {
|
||||||
|
|
||||||
// Message is received after importing is done in KeychainIntentService
|
// Message is received after importing is done in KeychainService
|
||||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
|
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(this) {
|
||||||
this,
|
@Override
|
||||||
getString(R.string.progress_importing),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
true,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -258,22 +253,24 @@ public class ImportKeysProxyActivity extends FragmentActivity {
|
|||||||
Preferences prefs = Preferences.getPreferences(this);
|
Preferences prefs = Preferences.getPreferences(this);
|
||||||
Preferences.CloudSearchPrefs cloudPrefs =
|
Preferences.CloudSearchPrefs cloudPrefs =
|
||||||
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
|
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
|
||||||
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
data.putString(KeychainService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
||||||
}
|
}
|
||||||
|
|
||||||
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyRings);
|
data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, keyRings);
|
||||||
|
|
||||||
// Send all information needed to service to query keys in other thread
|
// Send all information needed to service to query keys in other thread
|
||||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
Intent intent = new Intent(this, KeychainService.class);
|
||||||
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
|
intent.setAction(KeychainService.ACTION_IMPORT_KEYRING);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
intent.putExtra(KeychainService.EXTRA_DATA, data);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(serviceHandler);
|
Messenger messenger = new Messenger(serviceHandler);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
// show progress dialog
|
// show progress dialog
|
||||||
serviceHandler.showProgressDialog(this);
|
serviceHandler.showProgressDialog(
|
||||||
|
getString(R.string.progress_importing),
|
||||||
|
ProgressDialog.STYLE_HORIZONTAL, true);
|
||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
startService(intent);
|
startService(intent);
|
||||||
|
@ -64,15 +64,14 @@ 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.KeychainDatabase;
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.service.CloudImportService;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||||
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.util.ExportHelper;
|
import org.sufficientlysecure.keychain.util.ExportHelper;
|
||||||
import org.sufficientlysecure.keychain.util.FabContainer;
|
import org.sufficientlysecure.keychain.util.FabContainer;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@ -555,9 +554,12 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateAllKeys() {
|
private void updateAllKeys() {
|
||||||
Context context = getActivity();
|
Activity activity = getActivity();
|
||||||
|
if (activity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ProviderHelper providerHelper = new ProviderHelper(context);
|
ProviderHelper providerHelper = new ProviderHelper(activity);
|
||||||
|
|
||||||
Cursor cursor = providerHelper.getContentResolver().query(
|
Cursor cursor = providerHelper.getContentResolver().query(
|
||||||
KeyRings.buildUnifiedKeyRingsUri(), new String[]{
|
KeyRings.buildUnifiedKeyRingsUri(), new String[]{
|
||||||
@ -565,21 +567,25 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
}, null, null, null
|
}, null, null, null
|
||||||
);
|
);
|
||||||
|
|
||||||
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
|
if (cursor == null) {
|
||||||
|
Notify.create(activity, R.string.error_loading_keys, Style.ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
|
||||||
|
try {
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
byte[] blob = cursor.getBlob(0);//fingerprint column is 0
|
byte[] blob = cursor.getBlob(0);//fingerprint column is 0
|
||||||
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
|
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
|
||||||
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
|
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
|
||||||
keyList.add(keyEntry);
|
keyList.add(keyEntry);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
|
||||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
|
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(getActivity()) {
|
||||||
getActivity(),
|
@Override
|
||||||
getString(R.string.progress_updating),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
true,
|
|
||||||
ProgressDialogFragment.ServiceType.CLOUD_IMPORT) {
|
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -603,7 +609,8 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Send all information needed to service to query keys in other thread
|
// Send all information needed to service to query keys in other thread
|
||||||
Intent intent = new Intent(getActivity(), CloudImportService.class);
|
Intent intent = new Intent(getActivity(), KeychainService.class);
|
||||||
|
intent.setAction(KeychainService.ACTION_IMPORT_KEYRING);
|
||||||
|
|
||||||
// fill values for this action
|
// fill values for this action
|
||||||
Bundle data = new Bundle();
|
Bundle data = new Bundle();
|
||||||
@ -613,31 +620,30 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
Preferences prefs = Preferences.getPreferences(getActivity());
|
Preferences prefs = Preferences.getPreferences(getActivity());
|
||||||
Preferences.CloudSearchPrefs cloudPrefs =
|
Preferences.CloudSearchPrefs cloudPrefs =
|
||||||
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
|
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
|
||||||
data.putString(CloudImportService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
data.putString(KeychainService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
||||||
}
|
}
|
||||||
|
|
||||||
data.putParcelableArrayList(CloudImportService.IMPORT_KEY_LIST, keyList);
|
data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, keyList);
|
||||||
|
|
||||||
intent.putExtra(CloudImportService.EXTRA_DATA, data);
|
intent.putExtra(KeychainService.EXTRA_DATA, data);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(serviceHandler);
|
Messenger messenger = new Messenger(serviceHandler);
|
||||||
intent.putExtra(CloudImportService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
// show progress dialog
|
// show progress dialog
|
||||||
serviceHandler.showProgressDialog(getActivity());
|
serviceHandler.showProgressDialog(
|
||||||
|
getString(R.string.progress_updating),
|
||||||
|
ProgressDialog.STYLE_HORIZONTAL, true);
|
||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
getActivity().startService(intent);
|
getActivity().startService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void consolidate() {
|
private void consolidate() {
|
||||||
// Message is received after importing is done in KeychainIntentService
|
// Message is received after importing is done in KeychainService
|
||||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
|
||||||
getActivity(),
|
@Override
|
||||||
getString(R.string.progress_importing),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -660,21 +666,23 @@ public class KeyListFragment extends LoaderFragment
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Send all information needed to service to import key in other thread
|
// Send all information needed to service to import key in other thread
|
||||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
Intent intent = new Intent(getActivity(), KeychainService.class);
|
||||||
|
|
||||||
intent.setAction(KeychainIntentService.ACTION_CONSOLIDATE);
|
intent.setAction(KeychainService.ACTION_CONSOLIDATE);
|
||||||
|
|
||||||
// fill values for this action
|
// fill values for this action
|
||||||
Bundle data = new Bundle();
|
Bundle data = new Bundle();
|
||||||
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
intent.putExtra(KeychainService.EXTRA_DATA, data);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(saveHandler);
|
Messenger messenger = new Messenger(saveHandler);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
// show progress dialog
|
// show progress dialog
|
||||||
saveHandler.showProgressDialog(getActivity());
|
saveHandler.showProgressDialog(
|
||||||
|
getString(R.string.progress_importing),
|
||||||
|
ProgressDialog.STYLE_HORIZONTAL, false);
|
||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
getActivity().startService(intent);
|
getActivity().startService(intent);
|
||||||
|
@ -23,6 +23,7 @@ import android.content.Intent;
|
|||||||
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.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
import android.support.v4.app.FragmentTransaction;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -42,7 +43,7 @@ import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
|||||||
import org.sufficientlysecure.keychain.util.FabContainer;
|
import org.sufficientlysecure.keychain.util.FabContainer;
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
|
|
||||||
public class MainActivity extends BaseNfcActivity implements FabContainer {
|
public class MainActivity extends BaseNfcActivity implements FabContainer, OnBackStackChangedListener {
|
||||||
|
|
||||||
private static final int ID_KEYS = 1;
|
private static final int ID_KEYS = 1;
|
||||||
private static final int ID_ENCRYPT_DECRYPT = 2;
|
private static final int ID_ENCRYPT_DECRYPT = 2;
|
||||||
@ -50,6 +51,8 @@ public class MainActivity extends BaseNfcActivity implements FabContainer {
|
|||||||
private static final int ID_SETTINGS = 4;
|
private static final int ID_SETTINGS = 4;
|
||||||
private static final int ID_HELP = 5;
|
private static final int ID_HELP = 5;
|
||||||
|
|
||||||
|
public static final String EXTRA_SKIP_FIRST_TIME = "skip_first_time";
|
||||||
|
|
||||||
public Drawer.Result mDrawerResult;
|
public Drawer.Result mDrawerResult;
|
||||||
private Toolbar mToolbar;
|
private Toolbar mToolbar;
|
||||||
|
|
||||||
@ -113,7 +116,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer {
|
|||||||
|
|
||||||
// if this is the first time show first time activity
|
// if this is the first time show first time activity
|
||||||
Preferences prefs = Preferences.getPreferences(this);
|
Preferences prefs = Preferences.getPreferences(this);
|
||||||
if (prefs.isFirstTime()) {
|
if (!getIntent().getBooleanExtra(EXTRA_SKIP_FIRST_TIME, false) && prefs.isFirstTime()) {
|
||||||
Intent intent = new Intent(this, CreateKeyActivity.class);
|
Intent intent = new Intent(this, CreateKeyActivity.class);
|
||||||
intent.putExtra(CreateKeyActivity.EXTRA_FIRST_TIME, true);
|
intent.putExtra(CreateKeyActivity.EXTRA_FIRST_TIME, true);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
@ -121,6 +124,8 @@ public class MainActivity extends BaseNfcActivity implements FabContainer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||||
|
|
||||||
Intent data = getIntent();
|
Intent data = getIntent();
|
||||||
// If we got an EXTRA_RESULT in the intent, show the notification
|
// If we got an EXTRA_RESULT in the intent, show the notification
|
||||||
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
||||||
@ -206,4 +211,25 @@ public class MainActivity extends BaseNfcActivity implements FabContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackStackChanged() {
|
||||||
|
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||||
|
if (fragmentManager == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Fragment frag = fragmentManager.findFragmentById(R.id.main_fragment_container);
|
||||||
|
if (frag == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the selected icon is the one shown at this point
|
||||||
|
if (frag instanceof KeyListFragment) {
|
||||||
|
mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_KEYS), false);
|
||||||
|
} else if (frag instanceof EncryptDecryptOverviewFragment) {
|
||||||
|
mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_ENCRYPT_DECRYPT), false);
|
||||||
|
} else if (frag instanceof AppsListFragment) {
|
||||||
|
mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_APPS), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,15 +12,22 @@ import android.view.WindowManager;
|
|||||||
|
|
||||||
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.CanonicalizedSecretKeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;
|
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;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
|
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
|
||||||
@ -40,6 +47,8 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
|||||||
private RequiredInputParcel mRequiredInput;
|
private RequiredInputParcel mRequiredInput;
|
||||||
private Intent mServiceIntent;
|
private Intent mServiceIntent;
|
||||||
|
|
||||||
|
private static final byte[] BLANK_FINGERPRINT = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@ -54,8 +63,10 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
|||||||
mServiceIntent = data.getParcelable(EXTRA_SERVICE_INTENT);
|
mServiceIntent = data.getParcelable(EXTRA_SERVICE_INTENT);
|
||||||
|
|
||||||
// obtain passphrase for this subkey
|
// obtain passphrase for this subkey
|
||||||
|
if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_KEYTOCARD) {
|
||||||
obtainYubiKeyPin(mRequiredInput);
|
obtainYubiKeyPin(mRequiredInput);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initLayout() {
|
protected void initLayout() {
|
||||||
@ -85,6 +96,68 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case NFC_KEYTOCARD: {
|
||||||
|
ProviderHelper providerHelper = new ProviderHelper(this);
|
||||||
|
CanonicalizedSecretKeyRing secretKeyRing;
|
||||||
|
try {
|
||||||
|
secretKeyRing = providerHelper.getCanonicalizedSecretKeyRing(
|
||||||
|
KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(mRequiredInput.getMasterKeyId())
|
||||||
|
);
|
||||||
|
} catch (ProviderHelper.NotFoundException e) {
|
||||||
|
throw new IOException("Couldn't find subkey for key to card operation.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) {
|
||||||
|
byte[] subkeyBytes = mRequiredInput.mInputHashes[i];
|
||||||
|
ByteBuffer buf = ByteBuffer.wrap(subkeyBytes);
|
||||||
|
long subkeyId = buf.getLong();
|
||||||
|
|
||||||
|
CanonicalizedSecretKey key = secretKeyRing.getSecretKey(subkeyId);
|
||||||
|
|
||||||
|
long keyGenerationTimestampMillis = key.getCreationTime().getTime();
|
||||||
|
long keyGenerationTimestamp = keyGenerationTimestampMillis / 1000;
|
||||||
|
byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array();
|
||||||
|
byte[] cardSerialNumber = Arrays.copyOf(nfcGetAid(), 16);
|
||||||
|
|
||||||
|
Passphrase passphrase;
|
||||||
|
try {
|
||||||
|
passphrase = PassphraseCacheService.getCachedPassphrase(this,
|
||||||
|
mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId());
|
||||||
|
} catch (PassphraseCacheService.KeyNotFoundException e) {
|
||||||
|
throw new IOException("Unable to get cached passphrase!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key.canSign() || key.canCertify()) {
|
||||||
|
if (shouldPutKey(key.getFingerprint(), 0)) {
|
||||||
|
nfcPutKey(0xB6, key, passphrase);
|
||||||
|
nfcPutData(0xCE, timestampBytes);
|
||||||
|
nfcPutData(0xC7, key.getFingerprint());
|
||||||
|
} else {
|
||||||
|
throw new IOException("Key slot occupied; card must be reset to put new signature key.");
|
||||||
|
}
|
||||||
|
} else if (key.canEncrypt()) {
|
||||||
|
if (shouldPutKey(key.getFingerprint(), 1)) {
|
||||||
|
nfcPutKey(0xB8, key, passphrase);
|
||||||
|
nfcPutData(0xCF, timestampBytes);
|
||||||
|
nfcPutData(0xC8, key.getFingerprint());
|
||||||
|
} else {
|
||||||
|
throw new IOException("Key slot occupied; card must be reset to put new decryption key.");
|
||||||
|
}
|
||||||
|
} else if (key.canAuthenticate()) {
|
||||||
|
if (shouldPutKey(key.getFingerprint(), 2)) {
|
||||||
|
nfcPutKey(0xA4, key, passphrase);
|
||||||
|
nfcPutData(0xD0, timestampBytes);
|
||||||
|
nfcPutData(0xC9, key.getFingerprint());
|
||||||
|
} else {
|
||||||
|
throw new IOException("Key slot occupied; card must be reset to put new authentication key.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IOException("Inappropriate key flags for smart card key.");
|
||||||
|
}
|
||||||
|
|
||||||
|
inputParcel.addCryptoData(subkeyBytes, cardSerialNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mServiceIntent != null) {
|
if (mServiceIntent != null) {
|
||||||
@ -99,6 +172,18 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException {
|
||||||
|
byte[] cardFingerprint = nfcGetFingerprint(idx);
|
||||||
|
// Slot is empty, or contains this key already. PUT KEY operation is safe
|
||||||
|
if (Arrays.equals(cardFingerprint, BLANK_FINGERPRINT) ||
|
||||||
|
Arrays.equals(cardFingerprint, fingerprint)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slot already contains a different key; don't overwrite it.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handlePinError() {
|
public void handlePinError() {
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
@ -43,15 +44,15 @@ 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.DialogFragmentWorkaround;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||||
|
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.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||||
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.remote.CryptoInputParcelCacheService;
|
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;
|
||||||
@ -73,8 +74,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
|
|
||||||
// special extra for OpenPgpService
|
// special extra for OpenPgpService
|
||||||
public static final String EXTRA_SERVICE_INTENT = "data";
|
public static final String EXTRA_SERVICE_INTENT = "data";
|
||||||
|
private long mSubKeyId;
|
||||||
private static final int REQUEST_CODE_ENTER_PATTERN = 2;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@ -91,18 +91,40 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
|
|
||||||
// this activity itself has no content view (see manifest)
|
// this activity itself has no content view (see manifest)
|
||||||
|
|
||||||
long keyId;
|
|
||||||
if (getIntent().hasExtra(EXTRA_SUBKEY_ID)) {
|
if (getIntent().hasExtra(EXTRA_SUBKEY_ID)) {
|
||||||
keyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
|
mSubKeyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
|
||||||
} else {
|
} else {
|
||||||
RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT);
|
RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT);
|
||||||
switch (requiredInput.mType) {
|
switch (requiredInput.mType) {
|
||||||
case PASSPHRASE_SYMMETRIC: {
|
case PASSPHRASE_SYMMETRIC: {
|
||||||
keyId = Constants.key.symmetric;
|
mSubKeyId = Constants.key.symmetric;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PASSPHRASE: {
|
case PASSPHRASE: {
|
||||||
keyId = requiredInput.getSubKeyId();
|
|
||||||
|
// handle empty passphrases by directly returning an empty crypto input parcel
|
||||||
|
try {
|
||||||
|
CanonicalizedSecretKeyRing pubRing =
|
||||||
|
new ProviderHelper(this).getCanonicalizedSecretKeyRing(
|
||||||
|
requiredInput.getMasterKeyId());
|
||||||
|
// use empty passphrase for empty passphrase
|
||||||
|
if (pubRing.getSecretKey(requiredInput.getSubKeyId()).getSecretKeyType() ==
|
||||||
|
SecretKeyType.PASSPHRASE_EMPTY) {
|
||||||
|
// also return passphrase back to activity
|
||||||
|
Intent returnIntent = new Intent();
|
||||||
|
returnIntent.putExtra(RESULT_CRYPTO_INPUT, new CryptoInputParcel(new Passphrase("")));
|
||||||
|
setResult(RESULT_OK, returnIntent);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (NotFoundException e) {
|
||||||
|
Log.e(Constants.TAG, "Key not found?!", e);
|
||||||
|
setResult(RESULT_CANCELED);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mSubKeyId = requiredInput.getSubKeyId();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@ -111,64 +133,35 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_SERVICE_INTENT);
|
|
||||||
|
|
||||||
show(this, keyId, serviceIntent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onResume() {
|
||||||
switch (requestCode) {
|
super.onResume();
|
||||||
case REQUEST_CODE_ENTER_PATTERN: {
|
|
||||||
/*
|
|
||||||
* NOTE that there are 4 possible result codes!!!
|
|
||||||
*/
|
|
||||||
switch (resultCode) {
|
|
||||||
case RESULT_OK:
|
|
||||||
// The user passed
|
|
||||||
break;
|
|
||||||
case RESULT_CANCELED:
|
|
||||||
// The user cancelled the task
|
|
||||||
break;
|
|
||||||
// case LockPatternActivity.RESULT_FAILED:
|
|
||||||
// // The user failed to enter the pattern
|
|
||||||
// break;
|
|
||||||
// case LockPatternActivity.RESULT_FORGOT_PATTERN:
|
|
||||||
// // The user forgot the pattern and invoked your recovery Activity.
|
|
||||||
// break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/* Show passphrase dialog to cache a new passphrase the user enters for using it later for
|
||||||
* In any case, there's always a key EXTRA_RETRY_COUNT, which holds
|
|
||||||
* the number of tries that the user did.
|
|
||||||
*/
|
|
||||||
// int retryCount = data.getIntExtra(
|
|
||||||
// LockPatternActivity.EXTRA_RETRY_COUNT, 0);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows passphrase dialog to cache a new passphrase the user enters for using it later for
|
|
||||||
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
|
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
|
||||||
* for a symmetric passphrase
|
* for a symmetric passphrase
|
||||||
*/
|
*/
|
||||||
public static void show(final FragmentActivity context, final long keyId, final Intent serviceIntent) {
|
|
||||||
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
|
Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_SERVICE_INTENT);
|
||||||
public void run() {
|
|
||||||
// do NOT check if the key even needs a passphrase. that's not our job here.
|
|
||||||
PassphraseDialogFragment frag = new PassphraseDialogFragment();
|
PassphraseDialogFragment frag = new PassphraseDialogFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putLong(EXTRA_SUBKEY_ID, keyId);
|
args.putLong(EXTRA_SUBKEY_ID, mSubKeyId);
|
||||||
args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent);
|
args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent);
|
||||||
|
|
||||||
frag.setArguments(args);
|
frag.setArguments(args);
|
||||||
|
frag.show(getSupportFragmentManager(), "passphraseDialog");
|
||||||
frag.show(context.getSupportFragmentManager(), "passphraseDialog");
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
|
||||||
|
DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag("passphraseDialog");
|
||||||
|
if (dialog != null) {
|
||||||
|
dialog.dismiss();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener {
|
public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener {
|
||||||
@ -182,9 +175,6 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
|
|
||||||
private Intent mServiceIntent;
|
private Intent mServiceIntent;
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates dialog
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
@ -245,12 +235,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
userId = null;
|
userId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get key type for message */
|
keyType = mSecretRing.getSecretKey(mSubKeyId).getSecretKeyType();
|
||||||
// find a master key id for our key
|
|
||||||
long masterKeyId = new ProviderHelper(activity).getMasterKeyId(mSubKeyId);
|
|
||||||
CachedPublicKeyRing keyRing = new ProviderHelper(activity).getCachedPublicKeyRing(masterKeyId);
|
|
||||||
// get the type of key (from the database)
|
|
||||||
keyType = keyRing.getSecretKeyType(mSubKeyId);
|
|
||||||
switch (keyType) {
|
switch (keyType) {
|
||||||
case PASSPHRASE:
|
case PASSPHRASE:
|
||||||
message = getString(R.string.passphrase_for, userId);
|
message = getString(R.string.passphrase_for, userId);
|
||||||
@ -445,20 +430,16 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
|
|
||||||
// note we need no synchronization here, this variable is only accessed in the ui thread
|
// note we need no synchronization here, this variable is only accessed in the ui thread
|
||||||
mIsCancelled = true;
|
mIsCancelled = true;
|
||||||
|
|
||||||
|
getActivity().setResult(RESULT_CANCELED);
|
||||||
|
getActivity().finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDismiss(DialogInterface dialog) {
|
public void onDismiss(DialogInterface dialog) {
|
||||||
super.onDismiss(dialog);
|
super.onDismiss(dialog);
|
||||||
|
|
||||||
if (getActivity() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hideKeyboard();
|
hideKeyboard();
|
||||||
|
|
||||||
getActivity().setResult(RESULT_CANCELED);
|
|
||||||
getActivity().finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hideKeyboard() {
|
private void hideKeyboard() {
|
||||||
@ -472,11 +453,9 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Associate the "done" button on the soft keyboard with the okay button in the view
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
|
// Associate the "done" button on the soft keyboard with the okay button in the view
|
||||||
if (EditorInfo.IME_ACTION_DONE == actionId) {
|
if (EditorInfo.IME_ACTION_DONE == actionId) {
|
||||||
AlertDialog dialog = ((AlertDialog) getDialog());
|
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||||
Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||||
|
@ -38,10 +38,9 @@ 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.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
||||||
@ -124,13 +123,9 @@ public class SafeSlingerActivity extends BaseActivity {
|
|||||||
|
|
||||||
final FragmentActivity activity = SafeSlingerActivity.this;
|
final FragmentActivity activity = SafeSlingerActivity.this;
|
||||||
|
|
||||||
// Message is received after importing is done in KeychainIntentService
|
// Message is received after importing is done in KeychainService
|
||||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
ServiceProgressHandler saveHandler = new ServiceProgressHandler(activity) {
|
||||||
activity,
|
@Override
|
||||||
getString(R.string.progress_importing),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
true,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -176,9 +171,9 @@ public class SafeSlingerActivity extends BaseActivity {
|
|||||||
Log.d(Constants.TAG, "importKeys started");
|
Log.d(Constants.TAG, "importKeys started");
|
||||||
|
|
||||||
// Send all information needed to service to import key in other thread
|
// Send all information needed to service to import key in other thread
|
||||||
Intent intent = new Intent(activity, KeychainIntentService.class);
|
Intent intent = new Intent(activity, KeychainService.class);
|
||||||
|
|
||||||
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
|
intent.setAction(KeychainService.ACTION_IMPORT_KEYRING);
|
||||||
|
|
||||||
// instead of giving the entries by Intent extra, cache them into a
|
// instead of giving the entries by Intent extra, cache them into a
|
||||||
// file to prevent Java Binder problems on heavy imports
|
// file to prevent Java Binder problems on heavy imports
|
||||||
@ -195,14 +190,17 @@ public class SafeSlingerActivity extends BaseActivity {
|
|||||||
|
|
||||||
// fill values for this action
|
// fill values for this action
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, bundle);
|
intent.putExtra(KeychainService.EXTRA_DATA, bundle);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(saveHandler);
|
Messenger messenger = new Messenger(saveHandler);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
// show progress dialog
|
// show progress dialog
|
||||||
saveHandler.showProgressDialog(activity);
|
saveHandler.showProgressDialog(
|
||||||
|
getString(R.string.progress_importing),
|
||||||
|
ProgressDialog.STYLE_HORIZONTAL, true
|
||||||
|
);
|
||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
activity.startService(intent);
|
activity.startService(intent);
|
||||||
|
@ -35,10 +35,9 @@ import org.sufficientlysecure.keychain.Constants;
|
|||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
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.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
|
|
||||||
@ -92,9 +91,9 @@ public class UploadKeyActivity extends BaseActivity {
|
|||||||
|
|
||||||
private void uploadKey() {
|
private void uploadKey() {
|
||||||
// Send all information needed to service to upload key in other thread
|
// Send all information needed to service to upload key in other thread
|
||||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
Intent intent = new Intent(this, KeychainService.class);
|
||||||
|
|
||||||
intent.setAction(KeychainIntentService.ACTION_UPLOAD_KEYRING);
|
intent.setAction(KeychainService.ACTION_UPLOAD_KEYRING);
|
||||||
|
|
||||||
// set data uri as path to keyring
|
// set data uri as path to keyring
|
||||||
Uri blobUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
Uri blobUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
||||||
@ -104,16 +103,13 @@ public class UploadKeyActivity extends BaseActivity {
|
|||||||
Bundle data = new Bundle();
|
Bundle data = new Bundle();
|
||||||
|
|
||||||
String server = (String) mKeyServerSpinner.getSelectedItem();
|
String server = (String) mKeyServerSpinner.getSelectedItem();
|
||||||
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, server);
|
data.putString(KeychainService.UPLOAD_KEY_SERVER, server);
|
||||||
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
intent.putExtra(KeychainService.EXTRA_DATA, data);
|
||||||
|
|
||||||
// Message is received after uploading is done in KeychainIntentService
|
// Message is received after uploading is done in KeychainService
|
||||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
ServiceProgressHandler saveHandler = new ServiceProgressHandler(this) {
|
||||||
this,
|
@Override
|
||||||
getString(R.string.progress_uploading),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -129,10 +125,12 @@ public class UploadKeyActivity extends BaseActivity {
|
|||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(saveHandler);
|
Messenger messenger = new Messenger(saveHandler);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
// show progress dialog
|
// show progress dialog
|
||||||
saveHandler.showProgressDialog(this);
|
saveHandler.showProgressDialog(
|
||||||
|
getString(R.string.progress_uploading),
|
||||||
|
ProgressDialog.STYLE_HORIZONTAL, false);
|
||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
startService(intent);
|
startService(intent);
|
||||||
|
@ -65,7 +65,7 @@ import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
|||||||
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.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
|
||||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||||
@ -402,8 +402,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void startCertifyIntent(Intent intent) {
|
private void startCertifyIntent(Intent intent) {
|
||||||
// Message is received after signing is done in KeychainIntentService
|
// Message is received after signing is done in KeychainService
|
||||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(this) {
|
ServiceProgressHandler saveHandler = new ServiceProgressHandler(this) {
|
||||||
|
@Override
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -418,7 +419,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
};
|
};
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(saveHandler);
|
Messenger messenger = new Messenger(saveHandler);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
startActivityForResult(intent, 0);
|
startActivityForResult(intent, 0);
|
||||||
}
|
}
|
||||||
@ -651,8 +652,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
ArrayList<ParcelableKeyRing> entries = new ArrayList<>();
|
ArrayList<ParcelableKeyRing> entries = new ArrayList<>();
|
||||||
entries.add(keyEntry);
|
entries.add(keyEntry);
|
||||||
|
|
||||||
// Message is received after importing is done in KeychainIntentService
|
// Message is received after importing is done in KeychainService
|
||||||
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(this) {
|
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(this) {
|
||||||
|
@Override
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -682,22 +684,19 @@ public class ViewKeyActivity extends BaseNfcActivity implements
|
|||||||
Preferences prefs = Preferences.getPreferences(this);
|
Preferences prefs = Preferences.getPreferences(this);
|
||||||
Preferences.CloudSearchPrefs cloudPrefs =
|
Preferences.CloudSearchPrefs cloudPrefs =
|
||||||
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
|
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
|
||||||
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
data.putString(KeychainService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
||||||
}
|
}
|
||||||
|
|
||||||
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, entries);
|
data.putParcelableArrayList(KeychainService.IMPORT_KEY_LIST, entries);
|
||||||
|
|
||||||
// Send all information needed to service to query keys in other thread
|
// Send all information needed to service to query keys in other thread
|
||||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
Intent intent = new Intent(this, KeychainService.class);
|
||||||
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
|
intent.setAction(KeychainService.ACTION_IMPORT_KEYRING);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
intent.putExtra(KeychainService.EXTRA_DATA, data);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(serviceHandler);
|
Messenger messenger = new Messenger(serviceHandler);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
// show progress dialog
|
|
||||||
serviceHandler.showProgressDialog(this);
|
|
||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
startService(intent);
|
startService(intent);
|
||||||
|
@ -140,12 +140,17 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
mKeyNfcButton.setVisibility(View.VISIBLE);
|
||||||
mKeyNfcButton.setOnClickListener(new View.OnClickListener() {
|
mKeyNfcButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
mNfcHelper.invokeNfcBeam();
|
mNfcHelper.invokeNfcBeam();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
mKeyNfcButton.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
mKeySafeSlingerButton.setOnClickListener(new View.OnClickListener() {
|
mKeySafeSlingerButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -300,7 +300,7 @@ public class ViewKeyFragment extends LoaderFragment implements
|
|||||||
* because the notification triggers faster than the activity closes.
|
* because the notification triggers faster than the activity closes.
|
||||||
*/
|
*/
|
||||||
// Avoid NullPointerExceptions...
|
// Avoid NullPointerExceptions...
|
||||||
if (data.getCount() == 0) {
|
if (data == null || data.getCount() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Swap the new cursor in. (The framework will take care of closing the
|
// Swap the new cursor in. (The framework will take care of closing the
|
||||||
|
@ -49,9 +49,8 @@ import com.textuality.keybase.lib.User;
|
|||||||
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.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
|
||||||
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;
|
||||||
|
|
||||||
@ -350,23 +349,19 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void verify(final Proof proof, final String fingerprint) {
|
private void verify(final Proof proof, final String fingerprint) {
|
||||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
Intent intent = new Intent(getActivity(), KeychainService.class);
|
||||||
Bundle data = new Bundle();
|
Bundle data = new Bundle();
|
||||||
intent.setAction(KeychainIntentService.ACTION_VERIFY_KEYBASE_PROOF);
|
intent.setAction(KeychainService.ACTION_VERIFY_KEYBASE_PROOF);
|
||||||
|
|
||||||
data.putString(KeychainIntentService.KEYBASE_PROOF, proof.toString());
|
data.putString(KeychainService.KEYBASE_PROOF, proof.toString());
|
||||||
data.putString(KeychainIntentService.KEYBASE_REQUIRED_FINGERPRINT, fingerprint);
|
data.putString(KeychainService.KEYBASE_REQUIRED_FINGERPRINT, fingerprint);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
intent.putExtra(KeychainService.EXTRA_DATA, data);
|
||||||
|
|
||||||
mProofVerifyDetail.setVisibility(View.GONE);
|
mProofVerifyDetail.setVisibility(View.GONE);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back after proof work is done
|
// Create a new Messenger for the communication back after proof work is done
|
||||||
//
|
ServiceProgressHandler handler = new ServiceProgressHandler(getActivity()) {
|
||||||
ServiceProgressHandler handler = new ServiceProgressHandler(
|
@Override
|
||||||
getActivity(),
|
|
||||||
getString(R.string.progress_verifying_signature),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -451,10 +446,13 @@ public class ViewKeyTrustFragment extends LoaderFragment implements
|
|||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(handler);
|
Messenger messenger = new Messenger(handler);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
// show progress dialog
|
// show progress dialog
|
||||||
handler.showProgressDialog(getActivity());
|
handler.showProgressDialog(
|
||||||
|
getString(R.string.progress_verifying_signature),
|
||||||
|
ProgressDialog.STYLE_HORIZONTAL, false
|
||||||
|
);
|
||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
getActivity().startService(intent);
|
getActivity().startService(intent);
|
||||||
|
@ -43,7 +43,7 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
|||||||
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
|
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
|
|
||||||
@ -129,6 +129,7 @@ public class ViewKeyYubiKeyFragment extends Fragment
|
|||||||
public void promoteToSecretKey() {
|
public void promoteToSecretKey() {
|
||||||
|
|
||||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
|
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
|
||||||
|
@Override
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard KeychainIntentServiceHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -147,25 +148,25 @@ public class ViewKeyYubiKeyFragment extends Fragment
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Send all information needed to service to decrypt in other thread
|
// Send all information needed to service to decrypt in other thread
|
||||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
Intent intent = new Intent(getActivity(), KeychainService.class);
|
||||||
|
|
||||||
// fill values for this action
|
// fill values for this action
|
||||||
|
|
||||||
intent.setAction(KeychainIntentService.ACTION_PROMOTE_KEYRING);
|
intent.setAction(KeychainService.ACTION_PROMOTE_KEYRING);
|
||||||
|
|
||||||
Bundle data = new Bundle();
|
Bundle data = new Bundle();
|
||||||
data.putLong(KeychainIntentService.PROMOTE_MASTER_KEY_ID, mMasterKeyId);
|
data.putLong(KeychainService.PROMOTE_MASTER_KEY_ID, mMasterKeyId);
|
||||||
data.putByteArray(KeychainIntentService.PROMOTE_CARD_AID, mCardAid);
|
data.putByteArray(KeychainService.PROMOTE_CARD_AID, mCardAid);
|
||||||
long[] subKeyIds = new long[mFingerprints.length];
|
long[] subKeyIds = new long[mFingerprints.length];
|
||||||
for (int i = 0; i < subKeyIds.length; i++) {
|
for (int i = 0; i < subKeyIds.length; i++) {
|
||||||
subKeyIds[i] = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints[i]);
|
subKeyIds[i] = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints[i]);
|
||||||
}
|
}
|
||||||
data.putLongArray(KeychainIntentService.PROMOTE_SUBKEY_IDS, subKeyIds);
|
data.putLongArray(KeychainService.PROMOTE_SUBKEY_IDS, subKeyIds);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
intent.putExtra(KeychainService.EXTRA_DATA, data);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(saveHandler);
|
Messenger messenger = new Messenger(saveHandler);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
getActivity().startService(intent);
|
getActivity().startService(intent);
|
||||||
|
@ -109,7 +109,7 @@ public class ImportKeysListCloudLoader
|
|||||||
ImportKeysListEntry uniqueEntry = searchResult.get(0);
|
ImportKeysListEntry uniqueEntry = searchResult.get(0);
|
||||||
/*
|
/*
|
||||||
* set fingerprint explicitly after query
|
* set fingerprint explicitly after query
|
||||||
* to enforce a check when the key is imported by KeychainIntentService
|
* to enforce a check when the key is imported by KeychainService
|
||||||
*/
|
*/
|
||||||
uniqueEntry.setFingerprintHex(fingerprint);
|
uniqueEntry.setFingerprintHex(fingerprint);
|
||||||
uniqueEntry.setSelected(true);
|
uniqueEntry.setSelected(true);
|
||||||
|
@ -18,15 +18,17 @@
|
|||||||
package org.sufficientlysecure.keychain.ui.adapter;
|
package org.sufficientlysecure.keychain.ui.adapter;
|
||||||
|
|
||||||
|
|
||||||
import java.util.Calendar;
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.TimeZone;
|
import java.util.List;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.support.v4.widget.CursorAdapter;
|
import android.support.v4.widget.CursorAdapter;
|
||||||
import android.text.format.DateFormat;
|
|
||||||
import android.text.format.DateUtils;
|
import android.text.format.DateUtils;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -59,7 +61,6 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
KeyRings.VERIFIED,
|
KeyRings.VERIFIED,
|
||||||
KeyRings.HAS_ANY_SECRET,
|
KeyRings.HAS_ANY_SECRET,
|
||||||
KeyRings.HAS_DUPLICATE_USER_ID,
|
KeyRings.HAS_DUPLICATE_USER_ID,
|
||||||
KeyRings.HAS_ENCRYPT,
|
|
||||||
KeyRings.FINGERPRINT,
|
KeyRings.FINGERPRINT,
|
||||||
KeyRings.CREATION,
|
KeyRings.CREATION,
|
||||||
};
|
};
|
||||||
@ -71,9 +72,8 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
public static final int INDEX_VERIFIED = 5;
|
public static final int INDEX_VERIFIED = 5;
|
||||||
public static final int INDEX_HAS_ANY_SECRET = 6;
|
public static final int INDEX_HAS_ANY_SECRET = 6;
|
||||||
public static final int INDEX_HAS_DUPLICATE_USER_ID = 7;
|
public static final int INDEX_HAS_DUPLICATE_USER_ID = 7;
|
||||||
public static final int INDEX_HAS_ENCRYPT = 8;
|
public static final int INDEX_FINGERPRINT = 8;
|
||||||
public static final int INDEX_FINGERPRINT = 9;
|
public static final int INDEX_CREATION = 9;
|
||||||
public static final int INDEX_CREATION = 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);
|
||||||
@ -86,6 +86,7 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class KeyItemViewHolder {
|
public static class KeyItemViewHolder {
|
||||||
|
public View mView;
|
||||||
public Long mMasterKeyId;
|
public Long mMasterKeyId;
|
||||||
public TextView mMainUserId;
|
public TextView mMainUserId;
|
||||||
public TextView mMainUserIdRest;
|
public TextView mMainUserIdRest;
|
||||||
@ -95,6 +96,7 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
public ImageButton mSlingerButton;
|
public ImageButton mSlingerButton;
|
||||||
|
|
||||||
public KeyItemViewHolder(View view) {
|
public KeyItemViewHolder(View view) {
|
||||||
|
mView = view;
|
||||||
mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name);
|
mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name);
|
||||||
mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email);
|
mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email);
|
||||||
mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon);
|
mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon);
|
||||||
@ -103,11 +105,10 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation);
|
mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setData(Context context, Cursor cursor, Highlighter highlighter) {
|
public void setData(Context context, KeyItem item, Highlighter highlighter) {
|
||||||
|
|
||||||
{ // set name and stuff, common to both key types
|
{ // set name and stuff, common to both key types
|
||||||
String userId = cursor.getString(INDEX_USER_ID);
|
KeyRing.UserId userIdSplit = item.mUserId;
|
||||||
KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId);
|
|
||||||
if (userIdSplit.name != null) {
|
if (userIdSplit.name != null) {
|
||||||
mMainUserId.setText(highlighter.highlight(userIdSplit.name));
|
mMainUserId.setText(highlighter.highlight(userIdSplit.name));
|
||||||
} else {
|
} else {
|
||||||
@ -123,30 +124,23 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
{ // set edit button and status, specific by key type
|
{ // set edit button and status, specific by key type
|
||||||
|
|
||||||
long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
|
mMasterKeyId = item.mKeyId;
|
||||||
boolean isSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
|
||||||
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
|
||||||
boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0;
|
|
||||||
boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0;
|
|
||||||
boolean hasDuplicate = cursor.getInt(INDEX_HAS_DUPLICATE_USER_ID) != 0;
|
|
||||||
|
|
||||||
mMasterKeyId = masterKeyId;
|
|
||||||
|
|
||||||
// Note: order is important!
|
// Note: order is important!
|
||||||
if (isRevoked) {
|
if (item.mIsRevoked) {
|
||||||
KeyFormattingUtils
|
KeyFormattingUtils
|
||||||
.setStatusImage(context, mStatus, null, State.REVOKED, R.color.bg_gray);
|
.setStatusImage(context, mStatus, null, State.REVOKED, R.color.bg_gray);
|
||||||
mStatus.setVisibility(View.VISIBLE);
|
mStatus.setVisibility(View.VISIBLE);
|
||||||
mSlinger.setVisibility(View.GONE);
|
mSlinger.setVisibility(View.GONE);
|
||||||
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
||||||
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
||||||
} else if (isExpired) {
|
} else if (item.mIsExpired) {
|
||||||
KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.bg_gray);
|
KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.bg_gray);
|
||||||
mStatus.setVisibility(View.VISIBLE);
|
mStatus.setVisibility(View.VISIBLE);
|
||||||
mSlinger.setVisibility(View.GONE);
|
mSlinger.setVisibility(View.GONE);
|
||||||
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
||||||
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
||||||
} else if (isSecret) {
|
} else if (item.mIsSecret) {
|
||||||
mStatus.setVisibility(View.GONE);
|
mStatus.setVisibility(View.GONE);
|
||||||
if (mSlingerButton.hasOnClickListeners()) {
|
if (mSlingerButton.hasOnClickListeners()) {
|
||||||
mSlinger.setVisibility(View.VISIBLE);
|
mSlinger.setVisibility(View.VISIBLE);
|
||||||
@ -157,7 +151,7 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
|
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
|
||||||
} else {
|
} else {
|
||||||
// this is a public key - show if it's verified
|
// this is a public key - show if it's verified
|
||||||
if (isVerified) {
|
if (item.mIsVerified) {
|
||||||
KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED);
|
KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED);
|
||||||
mStatus.setVisibility(View.VISIBLE);
|
mStatus.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
@ -169,9 +163,9 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
|
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasDuplicate) {
|
if (item.mHasDuplicate) {
|
||||||
String dateTime = DateUtils.formatDateTime(context,
|
String dateTime = DateUtils.formatDateTime(context,
|
||||||
cursor.getLong(INDEX_CREATION) * 1000,
|
item.mCreation.getTime(),
|
||||||
DateUtils.FORMAT_SHOW_DATE
|
DateUtils.FORMAT_SHOW_DATE
|
||||||
| DateUtils.FORMAT_SHOW_YEAR
|
| DateUtils.FORMAT_SHOW_YEAR
|
||||||
| DateUtils.FORMAT_ABBREV_MONTH);
|
| DateUtils.FORMAT_ABBREV_MONTH);
|
||||||
@ -189,6 +183,10 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled(Cursor cursor) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||||
View view = mInflater.inflate(R.layout.key_list_item, parent, false);
|
View view = mInflater.inflate(R.layout.key_list_item, parent, false);
|
||||||
@ -203,7 +201,8 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
Highlighter highlighter = new Highlighter(context, mQuery);
|
Highlighter highlighter = new Highlighter(context, mQuery);
|
||||||
KeyItemViewHolder h = (KeyItemViewHolder) view.getTag();
|
KeyItemViewHolder h = (KeyItemViewHolder) view.getTag();
|
||||||
h.setData(context, cursor, highlighter);
|
KeyItem item = new KeyItem(cursor);
|
||||||
|
h.setData(context, item, highlighter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSecretAvailable(int id) {
|
public boolean isSecretAvailable(int id) {
|
||||||
@ -233,14 +232,16 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getItemId(int position) {
|
public long getItemId(int position) {
|
||||||
|
Cursor cursor = getCursor();
|
||||||
// prevent a crash on rapid cursor changes
|
// prevent a crash on rapid cursor changes
|
||||||
if (getCursor().isClosed()) {
|
if (cursor != null && getCursor().isClosed()) {
|
||||||
return 0L;
|
return 0L;
|
||||||
}
|
}
|
||||||
return super.getItemId(position);
|
return super.getItemId(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class KeyItem {
|
// must be serializable for TokenCompleTextView state
|
||||||
|
public static class KeyItem implements Serializable {
|
||||||
|
|
||||||
public final String mUserIdFull;
|
public final String mUserIdFull;
|
||||||
public final KeyRing.UserId mUserId;
|
public final KeyRing.UserId mUserId;
|
||||||
@ -248,6 +249,7 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
public final boolean mHasDuplicate;
|
public final boolean mHasDuplicate;
|
||||||
public final Date mCreation;
|
public final Date mCreation;
|
||||||
public final String mFingerprint;
|
public final String mFingerprint;
|
||||||
|
public final boolean mIsSecret, mIsRevoked, mIsExpired, mIsVerified;
|
||||||
|
|
||||||
private KeyItem(Cursor cursor) {
|
private KeyItem(Cursor cursor) {
|
||||||
String userId = cursor.getString(INDEX_USER_ID);
|
String userId = cursor.getString(INDEX_USER_ID);
|
||||||
@ -258,6 +260,10 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
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));
|
||||||
|
mIsSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||||
|
mIsRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
||||||
|
mIsExpired = cursor.getInt(INDEX_IS_EXPIRED) > 0;
|
||||||
|
mIsVerified = cursor.getInt(INDEX_VERIFIED) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyItem(CanonicalizedPublicKeyRing ring) {
|
public KeyItem(CanonicalizedPublicKeyRing ring) {
|
||||||
@ -270,6 +276,12 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
mCreation = key.getCreationTime();
|
mCreation = key.getCreationTime();
|
||||||
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
|
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
|
||||||
ring.getFingerprint());
|
ring.getFingerprint());
|
||||||
|
mIsRevoked = key.isRevoked();
|
||||||
|
mIsExpired = key.isExpired();
|
||||||
|
|
||||||
|
// these two are actually "don't know"s
|
||||||
|
mIsSecret = false;
|
||||||
|
mIsVerified = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getReadableName() {
|
public String getReadableName() {
|
||||||
@ -282,4 +294,11 @@ public class KeyAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String[] getProjectionWith(String[] projection) {
|
||||||
|
List<String> list = new ArrayList<>();
|
||||||
|
list.addAll(Arrays.asList(PROJECTION));
|
||||||
|
list.addAll(Arrays.asList(projection));
|
||||||
|
return list.toArray(new String[list.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -40,20 +40,19 @@ public class MultiUserIdsAdapter extends CursorAdapter {
|
|||||||
private LayoutInflater mInflater;
|
private LayoutInflater mInflater;
|
||||||
private final ArrayList<Boolean> mCheckStates;
|
private final ArrayList<Boolean> mCheckStates;
|
||||||
|
|
||||||
public MultiUserIdsAdapter(Context context, Cursor c, int flags) {
|
public MultiUserIdsAdapter(Context context, Cursor c, int flags, ArrayList<Boolean> preselectStates) {
|
||||||
super(context, c, flags);
|
super(context, c, flags);
|
||||||
mInflater = LayoutInflater.from(context);
|
mInflater = LayoutInflater.from(context);
|
||||||
mCheckStates = new ArrayList<>();
|
mCheckStates = preselectStates == null ? new ArrayList<Boolean>() : preselectStates;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor swapCursor(Cursor newCursor) {
|
public Cursor swapCursor(Cursor newCursor) {
|
||||||
mCheckStates.clear();
|
|
||||||
if (newCursor != null) {
|
if (newCursor != null) {
|
||||||
int count = newCursor.getCount();
|
int count = newCursor.getCount();
|
||||||
mCheckStates.ensureCapacity(count);
|
mCheckStates.ensureCapacity(count);
|
||||||
// initialize to true (use case knowledge: we usually want to sign all uids)
|
// initialize new fields to true (use case knowledge: we usually want to sign all uids)
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = mCheckStates.size(); i < count; i++) {
|
||||||
mCheckStates.add(true);
|
mCheckStates.add(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,6 +150,10 @@ public class MultiUserIdsAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ArrayList<Boolean> getCheckStates() {
|
||||||
|
return mCheckStates;
|
||||||
|
}
|
||||||
|
|
||||||
public ArrayList<CertifyAction> getSelectedCertifyActions() {
|
public ArrayList<CertifyAction> getSelectedCertifyActions() {
|
||||||
LongSparseArray<CertifyAction> actions = new LongSparseArray<>();
|
LongSparseArray<CertifyAction> actions = new LongSparseArray<>();
|
||||||
for (int i = 0; i < mCheckStates.size(); i++) {
|
for (int i = 0; i < mCheckStates.size(); i++) {
|
||||||
|
@ -116,6 +116,21 @@ public class SubkeysAdapter extends CursorAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getAlgorithm(int position) {
|
||||||
|
mCursor.moveToPosition(position);
|
||||||
|
return mCursor.getInt(INDEX_ALGORITHM);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getKeySize(int position) {
|
||||||
|
mCursor.moveToPosition(position);
|
||||||
|
return mCursor.getInt(INDEX_KEY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecretKeyType getSecretKeyType(int position) {
|
||||||
|
mCursor.moveToPosition(position);
|
||||||
|
return SecretKeyType.fromNum(mCursor.getInt(INDEX_HAS_SECRET));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor swapCursor(Cursor newCursor) {
|
public Cursor swapCursor(Cursor newCursor) {
|
||||||
hasAnySecret = false;
|
hasAnySecret = false;
|
||||||
@ -164,13 +179,23 @@ public class SubkeysAdapter extends CursorAdapter {
|
|||||||
? mSaveKeyringParcel.getSubkeyChange(keyId)
|
? mSaveKeyringParcel.getSubkeyChange(keyId)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (change != null && change.mDummyStrip) {
|
if (change != null && (change.mDummyStrip || change.mMoveKeyToCard)) {
|
||||||
|
if (change.mDummyStrip) {
|
||||||
algorithmStr.append(", ");
|
algorithmStr.append(", ");
|
||||||
final SpannableString boldStripped = new SpannableString(
|
final SpannableString boldStripped = new SpannableString(
|
||||||
context.getString(R.string.key_stripped)
|
context.getString(R.string.key_stripped)
|
||||||
);
|
);
|
||||||
boldStripped.setSpan(new StyleSpan(Typeface.BOLD), 0, boldStripped.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
boldStripped.setSpan(new StyleSpan(Typeface.BOLD), 0, boldStripped.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
algorithmStr.append(boldStripped);
|
algorithmStr.append(boldStripped);
|
||||||
|
}
|
||||||
|
if (change.mMoveKeyToCard) {
|
||||||
|
algorithmStr.append(", ");
|
||||||
|
final SpannableString boldDivert = new SpannableString(
|
||||||
|
context.getString(R.string.key_divert)
|
||||||
|
);
|
||||||
|
boldDivert.setSpan(new StyleSpan(Typeface.BOLD), 0, boldDivert.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
algorithmStr.append(boldDivert);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (SecretKeyType.fromNum(cursor.getInt(INDEX_HAS_SECRET))) {
|
switch (SecretKeyType.fromNum(cursor.getInt(INDEX_HAS_SECRET))) {
|
||||||
case GNU_DUMMY:
|
case GNU_DUMMY:
|
||||||
|
@ -19,7 +19,9 @@
|
|||||||
package org.sufficientlysecure.keychain.ui.base;
|
package org.sufficientlysecure.keychain.ui.base;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.interfaces.RSAPrivateCrtKey;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
@ -32,9 +34,12 @@ import android.os.Bundle;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||||
|
import org.spongycastle.util.Arrays;
|
||||||
import org.spongycastle.util.encoders.Hex;
|
import org.spongycastle.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.pgp.CanonicalizedSecretKey;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
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;
|
||||||
@ -56,12 +61,14 @@ import org.sufficientlysecure.keychain.util.Preferences;
|
|||||||
|
|
||||||
public abstract class BaseNfcActivity extends BaseActivity {
|
public abstract class BaseNfcActivity extends BaseActivity {
|
||||||
|
|
||||||
public static final int REQUEST_CODE_PASSPHRASE = 1;
|
public static final int REQUEST_CODE_PIN = 1;
|
||||||
|
|
||||||
protected Passphrase mPin;
|
protected Passphrase mPin;
|
||||||
|
protected Passphrase mAdminPin;
|
||||||
protected boolean mPw1ValidForMultipleSignatures;
|
protected boolean mPw1ValidForMultipleSignatures;
|
||||||
protected boolean mPw1ValidatedForSignature;
|
protected boolean mPw1ValidatedForSignature;
|
||||||
protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming?
|
protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming?
|
||||||
|
protected boolean mPw3Validated;
|
||||||
private NfcAdapter mNfcAdapter;
|
private NfcAdapter mNfcAdapter;
|
||||||
private IsoDep mIsoDep;
|
private IsoDep mIsoDep;
|
||||||
|
|
||||||
@ -88,6 +95,8 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
|||||||
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
|
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
|
||||||
try {
|
try {
|
||||||
handleNdefDiscoveredIntent(intent);
|
handleNdefDiscoveredIntent(intent);
|
||||||
|
} catch (CardException e) {
|
||||||
|
handleNfcError(e);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
handleNfcError(e);
|
handleNfcError(e);
|
||||||
}
|
}
|
||||||
@ -98,6 +107,81 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
|||||||
|
|
||||||
Log.e(Constants.TAG, "nfc error", e);
|
Log.e(Constants.TAG, "nfc error", e);
|
||||||
Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show();
|
Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleNfcError(CardException e) {
|
||||||
|
Log.e(Constants.TAG, "card error", e);
|
||||||
|
|
||||||
|
short status = e.getResponseCode();
|
||||||
|
// When entering a PIN, a status of 63CX indicates X attempts remaining.
|
||||||
|
if ((status & (short)0xFFF0) == 0x63C0) {
|
||||||
|
Notify.create(this, getString(R.string.error_pin, status & 0x000F), Style.WARN).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, all status codes are fixed values.
|
||||||
|
switch (status) {
|
||||||
|
// These errors should not occur in everyday use; if they are returned, it means we
|
||||||
|
// made a mistake sending data to the card, or the card is misbehaving.
|
||||||
|
case 0x6A80: {
|
||||||
|
Notify.create(this, getString(R.string.error_nfc_bad_data), Style.WARN).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x6883: {
|
||||||
|
Notify.create(this, getString(R.string.error_nfc_chaining_error), Style.WARN).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x6B00: {
|
||||||
|
Notify.create(this, getString(R.string.error_nfc_header, "P1/P2"), Style.WARN).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x6D00: {
|
||||||
|
Notify.create(this, getString(R.string.error_nfc_header, "INS"), Style.WARN).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x6E00: {
|
||||||
|
Notify.create(this, getString(R.string.error_nfc_header, "CLA"), Style.WARN).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// These error conditions are more likely to be experienced by an end user.
|
||||||
|
case 0x6285: {
|
||||||
|
Notify.create(this, getString(R.string.error_nfc_terminated), Style.WARN).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x6700: {
|
||||||
|
Notify.create(this, getString(R.string.error_nfc_wrong_length), Style.WARN).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x6982: {
|
||||||
|
Notify.create(this, getString(R.string.error_nfc_security_not_satisfied),
|
||||||
|
Style.WARN).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x6983: {
|
||||||
|
Notify.create(this, getString(R.string.error_nfc_authentication_blocked),
|
||||||
|
Style.WARN).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x6985: {
|
||||||
|
Notify.create(this, getString(R.string.error_nfc_conditions_not_satisfied),
|
||||||
|
Style.WARN).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases.
|
||||||
|
case 0x6A88:
|
||||||
|
case 0x6A83: {
|
||||||
|
Notify.create(this, getString(R.string.error_nfc_data_not_found), Style.WARN).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an
|
||||||
|
// unhandled exception on the smart card.
|
||||||
|
case 0x6F00: {
|
||||||
|
Notify.create(this, getString(R.string.error_nfc_unknown), Style.WARN).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +233,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
|||||||
Intent intent = new Intent(this, PassphraseDialogActivity.class);
|
Intent intent = new Intent(this, PassphraseDialogActivity.class);
|
||||||
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT,
|
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT,
|
||||||
RequiredInputParcel.createRequiredPassphrase(requiredInput));
|
RequiredInputParcel.createRequiredPassphrase(requiredInput));
|
||||||
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
|
startActivityForResult(intent, REQUEST_CODE_PIN);
|
||||||
} catch (KeyNotFoundException e) {
|
} catch (KeyNotFoundException e) {
|
||||||
throw new AssertionError(
|
throw new AssertionError(
|
||||||
"tried to find passphrase for non-existing key. this is a programming error!");
|
"tried to find passphrase for non-existing key. this is a programming error!");
|
||||||
@ -164,7 +248,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case REQUEST_CODE_PASSPHRASE:
|
case REQUEST_CODE_PIN: {
|
||||||
if (resultCode != Activity.RESULT_OK) {
|
if (resultCode != Activity.RESULT_OK) {
|
||||||
setResult(resultCode);
|
setResult(resultCode);
|
||||||
finish();
|
finish();
|
||||||
@ -173,6 +257,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
|||||||
CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
|
CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
|
||||||
mPin = input.getPassphrase();
|
mPin = input.getPassphrase();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
@ -215,14 +300,19 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
|||||||
+ "06" // Lc (number of bytes)
|
+ "06" // Lc (number of bytes)
|
||||||
+ "D27600012401" // Data (6 bytes)
|
+ "D27600012401" // Data (6 bytes)
|
||||||
+ "00"; // Le
|
+ "00"; // Le
|
||||||
if ( ! nfcCommunicate(opening).endsWith(accepted)) { // activate connection
|
String response = nfcCommunicate(opening); // activate connection
|
||||||
throw new IOException("Initialization failed!");
|
if ( ! response.endsWith(accepted) ) {
|
||||||
|
throw new CardException("Initialization failed!", parseCardStatus(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] pwStatusBytes = nfcGetPwStatusBytes();
|
byte[] pwStatusBytes = nfcGetPwStatusBytes();
|
||||||
mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1);
|
mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1);
|
||||||
mPw1ValidatedForSignature = false;
|
mPw1ValidatedForSignature = false;
|
||||||
mPw1ValidatedForDecrypt = false;
|
mPw1ValidatedForDecrypt = false;
|
||||||
|
mPw3Validated = false;
|
||||||
|
|
||||||
|
// TODO: Handle non-default Admin PIN
|
||||||
|
mAdminPin = new Passphrase("12345678");
|
||||||
|
|
||||||
onNfcPerform();
|
onNfcPerform();
|
||||||
|
|
||||||
@ -427,7 +517,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( ! "9000".equals(status)) {
|
if ( ! "9000".equals(status)) {
|
||||||
throw new IOException("Bad NFC response code: " + status);
|
throw new CardException("Bad NFC response code: " + status, parseCardStatus(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the signature we received is actually the expected number of bytes long!
|
// Make sure the signature we received is actually the expected number of bytes long!
|
||||||
@ -472,13 +562,21 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
|||||||
return Hex.decode(decryptedSessionKey);
|
return Hex.decode(decryptedSessionKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Verifies the user's PW1 with the appropriate mode.
|
/** Verifies the user's PW1 or PW3 with the appropriate mode.
|
||||||
*
|
*
|
||||||
* @param mode This is 0x81 for signing, 0x82 for everything else
|
* @param mode For PW1, this is 0x81 for signing, 0x82 for everything else.
|
||||||
|
* For PW3 (Admin PIN), mode is 0x83.
|
||||||
*/
|
*/
|
||||||
public void nfcVerifyPIN(int mode) throws IOException {
|
public void nfcVerifyPIN(int mode) throws IOException {
|
||||||
if (mPin != null) {
|
if (mPin != null || mode == 0x83) {
|
||||||
byte[] pin = new String(mPin.getCharArray()).getBytes();
|
byte[] pin;
|
||||||
|
|
||||||
|
if (mode == 0x83) {
|
||||||
|
pin = new String(mAdminPin.getCharArray()).getBytes();
|
||||||
|
} else {
|
||||||
|
pin = new String(mPin.getCharArray()).getBytes();
|
||||||
|
}
|
||||||
|
|
||||||
// SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
|
// SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
|
||||||
// See specification, page 51
|
// See specification, page 51
|
||||||
String accepted = "9000";
|
String accepted = "9000";
|
||||||
@ -491,19 +589,233 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
|||||||
+ String.format("%02x", mode) // P2
|
+ String.format("%02x", mode) // P2
|
||||||
+ String.format("%02x", pin.length) // Lc
|
+ String.format("%02x", pin.length) // Lc
|
||||||
+ Hex.toHexString(pin);
|
+ Hex.toHexString(pin);
|
||||||
if (!nfcCommunicate(login).equals(accepted)) { // login
|
String response = nfcCommunicate(login); // login
|
||||||
|
if (!response.equals(accepted)) {
|
||||||
handlePinError();
|
handlePinError();
|
||||||
throw new IOException("Bad PIN!");
|
throw new CardException("Bad PIN!", parseCardStatus(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode == 0x81) {
|
if (mode == 0x81) {
|
||||||
mPw1ValidatedForSignature = true;
|
mPw1ValidatedForSignature = true;
|
||||||
} else if (mode == 0x82) {
|
} else if (mode == 0x82) {
|
||||||
mPw1ValidatedForDecrypt = true;
|
mPw1ValidatedForDecrypt = true;
|
||||||
|
} else if (mode == 0x83) {
|
||||||
|
mPw3Validated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for
|
||||||
|
* conformance to the card's requirements for key length.
|
||||||
|
*
|
||||||
|
* @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83.
|
||||||
|
* @param newPinString The new PW1 or PW3.
|
||||||
|
*/
|
||||||
|
public void nfcModifyPIN(int pw, String newPinString) throws IOException {
|
||||||
|
final int MAX_PW1_LENGTH_INDEX = 1;
|
||||||
|
final int MAX_PW3_LENGTH_INDEX = 3;
|
||||||
|
|
||||||
|
byte[] pwStatusBytes = nfcGetPwStatusBytes();
|
||||||
|
byte[] newPin = newPinString.getBytes();
|
||||||
|
|
||||||
|
if (pw == 0x81) {
|
||||||
|
if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) {
|
||||||
|
throw new IOException("Invalid PIN length");
|
||||||
|
}
|
||||||
|
} else if (pw == 0x83) {
|
||||||
|
if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) {
|
||||||
|
throw new IOException("Invalid PIN length");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IOException("Invalid PW index for modify PIN operation");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] pin;
|
||||||
|
|
||||||
|
if (pw == 0x83) {
|
||||||
|
pin = new String(mAdminPin.getCharArray()).getBytes();
|
||||||
|
} else {
|
||||||
|
pin = new String(mPin.getCharArray()).getBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command APDU for CHANGE REFERENCE DATA command (page 32)
|
||||||
|
String changeReferenceDataApdu = "00" // CLA
|
||||||
|
+ "24" // INS
|
||||||
|
+ "00" // P1
|
||||||
|
+ String.format("%02x", pw) // P2
|
||||||
|
+ String.format("%02x", pin.length + newPin.length) // Lc
|
||||||
|
+ getHex(pin)
|
||||||
|
+ getHex(newPin);
|
||||||
|
String response = nfcCommunicate(changeReferenceDataApdu); // change PIN
|
||||||
|
if (!response.equals("9000")) {
|
||||||
|
handlePinError();
|
||||||
|
throw new CardException("Failed to change PIN", parseCardStatus(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a data object on the card. Automatically validates the proper PIN for the operation.
|
||||||
|
* Supported for all data objects < 255 bytes in length. Only the cardholder certificate
|
||||||
|
* (0x7F21) can exceed this length.
|
||||||
|
*
|
||||||
|
* @param dataObject The data object to be stored.
|
||||||
|
* @param data The data to store in the object
|
||||||
|
*/
|
||||||
|
public void nfcPutData(int dataObject, byte[] data) throws IOException {
|
||||||
|
if (data.length > 254) {
|
||||||
|
throw new IOException("Cannot PUT DATA with length > 254");
|
||||||
|
}
|
||||||
|
if (dataObject == 0x0101 || dataObject == 0x0103) {
|
||||||
|
if (!mPw1ValidatedForDecrypt) {
|
||||||
|
nfcVerifyPIN(0x82); // (Verify PW1 for non-signing operations)
|
||||||
|
}
|
||||||
|
} else if (!mPw3Validated) {
|
||||||
|
nfcVerifyPIN(0x83); // (Verify PW3)
|
||||||
|
}
|
||||||
|
|
||||||
|
String putDataApdu = "00" // CLA
|
||||||
|
+ "DA" // INS
|
||||||
|
+ String.format("%02x", (dataObject & 0xFF00) >> 8) // P1
|
||||||
|
+ String.format("%02x", dataObject & 0xFF) // P2
|
||||||
|
+ String.format("%02x", data.length) // Lc
|
||||||
|
+ getHex(data);
|
||||||
|
|
||||||
|
String response = nfcCommunicate(putDataApdu); // put data
|
||||||
|
if (!response.equals("9000")) {
|
||||||
|
throw new CardException("Failed to put data.", parseCardStatus(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts a key on the card in the given slot.
|
||||||
|
*
|
||||||
|
* @param slot The slot on the card where the key should be stored:
|
||||||
|
* 0xB6: Signature Key
|
||||||
|
* 0xB8: Decipherment Key
|
||||||
|
* 0xA4: Authentication Key
|
||||||
|
*/
|
||||||
|
public void nfcPutKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase)
|
||||||
|
throws IOException {
|
||||||
|
if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) {
|
||||||
|
throw new IOException("Invalid key slot");
|
||||||
|
}
|
||||||
|
|
||||||
|
RSAPrivateCrtKey crtSecretKey = null;
|
||||||
|
try {
|
||||||
|
secretKey.unlock(passphrase);
|
||||||
|
crtSecretKey = secretKey.getCrtSecretKey();
|
||||||
|
} catch (PgpGeneralException e) {
|
||||||
|
throw new IOException(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shouldn't happen; the UI should block the user from getting an incompatible key this far.
|
||||||
|
if (crtSecretKey.getModulus().bitLength() > 2048) {
|
||||||
|
throw new IOException("Key too large to export to smart card.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537.
|
||||||
|
if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) {
|
||||||
|
throw new IOException("Invalid public exponent for smart card key.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mPw3Validated) {
|
||||||
|
nfcVerifyPIN(0x83); // (Verify PW1 with mode 83)
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] header= Hex.decode(
|
||||||
|
"4D82" + "03A2" // Extended header list 4D82, length of 930 bytes. (page 23)
|
||||||
|
+ String.format("%02x", slot) + "00" // CRT to indicate targeted key, no length
|
||||||
|
+ "7F48" + "15" // Private key template 0x7F48, length 21 (decimal, 0x15 hex)
|
||||||
|
+ "9103" // Public modulus, length 3
|
||||||
|
+ "928180" // Prime P, length 128
|
||||||
|
+ "938180" // Prime Q, length 128
|
||||||
|
+ "948180" // Coefficient (1/q mod p), length 128
|
||||||
|
+ "958180" // Prime exponent P (d mod (p - 1)), length 128
|
||||||
|
+ "968180" // Prime exponent Q (d mod (1 - 1)), length 128
|
||||||
|
+ "97820100" // Modulus, length 256, last item in private key template
|
||||||
|
+ "5F48" + "820383");// DO 5F48; 899 bytes of concatenated key data will follow
|
||||||
|
byte[] dataToSend = new byte[934];
|
||||||
|
byte[] currentKeyObject;
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
System.arraycopy(header, 0, dataToSend, offset, header.length);
|
||||||
|
offset += header.length;
|
||||||
|
currentKeyObject = crtSecretKey.getPublicExponent().toByteArray();
|
||||||
|
System.arraycopy(currentKeyObject, 0, dataToSend, offset, 3);
|
||||||
|
offset += 3;
|
||||||
|
// NOTE: For a 2048-bit key, these lengths are fixed. However, bigint includes a leading 0
|
||||||
|
// in the array to represent sign, so we take care to set the offset to 1 if necessary.
|
||||||
|
currentKeyObject = crtSecretKey.getPrimeP().toByteArray();
|
||||||
|
System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
|
||||||
|
Arrays.fill(currentKeyObject, (byte)0);
|
||||||
|
offset += 128;
|
||||||
|
currentKeyObject = crtSecretKey.getPrimeQ().toByteArray();
|
||||||
|
System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
|
||||||
|
Arrays.fill(currentKeyObject, (byte)0);
|
||||||
|
offset += 128;
|
||||||
|
currentKeyObject = crtSecretKey.getCrtCoefficient().toByteArray();
|
||||||
|
System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
|
||||||
|
Arrays.fill(currentKeyObject, (byte)0);
|
||||||
|
offset += 128;
|
||||||
|
currentKeyObject = crtSecretKey.getPrimeExponentP().toByteArray();
|
||||||
|
System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
|
||||||
|
Arrays.fill(currentKeyObject, (byte)0);
|
||||||
|
offset += 128;
|
||||||
|
currentKeyObject = crtSecretKey.getPrimeExponentQ().toByteArray();
|
||||||
|
System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
|
||||||
|
Arrays.fill(currentKeyObject, (byte)0);
|
||||||
|
offset += 128;
|
||||||
|
currentKeyObject = crtSecretKey.getModulus().toByteArray();
|
||||||
|
System.arraycopy(currentKeyObject, currentKeyObject.length - 256, dataToSend, offset, 256);
|
||||||
|
|
||||||
|
String putKeyCommand = "10DB3FFF";
|
||||||
|
String lastPutKeyCommand = "00DB3FFF";
|
||||||
|
|
||||||
|
// Now we're ready to communicate with the card.
|
||||||
|
offset = 0;
|
||||||
|
String response;
|
||||||
|
while(offset < dataToSend.length) {
|
||||||
|
int dataRemaining = dataToSend.length - offset;
|
||||||
|
if (dataRemaining > 254) {
|
||||||
|
response = nfcCommunicate(
|
||||||
|
putKeyCommand + "FE" + Hex.toHexString(dataToSend, offset, 254)
|
||||||
|
);
|
||||||
|
offset += 254;
|
||||||
|
} else {
|
||||||
|
int length = dataToSend.length - offset;
|
||||||
|
response = nfcCommunicate(
|
||||||
|
lastPutKeyCommand + String.format("%02x", length)
|
||||||
|
+ Hex.toHexString(dataToSend, offset, length));
|
||||||
|
offset += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.endsWith("9000")) {
|
||||||
|
throw new CardException("Key export to card failed", parseCardStatus(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear array with secret data before we return.
|
||||||
|
Arrays.fill(dataToSend, (byte) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses out the status word from a JavaCard response string.
|
||||||
|
*
|
||||||
|
* @param response A hex string with the response from the card
|
||||||
|
* @return A short indicating the SW1/SW2, or 0 if a status could not be determined.
|
||||||
|
*/
|
||||||
|
short parseCardStatus(String response) {
|
||||||
|
if (response.length() < 4) {
|
||||||
|
return 0; // invalid input
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Short.parseShort(response.substring(response.length() - 4), 16);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prints a message to the screen
|
* Prints a message to the screen
|
||||||
*
|
*
|
||||||
@ -573,4 +885,18 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
|||||||
return new String(Hex.encode(raw));
|
return new String(Hex.encode(raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class CardException extends IOException {
|
||||||
|
private short mResponseCode;
|
||||||
|
|
||||||
|
public CardException(String detailMessage, short responseCode) {
|
||||||
|
super(detailMessage);
|
||||||
|
mResponseCode = responseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getResponseCode() {
|
||||||
|
return mResponseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
package org.sufficientlysecure.keychain.ui.base;
|
||||||
|
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class CachingCryptoOperationFragment <T extends Parcelable, S extends OperationResult>
|
||||||
|
extends CryptoOperationFragment<T, S> {
|
||||||
|
|
||||||
|
public static final String ARG_CACHED_ACTIONS = "cached_actions";
|
||||||
|
|
||||||
|
private T mCachedActionsParcel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
|
||||||
|
outState.putParcelable(ARG_CACHED_ACTIONS, mCachedActionsParcel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
mCachedActionsParcel = savedInstanceState.getParcelable(ARG_CACHED_ACTIONS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCryptoOperationResult(S result) {
|
||||||
|
super.onCryptoOperationResult(result);
|
||||||
|
mCachedActionsParcel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract T createOperationInput();
|
||||||
|
|
||||||
|
protected T getCachedActionsParcel() {
|
||||||
|
return mCachedActionsParcel;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void cacheActionsParcel(T cachedActionsParcel) {
|
||||||
|
mCachedActionsParcel = cachedActionsParcel;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onCryptoOperationCancelled() {
|
||||||
|
mCachedActionsParcel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -19,23 +19,32 @@
|
|||||||
package org.sufficientlysecure.keychain.ui.base;
|
package org.sufficientlysecure.keychain.ui.base;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
|
import android.os.Messenger;
|
||||||
|
import android.os.Parcelable;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
|
import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
|
import org.sufficientlysecure.keychain.service.KeychainNewService;
|
||||||
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
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.NfcOperationActivity;
|
import org.sufficientlysecure.keychain.ui.NfcOperationActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
||||||
|
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All fragments executing crypto operations need to extend this class.
|
* All fragments executing crypto operations need to extend this class.
|
||||||
*/
|
*/
|
||||||
public abstract class CryptoOperationFragment extends Fragment {
|
public abstract class CryptoOperationFragment <T extends Parcelable, S extends OperationResult>
|
||||||
|
extends Fragment {
|
||||||
|
|
||||||
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
|
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
|
||||||
public static final int REQUEST_CODE_NFC = 0x00008002;
|
public static final int REQUEST_CODE_NFC = 0x00008002;
|
||||||
@ -43,6 +52,7 @@ public abstract class CryptoOperationFragment extends Fragment {
|
|||||||
private void initiateInputActivity(RequiredInputParcel requiredInput) {
|
private void initiateInputActivity(RequiredInputParcel requiredInput) {
|
||||||
|
|
||||||
switch (requiredInput.mType) {
|
switch (requiredInput.mType) {
|
||||||
|
case NFC_KEYTOCARD:
|
||||||
case NFC_DECRYPT:
|
case NFC_DECRYPT:
|
||||||
case NFC_SIGN: {
|
case NFC_SIGN: {
|
||||||
Intent intent = new Intent(getActivity(), NfcOperationActivity.class);
|
Intent intent = new Intent(getActivity(), NfcOperationActivity.class);
|
||||||
@ -97,35 +107,111 @@ public abstract class CryptoOperationFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean handlePendingMessage(Message message) {
|
protected void dismissProgress() {
|
||||||
|
|
||||||
if (message.arg1 == ServiceProgressHandler.MessageStatus.OKAY.ordinal()) {
|
ProgressDialogFragment progressDialogFragment =
|
||||||
Bundle data = message.getData();
|
(ProgressDialogFragment) getFragmentManager().findFragmentByTag("progressDialog");
|
||||||
|
|
||||||
OperationResult result = data.getParcelable(OperationResult.EXTRA_RESULT);
|
if (progressDialogFragment == null) {
|
||||||
if (result == null || !(result instanceof InputPendingResult)) {
|
return;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InputPendingResult pendingResult = (InputPendingResult) result;
|
progressDialogFragment.dismissAllowingStateLoss();
|
||||||
if (pendingResult.isPending()) {
|
|
||||||
RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel();
|
|
||||||
initiateInputActivity(requiredInput);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
protected abstract T createOperationInput();
|
||||||
|
|
||||||
|
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||||
|
|
||||||
|
T operationInput = createOperationInput();
|
||||||
|
if (operationInput == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send all information needed to service to edit key in other thread
|
||||||
|
Intent intent = new Intent(getActivity(), KeychainNewService.class);
|
||||||
|
|
||||||
|
intent.putExtra(KeychainNewService.EXTRA_OPERATION_INPUT, operationInput);
|
||||||
|
intent.putExtra(KeychainNewService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||||
|
|
||||||
|
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message message) {
|
||||||
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
|
super.handleMessage(message);
|
||||||
|
|
||||||
|
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||||
|
|
||||||
|
// get returned data bundle
|
||||||
|
Bundle returnData = message.getData();
|
||||||
|
if (returnData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final OperationResult result =
|
||||||
|
returnData.getParcelable(OperationResult.EXTRA_RESULT);
|
||||||
|
|
||||||
|
onHandleResult(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a new Messenger for the communication back
|
||||||
|
Messenger messenger = new Messenger(saveHandler);
|
||||||
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
|
saveHandler.showProgressDialog(
|
||||||
|
getString(R.string.progress_building_key),
|
||||||
|
ProgressDialog.STYLE_HORIZONTAL, false);
|
||||||
|
|
||||||
|
getActivity().startService(intent);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void cryptoOperation() {
|
protected void cryptoOperation() {
|
||||||
cryptoOperation(new CryptoInputParcel());
|
cryptoOperation(new CryptoInputParcel());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void cryptoOperation(CryptoInputParcel cryptoInput);
|
protected void onCryptoOperationResult(S result) {
|
||||||
|
if (result.success()) {
|
||||||
protected void onCryptoOperationCancelled() {
|
onCryptoOperationSuccess(result);
|
||||||
// Nothing to do here, in most cases
|
} else {
|
||||||
|
onCryptoOperationError(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract protected void onCryptoOperationSuccess(S result);
|
||||||
|
|
||||||
|
protected void onCryptoOperationError(S result) {
|
||||||
|
result.createNotify(getActivity()).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onCryptoOperationCancelled() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onHandleResult(OperationResult result) {
|
||||||
|
|
||||||
|
if (result instanceof InputPendingResult) {
|
||||||
|
InputPendingResult pendingResult = (InputPendingResult) result;
|
||||||
|
if (pendingResult.isPending()) {
|
||||||
|
RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel();
|
||||||
|
initiateInputActivity(requiredInput);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dismissProgress();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// noinspection unchecked, because type erasure :(
|
||||||
|
onCryptoOperationResult((S) result);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new AssertionError("bad return class ("
|
||||||
|
+ result.getClass().getSimpleName() + "), this is a programming error!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,12 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui.dialog;
|
package org.sufficientlysecure.keychain.ui.dialog;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
@ -29,8 +35,8 @@ import android.os.Bundle;
|
|||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.support.v4.app.DialogFragment;
|
||||||
import android.test.suitebuilder.TestSuiteBuilder;
|
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -47,15 +53,6 @@ import org.sufficientlysecure.keychain.R;
|
|||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.TlsHelper;
|
import org.sufficientlysecure.keychain.util.TlsHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
|
|
||||||
public class AddKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener {
|
public class AddKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener {
|
||||||
private static final String ARG_MESSENGER = "messenger";
|
private static final String ARG_MESSENGER = "messenger";
|
||||||
|
|
||||||
@ -70,20 +67,11 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
|
|||||||
private EditText mKeyserverEditText;
|
private EditText mKeyserverEditText;
|
||||||
private CheckBox mVerifyKeyserverCheckBox;
|
private CheckBox mVerifyKeyserverCheckBox;
|
||||||
|
|
||||||
public static enum FailureReason {
|
public enum FailureReason {
|
||||||
INVALID_URL,
|
INVALID_URL,
|
||||||
CONNECTION_FAILED
|
CONNECTION_FAILED
|
||||||
}
|
}
|
||||||
|
|
||||||
;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates new instance of this dialog fragment
|
|
||||||
*
|
|
||||||
* @param title title of dialog
|
|
||||||
* @param messenger to communicate back after setting the passphrase
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static AddKeyserverDialogFragment newInstance(Messenger messenger) {
|
public static AddKeyserverDialogFragment newInstance(Messenger messenger) {
|
||||||
AddKeyserverDialogFragment frag = new AddKeyserverDialogFragment();
|
AddKeyserverDialogFragment frag = new AddKeyserverDialogFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
@ -94,9 +82,7 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
|
|||||||
return frag;
|
return frag;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@NonNull
|
||||||
* Creates dialog
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
@ -222,19 +208,23 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
|
|||||||
String scheme = keyserverUri.getScheme();
|
String scheme = keyserverUri.getScheme();
|
||||||
String schemeSpecificPart = keyserverUri.getSchemeSpecificPart();
|
String schemeSpecificPart = keyserverUri.getSchemeSpecificPart();
|
||||||
String fragment = keyserverUri.getFragment();
|
String fragment = keyserverUri.getFragment();
|
||||||
if (scheme == null) throw new MalformedURLException();
|
if (scheme == null) {
|
||||||
if (scheme.equalsIgnoreCase("hkps")) scheme = "https";
|
throw new MalformedURLException();
|
||||||
else if (scheme.equalsIgnoreCase("hkp")) scheme = "http";
|
}
|
||||||
|
if ("hkps".equalsIgnoreCase(scheme)) {
|
||||||
|
scheme = "https";
|
||||||
|
} else if ("hkp".equalsIgnoreCase(scheme)) {
|
||||||
|
scheme = "http";
|
||||||
|
}
|
||||||
URI newKeyserver = new URI(scheme, schemeSpecificPart, fragment);
|
URI newKeyserver = new URI(scheme, schemeSpecificPart, fragment);
|
||||||
|
|
||||||
Log.d("Converted URL", newKeyserver.toString());
|
Log.d(Constants.TAG, "Converted URL" + newKeyserver);
|
||||||
TlsHelper.openConnection(newKeyserver.toURL()).getInputStream();
|
|
||||||
|
// just see if we can get a connection, then immediately close
|
||||||
|
TlsHelper.openConnection(newKeyserver.toURL()).getInputStream().close();
|
||||||
} catch (TlsHelper.TlsHelperException e) {
|
} catch (TlsHelper.TlsHelperException e) {
|
||||||
reason = FailureReason.CONNECTION_FAILED;
|
reason = FailureReason.CONNECTION_FAILED;
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException | URISyntaxException e) {
|
||||||
Log.w(Constants.TAG, "Invalid keyserver URL entered by user.");
|
|
||||||
reason = FailureReason.INVALID_URL;
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
Log.w(Constants.TAG, "Invalid keyserver URL entered by user.");
|
Log.w(Constants.TAG, "Invalid keyserver URL entered by user.");
|
||||||
reason = FailureReason.INVALID_URL;
|
reason = FailureReason.INVALID_URL;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -36,7 +36,7 @@ import org.sufficientlysecure.keychain.R;
|
|||||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||||
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.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
@ -130,17 +130,12 @@ public class DeleteKeyDialogFragment extends DialogFragment {
|
|||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
|
||||||
// Send all information needed to service to import key in other thread
|
// Send all information needed to service to import key in other thread
|
||||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
Intent intent = new Intent(getActivity(), KeychainService.class);
|
||||||
|
|
||||||
intent.setAction(KeychainIntentService.ACTION_DELETE);
|
intent.setAction(KeychainService.ACTION_DELETE);
|
||||||
|
|
||||||
// Message is received after importing is done in KeychainIntentService
|
// Message is received after importing is done in KeychainService
|
||||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
|
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
|
||||||
getActivity(),
|
|
||||||
getString(R.string.progress_deleting),
|
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
|
||||||
true,
|
|
||||||
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
@ -159,16 +154,17 @@ public class DeleteKeyDialogFragment extends DialogFragment {
|
|||||||
|
|
||||||
// fill values for this action
|
// fill values for this action
|
||||||
Bundle data = new Bundle();
|
Bundle data = new Bundle();
|
||||||
data.putLongArray(KeychainIntentService.DELETE_KEY_LIST, masterKeyIds);
|
data.putLongArray(KeychainService.DELETE_KEY_LIST, masterKeyIds);
|
||||||
data.putBoolean(KeychainIntentService.DELETE_IS_SECRET, hasSecret);
|
data.putBoolean(KeychainService.DELETE_IS_SECRET, hasSecret);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
intent.putExtra(KeychainService.EXTRA_DATA, data);
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(saveHandler);
|
Messenger messenger = new Messenger(saveHandler);
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||||
|
|
||||||
// show progress dialog
|
// show progress dialog
|
||||||
saveHandler.showProgressDialog(getActivity());
|
saveHandler.showProgressDialog(getString(R.string.progress_deleting),
|
||||||
|
ProgressDialog.STYLE_HORIZONTAL, true);
|
||||||
|
|
||||||
// start service with intent
|
// start service with intent
|
||||||
getActivity().startService(intent);
|
getActivity().startService(intent);
|
||||||
|
@ -35,6 +35,7 @@ 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_KEYTOCARD = 4;
|
||||||
|
|
||||||
private Messenger mMessenger;
|
private Messenger mMessenger;
|
||||||
|
|
||||||
@ -76,6 +77,9 @@ public class EditSubkeyDialogFragment extends DialogFragment {
|
|||||||
case 2:
|
case 2:
|
||||||
sendMessageToHandler(MESSAGE_STRIP, null);
|
sendMessageToHandler(MESSAGE_STRIP, null);
|
||||||
break;
|
break;
|
||||||
|
case 3:
|
||||||
|
sendMessageToHandler(MESSAGE_KEYTOCARD, null);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|