Merge pull request #2553 from open-keychain/cleanup-bc
Update to AndroidX, use upstream bouncycastle provider
This commit is contained in:
commit
f3aceb4f12
407 changed files with 1103 additions and 6756 deletions
12
.gitmodules
vendored
12
.gitmodules
vendored
|
@ -2,18 +2,6 @@
|
|||
path = extern/openpgp-api-lib
|
||||
url = https://github.com/open-keychain/openpgp-api.git
|
||||
ignore = dirty
|
||||
[submodule "extern/KeybaseLib"]
|
||||
path = extern/KeybaseLib
|
||||
url = https://github.com/open-keychain/KeybaseLib.git
|
||||
ignore = dirty
|
||||
[submodule "extern/minidns"]
|
||||
path = extern/minidns
|
||||
url = https://github.com/open-keychain/minidns.git
|
||||
ignore = dirty
|
||||
[submodule "extern/safeslinger-exchange"]
|
||||
path = extern/safeslinger-exchange
|
||||
url = https://github.com/open-keychain/exchange-android
|
||||
ignore = dirty
|
||||
[submodule "OpenKeychain/src/test/resources/openpgp-interop"]
|
||||
path = OpenKeychain/src/test/resources/openpgp-interop
|
||||
url = https://github.com/google/openpgp-interop
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'com.squareup.sqldelight'
|
||||
// apply plugin: 'com.github.kt3k.coveralls'
|
||||
|
||||
|
@ -8,113 +7,94 @@ dependencies {
|
|||
// NOTE: libraries are pinned to a specific build, see below
|
||||
|
||||
// from local Android SDK
|
||||
compile 'com.android.support:support-v4:27.1.1'
|
||||
compile 'com.android.support:appcompat-v7:27.1.1'
|
||||
compile 'com.android.support:design:27.1.1'
|
||||
compile 'com.android.support:recyclerview-v7:27.1.1'
|
||||
compile 'com.android.support:cardview-v7:27.1.1'
|
||||
compile 'com.android.support:support-annotations:27.1.1'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.annotation:annotation:1.1.0'
|
||||
|
||||
// JCenter etc.
|
||||
compile 'com.journeyapps:zxing-android-embedded:3.4.0'
|
||||
compile 'com.google.zxing:core:3.3.0'
|
||||
compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.1'
|
||||
compile 'org.sufficientlysecure:donations:2.5'
|
||||
compile 'com.squareup.okhttp3:okhttp:3.9.1'
|
||||
compile 'com.squareup.okhttp3:okhttp-urlconnection:3.9.1'
|
||||
compile 'org.apache.james:apache-mime4j-core:0.8.0'
|
||||
compile 'org.apache.james:apache-mime4j-dom:0.8.0'
|
||||
implementation 'com.journeyapps:zxing-android-embedded:3.4.0'
|
||||
implementation 'com.google.zxing:core:3.3.0'
|
||||
implementation 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.1'
|
||||
implementation 'org.sufficientlysecure:donations:2.5'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.13.1'
|
||||
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.9.1'
|
||||
implementation 'org.apache.james:apache-mime4j-core:0.8.1'
|
||||
implementation 'org.apache.james:apache-mime4j-dom:0.8.1'
|
||||
|
||||
// UI
|
||||
compile 'org.sufficientlysecure:html-textview:3.1'
|
||||
compile 'com.jpardogo.materialtabstrip:library:1.1.1'
|
||||
compile 'com.getbase:floatingactionbutton:1.10.1'
|
||||
compile 'com.nispok:snackbar:2.11.0'
|
||||
compile 'com.cocosw:bottomsheet:1.3.1@aar'
|
||||
implementation 'org.sufficientlysecure:html-textview:3.1'
|
||||
implementation 'com.jpardogo.materialtabstrip:library:1.1.1'
|
||||
implementation 'com.getbase:floatingactionbutton:1.10.1'
|
||||
implementation 'com.nispok:snackbar:2.11.0'
|
||||
implementation 'com.cocosw:bottomsheet:1.5.0@aar'
|
||||
|
||||
// RecyclerView
|
||||
compile 'eu.davidea:flexible-adapter:5.0.5'
|
||||
compile 'eu.davidea:flexible-adapter-ui:1.0.0-b5'
|
||||
compile 'eu.davidea:flexible-adapter-livedata:1.0.0-b2'
|
||||
implementation 'eu.davidea:flexible-adapter:5.1.0'
|
||||
implementation 'eu.davidea:flexible-adapter-ui:1.0.0-b5'
|
||||
implementation 'eu.davidea:flexible-adapter-livedata:1.0.0-b2'
|
||||
|
||||
// Material Drawer
|
||||
compile 'com.mikepenz:materialdrawer:5.6.0@aar'
|
||||
compile 'com.mikepenz:fastadapter:1.8.2'
|
||||
compile 'com.mikepenz:materialize:1.0.0'
|
||||
compile 'com.mikepenz:iconics-core:2.8.1@aar'
|
||||
compile 'com.mikepenz:google-material-typeface:2.2.0.3.original@aar'
|
||||
compile 'com.mikepenz:fontawesome-typeface:4.6.0.3@aar'
|
||||
compile 'com.mikepenz:community-material-typeface:1.5.54.2@aar'
|
||||
implementation 'com.mikepenz:materialdrawer:6.1.2@aar'
|
||||
implementation 'com.mikepenz:fastadapter:3.3.0'
|
||||
implementation 'com.mikepenz:fastadapter-extensions-expandable:3.3.0'
|
||||
implementation 'com.mikepenz:materialize:1.2.0'
|
||||
implementation 'com.mikepenz:iconics-core:3.1.0@aar'
|
||||
implementation 'com.mikepenz:google-material-typeface:2.2.0.3.original@aar'
|
||||
implementation 'com.mikepenz:fontawesome-typeface:5.3.1.1@aar'
|
||||
implementation 'com.mikepenz:community-material-typeface:1.5.54.2@aar'
|
||||
|
||||
// Nordpol
|
||||
compile 'com.fidesmo:nordpol-android:0.1.22'
|
||||
implementation 'com.fidesmo:nordpol-android:0.1.22'
|
||||
|
||||
// piwik
|
||||
implementation 'org.piwik.sdk:piwik-sdk:3.0.3'
|
||||
|
||||
// libs as submodules
|
||||
implementation project(':libkeychain')
|
||||
implementation project(':openpgp-api-lib')
|
||||
implementation project(':nfcsweetspot')
|
||||
implementation project(':sshauthentication-api')
|
||||
implementation project(':extern:bouncycastle:core')
|
||||
implementation project(':extern:bouncycastle:pg')
|
||||
implementation project(':extern:bouncycastle:prov')
|
||||
implementation project(':extern:minidns')
|
||||
implementation project(':KeybaseLib')
|
||||
implementation project(':safeslinger-exchange')
|
||||
implementation project(':extern:MaterialChipsInput')
|
||||
|
||||
implementation "android.arch.work:work-runtime:1.0.0-alpha02"
|
||||
// implementation project(':openkeychain:extern:bouncycastle:core')
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.65.01'
|
||||
implementation project(':extern:bouncycastle:pg')
|
||||
// implementation project(':openkeychain:extern:bouncycastle:prov')
|
||||
|
||||
implementation 'androidx.work:work-runtime:2.3.4'
|
||||
|
||||
// Unit tests in the local JVM with Robolectric
|
||||
// https://developer.android.com/training/testing/unit-testing/local-unit-tests.html
|
||||
// http://robolectric.org/getting-started/
|
||||
// http://www.vogella.com/tutorials/Robolectric/article.html
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile ('org.robolectric:robolectric:3.6.1') {
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation ('org.robolectric:robolectric:3.8') {
|
||||
exclude group: 'org.bouncycastle', module: 'bcprov-jdk16'
|
||||
}
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
testImplementation 'org.mockito:mockito-core:2.18.0'
|
||||
|
||||
// UI testing with Espresso
|
||||
// Force usage of support libs in the test app, since they are internally used by the runner module.
|
||||
// https://github.com/googlesamples/android-testing/blob/master/ui/espresso/BasicSample/app/build.gradle#L28
|
||||
androidTestCompile 'com.android.support:support-annotations:27.1.1'
|
||||
androidTestCompile 'com.android.support:appcompat-v7:27.1.1'
|
||||
androidTestCompile 'com.android.support:design:27.1.1'
|
||||
androidTestCompile 'com.android.support.test:runner:0.5'
|
||||
androidTestCompile 'com.android.support.test:rules:0.5'
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
|
||||
androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2.2') {
|
||||
exclude group: 'com.android.support', module: 'appcompat'
|
||||
exclude group: 'com.android.support', module: 'support-v4'
|
||||
exclude module: 'recyclerview-v7'
|
||||
}
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
|
||||
compile "com.jakewharton.timber:timber:4.5.1"
|
||||
implementation 'org.glassfish:javax.annotation:10.0-b28'
|
||||
api "com.google.auto.value:auto-value-annotations:1.6.5"
|
||||
annotationProcessor "com.google.auto.value:auto-value:1.6.2"
|
||||
implementation 'com.ryanharter.auto.value:auto-value-parcel-adapter:0.2.6'
|
||||
annotationProcessor "com.ryanharter.auto.value:auto-value-parcel:0.2.6"
|
||||
|
||||
compile 'org.glassfish:javax.annotation:10.0-b28'
|
||||
provided "com.google.auto.value:auto-value:1.5"
|
||||
annotationProcessor "com.google.auto.value:auto-value:1.5"
|
||||
annotationProcessor "com.ryanharter.auto.value:auto-value-parcel:0.2.5"
|
||||
compile 'com.ryanharter.auto.value:auto-value-parcel-adapter:0.2.5'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.2.0'
|
||||
|
||||
compile "android.arch.lifecycle:extensions:1.0.0"
|
||||
annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
|
||||
|
||||
compile "android.arch.persistence:db-framework:1.0.0"
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
implementation 'androidx.sqlite:sqlite-framework:2.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
|
||||
// for debugging the db. don't enable by default, this will expose the database no the network!
|
||||
// debugImplementation 'com.amitshekhar.android:debug-db:1.0.3'
|
||||
}
|
||||
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 15
|
||||
|
@ -124,7 +104,7 @@ android {
|
|||
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 "androidx.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
|
||||
|
@ -225,7 +205,7 @@ android {
|
|||
}
|
||||
|
||||
variantFilter { variant ->
|
||||
if(variant.buildType.name.equals('debug') && variant.getFlavors().get(0).name.equals('google')) {
|
||||
if(variant.buildType.name == 'debug' && variant.getFlavors().get(0).name == 'google') {
|
||||
variant.setIgnore(true)
|
||||
}
|
||||
}
|
||||
|
@ -292,34 +272,6 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
task jacocoTestReport(type:JacocoReport, dependsOn: "testFdroidDebugWithTestCoverageUnitTest") {
|
||||
group = "Reporting"
|
||||
description = "Generate Jacoco coverage reports"
|
||||
|
||||
classDirectories = fileTree(
|
||||
dir: "${buildDir}/intermediates/classes/fdroid/debugWithTestCoverage",
|
||||
excludes: ['**/R.class',
|
||||
'**/R$*.class',
|
||||
'**/*$ViewInjector*.*',
|
||||
'**/BuildConfig.*',
|
||||
'**/Manifest*.*',
|
||||
'**/*Activity*.*',
|
||||
'**/*Fragment*.*']
|
||||
)
|
||||
|
||||
sourceDirectories = files("${buildDir.parent}/src/main/java")
|
||||
additionalSourceDirs = files([
|
||||
"${buildDir}/generated/source/buildConfig/fdroid/debugWithTestCoverage",
|
||||
"${buildDir}/generated/source/r/fdroid/debugWithTestCoverage"
|
||||
])
|
||||
executionData = fileTree(dir: "${buildDir}/jacoco", include: "**/*.exec")
|
||||
|
||||
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'
|
||||
|
|
|
@ -1,163 +0,0 @@
|
|||
/*
|
||||
* 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.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Random;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.test.espresso.UiController;
|
||||
import android.support.test.espresso.ViewAction;
|
||||
import android.support.test.espresso.matcher.ViewMatchers;
|
||||
import android.view.View;
|
||||
|
||||
import com.nispok.snackbar.Snackbar;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow;
|
||||
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
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.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.hamcrest.CoreMatchers.endsWith;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSnackbarLineColor;
|
||||
|
||||
|
||||
public class AndroidTestHelpers {
|
||||
|
||||
public static void dismissSnackbar() {
|
||||
onView(withClassName(endsWith("Snackbar")))
|
||||
.perform(new ViewAction() {
|
||||
@Override
|
||||
public Matcher<View> getConstraints() {
|
||||
return ViewMatchers.isAssignableFrom(Snackbar.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "dismiss snackbar";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(UiController uiController, View view) {
|
||||
((Snackbar) view).dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void checkSnackbar(Style style, @StringRes Integer text) {
|
||||
|
||||
onView(withClassName(endsWith("Snackbar")))
|
||||
.check(matches(withSnackbarLineColor(style.mLineColor)));
|
||||
|
||||
if (text != null) {
|
||||
onView(withClassName(endsWith("Snackbar")))
|
||||
.check(matches(hasDescendant(withText(text))));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void checkAndDismissSnackbar(Style style, @StringRes Integer text) {
|
||||
checkSnackbar(style, text);
|
||||
dismissSnackbar();
|
||||
}
|
||||
|
||||
public static void importKeysFromResource(Context context, String name) throws Exception {
|
||||
IteratorWithIOThrow<UncachedKeyRing> stream = UncachedKeyRing.fromStream(
|
||||
getInstrumentation().getContext().getAssets().open(name));
|
||||
|
||||
KeyWritableRepository helper = KeyWritableRepository.create(context);
|
||||
while(stream.hasNext()) {
|
||||
UncachedKeyRing ring = stream.next();
|
||||
if (ring.isSecret()) {
|
||||
helper.saveSecretKeyRing(ring);
|
||||
} else {
|
||||
helper.savePublicKeyRing(ring);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void copyFiles() throws IOException {
|
||||
File cacheDir = getInstrumentation().getTargetContext().getFilesDir();
|
||||
byte[] buf = new byte[256];
|
||||
for (String filename : FILES) {
|
||||
File outFile = new File(cacheDir, filename);
|
||||
if (outFile.exists()) {
|
||||
continue;
|
||||
}
|
||||
InputStream in = new BufferedInputStream(getInstrumentation().getContext().getAssets().open(filename));
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(outFile));
|
||||
int len;
|
||||
while( (len = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final String[] FILES = new String[] { "pa.png", "re.png", "ci.png" };
|
||||
public static File[] getImageNames() {
|
||||
File cacheDir = getInstrumentation().getTargetContext().getFilesDir();
|
||||
File[] ret = new File[FILES.length];
|
||||
for (int i = 0; i < ret.length; i++) {
|
||||
ret[i] = new File(cacheDir, FILES[i]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static <T> T pickRandom(T[] haystack) {
|
||||
return haystack[new Random().nextInt(haystack.length)];
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public static void cleanupForTests(Context context) throws Exception {
|
||||
|
||||
// KeychainDatabase.getInstance(context).clearDatabase();
|
||||
|
||||
// import these two, make sure they're there
|
||||
importKeysFromResource(context, "x.sec.asc");
|
||||
|
||||
// make sure no passphrases are cached
|
||||
PassphraseCacheService.clearCachedPassphrases(context);
|
||||
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
* 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.view.View;
|
||||
|
||||
import com.tokenautocomplete.TokenCompleteTextView;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
|
||||
|
||||
import static android.support.test.InstrumentationRegistry.getTargetContext;
|
||||
|
||||
|
||||
public abstract class CustomActions {
|
||||
|
||||
public static ViewAction tokenEncryptViewAddToken(long keyId) throws Exception {
|
||||
CanonicalizedPublicKeyRing ring =
|
||||
KeyWritableRepository.create(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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
package org.sufficientlysecure.keychain.actions;
|
||||
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.support.test.espresso.UiController;
|
||||
import android.support.test.espresso.ViewAction;
|
||||
import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
|
||||
import android.support.test.runner.lifecycle.Stage;
|
||||
import android.view.View;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
|
||||
|
||||
public class OrientationChangeAction implements ViewAction {
|
||||
private final int orientation;
|
||||
|
||||
private OrientationChangeAction(int orientation) {
|
||||
this.orientation = orientation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matcher<View> getConstraints() {
|
||||
return isRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "change orientation to " + orientation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void perform(UiController uiController, View view) {
|
||||
uiController.loopMainThreadUntilIdle();
|
||||
|
||||
final Activity activity = findActivity(view.getContext());
|
||||
if (activity == null){
|
||||
throw new IllegalStateException("Could not find the current activity");
|
||||
}
|
||||
|
||||
activity.setRequestedOrientation(orientation);
|
||||
|
||||
Collection<Activity> resumedActivities = ActivityLifecycleMonitorRegistry
|
||||
.getInstance().getActivitiesInStage(Stage.RESUMED);
|
||||
|
||||
if (resumedActivities.isEmpty()) {
|
||||
throw new RuntimeException("Could not change orientation");
|
||||
}
|
||||
}
|
||||
|
||||
private static Activity findActivity(Context context) {
|
||||
if (context == null)
|
||||
return null;
|
||||
else if (context instanceof Activity)
|
||||
return (Activity) context;
|
||||
else if (context instanceof ContextWrapper)
|
||||
return findActivity(((ContextWrapper) context).getBaseContext());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ViewAction orientationLandscape() {
|
||||
return new OrientationChangeAction(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
}
|
||||
|
||||
public static ViewAction orientationPortrait() {
|
||||
return new OrientationChangeAction(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* 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.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
|
||||
|
||||
public class BitmapMatcher extends TypeSafeMatcher<View> {
|
||||
|
||||
private final Bitmap mBitmap;
|
||||
|
||||
public BitmapMatcher(Bitmap bitmap) {
|
||||
super(View.class);
|
||||
mBitmap = bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesSafely(View view) {
|
||||
if ( !(view instanceof ImageView) ) {
|
||||
return false;
|
||||
}
|
||||
Drawable drawable = ((ImageView) view).getDrawable();
|
||||
return drawable != null && (drawable instanceof BitmapDrawable)
|
||||
&& ((BitmapDrawable) drawable).getBitmap().sameAs(mBitmap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("with equivalent specified bitmap");
|
||||
}
|
||||
|
||||
public static BitmapMatcher withBitmap(Bitmap bitmap) {
|
||||
return new BitmapMatcher(bitmap);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
/*
|
||||
* 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.annotation.IdRes;
|
||||
import android.support.test.espresso.matcher.BoundedMatcher;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import com.nispok.snackbar.Snackbar;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
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.isDisplayed;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
|
||||
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.matcher.DrawableMatcher.withDrawable;
|
||||
|
||||
|
||||
public abstract class CustomMatchers {
|
||||
|
||||
public static Matcher<View> withDisplayedChild(final int child) {
|
||||
return new BoundedMatcher<View, ViewAnimator>(ViewAnimator.class) {
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("with displayed child: " + child);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesSafely(ViewAnimator viewAnimator) {
|
||||
return viewAnimator.getDisplayedChild() == child;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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<RecyclerView.ViewHolder> withKeyHolderId(final long keyId) {
|
||||
return new BoundedMatcher<RecyclerView.ViewHolder, RecyclerView.ViewHolder>(RecyclerView.ViewHolder.class) {
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("with ViewHolder id: " + keyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(View item) {
|
||||
return item.getItemId() == 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Matcher<View> withRecyclerView(@IdRes int viewId) {
|
||||
return allOf(isAssignableFrom(RecyclerView.class), withId(viewId));
|
||||
}
|
||||
|
||||
public static Matcher<View> isRecyclerItemView(@IdRes int recyclerId, Matcher<View> specificChildMatcher) {
|
||||
return allOf(withParent(withRecyclerView(recyclerId)), specificChildMatcher);
|
||||
}
|
||||
|
||||
public static Matcher<View> withEncryptionStatus(boolean encrypted) {
|
||||
|
||||
if (encrypted) {
|
||||
return allOf(
|
||||
hasDescendant(allOf(
|
||||
withId(R.id.result_encryption_text), withText(R.string.decrypt_result_encrypted))),
|
||||
hasDescendant(allOf(
|
||||
withId(R.id.result_encryption_icon), withDrawable(R.drawable.status_lock_closed_24dp, true)))
|
||||
);
|
||||
} else {
|
||||
return allOf(
|
||||
hasDescendant(allOf(
|
||||
withId(R.id.result_encryption_text), withText(R.string.decrypt_result_not_encrypted))),
|
||||
hasDescendant(allOf(
|
||||
withId(R.id.result_encryption_icon), withDrawable(R.drawable.status_lock_open_24dp, true)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static Matcher<View> withSignatureNone() {
|
||||
|
||||
return allOf(
|
||||
hasDescendant(allOf(
|
||||
withId(R.id.result_signature_text), withText(R.string.decrypt_result_no_signature))),
|
||||
hasDescendant(allOf(
|
||||
withId(R.id.result_signature_icon), withDrawable(R.drawable.status_signature_invalid_cutout_24dp, true))),
|
||||
hasDescendant(allOf(
|
||||
withId(R.id.result_signature_layout), not(isDisplayed())))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public static Matcher<View> withSignatureMyKey() {
|
||||
|
||||
return allOf(
|
||||
hasDescendant(allOf(
|
||||
withId(R.id.result_signature_text), withText(R.string.decrypt_result_signature_certified))),
|
||||
hasDescendant(allOf(
|
||||
withId(R.id.result_signature_icon), withDrawable(R.drawable.status_signature_verified_cutout_24dp, true))),
|
||||
hasDescendant(allOf(
|
||||
withId(R.id.result_signature_layout), isDisplayed()))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
/*
|
||||
* 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().sameAs((((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,74 +0,0 @@
|
|||
/*
|
||||
* 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.content.Context;
|
||||
import android.text.method.TransformationMethod;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
|
||||
public class EditTextMatchers {
|
||||
|
||||
public static TypeSafeMatcher<View> withError(final int errorResId) {
|
||||
return new TypeSafeMatcher<View>() {
|
||||
|
||||
@Override
|
||||
public boolean matchesSafely(View view) {
|
||||
Context context = view.getContext();
|
||||
|
||||
if (view instanceof EditText) {
|
||||
CharSequence error = ((EditText) view).getError();
|
||||
return error != null && error.equals(context.getString(errorResId));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("EditText with error");
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public static TypeSafeMatcher<View> withTransformationMethod(final Class<? extends TransformationMethod> transformationClass) {
|
||||
return new TypeSafeMatcher<View>() {
|
||||
|
||||
@Override
|
||||
public boolean matchesSafely(View view) {
|
||||
if (view instanceof EditText) {
|
||||
TransformationMethod transformation = ((EditText) view).getTransformationMethod();
|
||||
return transformation != null && transformationClass.isInstance(transformation);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("EditText with transformation method");
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -1,167 +0,0 @@
|
|||
package org.sufficientlysecure.keychain.remote;
|
||||
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.rule.ServiceTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.openintents.openpgp.IOpenPgpService2;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import static android.support.test.espresso.Espresso.closeSoftKeyboard;
|
||||
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.matcher.ViewMatchers.withId;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.cleanupForTests;
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class OpenPgpServiceTest {
|
||||
|
||||
public static final int ACTIVITY_WAIT_TIME = 2 * 1000;
|
||||
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = new ServiceTestRule();
|
||||
|
||||
private OpenPgpApi mApi;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
Context context = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
cleanupForTests(context);
|
||||
|
||||
Intent serviceIntent = new Intent(context, OpenPgpService2.class);
|
||||
IBinder binder = mServiceRule.bindService(serviceIntent);
|
||||
|
||||
mApi = new OpenPgpApi(context, IOpenPgpService2.Stub.asInterface(binder));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStuff() throws Exception {
|
||||
// TODO why does this not ask for general usage permissions?!
|
||||
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(OpenPgpApi.ACTION_ENCRYPT);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, new long[]{0x9D604D2F310716A3L});
|
||||
|
||||
ByteArrayInputStream is = new ByteArrayInputStream("swag".getBytes());
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
||||
Intent result = mApi.executeApi(intent, is, os);
|
||||
|
||||
assertThat("result is pending accept",
|
||||
result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR),
|
||||
is(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED));
|
||||
|
||||
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||
pi.send();
|
||||
|
||||
Thread.sleep(ACTIVITY_WAIT_TIME); // Wait for activity to start
|
||||
onView(withText(R.string.button_allow)).perform(click());
|
||||
}
|
||||
|
||||
byte[] ciphertext;
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(OpenPgpApi.ACTION_ENCRYPT);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, new long[]{0x9D604D2F310716A3L});
|
||||
|
||||
ByteArrayInputStream is = new ByteArrayInputStream("swag".getBytes());
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
||||
Intent result = mApi.executeApi(intent, is, os);
|
||||
|
||||
assertThat("result is encrypt ok",
|
||||
result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR),
|
||||
is(OpenPgpApi.RESULT_CODE_SUCCESS));
|
||||
|
||||
ciphertext = os.toByteArray();
|
||||
}
|
||||
|
||||
{ // decrypt
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
||||
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(ciphertext);
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
||||
Intent result = mApi.executeApi(intent, is, os);
|
||||
|
||||
assertThat("result is pending input",
|
||||
result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR),
|
||||
is(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED));
|
||||
|
||||
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||
pi.send();
|
||||
|
||||
Thread.sleep(ACTIVITY_WAIT_TIME); // Wait for activity to start
|
||||
onView(withText(R.string.button_allow)).perform(click());
|
||||
}
|
||||
|
||||
{ // decrypt again, this time pending passphrase
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
||||
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(ciphertext);
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
||||
Intent result = mApi.executeApi(intent, is, os);
|
||||
|
||||
assertThat("result is pending passphrase",
|
||||
result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR),
|
||||
is(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED));
|
||||
|
||||
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||
pi.send();
|
||||
|
||||
Thread.sleep(ACTIVITY_WAIT_TIME); // Wait for activity to start
|
||||
onView(withId(R.id.passphrase_passphrase)).perform(typeText("x"));
|
||||
|
||||
// Needed to correctly execute test on Travis
|
||||
closeSoftKeyboard();
|
||||
Thread.sleep(1 * 1000);
|
||||
|
||||
onView(withText(R.string.btn_unlock)).perform(click());
|
||||
}
|
||||
|
||||
{ // decrypt again, NOW it should work with passphrase cached =)
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
||||
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(ciphertext);
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
||||
Intent result = mApi.executeApi(intent, is, os);
|
||||
|
||||
assertThat("result is decrypt ok",
|
||||
result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR),
|
||||
is(OpenPgpApi.RESULT_CODE_SUCCESS));
|
||||
|
||||
byte[] plaintext = os.toByteArray();
|
||||
assertThat("decrypted plaintext matches plaintext", new String(plaintext), is("swag"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,385 +0,0 @@
|
|||
/*
|
||||
* 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.ui;
|
||||
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.Instrumentation.ActivityResult;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.support.test.espresso.intent.Intents;
|
||||
import android.support.test.espresso.intent.rule.IntentsTestRule;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.AndroidTestHelpers;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static android.support.test.InstrumentationRegistry.getInstrumentation;
|
||||
import static android.support.test.espresso.Espresso.onData;
|
||||
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.intent.matcher.IntentMatchers.hasAction;
|
||||
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasCategories;
|
||||
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtra;
|
||||
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtraWithKey;
|
||||
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasType;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.assertThat;
|
||||
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.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.hasItem;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.checkSnackbar;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.getImageNames;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.importKeysFromResource;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.pickRandom;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.randomString;
|
||||
import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.isRecyclerItemView;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withDisplayedChild;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withEncryptionStatus;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSignatureMyKey;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSignatureNone;
|
||||
|
||||
//TODO This test is disabled because it needs to be fixed to work with updated code
|
||||
//@RunWith(AndroidJUnit4.class)
|
||||
//@LargeTest
|
||||
public class AsymmetricFileOperationTests {
|
||||
|
||||
@Rule
|
||||
public final IntentsTestRule<MainActivity> mActivity
|
||||
= new IntentsTestRule<MainActivity>(MainActivity.class) {
|
||||
@Override
|
||||
protected Intent getActivityIntent() {
|
||||
Intent intent = super.getActivityIntent();
|
||||
intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true);
|
||||
intent.putExtra(MainActivity.EXTRA_INIT_FRAG, MainActivity.ID_ENCRYPT_DECRYPT);
|
||||
return intent;
|
||||
}
|
||||
};
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
Activity activity = mActivity.getActivity();
|
||||
|
||||
AndroidTestHelpers.copyFiles();
|
||||
|
||||
// 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 testFileSaveEncryptDecrypt() throws Exception {
|
||||
|
||||
// navigate to 'encrypt text'
|
||||
onView(withId(R.id.encrypt_files)).perform(click());
|
||||
|
||||
File file = pickRandom(getImageNames());
|
||||
File outputFile = new File(getInstrumentation().getTargetContext().getFilesDir(), "output-token.gpg");
|
||||
|
||||
{ // encrypt
|
||||
|
||||
// the EncryptKeyCompletionView is tested individually
|
||||
onView(withId(R.id.recipient_list)).perform(tokenEncryptViewAddToken(0x9D604D2F310716A3L));
|
||||
|
||||
handleAddFileIntent(file);
|
||||
onView(withId(R.id.file_list_entry_add)).perform(click());
|
||||
|
||||
handleSaveEncryptedFileIntent(outputFile);
|
||||
onView(withId(R.id.encrypt_save)).perform(click());
|
||||
|
||||
assertThat("output file has been written", true, is(outputFile.exists()));
|
||||
|
||||
}
|
||||
|
||||
// go to decrypt from clipboard view
|
||||
pressBack();
|
||||
|
||||
handleOpenFileIntentKitKat(outputFile);
|
||||
onView(withId(R.id.decrypt_files)).perform(click());
|
||||
|
||||
{ // decrypt
|
||||
onView(withId(R.id.passphrase_passphrase)).perform(typeText("x"));
|
||||
onView(withText(R.string.btn_unlock)).perform(click());
|
||||
|
||||
onView(isRecyclerItemView(R.id.decrypted_files_list,
|
||||
hasDescendant(withText(file.getName()))))
|
||||
.check(matches(allOf(withEncryptionStatus(true), withSignatureNone())));
|
||||
}
|
||||
|
||||
{ // delete original file
|
||||
|
||||
// open context menu
|
||||
onView(allOf(isDescendantOfA(isRecyclerItemView(R.id.decrypted_files_list,
|
||||
hasDescendant(withText(file.getName())))),
|
||||
withId(R.id.context_menu))).perform(click());
|
||||
|
||||
// delete file
|
||||
onView(withText(R.string.btn_delete_original)).perform(click());
|
||||
|
||||
checkSnackbar(Style.OK, R.string.file_delete_ok);
|
||||
assertThat("output file has been deleted", false, is(outputFile.exists()));
|
||||
|
||||
// open context menu
|
||||
onView(allOf(isDescendantOfA(isRecyclerItemView(R.id.decrypted_files_list,
|
||||
hasDescendant(withText(file.getName())))),
|
||||
withId(R.id.context_menu))).perform(click());
|
||||
|
||||
// delete file
|
||||
onView(withText(R.string.btn_delete_original)).perform(click());
|
||||
|
||||
checkSnackbar(Style.WARN, R.string.file_delete_none);
|
||||
|
||||
}
|
||||
|
||||
{ // save file (*after* deletion~)
|
||||
|
||||
// open context menu
|
||||
onView(allOf(isDescendantOfA(isRecyclerItemView(R.id.decrypted_files_list,
|
||||
hasDescendant(withText(file.getName())))),
|
||||
withId(R.id.context_menu))).perform(click());
|
||||
|
||||
File savedFile =
|
||||
new File(getInstrumentation().getTargetContext().getFilesDir(), "vo.png");
|
||||
handleSaveDecryptedFileIntent(savedFile, file.getName());
|
||||
|
||||
// save decrypted content
|
||||
onView(withText(R.string.btn_save_file)).perform(click());
|
||||
|
||||
checkSnackbar(Style.OK, R.string.file_saved);
|
||||
assertThat("decrypted file has been saved", true, is(savedFile.exists()));
|
||||
|
||||
// cleanup
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
file.delete();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void handleAddFileIntent(File file) {
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
|
||||
handleAddFileIntentKitKat(file);
|
||||
} else {
|
||||
handleAddFileIntentOlder(file);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.KITKAT)
|
||||
private void handleAddFileIntentKitKat(File file) {
|
||||
Intent data = new Intent();
|
||||
data.setData(Uri.fromFile(file));
|
||||
|
||||
Intents.intending(allOf(
|
||||
hasAction(Intent.ACTION_OPEN_DOCUMENT),
|
||||
hasType("*/*"),
|
||||
hasCategories(hasItem(Intent.CATEGORY_OPENABLE)),
|
||||
hasExtraWithKey(Intent.EXTRA_ALLOW_MULTIPLE)
|
||||
)).respondWith(
|
||||
new ActivityResult(Activity.RESULT_OK, data)
|
||||
);
|
||||
}
|
||||
|
||||
private void handleAddFileIntentOlder(File file) {
|
||||
Intent data = new Intent();
|
||||
data.setData(Uri.fromFile(file));
|
||||
|
||||
Intents.intending(allOf(
|
||||
hasAction(Intent.ACTION_GET_CONTENT),
|
||||
hasType("*/*"),
|
||||
hasCategories(hasItem(Intent.CATEGORY_OPENABLE))
|
||||
)).respondWith(
|
||||
new ActivityResult(Activity.RESULT_OK, data)
|
||||
);
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.KITKAT)
|
||||
private void handleSaveDecryptedFileIntent(File file, String expectedTitle) {
|
||||
Intent data = new Intent();
|
||||
data.setData(Uri.fromFile(file));
|
||||
|
||||
Intents.intending(allOf(
|
||||
hasAction(Intent.ACTION_CREATE_DOCUMENT),
|
||||
hasExtra("android.content.extra.SHOW_ADVANCED", true),
|
||||
hasExtra(Intent.EXTRA_TITLE, expectedTitle),
|
||||
hasCategories(hasItem(Intent.CATEGORY_OPENABLE))
|
||||
)).respondWith(
|
||||
new ActivityResult(Activity.RESULT_OK, data)
|
||||
);
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.KITKAT)
|
||||
private void handleSaveEncryptedFileIntent(File file) {
|
||||
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
file.delete();
|
||||
} catch (Exception e) {
|
||||
// nvm
|
||||
}
|
||||
|
||||
Intent data = new Intent();
|
||||
data.setData(Uri.fromFile(file));
|
||||
|
||||
Intents.intending(allOf(
|
||||
hasAction(Intent.ACTION_CREATE_DOCUMENT),
|
||||
hasType("*/*"),
|
||||
hasExtra("android.content.extra.SHOW_ADVANCED", true),
|
||||
hasCategories(hasItem(Intent.CATEGORY_OPENABLE))
|
||||
)).respondWith(
|
||||
new ActivityResult(Activity.RESULT_OK, data)
|
||||
);
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.KITKAT)
|
||||
private void handleOpenFileIntentKitKat(File file) {
|
||||
Intent data = new Intent();
|
||||
data.setData(Uri.fromFile(file));
|
||||
|
||||
Intents.intending(allOf(
|
||||
hasAction(Intent.ACTION_OPEN_DOCUMENT),
|
||||
hasType("*/*"),
|
||||
hasCategories(hasItem(Intent.CATEGORY_OPENABLE))
|
||||
// hasExtraWithKey(Intent.EXTRA_ALLOW_MULTIPLE)
|
||||
)).respondWith(
|
||||
new ActivityResult(Activity.RESULT_OK, data)
|
||||
);
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testSignVerify() throws Exception {
|
||||
|
||||
String cleartext = randomString(10, 30);
|
||||
|
||||
// navigate to 'encrypt text'
|
||||
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);
|
||||
|
||||
onView(withId(R.id.result_signature_icon)).check(matches(withDisplayedChild(0)));
|
||||
onView(withId(R.id.sign)).perform(click());
|
||||
onData(withKeyItemId(0x9D604D2F310716A3L))
|
||||
.inAdapterView(isAssignableFrom(AdapterView.class))
|
||||
.perform(click());
|
||||
onView(withId(R.id.result_signature_icon)).check(matches(withDisplayedChild(1)));
|
||||
|
||||
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(isRecyclerItemView(R.id.decrypted_files_list,
|
||||
hasDescendant(withText(R.string.filename_unknown))))
|
||||
.check(matches(allOf(withEncryptionStatus(false), withSignatureMyKey())));
|
||||
|
||||
// open context menu
|
||||
onView(allOf(isDescendantOfA(isRecyclerItemView(R.id.decrypted_files_list,
|
||||
hasDescendant(withText(R.string.filename_unknown)))),
|
||||
withId(R.id.context_menu))).perform(click());
|
||||
|
||||
// check if log looks ok
|
||||
onView(withText(R.string.snackbar_details)).perform(click());
|
||||
onView(withText(R.string.msg_dc_clear_signature_ok)).check(matches(isDisplayed()));
|
||||
pressBack();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testGeneralErrorHandling() throws Exception {
|
||||
|
||||
// navigate to encrypt files fragment
|
||||
onView(withId(R.id.encrypt_files)).perform(click());
|
||||
|
||||
File[] files = getImageNames();
|
||||
|
||||
{ // encrypt screen
|
||||
|
||||
onView(withId(R.id.encrypt_share)).perform(click());
|
||||
checkSnackbar(Style.ERROR, R.string.error_no_file_selected);
|
||||
|
||||
handleAddFileIntent(files[0]);
|
||||
onView(withId(R.id.file_list_entry_add)).perform(click());
|
||||
|
||||
handleAddFileIntent(files[1]);
|
||||
onView(withId(R.id.file_list_entry_add)).perform(click());
|
||||
|
||||
onView(withId(R.id.encrypt_share)).perform(click());
|
||||
checkSnackbar(Style.ERROR, R.string.select_encryption_key);
|
||||
|
||||
onView(withId(R.id.sign)).perform(click());
|
||||
onData(withKeyItemId(0x9D604D2F310716A3L))
|
||||
.inAdapterView(isAssignableFrom(AdapterView.class))
|
||||
.perform(click());
|
||||
|
||||
onView(withId(R.id.encrypt_share)).perform(click());
|
||||
checkSnackbar(Style.ERROR, R.string.error_detached_signature);
|
||||
|
||||
// the EncryptKeyCompletionView is tested individually
|
||||
onView(withId(R.id.recipient_list)).perform(tokenEncryptViewAddToken(0x9D604D2F310716A3L));
|
||||
|
||||
onView(withId(R.id.encrypt_save)).perform(click());
|
||||
checkSnackbar(Style.ERROR, R.string.error_multi_files);
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext());
|
||||
onView(withText(R.string.btn_copy_encrypted_signed)).perform(click());
|
||||
checkSnackbar(Style.ERROR, R.string.error_multi_clipboard);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,181 +0,0 @@
|
|||
/*
|
||||
* 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.ui;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
|
||||
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.matcher.ViewMatchers.hasDescendant;
|
||||
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.sufficientlysecure.keychain.AndroidTestHelpers.checkSnackbar;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.importKeysFromResource;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.randomString;
|
||||
import static org.sufficientlysecure.keychain.actions.CustomActions.tokenEncryptViewAddToken;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.isRecyclerItemView;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withDisplayedChild;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withEncryptionStatus;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyItemId;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSignatureMyKey;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSignatureNone;
|
||||
|
||||
//TODO This test is disabled because it needs to be fixed to work with updated code
|
||||
//@RunWith(AndroidJUnit4.class)
|
||||
//@LargeTest
|
||||
public class AsymmetricTextOperationTests {
|
||||
|
||||
@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);
|
||||
intent.putExtra(MainActivity.EXTRA_INIT_FRAG, MainActivity.ID_ENCRYPT_DECRYPT);
|
||||
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'
|
||||
onView(withId(R.id.encrypt_text)).perform(click());
|
||||
|
||||
String cleartext = randomString(10, 30);
|
||||
|
||||
{ // encrypt
|
||||
|
||||
// the EncryptKeyCompletionView is tested individually
|
||||
onView(withId(R.id.result_encryption_icon)).check(matches(withDisplayedChild(0)));
|
||||
onView(withId(R.id.recipient_list)).perform(tokenEncryptViewAddToken(0x9D604D2F310716A3L));
|
||||
onView(withId(R.id.result_encryption_icon)).check(matches(withDisplayedChild(1)));
|
||||
|
||||
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(isRecyclerItemView(R.id.decrypted_files_list,
|
||||
hasDescendant(withText(R.string.filename_unknown_text))))
|
||||
.check(matches(allOf(
|
||||
hasDescendant(withText(FileHelper.readableFileSize(cleartext.length()))),
|
||||
withEncryptionStatus(true),
|
||||
withSignatureNone()
|
||||
)));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testSignVerify() throws Exception {
|
||||
|
||||
String cleartext = randomString(10, 30);
|
||||
|
||||
// navigate to 'encrypt text'
|
||||
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);
|
||||
|
||||
onView(withId(R.id.result_signature_icon)).check(matches(withDisplayedChild(0)));
|
||||
onView(withId(R.id.sign)).perform(click());
|
||||
onData(withKeyItemId(0x9D604D2F310716A3L))
|
||||
.inAdapterView(isAssignableFrom(AdapterView.class))
|
||||
.perform(click());
|
||||
onView(withId(R.id.result_signature_icon)).check(matches(withDisplayedChild(1)));
|
||||
|
||||
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(isRecyclerItemView(R.id.decrypted_files_list,
|
||||
hasDescendant(withText(R.string.filename_unknown))))
|
||||
.check(matches(allOf(withEncryptionStatus(false), withSignatureMyKey())));
|
||||
|
||||
// open context menu
|
||||
onView(allOf(isDescendantOfA(isRecyclerItemView(R.id.decrypted_files_list,
|
||||
hasDescendant(withText(R.string.filename_unknown)))),
|
||||
withId(R.id.context_menu))).perform(click());
|
||||
|
||||
// check if log looks ok
|
||||
onView(withText(R.string.snackbar_details)).perform(click());
|
||||
onView(withText(R.string.msg_dc_clear_signature_ok)).check(matches(isDisplayed()));
|
||||
pressBack();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,205 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
|
||||
import android.support.test.espresso.matcher.ViewMatchers;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
import android.text.method.HideReturnsTransformationMethod;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
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.swipeLeft;
|
||||
import static android.support.test.espresso.action.ViewActions.typeText;
|
||||
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
|
||||
import static android.support.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static android.support.test.espresso.matcher.RootMatchers.isDialog;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.hasSibling;
|
||||
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.Matchers.allOf;
|
||||
import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withError;
|
||||
import static org.sufficientlysecure.keychain.matcher.EditTextMatchers.withTransformationMethod;
|
||||
|
||||
//TODO This test is disabled because it needs to be fixed to work with updated code
|
||||
//@RunWith(AndroidJUnit4.class)
|
||||
//@LargeTest
|
||||
public class CreateKeyActivityTest {
|
||||
|
||||
public static final String SAMPLE_NAME = "Sample Name";
|
||||
public static final String SAMPLE_EMAIL = "sample_email@gmail.com";
|
||||
public static final String SAMPLE_ADDITIONAL_EMAIL = "sample_additional_email@gmail.com";
|
||||
public static final String SAMPLE_PASSWORD = "sample_password";
|
||||
|
||||
@Rule
|
||||
public final ActivityTestRule<CreateKeyActivity> mActivity
|
||||
= new ActivityTestRule<>(CreateKeyActivity.class);
|
||||
|
||||
//@Test
|
||||
public void testCreateMyKey() {
|
||||
/*
|
||||
|
||||
mActivity.getActivity();
|
||||
|
||||
// Clicks create my key
|
||||
onView(ViewMatchers.withId(R.id.create_key_create_key_button))
|
||||
.perform(click());
|
||||
|
||||
// Clicks next with empty name
|
||||
onView(withId(R.id.create_key_next_button))
|
||||
.perform(click());
|
||||
onView(withId(R.id.create_key_name))
|
||||
.check(matches(withError(R.string.create_key_empty)));
|
||||
|
||||
// Types name and clicks next
|
||||
onView(withId(R.id.create_key_name))
|
||||
.perform(typeText(SAMPLE_NAME));
|
||||
onView(withId(R.id.create_key_next_button))
|
||||
.perform(click());
|
||||
|
||||
// Clicks next with empty email
|
||||
onView(withId(R.id.create_key_next_button))
|
||||
.perform(click());
|
||||
onView(withId(R.id.create_key_email))
|
||||
.check(matches(withError(R.string.create_key_empty)));
|
||||
|
||||
// Types email
|
||||
onView(withId(R.id.create_key_email))
|
||||
.perform(typeText(SAMPLE_EMAIL));
|
||||
|
||||
// Adds same email as additional email and dismisses the snackbar
|
||||
onView(withId(R.id.create_key_add_email))
|
||||
.perform(click());
|
||||
onView(withId(R.id.add_email_address))
|
||||
.perform(typeText(SAMPLE_EMAIL));
|
||||
onView(withText(android.R.string.ok))
|
||||
.inRoot(isDialog())
|
||||
.perform(click());
|
||||
onView(allOf(withId(R.id.sb__text), withText(R.string.create_key_email_already_exists_text)))
|
||||
.check(matches(isDisplayed()));
|
||||
onView(allOf(withId(R.id.sb__text), withText(R.string.create_key_email_already_exists_text)))
|
||||
.perform(swipeLeft());
|
||||
|
||||
// Adds additional email
|
||||
onView(withId(R.id.create_key_add_email))
|
||||
.perform(click());
|
||||
onView(withId(R.id.add_email_address))
|
||||
.perform(typeText(SAMPLE_ADDITIONAL_EMAIL));
|
||||
onView(withText(android.R.string.ok))
|
||||
.inRoot(isDialog())
|
||||
.perform(click());
|
||||
onView(withId(R.id.create_key_emails))
|
||||
.check(matches(hasDescendant(allOf(withId(R.id.create_key_email_item_email), withText(SAMPLE_ADDITIONAL_EMAIL)))));
|
||||
|
||||
// Removes additional email and clicks next
|
||||
onView(allOf(withId(R.id.create_key_email_item_delete_button), hasSibling(allOf(withId(R.id.create_key_email_item_email), withText(SAMPLE_ADDITIONAL_EMAIL)))))
|
||||
.perform(click())
|
||||
.check(doesNotExist());
|
||||
onView(withId(R.id.create_key_next_button))
|
||||
.perform(click(click()));
|
||||
|
||||
// Clicks next with empty password
|
||||
onView(withId(R.id.create_key_next_button))
|
||||
.perform(click());
|
||||
onView(withId(R.id.create_key_passphrase))
|
||||
.check(matches(withError(R.string.create_key_empty)));
|
||||
|
||||
// Types password
|
||||
onView(withId(R.id.create_key_passphrase))
|
||||
.perform(typeText(SAMPLE_PASSWORD));
|
||||
|
||||
// Clicks next with empty confirm password
|
||||
onView(withId(R.id.create_key_next_button))
|
||||
.perform(click());
|
||||
onView(withId(R.id.create_key_passphrase_again))
|
||||
.check(matches(withError(R.string.create_key_passphrases_not_equal)));
|
||||
|
||||
// Types confirm password
|
||||
onView(withId(R.id.create_key_passphrase_again))
|
||||
.perform(typeText(SAMPLE_PASSWORD));
|
||||
|
||||
// Clicks show password twice and clicks next
|
||||
onView(withId(R.id.create_key_show_passphrase))
|
||||
.perform(click());
|
||||
onView(withId(R.id.create_key_passphrase))
|
||||
.check(matches(withTransformationMethod(HideReturnsTransformationMethod.class)));
|
||||
onView(withId(R.id.create_key_passphrase_again))
|
||||
.check(matches(withTransformationMethod(HideReturnsTransformationMethod.class)));
|
||||
onView(withId(R.id.create_key_show_passphrase))
|
||||
.perform(click());
|
||||
onView(withId(R.id.create_key_passphrase))
|
||||
.check(matches(withTransformationMethod(PasswordTransformationMethod.class)));
|
||||
onView(withId(R.id.create_key_passphrase_again))
|
||||
.check(matches(withTransformationMethod(PasswordTransformationMethod.class)));
|
||||
onView(withId(R.id.create_key_next_button))
|
||||
.perform(click());
|
||||
|
||||
// Verifies name and email
|
||||
onView(withId(R.id.name))
|
||||
.check(matches(withText(SAMPLE_NAME)));
|
||||
onView(withId(R.id.email))
|
||||
.check(matches(withText(SAMPLE_EMAIL)));
|
||||
|
||||
// Verifies backstack
|
||||
onView(withId(R.id.create_key_back_button))
|
||||
.perform(click());
|
||||
onView(withId(R.id.create_key_back_button))
|
||||
.perform(click());
|
||||
onView(withId(R.id.create_key_back_button))
|
||||
.perform(click());
|
||||
|
||||
onView(withId(R.id.create_key_name))
|
||||
.check(matches(withText(SAMPLE_NAME)));
|
||||
onView(withId(R.id.create_key_next_button))
|
||||
.perform(click());
|
||||
|
||||
onView(withId(R.id.create_key_email))
|
||||
.check(matches(withText(SAMPLE_EMAIL)));
|
||||
onView(withId(R.id.create_key_next_button))
|
||||
.perform(click());
|
||||
|
||||
// TODO: Uncomment when fixed in main
|
||||
// onView(withId(R.id.create_key_passphrase))
|
||||
// .check(matches(withText(SAMPLE_PASSWORD)));
|
||||
// onView(withId(R.id.create_key_passphrase_again))
|
||||
// .check(matches(withText(SAMPLE_PASSWORD)));
|
||||
onView(withId(R.id.create_key_next_button))
|
||||
.perform(click());
|
||||
|
||||
onView(withId(R.id.name))
|
||||
.check(matches(withText(SAMPLE_NAME)));
|
||||
onView(withId(R.id.email))
|
||||
.check(matches(withText(SAMPLE_EMAIL)));
|
||||
|
||||
// Clicks create key
|
||||
onView(withId(R.id.create_key_next_button))
|
||||
.perform(click());
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* 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.ui;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Rule;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
|
||||
import static android.support.test.espresso.Espresso.onView;
|
||||
import static android.support.test.espresso.action.ViewActions.click;
|
||||
import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnHolderItem;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
|
||||
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.AndroidTestHelpers.checkSnackbar;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.importKeysFromResource;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyHolderId;
|
||||
|
||||
|
||||
//TODO This test is disabled because it needs to be fixed to work with updated code
|
||||
@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();
|
||||
|
||||
// KeychainDatabase.getInstance(activity).clearDatabase();
|
||||
|
||||
// import key for testing, get a stable initial state
|
||||
importKeysFromResource(activity, "x.sec.asc");
|
||||
|
||||
// navigate to edit key dialog
|
||||
onView(allOf(
|
||||
isAssignableFrom(RecyclerView.class),
|
||||
withId(android.R.id.list)))
|
||||
.perform(actionOnHolderItem(
|
||||
withKeyHolderId(0x9D604D2F310716A3L), click()));
|
||||
|
||||
onView(withId(R.id.view_key_card_user_ids_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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,289 +0,0 @@
|
|||
/*
|
||||
* 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.ui;
|
||||
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.Instrumentation.ActivityResult;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.support.test.espresso.intent.Intents;
|
||||
import android.support.test.espresso.intent.rule.IntentsTestRule;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.AndroidTestHelpers;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
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.assertion.ViewAssertions.matches;
|
||||
import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnHolderItem;
|
||||
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction;
|
||||
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasCategories;
|
||||
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasType;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.hasSibling;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isChecked;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.isNotChecked;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.withChild;
|
||||
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.hasItem;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.checkSnackbar;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.dismissSnackbar;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.getImageNames;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.importKeysFromResource;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.pickRandom;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.randomString;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.isRecyclerItemView;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withDisplayedChild;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withKeyHolderId;
|
||||
import static org.sufficientlysecure.keychain.matcher.DrawableMatcher.withDrawable;
|
||||
|
||||
//TODO This test is disabled because it needs to be fixed to work with updated code
|
||||
//@RunWith(AndroidJUnit4.class)
|
||||
//@LargeTest
|
||||
public class MiscCryptOperationTests {
|
||||
|
||||
@Rule
|
||||
public final IntentsTestRule<MainActivity> mActivityRule
|
||||
= new IntentsTestRule<MainActivity>(MainActivity.class) {
|
||||
@Override
|
||||
protected Intent getActivityIntent() {
|
||||
Intent intent = super.getActivityIntent();
|
||||
intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true);
|
||||
intent.putExtra(MainActivity.EXTRA_INIT_FRAG, MainActivity.ID_ENCRYPT_DECRYPT);
|
||||
return intent;
|
||||
}
|
||||
};
|
||||
private Activity mActivity;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
// clear dis shit
|
||||
Preferences.getPreferences(getInstrumentation().getTargetContext()).clear();
|
||||
|
||||
mActivity = mActivityRule.getActivity();
|
||||
|
||||
AndroidTestHelpers.copyFiles();
|
||||
|
||||
// import these two, make sure they're there
|
||||
importKeysFromResource(mActivity, "x.sec.asc");
|
||||
|
||||
// make sure no passphrases are cached
|
||||
PassphraseCacheService.clearCachedPassphrases(mActivity);
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testDecryptNonPgpFile() throws Exception {
|
||||
|
||||
// decrypt any non-pgp file
|
||||
File file = pickRandom(getImageNames());
|
||||
handleOpenFileIntentKitKat(file);
|
||||
onView(withId(R.id.decrypt_files)).perform(click());
|
||||
|
||||
{ // decrypt
|
||||
|
||||
// open context menu
|
||||
onView(allOf(isDescendantOfA(isRecyclerItemView(R.id.decrypted_files_list,
|
||||
hasDescendant(allOf(
|
||||
hasDescendant(withDrawable(R.drawable.status_signature_invalid_cutout_24dp, true)),
|
||||
hasDescendant(withText(R.string.msg_dc_error_invalid_data)))))),
|
||||
withId(R.id.result_error_log))).perform(click());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testDecryptEmptySelection() throws Exception {
|
||||
|
||||
// decrypt any non-pgp file
|
||||
handleOpenFileEmptyKitKat();
|
||||
onView(withId(R.id.decrypt_files)).perform(click());
|
||||
|
||||
checkSnackbar(Style.ERROR, R.string.no_file_selected);
|
||||
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testDecryptEmptyClipboard() throws Exception {
|
||||
|
||||
// decrypt any non-pgp file
|
||||
ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText("", ""));
|
||||
|
||||
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
|
||||
checkSnackbar(Style.ERROR, R.string.error_clipboard_empty);
|
||||
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testDecryptNonPgpClipboard() throws Exception {
|
||||
|
||||
// decrypt any non-pgp file
|
||||
ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, randomString(0, 50));
|
||||
clipboard.setPrimaryClip(clip);
|
||||
onView(withId(R.id.decrypt_from_clipboard)).perform(click());
|
||||
|
||||
{ // decrypt
|
||||
|
||||
// open context menu
|
||||
onView(allOf(isDescendantOfA(isRecyclerItemView(R.id.decrypted_files_list,
|
||||
hasDescendant(allOf(
|
||||
hasDescendant(withDrawable(R.drawable.status_signature_invalid_cutout_24dp, true)),
|
||||
hasDescendant(withText(R.string.msg_dc_error_invalid_data)))))),
|
||||
withId(R.id.result_error_log))).perform(click());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.KITKAT)
|
||||
private void handleOpenFileEmptyKitKat() {
|
||||
Intent data = new Intent();
|
||||
data.setData(null);
|
||||
|
||||
Intents.intending(allOf(
|
||||
hasAction(Intent.ACTION_OPEN_DOCUMENT),
|
||||
hasType("*/*"),
|
||||
hasCategories(hasItem(Intent.CATEGORY_OPENABLE))
|
||||
// hasExtraWithKey(Intent.EXTRA_ALLOW_MULTIPLE)
|
||||
)).respondWith(
|
||||
new ActivityResult(Activity.RESULT_OK, data)
|
||||
);
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.KITKAT)
|
||||
private void handleOpenFileIntentKitKat(File file) {
|
||||
Intent data = new Intent();
|
||||
data.setData(Uri.fromFile(file));
|
||||
|
||||
Intents.intending(allOf(
|
||||
hasAction(Intent.ACTION_OPEN_DOCUMENT),
|
||||
hasType("*/*"),
|
||||
hasCategories(hasItem(Intent.CATEGORY_OPENABLE))
|
||||
// hasExtraWithKey(Intent.EXTRA_ALLOW_MULTIPLE)
|
||||
)).respondWith(
|
||||
new ActivityResult(Activity.RESULT_OK, data)
|
||||
);
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testEncryptTokenFromKeyView() throws Exception {
|
||||
onView(allOf(
|
||||
isAssignableFrom(RecyclerView.class),
|
||||
withId(android.R.id.list)))
|
||||
.perform(actionOnHolderItem(
|
||||
withKeyHolderId(0x9D604D2F310716A3L), click()));
|
||||
|
||||
onView(withId(R.id.view_key_action_encrypt_text)).perform(click());
|
||||
|
||||
// make sure the encrypt is correctly set
|
||||
onView(withId(R.id.result_encryption_icon)).check(matches(withDisplayedChild(1)));
|
||||
// TODO check token id
|
||||
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testMenuSaveDefault() throws Exception {
|
||||
|
||||
onView(withId(R.id.encrypt_files)).perform(click());
|
||||
|
||||
{ // save checked options
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(mActivity);
|
||||
|
||||
// check initial button states
|
||||
onView(allOf(withId(R.id.checkbox),
|
||||
hasSibling(withChild(withText(R.string.label_delete_after_encryption)))))
|
||||
.check(matches(isNotChecked()));
|
||||
onView(allOf(withId(R.id.checkbox), hasSibling(withChild(withText(R.string.label_enable_compression)))))
|
||||
.check(matches(isChecked()));
|
||||
onView(allOf(withId(R.id.checkbox), hasSibling(withChild(withText(R.string.label_encrypt_filenames)))))
|
||||
.check(matches(isChecked()));
|
||||
onView(allOf(withId(R.id.checkbox), hasSibling(withChild(withText(R.string.label_file_ascii_armor)))))
|
||||
.check(matches(isNotChecked()));
|
||||
|
||||
// press some buttons
|
||||
|
||||
onView(withText(R.string.label_enable_compression)).perform(click());
|
||||
checkSnackbar(Style.OK, R.string.snack_compression_off);
|
||||
onView(withText(R.string.btn_save_default)).perform(click());
|
||||
checkSnackbar(Style.OK, R.string.btn_saved);
|
||||
dismissSnackbar();
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(mActivity);
|
||||
onView(withText(R.string.label_encrypt_filenames)).perform(click());
|
||||
checkSnackbar(Style.OK, R.string.snack_encrypt_filenames_off);
|
||||
onView(withText(R.string.btn_save_default)).perform(click());
|
||||
checkSnackbar(Style.OK, R.string.btn_saved);
|
||||
dismissSnackbar();
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(mActivity);
|
||||
onView(withText(R.string.label_file_ascii_armor)).perform(click());
|
||||
checkSnackbar(Style.OK, R.string.snack_armor_on);
|
||||
onView(withText(R.string.btn_save_default)).perform(click());
|
||||
checkSnackbar(Style.OK, R.string.btn_saved);
|
||||
dismissSnackbar();
|
||||
|
||||
}
|
||||
|
||||
pressBack();
|
||||
onView(withId(R.id.encrypt_files)).perform(click());
|
||||
|
||||
{ // save checked options
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(mActivity);
|
||||
|
||||
// check initial button states (as saved from before!)
|
||||
onView(allOf(withId(R.id.checkbox),
|
||||
hasSibling(withChild(withText(R.string.label_delete_after_encryption)))))
|
||||
.check(matches(isNotChecked()));
|
||||
onView(allOf(withId(R.id.checkbox), hasSibling(withChild(withText(R.string.label_enable_compression)))))
|
||||
.check(matches(isNotChecked()));
|
||||
onView(allOf(withId(R.id.checkbox), hasSibling(withChild(withText(R.string.label_encrypt_filenames)))))
|
||||
.check(matches(isNotChecked()));
|
||||
onView(allOf(withId(R.id.checkbox), hasSibling(withChild(withText(R.string.label_file_ascii_armor)))))
|
||||
.check(matches(isChecked()));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,181 +0,0 @@
|
|||
/*
|
||||
* 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.ui;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Instrumentation.ActivityResult;
|
||||
import android.content.Intent;
|
||||
import android.support.test.espresso.intent.rule.IntentsTestRule;
|
||||
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Rule;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
|
||||
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.intent.Intents.intending;
|
||||
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction;
|
||||
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasData;
|
||||
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtra;
|
||||
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtraWithKey;
|
||||
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasFlags;
|
||||
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasType;
|
||||
import static android.support.test.espresso.intent.matcher.UriMatchers.hasHost;
|
||||
import static android.support.test.espresso.intent.matcher.UriMatchers.hasScheme;
|
||||
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||
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.hamcrest.Matchers.equalTo;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.checkSnackbar;
|
||||
import static org.sufficientlysecure.keychain.AndroidTestHelpers.randomString;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.isRecyclerItemView;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withEncryptionStatus;
|
||||
import static org.sufficientlysecure.keychain.matcher.CustomMatchers.withSignatureNone;
|
||||
|
||||
//TODO This test is disabled because it needs to be fixed to work with updated code
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
//@RunWith(AndroidJUnit4.class)
|
||||
//@LargeTest
|
||||
public class SymmetricTextOperationTests {
|
||||
|
||||
public static final String PASSPHRASE = randomString(5, 20);
|
||||
|
||||
@Rule
|
||||
public final IntentsTestRule<MainActivity> mActivity
|
||||
= new IntentsTestRule<MainActivity>(MainActivity.class) {
|
||||
@Override
|
||||
protected Intent getActivityIntent() {
|
||||
Intent intent = super.getActivityIntent();
|
||||
intent.putExtra(MainActivity.EXTRA_SKIP_FIRST_TIME, true);
|
||||
intent.putExtra(MainActivity.EXTRA_INIT_FRAG, MainActivity.ID_ENCRYPT_DECRYPT);
|
||||
return intent;
|
||||
}
|
||||
};
|
||||
|
||||
//@Test
|
||||
public void testSymmetricCryptClipboard() throws Exception {
|
||||
|
||||
mActivity.getActivity();
|
||||
|
||||
String text = randomString(10, 30);
|
||||
|
||||
// navigate to encrypt/decrypt
|
||||
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(isRecyclerItemView(R.id.decrypted_files_list,
|
||||
hasDescendant(withText(R.string.filename_unknown_text))))
|
||||
.check(matches(allOf(withEncryptionStatus(true), withSignatureNone())));
|
||||
|
||||
intending(allOf(
|
||||
hasAction("android.intent.action.CHOOSER"),
|
||||
hasExtra(equalTo(Intent.EXTRA_INTENT), allOf(
|
||||
hasAction(Intent.ACTION_VIEW),
|
||||
hasFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION),
|
||||
hasData(allOf(hasScheme("content"), hasHost(TemporaryFileProvider.AUTHORITY))),
|
||||
hasType("text/plain")
|
||||
))
|
||||
)).respondWith(new ActivityResult(Activity.RESULT_OK, null));
|
||||
|
||||
onView(allOf(isDescendantOfA(isRecyclerItemView(R.id.decrypted_files_list,
|
||||
hasDescendant(withText(R.string.filename_unknown_text)))),
|
||||
withId(R.id.file))).perform(click());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//@Test
|
||||
public void testSymmetricCryptShare() throws Exception {
|
||||
|
||||
mActivity.getActivity();
|
||||
|
||||
String text = randomString(10, 30);
|
||||
|
||||
// navigate to encrypt/decrypt
|
||||
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.passphraseAgain)).perform(typeText(PASSPHRASE));
|
||||
|
||||
onView(withId(R.id.encrypt_text_text)).check(matches(withText(text)));
|
||||
|
||||
intending(allOf(
|
||||
hasAction("android.intent.action.CHOOSER"),
|
||||
hasExtra(equalTo(Intent.EXTRA_INTENT), allOf(
|
||||
hasAction(Intent.ACTION_SEND),
|
||||
hasFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION),
|
||||
hasExtraWithKey(Intent.EXTRA_TEXT),
|
||||
hasType("text/plain")
|
||||
))
|
||||
)).respondWith(new ActivityResult(Activity.RESULT_OK, null));
|
||||
|
||||
onView(withId(R.id.encrypt_share)).perform(click());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* 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.ui.widget;
|
||||
|
||||
|
||||
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.espresso.matcher.ViewMatchers;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.view.KeyEvent;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
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.AndroidTestHelpers.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;
|
||||
|
||||
|
||||
//TODO This test is disabled because it needs to be fixed to work with updated code
|
||||
//@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(ViewMatchers.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));
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -166,16 +166,6 @@
|
|||
<data android:mimeType="vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.SafeSlingerActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/title_exchange_keys"
|
||||
android:parentActivityName=".ui.MainActivity"
|
||||
android:windowSoftInputMode="stateHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".ui.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.EncryptFilesActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIGmzCCBIOgAwIBAgIJAPzhpcIBaOeNMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYD
|
||||
VQQGEwJVUzELMAkGA1UECBMCTlkxETAPBgNVBAcTCE5ldyBZb3JrMRQwEgYDVQQK
|
||||
EwtLZXliYXNlIExMQzEXMBUGA1UECxMOQ2VydCBBdXRob3JpdHkxEzARBgNVBAMT
|
||||
CmtleWJhc2UuaW8xHDAaBgkqhkiG9w0BCQEWDWNhQGtleWJhc2UuaW8wHhcNMTQw
|
||||
MTAyMTY0MjMzWhcNMjMxMjMxMTY0MjMzWjCBjzELMAkGA1UEBhMCVVMxCzAJBgNV
|
||||
BAgTAk5ZMREwDwYDVQQHEwhOZXcgWW9yazEUMBIGA1UEChMLS2V5YmFzZSBMTEMx
|
||||
FzAVBgNVBAsTDkNlcnQgQXV0aG9yaXR5MRMwEQYDVQQDEwprZXliYXNlLmlvMRww
|
||||
GgYJKoZIhvcNAQkBFg1jYUBrZXliYXNlLmlvMIICIjANBgkqhkiG9w0BAQEFAAOC
|
||||
Ag8AMIICCgKCAgEA3sLA6ZG8uOvmlFvFLVIOURmcQrZyMFKbVu9/TeDiemls3w3/
|
||||
JzVTduD+7KiUi9R7QcCW/V1ZpReTfunm7rfACiJ1fpIkjSQrgsvKDLghIzxIS5FM
|
||||
I8utet5p6QtuJhaAwmmXn8xX05FvqWNbrcXRdpL4goFdigPsFK2xhTUiWatLMste
|
||||
oShI7+zmrgkx75LeLMD0bL2uOf87JjOzbY8x2sUIZLGwPoATyG8WS38ey6KkJxRj
|
||||
AhG3p+OTYEjYSrsAtQA6ImbeDpfSHKOB8HF3nVp//Eb4HEiEsWwBRbQXvAWh3DYL
|
||||
GukFW0wiO0HVCoWY+bHL/Mqa0NdRGOlLsbL4Z4pLrhqKgSDU8umX9YuNRRaB0P5n
|
||||
TkzyU6axHqzq990Gep/I62bjsBdYYp+DjSPK43mXRrfWJl2NTcl8xKAyfsOW+9hQ
|
||||
9vwK0tpSicNxfYuUZs0BhfjSZ/Tc6Z1ERdgUYRiXTtohl+SRA2IgZMloHCllVMNj
|
||||
EjXhguvHgLAOrcuyhVBupiUQGUHQvkMsr1Uz8VPNDFOJedwucRU2AaR881bknnSb
|
||||
ds9+zNLsvUFV+BK7Qdnt/WkFpYL78rGwY47msi9Ooddx6fPyeg3qkJGM6cwn/boy
|
||||
w9lQeleYDq8kyJdixIAxtAskNzRPJ4nDu2izTfByQoM8epwAWboc/gNFObMCAwEA
|
||||
AaOB9zCB9DAdBgNVHQ4EFgQURqpATOw1gVVrzlqqFKbkfaKXvwowgcQGA1UdIwSB
|
||||
vDCBuYAURqpATOw1gVVrzlqqFKbkfaKXvwqhgZWkgZIwgY8xCzAJBgNVBAYTAlVT
|
||||
MQswCQYDVQQIEwJOWTERMA8GA1UEBxMITmV3IFlvcmsxFDASBgNVBAoTC0tleWJh
|
||||
c2UgTExDMRcwFQYDVQQLEw5DZXJ0IEF1dGhvcml0eTETMBEGA1UEAxMKa2V5YmFz
|
||||
ZS5pbzEcMBoGCSqGSIb3DQEJARYNY2FAa2V5YmFzZS5pb4IJAPzhpcIBaOeNMAwG
|
||||
A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggIBAA3Z5FIhulYghMuHdcHYTYWc
|
||||
7xT5WD4hXQ0WALZs4p5Y+b2Af54o6v1wUE1Au97FORq5CsFXX/kGl/JzzTimeucn
|
||||
YJwGuXMpilrlHCBAL5/lSQjA7qbYIolQ3SB9ON+LYuF1jKB9k8SqNp7qzucxT3tO
|
||||
b8ZMDEPNsseC7NE2uwNtcW3yrTh6WZnSqg/jwswiWjHYDdG7U8FjMYlRol3wPux2
|
||||
PizGbSgiR+ztI2OthxtxNWMrT9XKxNQTpcxOXnLuhiSwqH8PoY17ecP8VPpaa0K6
|
||||
zym0zSkbroqydazaxcXRk3eSlc02Ktk7HzRzuqQQXhRMkxVnHbFHgGsz03L533pm
|
||||
mlIEgBMggZkHwNvs1LR7f3v2McdKulDH7Mv8yyfguuQ5Jxxt7RJhUuqSudbEhoaM
|
||||
6jAJwBkMFxsV2YnyFEd3eZ/qBYPf7TYHhyzmHW6WkSypGqSnXd4gYpJ8o7LxSf4F
|
||||
inLjxRD+H9Xn1UVXWLM0gaBB7zZcXd2zjMpRsWgezf5IR5vyakJsc7fxzgor3Qeq
|
||||
Ri6LvdEkhhFVl5rHMQBwNOPngySrq8cs/ikTLTfQVTYXXA4Ba1YyiMOlfaR1LhKw
|
||||
If1AkUV0tfCTNRZ01EotKSK77+o+k214n+BAu+7mO+9B5Kb7lMFQcuWCHXKYB2Md
|
||||
cT7Yh09F0QpFUd0ymEfv
|
||||
-----END CERTIFICATE-----
|
|
@ -1,30 +0,0 @@
|
|||
package android.support.design.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.view.WindowInsetsCompat;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class FixedCollapsingToolbarLayout extends CollapsingToolbarLayout {
|
||||
|
||||
public FixedCollapsingToolbarLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public FixedCollapsingToolbarLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
// During the super call to onMeasure(), we'll save a copy of mLastInsets,
|
||||
// consume the insets of mLastInsets so the super call has no insets to work with,
|
||||
// then re-assign mLastInsets to what it was before the super call.
|
||||
WindowInsetsCompat oldInsets = mLastInsets;
|
||||
if (mLastInsets != null) {
|
||||
mLastInsets = mLastInsets.consumeSystemWindowInsets();
|
||||
}
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
mLastInsets = oldInsets;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,17 +1,19 @@
|
|||
package org.bouncycastle.openpgp.operator.jcajce;
|
||||
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.eddsa.EdDSAEngine;
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.eddsa.spec.EdDSANamedCurveTable;
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.eddsa.spec.EdDSAParameterSpec;
|
||||
import org.bouncycastle.jcajce.util.DefaultJcaJceHelper;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPRuntimeOperationException;
|
||||
import org.bouncycastle.openpgp.operator.PGPContentSigner;
|
||||
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
|
||||
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.*;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Provider;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
|
||||
public class EdDsaAuthenticationContentSignerBuilder implements PGPContentSignerBuilder {
|
||||
private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
|
||||
|
@ -33,9 +35,8 @@ public class EdDsaAuthenticationContentSignerBuilder implements PGPContentSigner
|
|||
return this;
|
||||
}
|
||||
|
||||
private Signature createSignature() throws NoSuchAlgorithmException {
|
||||
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519");
|
||||
return new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
|
||||
private Signature createSignature() throws PGPException {
|
||||
return new OperatorHelper(new DefaultJcaJceHelper()).createSignature(keyAlgorithm, hashAlgorithm);
|
||||
}
|
||||
|
||||
public PGPContentSigner build(final int signatureType, final long keyID, final PrivateKey privateKey)
|
||||
|
@ -43,12 +44,12 @@ public class EdDsaAuthenticationContentSignerBuilder implements PGPContentSigner
|
|||
Signature signatureEdDsa;
|
||||
try {
|
||||
signatureEdDsa = createSignature();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
} catch (PGPException e) {
|
||||
throw new PGPException("unable to create Signature.", e);
|
||||
}
|
||||
final Signature signature = signatureEdDsa;
|
||||
|
||||
final ByteArrayOutputStream dataOutputStream = new ByteArrayOutputStream();
|
||||
final PGPDigestCalculator digestCalculator = new JcaPGPDigestCalculatorProviderBuilder().build().get(hashAlgorithm);
|
||||
|
||||
try {
|
||||
signature.initSign(privateKey);
|
||||
|
@ -74,11 +75,12 @@ public class EdDsaAuthenticationContentSignerBuilder implements PGPContentSigner
|
|||
}
|
||||
|
||||
public OutputStream getOutputStream() {
|
||||
return new SignatureOutputStream(signature);
|
||||
return digestCalculator.getOutputStream();
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
try {
|
||||
signature.update(digestCalculator.getDigest());
|
||||
return signature.sign();
|
||||
} catch (SignatureException e) {
|
||||
throw new PGPRuntimeOperationException("Unable to create signature: " + e.getMessage(), e);
|
||||
|
@ -86,7 +88,7 @@ public class EdDsaAuthenticationContentSignerBuilder implements PGPContentSigner
|
|||
}
|
||||
|
||||
public byte[] getDigest() {
|
||||
return null;
|
||||
return digestCalculator.getDigest();
|
||||
}
|
||||
};
|
||||
}
|
|
@ -16,8 +16,6 @@ import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
|
|||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.Provider;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
|
@ -7,6 +7,15 @@
|
|||
package org.bouncycastle.openpgp.operator.jcajce;
|
||||
|
||||
|
||||
import org.bouncycastle.bcpg.S2K;
|
||||
import org.bouncycastle.jcajce.util.DefaultJcaJceHelper;
|
||||
import org.bouncycastle.jcajce.util.NamedJcaJceHelper;
|
||||
import org.bouncycastle.jcajce.util.ProviderJcaJceHelper;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Provider;
|
||||
|
@ -15,13 +24,6 @@ import javax.crypto.BadPaddingException;
|
|||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import org.bouncycastle.bcpg.S2K;
|
||||
import org.bouncycastle.jcajce.util.DefaultJcaJceHelper;
|
||||
import org.bouncycastle.jcajce.util.NamedJcaJceHelper;
|
||||
import org.bouncycastle.jcajce.util.ProviderJcaJceHelper;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
|
||||
|
||||
|
||||
/** This is a builder for a special PBESecretKeyDecryptor which is parametrized by a
|
||||
|
@ -90,7 +92,7 @@ public class SessionKeySecretKeyDecryptorBuilder
|
|||
{
|
||||
Cipher c = helper.createCipher(PGPUtil.getSymmetricCipherName(encAlgorithm) + "/CFB/NoPadding");
|
||||
|
||||
c.init(Cipher.DECRYPT_MODE, PGPUtil.makeSymmetricKey(encAlgorithm, key), new IvParameterSpec(iv));
|
||||
c.init(Cipher.DECRYPT_MODE, JcaJcePGPUtil.makeSymmetricKey(encAlgorithm, key), new IvParameterSpec(iv));
|
||||
|
||||
return c.doFinal(keyData, keyOff, keyLen);
|
||||
}
|
|
@ -132,7 +132,6 @@ public final class Constants {
|
|||
public static final String FIRST_TIME_APP = "firstTimeApp";
|
||||
public static final String CACHED_CONSOLIDATE = "cachedConsolidate";
|
||||
public static final String SEARCH_KEYSERVER = "search_keyserver_pref";
|
||||
public static final String SEARCH_KEYBASE = "search_keybase_pref";
|
||||
public static final String SEARCH_WEB_KEY_DIRECTORY = "search_wkd_pref";
|
||||
public static final String USE_NUMKEYPAD_FOR_SECURITY_TOKEN_PIN = "useNumKeypadForYubikeyPin";
|
||||
public static final String ENCRYPT_FILENAMES = "encryptFilenames";
|
||||
|
@ -154,7 +153,6 @@ public final class Constants {
|
|||
public static final String ENABLE_WIFI_SYNC_ONLY = "enableWifiSyncOnly";
|
||||
public static final String SYNC_WORK_UUID = "syncWorkUuid";
|
||||
// other settings
|
||||
public static final String EXPERIMENTAL_ENABLE_KEYBASE = "experimentalEnableKeybase";
|
||||
public static final String EXPERIMENTAL_USB_ALLOW_UNTESTED = "experimentalUsbAllowUntested";
|
||||
public static final String EXPERIMENTAL_SMARTPGP_VERIFY_AUTHORITY = "smartpgp_authorities_pref";
|
||||
public static final String EXPERIMENTAL_SMARTPGP_AUTHORITIES = "smartpgp_authorities";
|
||||
|
@ -178,9 +176,9 @@ public final class Constants {
|
|||
|
||||
// we generally only track booleans. never snoop around in the user's string settings!!
|
||||
public static final List<String> ANALYTICS_PREFS = Arrays.asList(USE_NORMAL_PROXY, USE_TOR_PROXY,
|
||||
SYNC_CONTACTS, SYNC_KEYSERVER, ENABLE_WIFI_SYNC_ONLY, EXPERIMENTAL_ENABLE_KEYBASE,
|
||||
SYNC_CONTACTS, SYNC_KEYSERVER, ENABLE_WIFI_SYNC_ONLY,
|
||||
EXPERIMENTAL_USB_ALLOW_UNTESTED,
|
||||
PASSPHRASE_CACHE_SUBS, SEARCH_KEYSERVER, SEARCH_KEYBASE, SEARCH_WEB_KEY_DIRECTORY,
|
||||
PASSPHRASE_CACHE_SUBS, SEARCH_KEYSERVER, SEARCH_WEB_KEY_DIRECTORY,
|
||||
TEXT_USE_COMPRESSION, TEXT_SELF_ENCRYPT, FILE_USE_COMPRESSION, FILE_SELF_ENCRYPT, USE_ARMOR,
|
||||
USE_NUMKEYPAD_FOR_SECURITY_TOKEN_PIN, ENCRYPT_FILENAMES);
|
||||
}
|
||||
|
|
|
@ -18,23 +18,27 @@
|
|||
package org.sufficientlysecure.keychain;
|
||||
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.Security;
|
||||
import java.util.HashMap;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.os.Build;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.sufficientlysecure.keychain.analytics.AnalyticsManager;
|
||||
import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager;
|
||||
import org.sufficientlysecure.keychain.network.TlsCertificatePinning;
|
||||
import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
|
||||
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
|
||||
import org.sufficientlysecure.keychain.keysync.KeyserverSyncManager;
|
||||
import org.sufficientlysecure.keychain.util.PRNGFixes;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import timber.log.Timber;
|
||||
|
@ -102,12 +106,16 @@ public class KeychainApplication extends Application {
|
|||
TlsCertificatePinning.addPinnedCertificate("keys.openpgp.org", getAssets(), "LetsEncryptCA.cer");
|
||||
TlsCertificatePinning.addPinnedCertificate("hkps.pool.sks-keyservers.net", getAssets(), "hkps.pool.sks-keyservers.net.CA.cer");
|
||||
TlsCertificatePinning.addPinnedCertificate("pgp.mit.edu", getAssets(), "pgp.mit.edu.cer");
|
||||
TlsCertificatePinning.addPinnedCertificate("api.keybase.io", getAssets(), "api.keybase.io.CA.cer");
|
||||
TlsCertificatePinning.addPinnedCertificate("keyserver.ubuntu.com", getAssets(), "LetsEncryptCA.cer");
|
||||
|
||||
// only set up the rest on our main process
|
||||
if (!BuildConfig.APPLICATION_ID.equals(getProcessName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
KeyserverSyncManager.updateKeyserverSyncScheduleAsync(this, false);
|
||||
|
||||
TemporaryFileProvider.scheduleCleanupImmediately();
|
||||
TemporaryFileProvider.scheduleCleanupImmediately(getApplicationContext());
|
||||
|
||||
analyticsManager = AnalyticsManager.getInstance(getApplicationContext());
|
||||
analyticsManager.initialize(this);
|
||||
|
@ -162,4 +170,32 @@ public class KeychainApplication extends Application {
|
|||
public AnalyticsManager getAnalyticsManager() {
|
||||
return analyticsManager;
|
||||
}
|
||||
|
||||
public static String getProcessName() {
|
||||
if (Build.VERSION.SDK_INT >= 28)
|
||||
return Application.getProcessName();
|
||||
|
||||
// Using the same technique as Application.getProcessName() for older devices
|
||||
// Using reflection since ActivityThread is an internal API
|
||||
|
||||
try {
|
||||
@SuppressLint("PrivateApi")
|
||||
Class<?> activityThread = Class.forName("android.app.ActivityThread");
|
||||
|
||||
// Before API 18, the method was incorrectly named "currentPackageName", but it still returned the process name
|
||||
// See https://github.com/aosp-mirror/platform_frameworks_base/commit/b57a50bd16ce25db441da5c1b63d48721bb90687
|
||||
String methodName = Build.VERSION.SDK_INT >= 18 ? "currentProcessName" : "currentPackageName";
|
||||
|
||||
Method getProcessName = activityThread.getDeclaredMethod(methodName);
|
||||
return (String) getProcessName.invoke(null);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,11 +23,11 @@ import java.io.FileInputStream;
|
|||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import android.arch.persistence.db.SupportSQLiteOpenHelper;
|
||||
import android.arch.persistence.db.SupportSQLiteOpenHelper.Callback;
|
||||
import android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration;
|
||||
import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper;
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper.Callback;
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration;
|
||||
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.SQLException;
|
||||
|
|
|
@ -6,8 +6,8 @@ import android.app.NotificationManager;
|
|||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.annotation.StringRes;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
|
||||
public class NotificationChannelManager {
|
||||
|
|
|
@ -13,7 +13,6 @@ import android.text.method.LinkMovementMethod;
|
|||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.BuildConfig;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.KeychainApplication;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.SettingsActivity;
|
||||
|
|
|
@ -19,11 +19,11 @@ package org.sufficientlysecure.keychain.compatibility;
|
|||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatDelegate;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
|
|
@ -21,7 +21,7 @@ package org.sufficientlysecure.keychain.compatibility;
|
|||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ package org.sufficientlysecure.keychain.daos;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import android.arch.persistence.db.SupportSQLiteQuery;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteQuery;
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.squareup.sqldelight.RowMapper;
|
||||
|
|
|
@ -24,7 +24,7 @@ import java.util.List;
|
|||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.squareup.sqldelight.SqlDelightQuery;
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel.DeleteByIdentifier;
|
||||
|
|
|
@ -3,7 +3,7 @@ package org.sufficientlysecure.keychain.daos;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
|
||||
import org.sufficientlysecure.keychain.CertsModel.InsertCert;
|
||||
import org.sufficientlysecure.keychain.KeyRingsPublicModel.InsertKeyRingPublic;
|
||||
|
|
|
@ -24,7 +24,7 @@ import java.util.List;
|
|||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.squareup.sqldelight.SqlDelightQuery;
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
|
|
|
@ -25,10 +25,10 @@ import java.util.Collections;
|
|||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.collection.LongSparseArray;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
||||
import org.sufficientlysecure.keychain.KeyRingsPublicModel.DeleteByMasterKeyId;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package org.sufficientlysecure.keychain.keyimport;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.sufficientlysecure.keychain.util.ParcelableProxy;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
@ -45,9 +45,6 @@ public class CloudSearch {
|
|||
if (cloudPrefs.isKeyserverEnabled()) {
|
||||
servers.add(HkpKeyserverClient.fromHkpKeyserverAddress(cloudPrefs.getKeyserver()));
|
||||
}
|
||||
if (cloudPrefs.isKeybaseEnabled()) {
|
||||
servers.add(KeybaseKeyserverClient.getInstance());
|
||||
}
|
||||
if (cloudPrefs.isFacebookEnabled()) {
|
||||
servers.add(FacebookKeyserverClient.getInstance());
|
||||
}
|
||||
|
|
|
@ -26,8 +26,8 @@ import java.util.List;
|
|||
import java.util.regex.Matcher;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
|
|
|
@ -22,8 +22,8 @@ import java.net.URI;
|
|||
import java.net.URISyntaxException;
|
||||
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package org.sufficientlysecure.keychain.keyimport;
|
||||
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.ResponseBody;
|
||||
|
|
|
@ -77,14 +77,8 @@ public class ImportKeysList extends ArrayList<ImportKeysListEntry> {
|
|||
|
||||
if (incoming.getKeyserver() != null) {
|
||||
existing.setKeyserver(incoming.getKeyserver());
|
||||
// Mail addresses returned by HKP servers are preferred over keybase.io IDs
|
||||
existing.setPrimaryUserId(incoming.getPrimaryUserId());
|
||||
|
||||
modified = true;
|
||||
} else if (incoming.getKeybaseName() != null) {
|
||||
// to work properly, Keybase-sourced/Facebook-sourced entries need to pass along the
|
||||
// identifying name/id
|
||||
existing.setKeybaseName(incoming.getKeybaseName());
|
||||
modified = true;
|
||||
} else if (incoming.getFbUsername() != null) {
|
||||
existing.setFbUsername(incoming.getFbUsername());
|
||||
|
|
|
@ -17,6 +17,18 @@
|
|||
|
||||
package org.sufficientlysecure.keychain.keyimport;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
@ -27,18 +39,6 @@ import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
|||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
private static final long serialVersionUID = -7797972103284992662L;
|
||||
|
||||
|
@ -64,7 +64,6 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||
|
||||
private UserId mPrimaryUserId;
|
||||
private HkpKeyserverAddress mKeyserver;
|
||||
private String mKeybaseName;
|
||||
private String mFbUsername;
|
||||
|
||||
private String mQuery;
|
||||
|
@ -197,14 +196,6 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||
mKeyserver = keyserver;
|
||||
}
|
||||
|
||||
public String getKeybaseName() {
|
||||
return mKeybaseName;
|
||||
}
|
||||
|
||||
public void setKeybaseName(String keybaseName) {
|
||||
mKeybaseName = keybaseName;
|
||||
}
|
||||
|
||||
public String getFbUsername() {
|
||||
return mFbUsername;
|
||||
}
|
||||
|
@ -257,20 +248,11 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||
return modified;
|
||||
}
|
||||
|
||||
public ArrayList<String> getKeybaseUserIds() {
|
||||
ArrayList<String> keybaseUserIds = new ArrayList<>();
|
||||
for (String s : mUserIds) {
|
||||
if (s.contains(":"))
|
||||
keybaseUserIds.add(s);
|
||||
}
|
||||
return keybaseUserIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for later querying from keyserver
|
||||
*/
|
||||
public ImportKeysListEntry() {
|
||||
// keys from keyserver are always public keys; from keybase too
|
||||
// keys from keyserver are always public keys
|
||||
mSecretKey = false;
|
||||
|
||||
mUserIds = new ArrayList<>();
|
||||
|
@ -339,20 +321,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||
private void sortMergedUserIds() {
|
||||
mSortedUserIds = new ArrayList<>(mMergedUserIds.entrySet());
|
||||
|
||||
Collections.sort(mSortedUserIds, new Comparator<Map.Entry<String, HashSet<String>>>() {
|
||||
@Override
|
||||
public int compare(Map.Entry<String, HashSet<String>> entry1,
|
||||
Map.Entry<String, HashSet<String>> entry2) {
|
||||
|
||||
// sort keybase UserIds after non-Keybase
|
||||
boolean e1IsKeybase = entry1.getKey().contains(":");
|
||||
boolean e2IsKeybase = entry2.getKey().contains(":");
|
||||
if (e1IsKeybase != e2IsKeybase) {
|
||||
return (e1IsKeybase) ? 1 : -1;
|
||||
}
|
||||
return entry1.getKey().compareTo(entry2.getKey());
|
||||
}
|
||||
});
|
||||
Collections.sort(mSortedUserIds,
|
||||
(entry1, entry2) -> entry1.getKey().compareTo(entry2.getKey()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -377,7 +347,6 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||
dest.writeString(mAlgorithm);
|
||||
dest.writeByte((byte) (mSecretKey ? 1 : 0));
|
||||
dest.writeParcelable(mKeyserver, flags);
|
||||
dest.writeString(mKeybaseName);
|
||||
dest.writeString(mFbUsername);
|
||||
}
|
||||
|
||||
|
@ -400,7 +369,6 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||
vr.mAlgorithm = source.readString();
|
||||
vr.mSecretKey = source.readByte() == 1;
|
||||
vr.mKeyserver = source.readParcelable(HkpKeyserverAddress.class.getClassLoader());
|
||||
vr.mKeybaseName = source.readString();
|
||||
vr.mFbUsername = source.readString();
|
||||
|
||||
return vr;
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.keyimport;
|
||||
|
||||
import com.textuality.keybase.lib.KeybaseException;
|
||||
import com.textuality.keybase.lib.KeybaseQuery;
|
||||
import com.textuality.keybase.lib.Match;
|
||||
import com.textuality.keybase.lib.User;
|
||||
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.network.OkHttpKeybaseClient;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableProxy;
|
||||
import timber.log.Timber;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class KeybaseKeyserverClient implements KeyserverClient {
|
||||
|
||||
public static KeybaseKeyserverClient getInstance() {
|
||||
return new KeybaseKeyserverClient();
|
||||
}
|
||||
|
||||
private KeybaseKeyserverClient() { }
|
||||
|
||||
@Override
|
||||
public ArrayList<ImportKeysListEntry> search(String query, ParcelableProxy proxy) throws QueryFailedException,
|
||||
QueryNeedsRepairException {
|
||||
ArrayList<ImportKeysListEntry> results = new ArrayList<>();
|
||||
|
||||
if (query.startsWith("0x")) {
|
||||
// cut off "0x" if a user is searching for a key id
|
||||
query = query.substring(2);
|
||||
}
|
||||
if (query.isEmpty()) {
|
||||
throw new QueryTooShortException();
|
||||
}
|
||||
|
||||
try {
|
||||
KeybaseQuery keybaseQuery = new KeybaseQuery(new OkHttpKeybaseClient());
|
||||
keybaseQuery.setProxy(proxy.getProxy());
|
||||
Iterable<Match> matches = keybaseQuery.search(query);
|
||||
for (Match match : matches) {
|
||||
results.add(makeEntry(match, query));
|
||||
}
|
||||
} catch (KeybaseException e) {
|
||||
Timber.e(e, "keybase result parsing error");
|
||||
throw new QueryFailedException("Unexpected structure in keybase search result: " + e.getMessage());
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private ImportKeysListEntry makeEntry(Match match, String query) throws KeybaseException {
|
||||
final ImportKeysListEntry entry = new ImportKeysListEntry();
|
||||
entry.setQuery(query);
|
||||
|
||||
entry.setRevoked(false); // keybase doesn’t say anything about revoked keys
|
||||
|
||||
String username = match.getUsername();
|
||||
String fullName = match.getFullName();
|
||||
String fingerprint = match.getFingerprint();
|
||||
entry.setFingerprint(KeyFormattingUtils.convertFingerprintHexFingerprint(fingerprint));
|
||||
|
||||
entry.setKeyIdHex("0x" + match.getKeyID());
|
||||
// so we can query for the keybase id directly, and to identify the location from which the
|
||||
// key is to be retrieved
|
||||
entry.setKeybaseName(username);
|
||||
|
||||
final int bitStrength = match.getBitStrength();
|
||||
entry.setBitStrength(bitStrength);
|
||||
final int algorithmId = match.getAlgorithmId();
|
||||
entry.setAlgorithm(KeyFormattingUtils.getAlgorithmInfo(algorithmId, bitStrength, null));
|
||||
|
||||
ArrayList<String> userIds = new ArrayList<>();
|
||||
String name = "<keybase.io/" + username + ">";
|
||||
if (fullName != null) {
|
||||
name = fullName + " " + name;
|
||||
}
|
||||
userIds.add(name);
|
||||
|
||||
List<String> proofLabels = match.getProofLabels();
|
||||
for (String proofLabel : proofLabels) {
|
||||
userIds.add(proofLabel);
|
||||
}
|
||||
entry.setUserIds(userIds);
|
||||
entry.setPrimaryUserId(name);
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(String id, ParcelableProxy proxy) throws QueryFailedException {
|
||||
try {
|
||||
KeybaseQuery keybaseQuery = new KeybaseQuery(new OkHttpKeybaseClient());
|
||||
keybaseQuery.setProxy(proxy.getProxy());
|
||||
return User.keyForUsername(keybaseQuery, id);
|
||||
} catch (KeybaseException e) {
|
||||
throw new QueryFailedException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(String armoredKey, ParcelableProxy proxy) throws AddKeyException {
|
||||
throw new AddKeyException();
|
||||
}
|
||||
}
|
|
@ -19,14 +19,14 @@ package org.sufficientlysecure.keychain.keyimport;
|
|||
|
||||
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
|
||||
|
||||
/**
|
||||
* This class is a parcelable representation of either a keyring as raw data,
|
||||
* or a (unique) reference to one as a fingerprint, keyid, or keybase name.
|
||||
* or a (unique) reference to one as a fingerprint, or keyid.
|
||||
*/
|
||||
@AutoValue
|
||||
public abstract class ParcelableKeyRing implements Parcelable {
|
||||
|
@ -41,16 +41,14 @@ public abstract class ParcelableKeyRing implements Parcelable {
|
|||
@Nullable
|
||||
public abstract String getKeyIdHex();
|
||||
@Nullable
|
||||
public abstract String getKeybaseName();
|
||||
@Nullable
|
||||
public abstract String getFbUsername();
|
||||
|
||||
public static ParcelableKeyRing createFromEncodedBytes(byte[] bytes) {
|
||||
return new AutoValue_ParcelableKeyRing(bytes, null, null, null, null);
|
||||
return new AutoValue_ParcelableKeyRing(bytes, null, null, null);
|
||||
}
|
||||
|
||||
public static ParcelableKeyRing createFromReference(
|
||||
byte[] expectedFingerprint, String keyIdHex, String keybaseName, String fbUsername) {
|
||||
return new AutoValue_ParcelableKeyRing(null, expectedFingerprint, keyIdHex, keybaseName, fbUsername);
|
||||
byte[] expectedFingerprint, String keyIdHex, String fbUsername) {
|
||||
return new AutoValue_ParcelableKeyRing(null, expectedFingerprint, keyIdHex, fbUsername);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package org.sufficientlysecure.keychain.keyimport;
|
||||
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
package org.sufficientlysecure.keychain.keyimport.processing;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.loader.content.AsyncTaskLoader;
|
||||
import org.sufficientlysecure.keychain.keyimport.CloudSearch;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.keyimport.KeyserverClient;
|
||||
|
@ -51,8 +51,7 @@ public class ImportKeysListCloudLoader
|
|||
*
|
||||
* @param loaderState state containing the string to search on servers for (if it is a
|
||||
* fingerprint, will enforce fingerprint check) and the keyserver to
|
||||
* search on (whether to search on the keyserver, and whether to search
|
||||
* keybase.io)
|
||||
* search on (whether to search on the keyserver)
|
||||
* @param parcelableProxy explicit proxy to use. If null, will retrieve from preferences
|
||||
*/
|
||||
public ImportKeysListCloudLoader(Context context, CloudLoaderState loaderState,
|
||||
|
@ -84,7 +83,7 @@ public class ImportKeysListCloudLoader
|
|||
for (ImportKeysListEntry e : mEntryList) {
|
||||
if (e.getParcelableKeyRing() == null) {
|
||||
e.setParcelableKeyRing(ParcelableKeyRing.createFromReference(e.getFingerprint(), e.getKeyIdHex(),
|
||||
e.getKeybaseName(), e.getFbUsername()));
|
||||
e.getFbUsername()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,8 @@ package org.sufficientlysecure.keychain.keyimport.processing;
|
|||
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.loader.content.AsyncTaskLoader;
|
||||
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.operations.results.GetKeyResult;
|
||||
|
|
|
@ -19,22 +19,22 @@ package org.sufficientlysecure.keychain.keysync;
|
|||
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.support.annotation.WorkerThread;
|
||||
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.work.Constraints.Builder;
|
||||
import androidx.work.NetworkType;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.PeriodicWorkRequest;
|
||||
import androidx.work.State;
|
||||
import androidx.work.SynchronousWorkManager;
|
||||
import androidx.work.WorkInfo;
|
||||
import androidx.work.WorkInfo.State;
|
||||
import androidx.work.WorkManager;
|
||||
import androidx.work.WorkStatus;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
@ -58,30 +58,26 @@ public class KeyserverSyncManager {
|
|||
@WorkerThread
|
||||
private static void updateKeyserverSyncSchedule(Context context, boolean forceReschedule) {
|
||||
Preferences prefs = Preferences.getPreferences(context);
|
||||
WorkManager workManager = WorkManager.getInstance();
|
||||
if (workManager == null) {
|
||||
Timber.e("WorkManager unavailable!");
|
||||
return;
|
||||
}
|
||||
SynchronousWorkManager synchronousWorkManager = workManager.synchronous();
|
||||
if (synchronousWorkManager == null) {
|
||||
Timber.e("WorkManager unavailable!");
|
||||
return;
|
||||
}
|
||||
WorkManager workManager = WorkManager.getInstance(context);
|
||||
|
||||
UUID workUuid = prefs.getKeyserverSyncWorkUuid();
|
||||
WorkStatus status = workUuid != null ? synchronousWorkManager.getStatusByIdSync(workUuid) : null;
|
||||
boolean workIsScheduled = status != null && status.getState() != State.CANCELLED;
|
||||
if (workIsScheduled == prefs.isKeyserverSyncEnabled()) {
|
||||
if (!forceReschedule) {
|
||||
Timber.d("Key sync already scheduled, no changes necessary");
|
||||
return;
|
||||
try {
|
||||
WorkInfo info = workUuid != null ? workManager.getWorkInfoById(workUuid).get() : null;
|
||||
|
||||
boolean workIsScheduled = info != null && info.getState() != State.CANCELLED;
|
||||
if (workIsScheduled == prefs.isKeyserverSyncEnabled()) {
|
||||
if (!forceReschedule) {
|
||||
Timber.d("Key sync already scheduled, no changes necessary");
|
||||
return;
|
||||
}
|
||||
Timber.d("Key sync already scheduled, but forcing reschedule");
|
||||
}
|
||||
Timber.d("Key sync already scheduled, but forcing reschedule");
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
Timber.e(e, "Error getting info for scheduled key sync work?");
|
||||
}
|
||||
|
||||
Timber.d("Cancelling sync tasks…");
|
||||
synchronousWorkManager.cancelAllWorkByTagSync(PERIODIC_WORK_TAG);
|
||||
workManager.cancelAllWorkByTag(PERIODIC_WORK_TAG);
|
||||
|
||||
if (!prefs.isKeyserverSyncEnabled()) {
|
||||
Timber.d("Key sync disabled");
|
||||
|
@ -102,14 +98,17 @@ public class KeyserverSyncManager {
|
|||
.setConstraints(constraints.build())
|
||||
.addTag(PERIODIC_WORK_TAG)
|
||||
.build();
|
||||
synchronousWorkManager.enqueueSync(workRequest);
|
||||
|
||||
Timber.d("Work id: %s", workRequest.getId());
|
||||
prefs.setKeyserverSyncScheduled(workRequest.getId());
|
||||
try {
|
||||
workManager.enqueue(workRequest).getResult().get();
|
||||
Timber.d("Work id: %s", workRequest.getId());
|
||||
prefs.setKeyserverSyncScheduled(workRequest.getId());
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
Timber.e(e, "Error enqueueing job!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void debugRunSyncNow() {
|
||||
WorkManager workManager = WorkManager.getInstance();
|
||||
public static void debugRunSyncNow(Context context) {
|
||||
WorkManager workManager = WorkManager.getInstance(context);
|
||||
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(KeyserverSyncWorker.class).build();
|
||||
workManager.enqueue(workRequest);
|
||||
}
|
||||
|
|
|
@ -5,11 +5,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationCompat.Builder;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationCompat.Builder;
|
||||
import androidx.work.Worker;
|
||||
import androidx.work.WorkerParameters;
|
||||
import org.sufficientlysecure.keychain.Constants.NotificationIds;
|
||||
import org.sufficientlysecure.keychain.NotificationChannelManager;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
@ -28,9 +29,13 @@ import timber.log.Timber;
|
|||
public class KeyserverSyncWorker extends Worker {
|
||||
private AtomicBoolean cancellationSignal = new AtomicBoolean(false);
|
||||
|
||||
public KeyserverSyncWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
||||
super(context, workerParams);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public WorkerResult doWork() {
|
||||
public Result doWork() {
|
||||
KeyWritableRepository keyWritableRepository = KeyWritableRepository.create(getApplicationContext());
|
||||
|
||||
Timber.d("Starting key sync…");
|
||||
|
@ -47,7 +52,7 @@ public class KeyserverSyncWorker extends Worker {
|
|||
* @param result
|
||||
* result of keyserver sync
|
||||
*/
|
||||
private WorkerResult handleUpdateResult(ImportKeyResult result) {
|
||||
private Result handleUpdateResult(ImportKeyResult result) {
|
||||
if (result.isPending()) {
|
||||
Timber.d("Orbot required for sync but not running, attempting to start");
|
||||
// result is pending due to Orbot not being started
|
||||
|
@ -62,13 +67,13 @@ public class KeyserverSyncWorker extends Worker {
|
|||
OrbotRequiredDialogActivity.showOrbotRequiredNotification(getApplicationContext());
|
||||
}
|
||||
}.startOrbotAndListen(getApplicationContext(), false);
|
||||
return WorkerResult.RETRY;
|
||||
return Result.retry();
|
||||
} else if (isStopped()) {
|
||||
Timber.d("Keyserver sync cancelled");
|
||||
return WorkerResult.FAILURE;
|
||||
return Result.failure();
|
||||
} else {
|
||||
Timber.d("Keyserver sync completed: Updated: %d, Failed: %d", result.mUpdatedKeys, result.mBadKeys);
|
||||
return WorkerResult.SUCCESS;
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package org.sufficientlysecure.keychain.model;
|
|||
|
||||
import java.util.Date;
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import org.sufficientlysecure.keychain.CertsModel;
|
||||
|
|
|
@ -3,7 +3,7 @@ package org.sufficientlysecure.keychain.model;
|
|||
|
||||
import java.util.Date;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.squareup.sqldelight.ColumnAdapter;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin;
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package org.sufficientlysecure.keychain.model;
|
||||
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import org.sufficientlysecure.keychain.KeyRingsPublicModel;
|
||||
import org.sufficientlysecure.keychain.KeysModel.InsertKey;
|
||||
|
||||
|
||||
@AutoValue
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.sufficientlysecure.keychain.model;
|
||||
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import org.sufficientlysecure.keychain.KeySignaturesModel;
|
||||
|
|
|
@ -6,7 +6,7 @@ import java.util.Collections;
|
|||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.squareup.sqldelight.RowMapper;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package org.sufficientlysecure.keychain.model;
|
||||
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import org.sufficientlysecure.keychain.UserPacketsModel;
|
||||
|
|
|
@ -41,8 +41,8 @@ import java.util.Set;
|
|||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.network;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
|
||||
import com.textuality.keybase.lib.KeybaseUrlConnectionClient;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
|
||||
/**
|
||||
* Wrapper for Keybase Lib
|
||||
*/
|
||||
public class OkHttpKeybaseClient implements KeybaseUrlConnectionClient {
|
||||
|
||||
@Override
|
||||
public Response getUrlResponse(URL url, Proxy proxy, boolean isKeybase) throws IOException {
|
||||
OkHttpClient client;
|
||||
|
||||
if (proxy != null) {
|
||||
client = OkHttpClientFactory.getClientPinnedIfAvailable(url, proxy);
|
||||
} else {
|
||||
client = OkHttpClientFactory.getSimpleClient();
|
||||
}
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(url).build();
|
||||
okhttp3.Response okResponse = client.newCall(request).execute();
|
||||
return new Response(okResponse.body().byteStream(), okResponse.code(), okResponse.message(), okResponse.headers().toMultimap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKeybaseBaseUrl() {
|
||||
return "https://api.keybase.io/";
|
||||
}
|
||||
|
||||
}
|
|
@ -21,8 +21,8 @@ import java.net.URISyntaxException;
|
|||
import java.nio.charset.Charset;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import org.bouncycastle.util.encoders.DecoderException;
|
||||
|
|
|
@ -6,7 +6,7 @@ import java.security.KeyManagementException;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
|
|
@ -61,8 +61,8 @@ import android.net.Uri;
|
|||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
|
|
@ -32,8 +32,8 @@ import java.util.regex.Pattern;
|
|||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
|
|
|
@ -22,8 +22,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
|
||||
import android.content.Context;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.StringRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants.key;
|
||||
import org.sufficientlysecure.keychain.daos.KeyRepository;
|
||||
|
|
|
@ -21,7 +21,7 @@ package org.sufficientlysecure.keychain.operations;
|
|||
import java.util.Random;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.bouncycastle.bcpg.HashAlgorithmTags;
|
||||
import org.bouncycastle.bcpg.S2K;
|
||||
|
|
|
@ -22,7 +22,7 @@ import java.util.ArrayList;
|
|||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.sufficientlysecure.keychain.daos.KeyMetadataDao;
|
||||
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package org.sufficientlysecure.keychain.operations;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
|
||||
|
|
|
@ -21,7 +21,7 @@ package org.sufficientlysecure.keychain.operations;
|
|||
import java.util.Collections;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
|
|
|
@ -22,7 +22,7 @@ import java.io.IOException;
|
|||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.daos.KeyMetadataDao;
|
||||
|
|
|
@ -32,8 +32,8 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.daos.KeyMetadataDao;
|
||||
|
@ -41,7 +41,6 @@ import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
|
|||
import org.sufficientlysecure.keychain.keyimport.FacebookKeyserverClient;
|
||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress;
|
||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyserverClient;
|
||||
import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserverClient;
|
||||
import org.sufficientlysecure.keychain.keyimport.KeyserverClient;
|
||||
import org.sufficientlysecure.keychain.keyimport.KeyserverClient.QueryNotFoundException;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
|
@ -76,8 +75,7 @@ import timber.log.Timber;
|
|||
* all steps for this import.
|
||||
* For the import operation, the only valid source is an Iterator of
|
||||
* ParcelableKeyRing, each of which must contain either a single
|
||||
* keyring encoded as bytes, or a unique reference to a keyring
|
||||
* on keyservers and/or keybase.io.
|
||||
* keyring encoded as bytes, or a unique reference to a keyring on keyservers.
|
||||
* It is important to note that public keys should generally be imported before
|
||||
* secret keys, because some implementations (notably Symantec PGP Desktop) do
|
||||
* not include self certificates for user ids in the secret keyring. The import
|
||||
|
@ -93,7 +91,6 @@ public class ImportOperation extends BaseReadWriteOperation<ImportKeyringParcel>
|
|||
private final KeyMetadataDao keyMetadataDao;
|
||||
|
||||
private FacebookKeyserverClient facebookServer;
|
||||
private KeybaseKeyserverClient keybaseServer;
|
||||
|
||||
public ImportOperation(Context context, KeyWritableRepository databaseInteractor, Progressable progressable) {
|
||||
super(context, databaseInteractor, progressable);
|
||||
|
@ -355,14 +352,6 @@ public class ImportOperation extends BaseReadWriteOperation<ImportKeyringParcel>
|
|||
}
|
||||
}
|
||||
|
||||
boolean hasKeybaseName = entry.getKeybaseName() != null;
|
||||
if (hasKeybaseName) {
|
||||
UncachedKeyRing keybaseKey = fetchKeyFromKeybase(proxy, log, entry);
|
||||
if (keybaseKey != null) {
|
||||
key = mergeKeysOrUseEither(log, 3, key, keybaseKey);
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasFacebookName = entry.getFbUsername() != null;
|
||||
if (hasFacebookName) {
|
||||
UncachedKeyRing facebookKey = fetchKeyFromFacebook(proxy, log, entry);
|
||||
|
@ -414,32 +403,6 @@ public class ImportOperation extends BaseReadWriteOperation<ImportKeyringParcel>
|
|||
}
|
||||
}
|
||||
|
||||
private UncachedKeyRing fetchKeyFromKeybase(@NonNull ParcelableProxy proxy, OperationLog log, ParcelableKeyRing entry)
|
||||
throws PgpGeneralException, IOException {
|
||||
if (keybaseServer == null) {
|
||||
keybaseServer = KeybaseKeyserverClient.getInstance();
|
||||
}
|
||||
|
||||
try {
|
||||
log.add(LogType.MSG_IMPORT_FETCH_KEYBASE, 2, entry.getKeybaseName());
|
||||
byte[] data = keybaseServer.get(entry.getKeybaseName(), proxy).getBytes();
|
||||
UncachedKeyRing keybaseKey = UncachedKeyRing.decodeFromData(data);
|
||||
|
||||
if (keybaseKey != null) {
|
||||
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_OK, 3);
|
||||
} else {
|
||||
log.add(LogType.MSG_IMPORT_FETCH_ERROR_DECODE, 3);
|
||||
}
|
||||
|
||||
return keybaseKey;
|
||||
} catch (KeyserverClient.QueryFailedException e) {
|
||||
// download failed, too bad. just proceed
|
||||
Timber.e(e, "query failed");
|
||||
log.add(LogType.MSG_IMPORT_FETCH_ERROR_KEYSERVER, 3, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private UncachedKeyRing fetchKeyFromFacebook(@NonNull ParcelableProxy proxy, OperationLog log, ParcelableKeyRing entry)
|
||||
throws PgpGeneralException, IOException {
|
||||
if (facebookServer == null) {
|
||||
|
|
|
@ -28,7 +28,7 @@ import java.util.ArrayList;
|
|||
import android.content.ClipDescription;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.daos.KeyMetadataDao;
|
||||
|
@ -72,7 +72,7 @@ public class KeySyncOperation extends BaseReadWriteOperation<KeySyncParcel> {
|
|||
ArrayList<ParcelableKeyRing> result = new ArrayList<>(staleKeyFingerprints.size());
|
||||
for (byte[] fingerprint : staleKeyFingerprints) {
|
||||
Timber.d("Keyserver sync: Updating %s", KeyFormattingUtils.beautifyKeyId(fingerprint));
|
||||
result.add(ParcelableKeyRing.createFromReference(fingerprint, null, null, null));
|
||||
result.add(ParcelableKeyRing.createFromReference(fingerprint, null, null));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1,188 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.operations;
|
||||
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.textuality.keybase.lib.KeybaseQuery;
|
||||
import com.textuality.keybase.lib.Proof;
|
||||
import com.textuality.keybase.lib.prover.Prover;
|
||||
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;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.json.JSONObject;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.network.OkHttpKeybaseClient;
|
||||
import org.sufficientlysecure.keychain.network.orbot.OrbotHelper;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.daos.KeyWritableRepository;
|
||||
import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificationParcel> {
|
||||
|
||||
public KeybaseVerificationOperation(Context context, KeyWritableRepository databaseInteractor,
|
||||
Progressable progressable) {
|
||||
super(context, databaseInteractor, progressable);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public KeybaseVerificationResult execute(KeybaseVerificationParcel keybaseInput,
|
||||
CryptoInputParcel cryptoInput) {
|
||||
Proxy proxy;
|
||||
if (cryptoInput.getParcelableProxy() == null) {
|
||||
// explicit proxy not set
|
||||
if (!OrbotHelper.isOrbotInRequiredState(mContext)) {
|
||||
return new KeybaseVerificationResult(null,
|
||||
RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
|
||||
}
|
||||
proxy = Preferences.getPreferences(mContext).getParcelableProxy().getProxy();
|
||||
} else {
|
||||
proxy = cryptoInput.getParcelableProxy().getProxy();
|
||||
}
|
||||
|
||||
String requiredFingerprint = keybaseInput.mRequiredFingerprint;
|
||||
|
||||
OperationResult.OperationLog log = new OperationResult.OperationLog();
|
||||
log.add(OperationResult.LogType.MSG_KEYBASE_VERIFICATION, 0, requiredFingerprint);
|
||||
|
||||
try {
|
||||
KeybaseQuery keybaseQuery = new KeybaseQuery(new OkHttpKeybaseClient());
|
||||
keybaseQuery.setProxy(proxy);
|
||||
|
||||
String keybaseProof = keybaseInput.mKeybaseProof;
|
||||
Proof proof = new Proof(new JSONObject(keybaseProof));
|
||||
mProgressable.setProgress(R.string.keybase_message_fetching_data, 0, 100);
|
||||
|
||||
Prover prover = Prover.findProverFor(proof);
|
||||
|
||||
if (prover == null) {
|
||||
log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_NO_PROVER, 1,
|
||||
proof.getPrettyName());
|
||||
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
if (!prover.fetchProofData(keybaseQuery)) {
|
||||
log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_FETCH_PROOF, 1);
|
||||
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
if (!prover.checkFingerprint(requiredFingerprint)) {
|
||||
log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_FINGERPRINT_MISMATCH, 1);
|
||||
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
String domain = prover.dnsTxtCheckRequired();
|
||||
if (domain != null) {
|
||||
DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT));
|
||||
if (dnsQuery == null) {
|
||||
log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_DNS_FAIL, 1);
|
||||
log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_SPECIFIC, 2,
|
||||
getFlattenedProverLog(prover));
|
||||
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
|
||||
}
|
||||
Record[] records = dnsQuery.getAnswers();
|
||||
List<List<byte[]>> extents = new ArrayList<>();
|
||||
for (Record r : records) {
|
||||
Data d = r.getPayload();
|
||||
if (d instanceof TXT) {
|
||||
extents.add(((TXT) d).getExtents());
|
||||
}
|
||||
}
|
||||
if (!prover.checkDnsTxt(extents)) {
|
||||
log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_SPECIFIC, 1,
|
||||
getFlattenedProverLog(prover));
|
||||
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] messageBytes = prover.getPgpMessage().getBytes();
|
||||
if (prover.rawMessageCheckRequired()) {
|
||||
InputStream messageByteStream = PGPUtil.getDecoderStream(new
|
||||
ByteArrayInputStream
|
||||
(messageBytes));
|
||||
if (!prover.checkRawMessageBytes(messageByteStream)) {
|
||||
log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_SPECIFIC, 1,
|
||||
getFlattenedProverLog(prover));
|
||||
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
|
||||
}
|
||||
}
|
||||
|
||||
PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(mContext, mKeyRepository, mProgressable);
|
||||
|
||||
PgpDecryptVerifyInputParcel input = PgpDecryptVerifyInputParcel.builder()
|
||||
.setInputBytes(messageBytes)
|
||||
.build();
|
||||
|
||||
DecryptVerifyResult decryptVerifyResult = op.execute(input, CryptoInputParcel.createCryptoInputParcel());
|
||||
|
||||
if (!decryptVerifyResult.success()) {
|
||||
log.add(decryptVerifyResult, 1);
|
||||
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
long verifyingKeyId = decryptVerifyResult.getSignatureResult().getKeyId();
|
||||
byte[] verifyingFingerprint = mKeyRepository.getFingerprintByKeyId(verifyingKeyId);
|
||||
if (!requiredFingerprint.equals(KeyFormattingUtils.convertFingerprintToHex(verifyingFingerprint))) {
|
||||
log.add(LogType.MSG_KEYBASE_ERROR_FINGERPRINT_MISMATCH, 1);
|
||||
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
if (!prover.validate(new String(decryptVerifyResult.getOutputBytes()))) {
|
||||
log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_PAYLOAD_MISMATCH, 1);
|
||||
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
return new KeybaseVerificationResult(OperationResult.RESULT_OK, log, prover);
|
||||
} catch (Exception e) {
|
||||
// just adds the passed parameter, in this case e.getMessage()
|
||||
log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_SPECIFIC, 1, e.getMessage());
|
||||
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
|
||||
}
|
||||
}
|
||||
|
||||
private String getFlattenedProverLog(Prover prover) {
|
||||
String log = "";
|
||||
for (String line : prover.getLog()) {
|
||||
log += line + "\n";
|
||||
}
|
||||
return log;
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ import java.util.List;
|
|||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
|
||||
|
|
|
@ -19,7 +19,7 @@ package org.sufficientlysecure.keychain.operations;
|
|||
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
||||
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
|
||||
|
|
|
@ -24,7 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.sufficientlysecure.keychain.daos.KeyRepository;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
|
|
|
@ -24,8 +24,8 @@ import java.net.Proxy;
|
|||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
|
|
@ -20,7 +20,7 @@ package org.sufficientlysecure.keychain.operations.results;
|
|||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Parcel;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
|
|
|
@ -22,7 +22,7 @@ import java.util.ArrayList;
|
|||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpMetadata;
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package org.sufficientlysecure.keychain.operations.results;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.operations.results;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import com.textuality.keybase.lib.KeybaseException;
|
||||
import com.textuality.keybase.lib.prover.Prover;
|
||||
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
|
||||
public class KeybaseVerificationResult extends InputPendingResult {
|
||||
public final String mProofUrl;
|
||||
public final String mPresenceUrl;
|
||||
public final String mPresenceLabel;
|
||||
|
||||
public KeybaseVerificationResult(int result, OperationLog log) {
|
||||
super(result, log);
|
||||
mProofUrl = null;
|
||||
mPresenceLabel = null;
|
||||
mPresenceUrl = null;
|
||||
}
|
||||
|
||||
public KeybaseVerificationResult(int result, OperationLog log, Prover prover)
|
||||
throws KeybaseException {
|
||||
super(result, log);
|
||||
mProofUrl = prover.getProofUrl();
|
||||
mPresenceUrl = prover.getPresenceUrl();
|
||||
mPresenceLabel = prover.getPresenceLabel();
|
||||
}
|
||||
|
||||
public KeybaseVerificationResult(OperationLog log, RequiredInputParcel requiredInputParcel,
|
||||
CryptoInputParcel cryptoInputParcel) {
|
||||
super(log, requiredInputParcel, cryptoInputParcel);
|
||||
mProofUrl = null;
|
||||
mPresenceUrl = null;
|
||||
mPresenceLabel = null;
|
||||
}
|
||||
|
||||
protected KeybaseVerificationResult(Parcel in) {
|
||||
super(in);
|
||||
mProofUrl = in.readString();
|
||||
mPresenceUrl = in.readString();
|
||||
mPresenceLabel = in.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeString(mProofUrl);
|
||||
dest.writeString(mPresenceUrl);
|
||||
dest.writeString(mPresenceLabel);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<KeybaseVerificationResult> CREATOR = new Parcelable.Creator<KeybaseVerificationResult>() {
|
||||
@Override
|
||||
public KeybaseVerificationResult createFromParcel(Parcel in) {
|
||||
return new KeybaseVerificationResult(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeybaseVerificationResult[] newArray(int size) {
|
||||
return new KeybaseVerificationResult[size];
|
||||
}
|
||||
};
|
||||
}
|
|
@ -22,7 +22,7 @@ import android.content.Intent;
|
|||
import android.content.res.Resources;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
|
||||
|
@ -756,7 +756,6 @@ public abstract class OperationResult implements Parcelable {
|
|||
MSG_IMPORT_FETCH_ERROR_NOT_FOUND (LogLevel.ERROR, R.string.msg_import_fetch_error_not_found),
|
||||
MSG_IMPORT_FETCH_ERROR_KEYSERVER(LogLevel.ERROR, R.string.msg_import_fetch_error_keyserver),
|
||||
MSG_IMPORT_FETCH_ERROR_KEYSERVER_SECRET (LogLevel.ERROR, R.string.msg_import_fetch_error_keyserver_secret),
|
||||
MSG_IMPORT_FETCH_KEYBASE (LogLevel.INFO, R.string.msg_import_fetch_keybase),
|
||||
MSG_IMPORT_FETCH_FACEBOOK (LogLevel.INFO, R.string.msg_import_fetch_facebook),
|
||||
MSG_IMPORT_FETCH_KEYSERVER (LogLevel.INFO, R.string.msg_import_fetch_keyserver),
|
||||
MSG_IMPORT_FETCH_KEYSERVER_OK (LogLevel.DEBUG, R.string.msg_import_fetch_keyserver_ok),
|
||||
|
@ -820,18 +819,6 @@ public abstract class OperationResult implements Parcelable {
|
|||
MSG_REVOKE_ERROR_KEY_FAIL (LogLevel.ERROR, R.string.msg_revoke_key_fail),
|
||||
MSG_REVOKE_OK (LogLevel.OK, R.string.msg_revoke_ok),
|
||||
|
||||
// keybase verification
|
||||
MSG_KEYBASE_VERIFICATION(LogLevel.START, R.string.msg_keybase_verification),
|
||||
|
||||
MSG_KEYBASE_ERROR_NO_PROVER(LogLevel.ERROR, R.string.msg_keybase_error_no_prover),
|
||||
MSG_KEYBASE_ERROR_FETCH_PROOF(LogLevel.ERROR, R.string.msg_keybase_error_fetching_evidence),
|
||||
MSG_KEYBASE_ERROR_FINGERPRINT_MISMATCH(LogLevel.ERROR,
|
||||
R.string.msg_keybase_error_key_mismatch),
|
||||
MSG_KEYBASE_ERROR_DNS_FAIL(LogLevel.ERROR, R.string.msg_keybase_error_dns_fail),
|
||||
MSG_KEYBASE_ERROR_SPECIFIC(LogLevel.ERROR, R.string.msg_keybase_error_specific),
|
||||
MSG_KEYBASE_ERROR_PAYLOAD_MISMATCH(LogLevel.ERROR,
|
||||
R.string.msg_keybase_error_msg_payload_mismatch),
|
||||
|
||||
// InputData Operation
|
||||
MSG_DATA (LogLevel.START, R.string.msg_data),
|
||||
MSG_DATA_OPENPGP (LogLevel.DEBUG, R.string.msg_data_openpgp),
|
||||
|
|
|
@ -21,7 +21,7 @@ import android.app.Activity;
|
|||
import android.content.Intent;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package org.sufficientlysecure.keychain.operations.results;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
|
|
|
@ -24,7 +24,7 @@ import java.util.Iterator;
|
|||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
|
|
|
@ -23,7 +23,7 @@ import java.util.List;
|
|||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ import java.util.Map;
|
|||
import java.util.Map.Entry;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
|
|
|
@ -21,10 +21,10 @@ package org.sufficientlysecure.keychain.pgp;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import android.support.annotation.CheckResult;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.io.IOException;
|
|||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
|
@ -35,7 +36,7 @@ import java.util.List;
|
|||
import java.util.Stack;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||
|
@ -46,7 +47,11 @@ import org.bouncycastle.bcpg.S2K;
|
|||
import org.bouncycastle.bcpg.sig.Features;
|
||||
import org.bouncycastle.bcpg.sig.KeyFlags;
|
||||
import org.bouncycastle.bcpg.sig.RevocationReasonTags;
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.eddsa.spec.EdDSAGenParameterSpec;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator;
|
||||
import org.bouncycastle.crypto.params.Ed25519KeyGenerationParameters;
|
||||
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
|
||||
import org.bouncycastle.jce.spec.ElGamalParameterSpec;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPKeyFlags;
|
||||
|
@ -250,10 +255,8 @@ public class PgpKeyOperation {
|
|||
return null;
|
||||
}
|
||||
progress(R.string.progress_generating_eddsa, 30);
|
||||
EdDSAGenParameterSpec edParamSpec =
|
||||
new EdDSAGenParameterSpec("ed25519");
|
||||
keyGen = KeyPairGenerator.getInstance("EdDSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
keyGen.initialize(edParamSpec, new SecureRandom());
|
||||
keyGen = KeyPairGenerator.getInstance("ED25519", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
keyGen.initialize(256, new SecureRandom());
|
||||
|
||||
algorithm = PGPPublicKey.EDDSA;
|
||||
break;
|
||||
|
@ -281,7 +284,8 @@ public class PgpKeyOperation {
|
|||
}
|
||||
|
||||
// build new key pair
|
||||
return new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), creationTime);
|
||||
KeyPair keyPair = keyGen.generateKeyPair();
|
||||
return new JcaPGPKeyPair(algorithm, keyPair, creationTime);
|
||||
|
||||
} catch(NoSuchProviderException | InvalidAlgorithmParameterException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
|
|
@ -21,7 +21,7 @@ package org.sufficientlysecure.keychain.pgp;
|
|||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||
|
|
|
@ -24,7 +24,7 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
|
|
|
@ -20,7 +20,7 @@ package org.sufficientlysecure.keychain.pgp;
|
|||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.bcpg.BCPGOutputStream;
|
||||
|
@ -48,10 +48,10 @@ import org.bouncycastle.openpgp.PGPException;
|
|||
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
|
||||
import org.bouncycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.PGPUtil;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.daos.KeyRepository;
|
||||
|
|
|
@ -19,7 +19,7 @@ package org.sufficientlysecure.keychain.pgp;
|
|||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
|
|
@ -19,13 +19,11 @@ package org.sufficientlysecure.keychain.pgp;
|
|||
|
||||
import org.bouncycastle.bcpg.DSAPublicBCPGKey;
|
||||
import org.bouncycastle.bcpg.ECPublicBCPGKey;
|
||||
import org.bouncycastle.bcpg.EdDSAPublicBCPGKey;
|
||||
import org.bouncycastle.bcpg.RSAPublicBCPGKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.ssh.key.SshDSAPublicKey;
|
||||
import org.sufficientlysecure.keychain.ssh.key.SshECDSAPublicKey;
|
||||
import org.sufficientlysecure.keychain.ssh.key.SshEd25519PublicKey;
|
||||
import org.sufficientlysecure.keychain.ssh.key.SshRSAPublicKey;
|
||||
import org.sufficientlysecure.keychain.ssh.utils.SshUtils;
|
||||
|
||||
|
@ -48,8 +46,9 @@ public class SshPublicKey {
|
|||
return encodeRSAKey(key);
|
||||
case PGPPublicKey.ECDSA:
|
||||
return encodeECKey(key);
|
||||
case PGPPublicKey.EDDSA:
|
||||
return encodeEdDSAKey(key);
|
||||
// TODO
|
||||
// case PGPPublicKey.EDDSA:
|
||||
// return encodeEdDSAKey(key);
|
||||
case PGPPublicKey.DSA:
|
||||
return encodeDSAKey(key);
|
||||
default:
|
||||
|
@ -76,13 +75,13 @@ public class SshPublicKey {
|
|||
|
||||
|
||||
|
||||
private String encodeEdDSAKey(PGPPublicKey publicKey) {
|
||||
EdDSAPublicBCPGKey publicBCPGKey = (EdDSAPublicBCPGKey) publicKey.getPublicKeyPacket().getKey();
|
||||
|
||||
SshEd25519PublicKey pubkey = new SshEd25519PublicKey(publicBCPGKey.getEdDSAEncodedPoint());
|
||||
|
||||
return pubkey.getPublicKeyBlob();
|
||||
}
|
||||
// private String encodeEdDSAKey(PGPPublicKey publicKey) {
|
||||
// EdDSAPublicBCPGKey publicBCPGKey = (EdDSAPublicBCPGKey) publicKey.getPublicKeyPacket().getKey();
|
||||
//
|
||||
// SshEd25519PublicKey pubkey = new SshEd25519PublicKey(publicBCPGKey.getEdDSAEncodedPoint());
|
||||
//
|
||||
// return pubkey.getPublicKeyBlob();
|
||||
// }
|
||||
|
||||
private String encodeDSAKey(PGPPublicKey publicKey) {
|
||||
DSAPublicBCPGKey publicBCPGKey = (DSAPublicBCPGKey) publicKey.getPublicKeyPacket().getKey();
|
||||
|
|
|
@ -36,7 +36,7 @@ import java.util.Set;
|
|||
import java.util.TimeZone;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
|
@ -672,7 +672,7 @@ public class UncachedKeyRing {
|
|||
}
|
||||
|
||||
// If NO user ids remain, error out!
|
||||
if (modified == null || !modified.getUserIDs().hasNext()) {
|
||||
if (modified == null || !modified.getRawUserIDs().hasNext()) {
|
||||
log.add(LogType.MSG_KC_ERROR_NO_UID, indent);
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import android.content.ContentProvider;
|
|||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
||||
public class KeychainProvider extends ContentProvider {
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ClipDescription;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
|
@ -38,12 +39,15 @@ import android.database.sqlite.SQLiteOpenHelper;
|
|||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
import androidx.work.Worker;
|
||||
import androidx.work.WorkerParameters;
|
||||
import org.sufficientlysecure.keychain.BuildConfig;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.KeychainApplication;
|
||||
import org.sufficientlysecure.keychain.util.DatabaseUtil;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
@ -95,7 +99,7 @@ public class TemporaryFileProvider extends ContentProvider {
|
|||
contentValues.put(TemporaryFileColumns.COLUMN_TIME, System.currentTimeMillis());
|
||||
Uri resultUri = contentResolver.insert(CONTENT_URI, contentValues);
|
||||
|
||||
scheduleCleanupAfterTtl();
|
||||
scheduleCleanupAfterTtl(context);
|
||||
return resultUri;
|
||||
}
|
||||
|
||||
|
@ -308,24 +312,34 @@ public class TemporaryFileProvider extends ContentProvider {
|
|||
return openFileHelper(uri, mode);
|
||||
}
|
||||
|
||||
public static void scheduleCleanupAfterTtl() {
|
||||
public static void scheduleCleanupAfterTtl(Context context) {
|
||||
OneTimeWorkRequest cleanupWork = new OneTimeWorkRequest.Builder(CleanupWorker.class)
|
||||
.setInitialDelay(Constants.TEMPFILE_TTL, TimeUnit.MILLISECONDS).build();
|
||||
WorkManager.getInstance().enqueue(cleanupWork);
|
||||
workManagerEnqueue(context, cleanupWork);
|
||||
}
|
||||
|
||||
public static void scheduleCleanupImmediately() {
|
||||
public static void scheduleCleanupImmediately(Context context) {
|
||||
OneTimeWorkRequest cleanupWork = new OneTimeWorkRequest.Builder(CleanupWorker.class).build();
|
||||
WorkManager workManager = WorkManager.getInstance();
|
||||
if (workManager != null) { // it's possible this is null, if this is called in onCreate of secondary processes
|
||||
workManager.enqueue(cleanupWork);
|
||||
workManagerEnqueue(context, cleanupWork);
|
||||
}
|
||||
|
||||
private static void workManagerEnqueue(Context context, OneTimeWorkRequest cleanupWork) {
|
||||
// work manager is only available on the main thread
|
||||
if (!BuildConfig.APPLICATION_ID.equals(KeychainApplication.getProcessName())) {
|
||||
return;
|
||||
}
|
||||
WorkManager.getInstance(context).enqueue(cleanupWork);
|
||||
}
|
||||
|
||||
public static class CleanupWorker extends Worker {
|
||||
public CleanupWorker(@NonNull Context context,
|
||||
@NonNull WorkerParameters workerParams) {
|
||||
super(context, workerParams);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public WorkerResult doWork() {
|
||||
public Result doWork() {
|
||||
Timber.d("Cleaning up temporary files…");
|
||||
|
||||
ContentResolver contentResolver = getApplicationContext().getContentResolver();
|
||||
|
@ -335,7 +349,7 @@ public class TemporaryFileProvider extends ContentProvider {
|
|||
new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)}
|
||||
);
|
||||
|
||||
return WorkerResult.SUCCESS;
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue